Kivy app exiting on screen change - python

I'm trying to port my code from the .kv file all to Python (it seems easier for me that way). I've got two screens and I've been switching to the next one using root.manager.current = "main" in the kv file. However, when writing this bit in Python code, I've run into the crash of my app. This is the code I have:
class CustomScreenManager(ScreenManager):
def switch_view(self):
self.current = 'main'
class Intro(Screen):
pass
class Chat(Screen):
pass
class ChatApp(App):
def build(self):
Screens = CustomScreenManager(transition = NoTransition())
intro = Intro()
chat = Chat(name = "main")
bt1_intro = Button(on_press = Screens.switch_view())
intro.add_widget(bt1_intro)
Screens.add_widget(intro)
Screens.add_widget(chat)
return Screens
if __name__ == "__main__":
ChatApp().run()
ChatApp().screen_manager
I've also tried the switch_to method but it also crashes the app. What can I do to avoid the crashing and get the expected behaviour? Thanks in advance.

Change the definition of switch_view to
def switch_view(self, *args):
and add the Button with
bt1_intro = Button(on_press = Screens.switch_view)
The app crashes because in your original assignment of bt1_intro switch_view gets called (rather than passed to the function), and at the time the screen does not exist.

Related

VideoPlayer error loading video only plays sound - Warning: Removing channel layout 0x3, redundant with 2 channels

I am trying to use VideoPlayer to load a local video. The program runs fine when it is standalone (in its own file). But, when I bring it into my main program, it loads the video but only plays the sound. I get an error (warning, to be more precise) message:
[WARNING] [ffpyplayer ] [ffpyplayer_abuffersink # 000001e84fb55580]
Removing channel layout 0x3, redundant with 2 channels
Here's the standalone version:
import cv2
from kivy.uix.videoplayer import VideoPlayer
from kivymd.app import MDApp
class PlayVid(MDApp):
def build(self):
player = VideoPlayer(source="Roadhouse.mp4")
player.state = "play"
player.options = {"eos": "stop"}
player.allow_stretch = True
return player
if __name__ == '__main__':
PlayVid().run()
And here is the same thing split in functions and class in my main program:
class PlayVid(MDApp):
def playnow(self):
# player = VideoPlayer(source='Roadhouse.mp4')
# player.state = "play"
video = VideoPlayer(source='Roadhouse.mp4')
video.state = "play"
# player.options = {"eos": "stop"}
# player.allow_stretch = True
return video
class SecondWindow(Screen):
def build (self):
sm = ScreenManager()
self.sec_screen = SecondWindow()
sm.add_widget(self.sec_screen)
return sm
def start_play(self):
PlayVid.playnow(self)
A kv button in SecondWindow triggers start_play and then PlayVid.playnow(self). That's all. It runs, loads the file, and then just plays the sound. No video.
I can't understand what I'm doing wrong. Help?
Thanks!
I created a standalone program for it, and it works. I just can't understand why it drops the video and plays the sound when brought into the main program.
Your playnow() method returns the VideoPlayer widget, but that return is ignored. You must add that widget to your GUI in order to see it. Try using this version of start_play():
def start_play(self):
v = MDApp.get_running_app().playnow()
self.add_widget(v)

How to properly assign Kivy label text from Python file?

I'm pretty new to Kivy and Python so apologies for any glaring error.
I'm trying to make a home screen which displays the name used to login on the previous screen. Code snippets as follows:
My main app class;
class ProDuck(App):
logged_in_user = StringProperty()
print(logged_in_user)
def build(self):
self.icon = "LogoIcon.png"
GUI = Builder.load_file("GUI.kv")
return GUI
(Correctly formatted with indentation though)
Part of the login which changes the StringProperty, which inside another screen class. The logging_in_user is from self.username.text within that screen class;
if attempt == target_pass_hash[0]:
print("Login successful!")
print(logging_in_user)
ProDuck.logged_in_user = logging_in_user
print("New variable = " + ProDuck.logged_in_user)
Relevant part of .kv file;
GridLayout:
Label:
id: nametag
text: app.logged_in_user #<---
color: "grey"
The app runs, and prints the right variables, however the label which gets displayed is blank. I am really not sure why the label is not showing any text, am I assigning the StringProperty wrong in the login part? Appreciate any help :)
The expression ProDuck.logged_in_user is not the correct way to access a Property. You need to reference it through the instance of ProDuck. One way to do that is to use App.get_running_app().logged_in_user. So your code block could be:
if attempt == target_pass_hash[0]:
print("Login successful!")
print(logging_in_user)
App.get_running_app().logged_in_user = logging_in_user
print("New variable = " + App.get_running_app().logged_in_user)

