When running Python + Kivy program including multiprocessing, white blank screen appears - python

I want to use Kivy program including multiprocessing process.
Sample code worked as i thought;however, a blank white screen which does not accept the operation appeared. It appeared just when i started the multiprocessing process.
I already know that using threading.Thread, the blank screen won't appear;however, an actual program that i want to implement needs multiprocessing process.
How can i remove this blank screen?
This is what i saw.
P.S.
Considering comments, maybe it's a peculiar problem on windows.
I'm using windows 10, Python 3.6.4.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
import time
from datetime import datetime
import multiprocessing as mp
from multiprocessing import Process, Queue, freeze_support
sm = ScreenManager(transition=NoTransition())
Builder.load_string('''
<TestScreen>:
BoxLayout:
orientation: "vertical" # place object vertically
Label:
size_hint: 1, 1
text: 'TITLE'
font_size: 150
color: 1, 1, 1, 1
Button:
text: 'start'
on_press: root.start_mp()
Button:
text: 'stop'
on_press: root.stop_time()
''')
def count_time(x):
for i in range(x):
print('count={}'.format(i))
time.sleep(1)
class Test(App):
def build(self):
sm.add_widget(TestScreen(name='test'))
return sm
class TestScreen(Screen):
p1 = mp.Process(target=count_time, args=(10, ))
def start_mp(self):
self.p1 = mp.Process(target=count_time, args=(10, ))
self.p1.start()
def stop_time(self):
self.p1.terminate()
print(datetime.now())
if __name__ == '__main__':
Test().run()

Turns out that the problem is the ScreenManager that you create at the start of your file. Move the sm = ScreenManager(transition=NoTransition()) into the build method of your app and it should work without creating a second screen.
After some research, it appears that the problem results from the new Process importing the original python script. Since the sm = ScreenManager(transition=NoTransition()) is unprotected in the original file, it gets executed upon that import. So the solution is simply to put that command anywhere that is protected. So another possibility that will work is to move that command into the if __name__ == '__main__': block just before the Test().run() command.

Related

How do I make a Kivy app update values from other files?

I'm trying to make a Kivy interface that updates constantly when values imported from other files are constantly changing.
Questions:
How do I make it so, when I change the value of the "fk" variable in
test.py, that the Kivy app updates, without me having to restart the
program?
What is the use of "AnchorLayout" in class Interface(AnchorLayout): ?
I've tried to Google this, and still not 100% sure. Why put self in def build():?
What is the use of self.update in the code?
Questions 2 and 3 are unrelated to my main questions, but if anyone can help, that would be great.
What I have tried and failed:
main.py:
from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
from kivy.lang import Builder
import test123
Builder.load_file('main.kv')
Builder.load_file('robotinformation.kv')
class Interface(AnchorLayout):
val = NumericProperty(0)
class InterfaceApp(App):
def build(self):
Clock.schedule_interval(self.update, 1)
return Interface()
def update():
val = test123.numval()
if __name__ == '__main__':
InterfaceApp().run()
main.kv:
<Interface>:
BoxLayout:
orientation: 'vertical'
StackLayout:
orientation: 'tb-rl'
RobotInformation:
id: _robot_information
...
robotinformation.kv:
<RobotInformation#BoxLayout>:
BoxLayout:
orientation:'vertical'
Label:
text: app.val
...
test123.py:
def numval():
fk = 101
return fk

Python Kivy widgets duplicated on top of each other on a custom app restart method

