Running multiple Kivy apps at same time that communicate with each other - python

I would like my Kivy application to be able to spawn multiple apps (i.e. new windows) on a Windows machine that can communicate with each other.
ScreenManager and Popup options will not cut it because they live in the same window..I need to be able to drag new screens across multiple monitors and therefore need multiple windows.
Kivy docs explicitly state that "Kivy supports only one window
per application: please don't try to create more than one."
A google search produces this simple approach of simple spawning a new app from within another app, like so:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
class ChildApp(App):
def build(self):
return Label(text='Child')
class MainApp(App):
def build(self):
b = Button(text='Launch Child App')
b.bind(on_press=self.launchChild)
return b
def launchChild(self, button):
ChildApp().run()
if __name__ == '__main__':
MainApp().run()
However, when I do this, it launches the app within the same window and crashes, and my terminal spits out like crazy:
Original exception was:
Error in sys.exceptionhook:
I get the same result if instead of ChildApp().run() I do multiprocessing.Process(target=ChildApp().run()).start()
Using the subprocess library gets me closer to what I want:
# filename: test2.py
from kivy.app import App
from kivy.uix.label import Label
class ChildApp(App):
def build(self):
return Label(text='Child')
if __name__ == '__main__':
ChildApp().run()
# filename: test.py
from kivy.app import App
from kivy.uix.button import Button
import subprocess
class MainApp(App):
def build(self):
b = Button(text='Launch Child App')
b.bind(on_press=self.launchChild)
return b
def launchChild(self, button):
subprocess.call('ipython test2.py', shell=True)
if __name__ == '__main__':
MainApp().run()
This spawns the child window without error, however now the main window is locked (white canvas) and if I close the child window, it just gets reopened.
They need to be able pass data between one another. Any ideas on how to do this correctly in Windows? This post seems to suggest that this is possible but I'm not sure where to start.

I tried baconwichsand's code and can confirm with Python 3.6 and Windows 10 it does not work. Apparently only top level object classes can be pickled, and since both apps inherit from the App class python throws an error. However a top level definition that simply executes the ChildApp().run() command can be pickled and works. Here is my working code.
import multiprocessing
from kivy.app import App
from kivy.uix.label import Label
class MainApp(App):
def build(self):
return Label(text='Main App Window')
class OtherApp(App):
def build(self):
return Label(text='Other App Window')
def open_parent():
MainApp().run()
def open_child():
OtherApp().run()
if __name__ == '__main__':
a = multiprocessing.Process(target=open_parent)
b = multiprocessing.Process(target=open_child)
a.start()
b.start()
And here is the code I am using, including the Builder to use a shared .kv file for both windows.
import multiprocessing
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.widget import Widget
class MainRoot(Widget):
pass
class OtherRoot(Widget):
pass
class MainApp(App):
def build(self):
Builder.load_file('B:\Python_Codes\Testing Grounds\shared.kv')
main = MainRoot()
return main
class OtherApp(App):
def build(self):
Builder.load_file('B:\Python_Codes\Testing Grounds\shared.kv')
other = OtherRoot()
return other
def open_parent():
MainApp().run()
def open_child():
OtherApp().run()
if __name__ == '__main__':
a = multiprocessing.Process(target=open_parent)
b = multiprocessing.Process(target=open_child)
a.start()
b.start()