How to switch between two PyQt5 MainWindow widgets

I'm writing a program that has two different parts to it - let's call them sub1 and sub2. When I initially run my program, sub1 displays and I'm loading sub2 in the background but not displaying it. I have a menu action in sub1 that allows you to switch to sub2 and there is a menu action in sub2 that allows you to switch back to sub1. The problem I have is when trying to switch back from sub2 to sub1. Going from sub1 to sub2 works fine; sub1 gets hidden and sub2 is displayed. However, when trying to show sub1 again, sub2 doesn't get hidden. I'm new to PyQt as well as Python so I don't know all the intricacies yet. So, the method I'm using is just something I figured through trial and error and by no means needs to be this way. Simplified code below.
#mass module
class MASS(PyQt5.QtWidgets.QMainWindow, massUi.Ui_MainWindow):
def __init__(self):
super(MASS, self).__init__()
self.actionSwitchToCompEval.triggered.connect(self.switchToCompEval)
def switchToCompEval(self):
massForm = main.massForm
massForm.hide()
compForm = main.compForm
compForm.show()
def showMass(self):
main(1)
def main(initiate=None):
if initiate == None:
main.app = PyQt5.QtWidgets.QApplication(sys.argv)
main.massForm = MASS()
main.compForm = CompEval.CompEval()
main.massForm.show()
main.app.exec_()
elif initiate == 1:
main.massForm = MASS()
main.compForm = CompEval.CompEval()
main.compForm.hide()
main.massForm.show()
elif initiate == 2:
pass
if __name__ == '__main__':
main()
#comp module
class CompEval(PyQt5.QtWidgets.QMainWindow, compEvalUi.Ui_MainWindow):
def __init__(self):
super(CompEval, self).__init__()
self.actionSwitchToMASS.triggered.connect(self.switchToMass)
def switchToMass(self):
mass.MASS().showMass()
def main():
form = CompEval()
form.show()
In the switchToCompEval function, it seems to work fine to reference the main.massForm and main.compForm variables but when I try to go from sub2(comp) back to sub1(mass) I get an error that the function does not contain that variable, which seems odd to me. I am aware that several aspects of how I have this setup at the moment is odd and far from ideal, so any suggestions would be appreciated. Thanks.
So after much experimentation I determined the best solution to this problem is to combine the modules into one. IF you have multiple MainWindow widgets and you need to be able to switch back and forth between them, keep your classes that access the widgets all in the same module.
So I have my two widget classes:
import PyQt5
import sys
#Below are the modules that are auto-generated when using Qt Designer to make an interface
import compWidget as compEvalUi
import massWidget as massUi
class MASS(PyQt5.QtWidgets.QMainWindow, massUi.Ui_MainWindow
def __init__(self):
super(MASS, self).__init__()
#setupUi function is in the auto-generated module and contains all the attributes of the interface
self.setupUi(self)
#menuSwitch is the name of the button/menu item that would need to be clicked
#in order to switch windows.
#The example shown here is if the action was attached to a menu-dropdown item
#which is why 'triggered' is used as opposed to 'clicked' if it were attached to a button
self.menuSwitch.triggered.connect(self.switchToCompWidget)
def switchToCompWidget(self):
INITIALIZE.massForm.hide()
INITIALIZE.compForm.show()
class CompEval(PyQt5.QtWidgets.QMainWindow, compEvalUi.Ui_MainWindow):
def __init__(self):
super(CompEval, self).__init__()
self.setupUi(self)
self.menuSwitch.triggered.connect(self.switchToMassWidget)
def switchToMassWidget(self):
INITIALIZE.compForm.hide()
INITIALIZE.massForm.show()
class INITIALIZE:
def __init__(self):
app = PyQt5.QtWidgets.QApplication(sys.argv)
INITIALIZE.massForm = MASS()
INITIALIZE.massForm.show()
INITIALIZE.compForm = CompEval()
app.exec_()
def main():
program = INITIALIZE()
if __name__ =='__main__':
main()
You could use signals and slots to separate the logic instead of sharing a global parameters between classes.

Kivy - changing button color on press

I've recently started using kivy to design GUI for my python app. In the app one of the actions is sending requests to server using provided API. After a couple of tests I've noticed one annoying thing that happens only when I want to make a request - the button doesn't change color on press (I'm talking about default action). However, it is changing when simple print() is used.
How I can fix it? Any idea?
This is the code of my test app:
class TestApp(App):
def build(self):
parent = Widget()
btn = Button(text='Add')
btn.bind(on_press=self.add)
parent.add_widget(btn)
return parent
def add(self, obj):
print("Adding")
request = Request(url, urlencode(post_fields).encode())
urlopen(request)
That happened most likely because the UI froze. Theself.add is called, but right after that the UI waits until the request is done, which for you might result in that.
Try to do it like this:
import threading
class TestApp(App):
def build(self):
parent = Widget()
btn = Button(text='Add')
btn.bind(on_press=self.add)
parent.add_widget(btn)
return parent
def add(self, obj):
print("Adding")
#self.printer() # freezing
threading.Thread(target=self.printer).start()
def printer(self, *args):
while True:
print 'blob'
TestApp().run()
Also, instead of on_press use rather on_release. Prevents accidental events if I remember correctly (checks for collision touch↔button area).