Basically I'm working on a Kivy app that consists of multiple screens. Recently I decided to give my app a little Refresh button that refreshes the widgets and reread the savefile for sorta debugging purpose, and then I of course stole the code from this question's answer, like how every programmer would do. It worked, but there's one slight problem: every time I press the refresh button, for whatever reason the widgets got duplicated and placed on top of each other(even though the restart function cleared the former widgets beforehand). Here's my code, simplified:
test.py
from kivymd.app import MDApp
from kivy.uix.screenmanager import Screen
from kivy.config import Config
Config.set("kivy", "exit_on_escape", 0)
class First(Screen):
pass
class Second(Screen):
pass
class Test(MDApp):
def restart(self):
self.root.clear_widgets()
self.stop()
Test().run()
Test().run()
test.kv
ScreenManager:
First:
Second:
<First>:
MDFloatLayout:
MDTextField:
size_hint: 0.8, 0.2
pos_hint: {'center_x': 0.5, 'top': 0.9}
hint_text: 'owo uwu test im a furry yes'
MDRectangleFlatButton:
text: 'Restart'
size_hint: 0.15, 0.1
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
on_release: app.restart()
<Second>:
Also note that the reason to why I used screens in this example app although there's no way to access the Second screen, is because I did some tests and the result is that the "bug" only occurs with the ScreenManager being the root class, others like FloatLayout works properly. Secondly the reason why I used KivyMD in this example is that it's easier to detect the duplication of widgets using MDTextField because of the animation of the hint text.
Does anyone know what's the cause behind the problem, as well as the solution to this? Any help would be appreciated.
Edit: Here are images for demonstration:
Before restarting:
After restarting:
Th problem is that your test.kv file is getting loaded twice due to Kivy doing its automatic loading based on the file name. The fix is to control the kv file loading yourself. To do that, change the name of your kv file to anything other than test.kv, perhaps not_test.kv. Then create a build() method in your Test App:
class Test(MDApp):
def build(self):
from kivy.resources import resource_find
filename = 'not_test.kv'
filename = resource_find(filename) or filename
if filename in Builder.files:
Builder.unload_file(filename)
return Builder.load_file(filename)
This code unloads the kv file if it has already been loaded, then loads it again in order to get the root widget.

Issue when importing kivy.core.window and using the multiprocessing library

First of all, I'd like to clarify that I have absolutely 0 background as a software engineer and this is the first time I'm using python for something besides API's and creating excel/plots.
My issue is that I was trying to create an app using kivy and then I imported the kivy.core.window library a blank screen appears.
I've seen that this issue is fairly common when using kivy and multiprocessing, but none of the information I found fixed my problem. Result of running my code without the kivy.core.window library:
Result when adding said library:
Also, if I wait like 10 seconds the blank screen disappears and kills the count_time process.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
import time
from datetime import datetime
import multiprocessing as mp
from multiprocessing import Process, Queue, freeze_support
Builder.load_string('''
<TestScreen>:
BoxLayout:
orientation: "vertical" # place object vertically
Label:
size_hint: 1, 1
text: 'TITLE'
font_size: 150
color: 1, 1, 1, 1
Button:
text: 'start'
on_press: root.start_mp()
Button:
text: 'stop'
on_press: root.stop_time()
''')
def count_time(x):
for i in range(x):
print('count={}'.format(i))
time.sleep(1)
class Test(App):
def build(self):
sm.add_widget(TestScreen(name='test'))
return sm
class TestScreen(Screen):
def __init__(self, **kwargs):
super(TestScreen, self).__init__(**kwargs)
def start_mp(self):
self.p1 = mp.Process(target=count_time, args=(10, ))
self.p1.start()
def stop_time(self):
pass
if __name__ == '__main__':
mp.freeze_support()
sm = ScreenManager(transition=NoTransition())
Test().run()
The problem is that when you run Process, it starts a new python proces and imports the current .py file. On most platforms, the line:
if __name__ == '__main__':
protects the Test().run() from being executed and starting another App and is enough to protect from another window being opened. However, it appears that importing kivy.core.window is enough to open another window (on Windows platform) when the original .py file is imported for multiprocessing. You can read about it here.
A really ugly work around is to protect all kivy imports (and as a result, all the kivy code) with a if __name__ == '__main__': line. Like this:
import time
import multiprocessing as mp
def count_time(x):
for i in range(x):
print('count={}'.format(i))
time.sleep(1)
if __name__ == '__main__':
mp.freeze_support()
from kivy.core.window import Window
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
Builder.load_string('''
<TestScreen>:
BoxLayout:
orientation: "vertical" # place object vertically
Label:
size_hint: 1, 1
text: 'TITLE'
font_size: 150
color: 1, 1, 1, 1
Button:
text: 'start'
on_press: root.start_mp()
Button:
text: 'stop'
on_press: root.stop_time()
''')
class Test(App):
def build(self):
sm = ScreenManager(transition=NoTransition())
sm.add_widget(TestScreen(name='test'))
return sm
class TestScreen(Screen):
def __init__(self, **kwargs):
super(TestScreen, self).__init__(**kwargs)
def start_mp(self):
self.p1 = mp.Process(target=count_time, args=(10,))
self.p1.start()
def stop_time(self):
pass
Test().run()

