Reputation: 385
I have a socket client that calls a View()
class every time it receives a message. I've split my code in such a way such that this class can simply use print()
or any other display method, as I like. However, it seems that Kivy is not fond of this method. I've extended Kivy's BoxLayout
class for my view and can call the message()
function. The class looks something like this:
class View(BoxLayout):
def __init__(self, **kwargs):
super(View, self).__init__(**kwargs)
self.btn = Button(text='Default')
# Bind button press method
self.btn.bind(on_press=self.message)
self.add_widget(self.btn)
def message(self, message):
self.btn.text = 'Meow'
self.add_widget(Button(text='Meow'))
print(str(message))
The message function is indeed called and it prints but the interface does not update. When I press the button however, it does update the interface as well as print.
I've looked into using a StringProperty
to modify the button text but have failed that too. Just as a note, in case what I'm doing is completely infeasible, I'm trying to later draw an entire interface consisting of width * height
buttons, in the form of a board.
Any input is very much appreciated, it's been driving me insane.
Clock
class and have it schedule an update()
method from View
. The update method simply changes the text of a few elements. I've noticed it works when I schedule it to, as shown below:
def update(self, *args, **kwargs):
self.btn.text = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for i in range(32))
def message(self, message):
try:
print(message)
self.text = 'sending'
except Exception as e:
print(e)
The thread now simply assigns the text property as seen in message()
. The periodically triggered update()
methods works too, assigning random text. Now the issue however that it cannot set the text. This does not work:
def update(self, *args, **kwargs):
self.btn.text = self.text
I must be doing wrong elsewhere, any suggestions?
Upvotes: 15
Views: 6283
Reputation: 5682
Kivy is deisgned such that only the main thread in a Kivy app can modify the GUI. This is for system stability. The issue is that attempts to modify the GUI from a seconday thread fail SILENTLY. Kivy allows any thread "read-only access," so any thread can read from the GUI widgets and variables. But if you've called a function with multiple lines, and in one of the lines you try to modify the GUI, your function will silently fail from there. It behaves as if you've silently caught an exception.
If you want to pass a handler to a secondary thread that can update the Kivy GUI, wrap the handler you wanted to pass in with a call to Clock.schedule_once(). For instance, here's how I solved my problem:
Here's the original handler that works fine when called from within the Kivy thread, but fails silently when called from a secondary thread:
def gui_handler(self, txt):
self.entry.text = txt
Here's the wrapped version that is successfully called from a secondary thread:
def wrapped_gui_handler(self, txt):
return Clock.schedule_once(lambda dt: self.gui_handler(txt))
Upvotes: 0
Reputation: 8213
Since you don't post a full working example, I can only guess at what your doing. It seems you have an event (incoming message) on a thread, and you want to display some text when this occurs. You need to 'push' UI updates to the main thread, but you don't need to do periodic updates with Clock
, you can just schedule one-time calls with Clock.schedule_once
.
from functools import partial
def update(self, text, *a):
self.btn.text = text
def message(self, message):
Clock.schedule_once(partial(self.update, message), 0)
As inclement mentioned, you can do this 'push to main thread' automatically with the @mainthread
decorator:
@mainthread
def update(self, text):
self.btn.text = text
def message(self, message):
update(message)
This way, whenever you call update
it will be executed on the main thread.
Upvotes: 15