How to run a Kivy EventLoop in slave mode?

Related question here
I've discovered a slave property for the runTouchApp function that prevents the Kivy's event loop from running and forces to update it from somewhere else.
Here's a part of app.py where that property is used:
# we are in a slave mode, don't do dispatching.
if slave:
return
try:
if EventLoop.window is None:
_run_mainloop()
else:
EventLoop.window.mainloop()
finally:
stopTouchApp()
Here, if the app is not being run in slave mode, we have two choices on how to run the mainloop.
The first one, _run_mainloop() function works pretty straight-forward - it simply calls the EventLoop.run(), which in turn infinitely calls EventLoop.idle().
That could lead us to believe that to keep the GUI running, we only need to call idle.
But then there's the second option, which calls the kivy.core.window.WindowSDL's method mainloop.
That method works by calling another method, the _mainloop and this is where it gets interesting. The definition of said method is huge and it handles all sorts of events.
So okay, I ran my app in slave mode:
class TestApp(App):
def start_event(self):
pass
def build(self):
return Button(text = "hello")
def run(self):
# This definition is copied from the superclass
# except for the start_event call and slave set to True
if not self.built:
self.load_config()
self.load_kv(filename=self.kv_file)
root = self.build()
if root:
self.root = root
if self.root:
Window.add_widget(self.root)
window = EventLoop.window
if window:
self._app_window = window
window.set_title(self.get_application_name())
icon = self.get_application_icon()
if icon:
window.set_icon(icon)
self._install_settings_keys(window)
self.dispatch('on_start')
runTouchApp(slave = True)
self.start_event() # Here we start updating
self.stop()
Now, if I put this in the start_event method (by expectations):
def start_event(self):
while True:
EventLoop.idle()
Guess what, the app doesn't respond to touch events and freezes.
So I tried to call the Window's mainloop instead:
def start_event(self):
EventLoop.window.mainloop()
And suddenly everything started working normally again. But the problem here is that such a call blocks forever, as it is an infinite loop, so there's no one-time update call like EventLoop.idle
How to keep the app running using such one-time calls?
Well, this is Python so assuming you want to stick to WindowSDL provider, you can always monkey patch this mainloop function so it won't be infinite:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.base import EventLoop
Builder.load_string('''
<MyWidget>:
Button:
text: 'test'
on_press: print('doing my job')
''')
# https://github.com/kivy/kivy/blob/master/kivy/core/window/window_sdl2.py#L630
def mainloop(self):
# replaced while with if
if not EventLoop.quit and EventLoop.status == 'started':
try:
self._mainloop()
except EventLoop.BaseException as inst:
# use exception manager first
r = EventLoop.ExceptionManager.handle_exception(inst)
if r == EventLoop.ExceptionManager.RAISE:
EventLoop.stopTouchApp()
raise
else:
pass
class MyWidget(BoxLayout):
pass
if __name__ == '__main__':
from kivy.base import runTouchApp
runTouchApp(MyWidget(), slave=True)
# monkey patch
EventLoop.window.mainloop = mainloop
while True:
EventLoop.window.mainloop(EventLoop.window)
print('do the other stuff')
if EventLoop.quit:
break
It's really hacky though and thus I'd not recommend running something like that in a production code.

Categories