Kivy: Label text does not update during for-loop

I have an issue when I try to update a label text during a for-loop. There are similar entries (e.g.: Update properties of a kivy widget while running code) but they do not seem to fit my issue exactly (or I missed the point…).
I run following code:
*.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
#from time import sleep
class MyBox(BoxLayout):
tobeupd = StringProperty()
def __init__(self,*args,**kwargs):
super(MyBox,self).__init__(*args,**kwargs)
self.tobeupd = '#'
def upd_ltxt(self):
for i in range(1,10):
self.tobeupd = str(i)
print(self.tobeupd)
input('Write something: ') # new line, see edit below
#sleep(0.5)
class updApp(App):
def build(self):
return MyBox()
if __name__ == '__main__':
updApp().run()
*.kv
<MyBox>:
orientation: 'horizontal'
cols: 2
Label:
text: root.tobeupd
Button:
text: 'Start Update'
on_release: root.upd_ltxt()
Whereas the ‘print’ statement updates the shell regularly, the label text updates at the end of the for-loop only.
Can anyone explain to me why Kivy works this way and how I can overcome this problem?
EDIT: According to PM2Ring and Gugas, I changed the code in order to avoid the sleep-function. The problem remains if I ask the user to enter something before the loop can be continued. The values are updated in the shell but not on the label.
You can use threading for this.
When you do a loop or wait for an input in kivy, the main thread is waiting, and nothing will update on the app. threading will prevent that.
Use threading to make another thread besides the main thread.
Example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.lang import Builder
import threading
Builder.load_string('''
<MyBox>:
orientation: 'horizontal'
cols: 2
Label:
text: root.tobeupd
Button:
text: 'Start Update'
on_release: root.upd_ltxt()
''')
class MyBox(BoxLayout):
tobeupd = StringProperty()
def __init__(self,*args,**kwargs):
super(MyBox,self).__init__(*args,**kwargs)
self.tobeupd = '#'
def upd_ltxt(self):
threading.Thread(target=self.update_label).start()
def update_label(self):
for i in range(1,10):
print(self.tobeupd)
self.tobeupd = str(i)
input('Write something: ') # new line, see edit below
class updApp(App):
def build(self):
return MyBox()
if __name__ == '__main__':
updApp().run()
Now its worth mentioning that you can keep pushing the button and start threads, even if the first did not finish yet. This might be an unwanted behavior.
This can be prevented by disabling the button in the beginning of the thread, and enabling it again at the end.
Give the button an id in kv:
Button:
id: updatebutton
text: 'Start Update'
on_release: root.upd_ltxt()
And in the thread do like this:
def update_label(self):
self.ids.updatebutton.disabled = True
for i in range(1,10):
self.tobeupd = str(i)
input('Write something: ')
self.ids.updatebutton.disabled = False
You can also use Kivys clock Class, which is an Event dispatcher. It will schedule an event, which is a function. For example, updating your labels text.
from kivy.clock import Clock
def to_be_called_back(self,dt):
print("This function should be periodically executed")
def do_the_loop(self):
Clock.schedule_interval(self.to_be_called(),0.5)
Here the function to_be_called() will be called every 0.5 seconds. The dt variable stands for deltatime and is just apparently needed by the Clock class (without it, it made problems with my code)
I'd still put the do_the_loop() function into a separate thread. But thats what kivy provides for it. If you want to know more about the clock Class head over here.
I believe all you need to do is put
root.update()
whenever you want the GUI to update, so therefore in the loop, after the text change has been made. Works in tkinter like a charm. You can also restrict the update to a specific text area by changing root (or main) to the name of the text area.

