I could really really need some help with my actually quite simple Python Kivy Problem! I wrote a program that first announces counting to 5 and then should start counting from 1 to 5. The info should be shown in a scrollview-Label. The code roughly does its job but does not update the scrollview step-by-step but all at once after time is elapsed...can anybody please help? Thank you in advance!
import kivy
from kivy.config import Config
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
import time
kivy.require("2.0.0")
Config.set('kivy', 'keyboard_mode', 'systemandmulti')
class MainMenu(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 1
self.rows = 2
self.infowindow = ScrollableInfo(height=Window.size[1]*0.8, size_hint_y=None)
self.add_widget(self.infowindow)
self.ButtonCheckConnection = Button(text="Start Counting to 5")
self.ButtonCheckConnection.bind(on_press=self.countingtofive)
self.add_widget(self.ButtonCheckConnection)
def countingtofive(self, *_):
self.infowindow.update_scrollview(f"Counting to 5 is going to start in 3 seconds")
time.sleep(3)
countingmaximum = 5
for i in range(countingmaximum):
currentnumber = i+1
self.infowindow.update_scrollview(str(currentnumber))
time.sleep(1)
class ScrollableInfo(ScrollView):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.layout = GridLayout(cols=1, size_hint_y=None)
self.add_widget(self.layout)
self.connectioninfo_history = Label(size_hint_y=None, markup=True)
self.layout.add_widget(self.connectioninfo_history)
def update_scrollview(self, newinfo):
self.connectioninfo_history.text += '\n' + newinfo
self.layout.height = self.connectioninfo_history.texture_size[1]+15
self.connectioninfo_history.height = self.connectioninfo_history.texture_size[1]
self.connectioninfo_history.text_size = (self.connectioninfo_history.width*0.98, None)
class Counting(App):
def build(self):
self.screen_manager = ScreenManager()
self.mainmenu_page = MainMenu()
screen = Screen(name="MainMenu")
screen.add_widget(self.mainmenu_page)
self.screen_manager.add_widget(screen)
return self.screen_manager
if __name__ == "__main__":
counting_app = Counting()
counting_app.run()
The problem is that you are running your countingtofive() method on the main thread. Since Kivy uses the main thread to update the GUI, it cannot do that until you release the main thread (by returning from the countingtofive() method). That is why you never see anything until that method completes.
To fix that, run the countingtofive() method in another thread, like this:
def start_counting_thread(self, *args):
Thread(target=self.countingtofive, daemon=True).start()
And change the Button to bind to the start_counting_thread() method:
self.ButtonCheckConnection.bind(on_press=self.start_counting_thread)
And one minor change to the update_scrollview() method (add the #mainthread decorator):
#mainthread
def update_scrollview(self, newinfo):
The #mainthread decorator forces the decorated method to be run on the main thread. The same can be accomplished by using Clock.schedule_once(), but the decorator is easier. Just the piece of the code that actually updates the GUI must be run on the main thread. Generally, you should try to avoid long running methods on the main thread.
Related
I'm trying to make a button and I'm doing this print as a test, I'm not why it prints twice?
I'm new to Kivy so I'm not too sure on what I'm doing, any help is appreciated thank you.
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
def bannedBooksMain(self, instance):
print('The button test is being pressed')
class mainApp(GridLayout):
def __init__(self, **kwargs):
super(mainApp, self).__init__(**kwargs)
self.cols = 2
btn1 = Button(text='Home Page')
btn1.bind(state=bannedBooksMain)
self.add_widget(btn1)
class MyApp(App):
def build(self):
return mainApp()
MyApp().run()
btn1.bind(state=bannedBooksMain)
This (state=) will initiate a callback whenever the state property changes, this happens for both button press and release, which is why you're seeing it twice
You should use on_press= or on_release= instead, depending on when you want the action fired.
Personally, I prefer the on_release= combined with always_release being false. That way, if I inadvertently press the button, I can move the mouse point away from the button before releasing, and there will be no callback.
See here for further detail.
I have an app which depends on a system app's output as such, to get that output i'm using the subprocess module which works great, the problem is that this system app generates outputs as it is running as such I capture the output in realtime(no problem here) and I want to use this same output to update my Kivy ProgressBar in realtime. So far, i get the output and try to update the progressbar but the application simply hangs and updates the progressbar when the subprocess call is finished. here is a simple example.
Assume that the directory structure is like this:
project
|----------slow.py
|----------myapp.py
Suppose slow.py is the file handling the system calls and it gets the outputs and feeds it to a function, here is the minimal code for that
from time import sleep
class Slow(object):
def __init__(self):
pass
def call_me(self, callback):
for x in range(10):
sleep(2)
callback(x)
Now for The myapp.py code:
from kivy.uix.progressbar import ProgressBar
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from slow import Slow
class MyWindow(BoxLayout):
def __init__(self, **kw):
super().__init__(**kw)
self.padding = 100
self.sl = Slow()
self.pb = ProgressBar()
self.pb.min=0
self.pb.max=100
btn = Button(text='Start Update')
btn.bind(on_release=self.start_update)
self.add_widget(self.pb)
self.add_widget(btn)
def start_update(self, *args):
self.sl.call_me(self.update_progress)
def update_progress(self, progress):
print(progress)
self.pb.value = float(progress)
class MyApp(App):
def build(self):
return MyWindow()
MyApp().run()
If you run this app, you'll notice it freezes until the counting is done. How can I fix such a scenario?
NB: I already tried using Clock.schedule_once which, needless to say, gives the same result
I am using Kivy in an application where several widgets are used to modify/edit a set of data (the model). When some data is changed in one widget others need to be informed that something has changed so they can update their view.
To do this I use Kivy Properties to mirror the model data, and this works fine.
However the first time the Kivy Properties are set, when reading in the model data, this generates alot of unnecessary updates in the UI. I would like there to be a way to update a Kivy Property without generating update events.
I have looked in the documentation (https://kivy.org/docs/api-kivy.properties.html) and in the code (https://github.com/kivy/kivy/blob/master/kivy/properties.pyx) but I have not been able to find anything to do this.
How can this be solved?
I have done a very simple example app to show how the code is organized and where the problem occurs.
#! /usr/bin/env python
import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.listview import ListView
from kivy.adapters.listadapter import ListAdapter
from kivy.uix.listview import ListItemButton
from kivy.properties import StringProperty
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
model_data = ["Data1","Data2","Data3"]
class TestApp(App):
def build(self):
tl = TestLayout()
tl.read_data(model_data)
return tl
class TestLayout(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.test_list = TestList()
self.test_edit = TestEdit()
self.add_widget(self.test_list)
self.add_widget(self.test_edit)
self.test_list.adapter = ListAdapter(data=[],
cls=ListItemButton,
selection_mode='single')
self.test_list.adapter.bind(selection=self.select_data)
self.test_edit.bind(data=self.update_list)
def read_data(self, data_list):
self.test_list.init_list(data_list)
def select_data(self, list_adapter, selection):
if len(selection) > 0:
data = selection[0].text
self.test_edit.init_data(data)
def update_list(self, test_edit, data):
self.test_list.adapter.selection[0].text = data
class TestList(ListView):
def init_list(self, data_list):
self.adapter.data = [str(data) for data in data_list]
class TestEdit(BoxLayout):
data = StringProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
self.text_input=TextInput()
self.add_widget(self.text_input)
btn_update = Button(text="Update data")
self.add_widget(btn_update)
btn_update.bind(on_press=self.update_data)
def init_data(self, data):
# Setting the data property the first time triggers an unnecessary update.
# How can this be prevented?
self.data = data
self.text_input.text = data
def update_data(self, btn):
self.data = self.text_input.text
if __name__ == "__main__":
app = TestApp()
app.run()
Also as you can see in the example code, an unnecessary update is dispatch whenever you select a new item in the list view.
Have I overlooked something obvious? Or is this not supported by Kivy?
Thanks!
Looking to understand why this code is not updating. Any help is much appreciated.
TJ
Here is my Main.py. Here, I'm attempting to link up a queue that stores the state of a number producer process. This is to simulate some background process that will update the user interface over time.
import Consumer
import Producer
import multiprocessing
if __name__ == '__main__':
shared_queue = multiprocessing.Queue()
producer = Producer.NumberGenerator(shared_queue)
multiprocessing.Process(target=producer.generate_numbers).start()
app = Consumer.TimerApp()
app.set_queue(shared_queue)
app.run()
Producer.py runs as a separate process that generates a new number once a second. It's just there to show that a background task is able to continually update the user interface.
import time
class NumberGenerator(object):
def __init__(self, q):
self.q = q
self.counter = 0
def generate_numbers(self):
while True:
time.sleep(1)
self.counter += 1
# print self.counter
self.q.put(self.counter)
Consumer.py is our Kivy app. It is meant to listen to the queue by popping items from it. Then, update the UI to demonstrate things are working.
from kivy.app import App
from kivy.uix.label import Label
from kivy.properties import StringProperty
import multiprocessing
class YourWidget(Label):
temp = StringProperty()
def update_text(self, txt):
self.temp = txt
class TimerApp(App):
def build(self):
self.widget = YourWidget()
self.widget.update_text("Initial Text!")
# Build registry here
# Start queue reader here
# How do I pass in a reference? Setter function??
return self.widget
def set_queue(self, q):
self.q = q
def consumer_process_queue(self):
while True:
value = str(self.q.get())
print "Consumer: {}".format(value)
self.widget.update_text(value)
def on_start(self):
# self.widget.update_text("Hello World!")
multiprocessing.Process(target=self.consumer_process_queue).start()
timer.kv:
<YourWidget>:
text: root.temp
When things are processing, I can see the numbers updating to STDOUT. This indicates that "self.widget.update_text(value)" is not doing what I want it to.
Any ideas?
Also, if I use the commented "self.widget.update_text("Hello World!")" code and comment out the "multiprocessing.Process(target=self.consumer_process_queue).start()", the widget updates the text.
Problem
def consumer_process_queue(self):
while True:
value = str(self.q.get())
print("Consumer: {}".format(value))
self.widget.update_text(value)
When you run this, the program will never exit your loop, preventing Kivy from doing all of the other things that need doing. As a result, all you’ll see is a black window with "Initial Text!", which you won’t be able to interact with.
Solution
Instead, you need to “schedule” your consumer_process_queue() function to be called repeatedly.
Please refer to the example for details. No changes to Producer.py and main.py.
Example
Consumer.py
from kivy.app import App
from kivy.uix.label import Label
from kivy.clock import Clock
import multiprocessing
class YourWidget(Label):
def update_text(self, txt):
self.text = txt
class TimerApp(App):
def build(self):
self.widget = YourWidget()
self.widget.update_text("Initial Text!")
Clock.schedule_interval(self.consumer_process_queue, 1)
# Build registry here
# Start queue reader here
# How do I pass in a reference? Setter function??
return self.widget
def set_queue(self, q):
self.q = q
def consumer_process_queue(self, dt=0):
value = str(self.q.get())
print("Consumer: {}".format(value))
self.widget.update_text(value)
def on_start(self):
# self.update_text("Hello World!")
multiprocessing.Process(target=self.consumer_process_queue).start()
timer.kv
#:kivy 1.10.0
<YourWidget>:
text: ""
Output
I am trying to get the textinput widget to pass text into the callback function that makes a label with the text when called by the printbutton, should be fairly simple when you think about it. But I have a habit of not seeing the wood for the trees. Anyhoo, if anyone can figure this out then code it up :P
import kivy
kivy.require('1.5.1')
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
class kivyentrywidget(GridLayout):
def __init__(self, **kwargs):
super(kivyentrywidget, self).__init__(**kwargs)
self.cols = 2
self.add_widget(Label(text='What do you want to print?'))
self.text_input = TextInput(multiline=False)
self.add_widget(self.text_input)
self.printbutton = Button(text='Print')
self.printbutton.bind(on_press=callback)
self.add_widget(self.printbutton)
def callback(self):
return Label(text=self.text_input.text)
class Firstapp(App):
def build(self):
return kivyentrywidget()
if __name__ == '__main__':
Firstapp().run()
def callback(self,evt=None): #not sure if kivy sends event info so added optional arg just in case
return self.add_widget(Label(text=self.text_input.text))
maybe ... not overly familiar with kivy but i think that would do it ..
also
self.printbutton.bind(on_press=self.callback)
should fix your other problem