I'm not sure why it doesn't work with multiprocessing (I've never tried it), but it should at least work with subprocess. The reason your main window is locked is because subprocess.call blocks the thread that calls it while it waits for the subprocess to finish and return a result.
You want to use subprocess.Popen instead, which does not block.

bj0's answer regarding subprocess was correct.
Even better, I figured out how to do this via multiprocessing, which allows better communication and passing of information between apps. It wasn't working before because I did multiprocessing.Process(target=ChildApp().run()).start() when it should be multiprocessing.Process(target=ChildApp().run).start(). The following works
# filename: test.py
from kivy.app import App
from kivy.uix.button import Button
from test2 import ChildApp
import multiprocessing
class MainApp(App):
def build(self):
b = Button(text='Launch Child App')
b.bind(on_press=self.launchChild)
return b
def launchChild(self, button):
app = ChildApp()
p = multiprocessing.Process(target=app.run)
p.start()
if __name__ == '__main__':
MainApp().run()
# filename: test2.py
from kivy.app import App
from kivy.uix.label import Label
class ChildApp(App):
def build(self):
return Label(text='Child')
if __name__ == '__main__':
ChildApp().run()

Working on Ubuntu Python 3.10
Calling a subprocess in a multiprocess.
main.py
import multiprocessing
import subprocess
import shlex
#my kivy code..
def sub_window():
subprocess.call(shlex.split('python3 test.py'))
if __name__ == '__main__':
b = multiprocessing.Process(target=sub_window)
b.start()
MyApp().run()
test.py
#another kivy app script..
OtherApp().run()

Related

Kivy not opening new window on button press? (Subprocess/Popen, Python)

I am trying to open a new window upon a button press with kivy.
However, when I try to open the file with the separate window in it, it simply doesn't do anything, however I do know it runs the function the button calls.
Can any help with why this is happening?
Thank you
# Banned Book Project (Name pending) for digitech subject, Y13
from multiprocessing import Manager
from tkinter import Grid
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
import selenium
from selenium import webdriver
import subprocess
from kivy.uix.screenmanager import Screen,ScreenManager
def bannedBooksMain(self):
from subprocess import Popen, PIPE
process = Popen(['python3', 'seearch_parameters.py'], stdout=PIPE, stderr=PIPE)
class mainApp(GridLayout):
def __init__(self, **kwargs):
super(mainApp, self).__init__(**kwargs)
self.cols = 2
btn1 = Button(text='Database (Search)')
btn1.bind(on_release=bannedBooksMain)
self.add_widget(btn1)
class MyApp(App):
def build(self):
return mainApp()
MyApp().run()

can't import kivy properly

I am trying to learn kivy in python and I have built a small program that should display hello world on the screen but when I run it I get:
cannot import name 'app' from 'kivy.app'
code:
from kivy.app import app
from kivy.uix.label import label
class MyApp(App):
def build(self):
return label(text="hello world")
if __name__ == "__main__":
MyApp.run()
and I am running this in a virtual enviroment
Good day. As mentioned by buran in the comments of your question, your import code must change to:
from kivy.app import App
from kivy.uix.label import Label
Even later in your code, MyApp inherits from App.
Watch your syntax. Classes generally begin with capital letters.

How to update a Kivy Scrollview Label in real time?

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.

Update Kivy ProgressBar in Real-time from external input

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

Kivy how to use Builder.load_file?

I have the following directory structure:
project/
controller/
__init__.py
app1.py
view/
app1.kv
main.py
My main.pyis:
from controller.app1 import App1
def main():
App1().run()
if __name__ == '__main__':
main()
My app1.pyis:
from kivy.app import App
from kivy.lang import Builder
Builder.load_file('view/app1.kv')
class App1(App):
pass
I am running:
kivy main.py
However, the contents of my Kivy is not loaded.
When you load a .kv file using Build.load_file make sure the widget is the root
to avoid it from returning None.read the doc to see other things you do with Builder
from kivy.app import App
from kivy.lang import Builder
class App1(App):
def build(self):
self.root = Builder.load_file(os.path.join(dirname(__file__),/app1.kv')
)
It turns out that the method build() has to be implemented in the class that inherits from App and this method has to return Builder.load_file('view/app1.kv'), and using #Leon suggestion my app1.py is:
from kivy.app import App
from kivy.lang import Builder
class App1(App):
def build(self):
return Builder.load_file(
os.path.join(dirname(__file__), '../view/app1.kv')
)
Now, the Kivy file is properly loaded.

Categories

Resources