How to get console output printed using kivy

I have been trying to learn how to use Kivy python and I would like to
Know how to interact with the Os console/terminal to run commands and
receive results. The tutorials I have seen so far only show how to create
widgets,buttons,etc
For example how do You get the result from running the command "uname"
displayed in kivy. With such code below. using "on press". How can I let it interact with the OS run a command and display it back in the kivy app. Is there any tutorial on creating desktop apps/utilities
from kivy.app import App
from kivy.uix.button import Button
class tutap(App):
def build(self):
return Button(text="Press here")
tutap().run()
Update:
Here is example of what Im trying to achieve.This uses the easygui module:
import subprocess
from easygui import *
msg= "what you want"
out = subprocess.check_output("uname -a",shell=True)
title = "My choice"
choices=["kernel version","nothing"]
choice=boolbox(msg,title,choices)
if choice==1:
msgbox(out)
elif choice==0:
msgbox("The End")
The simplest way I can think to do this is just make a function that writes to a file at the top of your file like this
def printlog(message):
with open('./log.txt','a') as f: f.write(message+"\n")
then in your program when ever you want the print out put simply put printlog("whatever you wanted printed!")
The file will be saved in the same folder as your program.
More of a you can theoretically open it while the program is running, but this is more useful as a postmourtum.
I don't really see the point of doing something like that but if you want you can call App.run() method in separate thread so it won't block command line.
An example using cmd module:
import logging
logging.getLogger("kivy").disabled = True
from kivy.app import App
from kivy.uix.listview import ListView
from cmd import Cmd
from threading import Thread
class MyApp(App):
def build(self):
self.lv = ListView()
return self.lv
def update(self, line):
self.lv.adapter.data.append(line)
return "list updated"
class MyCmd(Cmd, object):
def __init__(self, app, *args):
super(HelloWorld, self).__init__(*args)
self.app = app
def do_EOF(self, line):
self.app.stop()
return True
def default(self, line):
ret = self.app.update(line)
print(ret)
if __name__ == '__main__':
app = MyApp()
Thread(target=app.run).start()
MyCmd(app).cmdloop()
Here I how I went about getting console command output.
The python code first:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.properties import ObjectProperty
from kivy.uix.label import Label
import subprocess
class shellcommand(BoxLayout):
first=ObjectProperty()
second=ObjectProperty()
third=ObjectProperty()
def uname(self):
v=subprocess.check_output("uname -a",shell=True)
result=Popup(title="RESULT",content=Label(text="kernel is\n" + v))
result.open()
def date(self):
d=subprocess.check_output("date",shell=True)
res=Popup(title="DATE",content=Label(text="the date today is\n" + d))
res.open()
def last(self):
last=subprocess.check_output("w",shell=True)
ls=Popup(title="LOGIN",content=Label(text="logged in \n" + last))
ls.open()
class shellApp(App):
def build(self):
return shellcommand()
shellApp().run()
And then the kivy file named shellapp.kv
<shellcommand>:
orientation: "vertical"
first:one
second:two
third:three
canvas:
Rectangle:
source: "snaps.png" #location of any picture
pos: self.pos
size: self.size
BoxLayout:
orientation: "horizontal"
Button:
id:one
text: "UNAME"
background_color: 0,0,0,1
font_size:32
size_hint:1,None
on_press: root.uname()
Button:
id:two
text: "DATE"
background_color: 1,1.5,0,1
font_size:32
size_hint:1,None
on_press: root.date()
Button:
id: three
text: "LOGGED IN"
background_color: 1,0,0,1
font_size:32
size_hint: 1,None
on_press: root.last()
If there is a way to improve this code please let Me know how to.Thanks

Categories

Resources