How do I call a function when the screen is rotated? - python

Problem
I am building in app (with Kivy) which renders some custom widgets to the screen. Unfortunately, their positions (relative to other widgets) changes when the tablet on which I am running the app is rotated. Now I have built a function that repositions them, and I would like to call this function when the tablet is rotated. How can I implement this?
My attempts so far...
I have seen from the Kivy documentation that the Window class has an on_rotate event, and have tried to incorporate this into my program in several different ways.
First, I tried implementing the callback after the if __name__ == '__main__' statement. Something like:
if __name__ == '__main__':
app = MainApp()
Window.on_rotate = lambda: app.reposition_widgets()
app.run()
That did nothing. In vain I also tried:
if __name__ == '__main__':
app = MainApp()
Window.on_rotate(app.reposition_widgets)
app.run()
I then attempted to bind the widgets to on_rotate events, something like:
class CustomWidget(Widget):
def __init__(self, **kwargs)
super().__init__(**kwargs)
self.bind(on_rotate = self.reposition_widgets)
That did nothing. Neither did:
class CustomWidget(Widget):
def __init__(self, **kwargs)
super().__init__(**kwargs)
on_rotate = reposition_widgets
My final attempt was to create a Window class in the accompanying kv file and the Python file, then specify the on_rotate event from there. Something like:
#kv file
Window:
on_rotate: app.reposition_widgets()
That didn't work either.
Possible work around
To be honest, rotating the screen is not all that useful for my app, and so it wouldn't be all that bad if I just disabled screen rotation. However, I was not able to find a good way of doing so in the documentation. Do you know how I would go about doing this?
Binding the widgets to the on_pos event - or something similar. Something like:
class CustomWidget:
def reposition_widgets():
# Code here
on_pos = reposition_widgets
The problem with this is that the widgets move about a lot, which means that the function gets called a lot - causing problems elsewhere.
Let me know if you would like to see more code. I have over 1000 lines spread over several files, and felt that just copy-and-pasting wouldn't be particularly useful.

I was able to solve this issue using the on_size event.
class CustomWidget(Widget):
def reposition_widgets(self):
# Reposition logic here
on_size = reposition_widgets
This worked quite well actually. I added an additional parameter in the reposition_widgets function to differentiate between when I was repositioning because of on_size and when I was repositioning because I was calling it (see example below). However, this is not always necessary.
class CustomWidget(Widget):
def reposition_widgets(self, rotation=False):
if rotation:
# When function is called due to on_size
else:
# When function is called in code
on_size = lambda self, *args: self.reposition_widgets(rotation=True)

Related

Python3.8 with PySide2 Class to Class Usage

My GUI essentially wraps various backend PowerShell scripts that perform some automated functions. Kind of beside the point, but alright, here's where I'm stuck at.
I've got my interface designed in Qt Designer, outputted to a .ui file, converted to a .py file via PySide2-UIC, and a mainwindow class that is a subclass of the main window class I created in Qt Designer. All is well. No issues with any of that.
I'm now on to a part in my programming that I'm capturing form data from QWidgets (which is working) to a list. I've got a completely custom written class that is meant to handle taking that user input, setting other variables like filenames or paths to certain configuration files that are needed, and executing a subprocess PowerShell command with all of that information. Where I'm stuck at is trying to determine what the right place is to instantiate this custom object, inside my MainWindow class, outside my MainWindow class? But if so, where? Here's some simplified code to help explain my dilemma.
Interface Sequence
App start
MainWindow appears
User browses to form with input controls
User enters info like (IP address, username, password)
User clicks button that is connected to a method in the class
Method recurses through the child widgets on the page and captures info into a dictionary via finding qLabels and qLineEdit (buddies)
Questions:
How do I call the next method (only once even though the capturing of data is recursive)? I'm thinking about just connecting the signal to a second method that handles taking the captured data and sending/setting it into the custom class object. However, when I instantiate my custom object inside of the MainWindow class and I try to reference the object by self.customObject.sendUsesrInput(self.userInputVariable), PyCharm doesn't think self is defined inside this particular method. It doesn't properly highlight the word "self" like in the rest of the class definition, and it suggests that I need to import self.
Update
I was able to clear the errors around "import self" in PyCharm. It had something to do with improper spaces vs. tabs, even though I only ever use the tab key to do indentation. Might need to go and check my inpection settings closer. The other questions still stand though. Where is the best place to call methods on my custom class to "form a command", and "run a command", should that be executed by the mainWindow class, or should I set a flag on the customObject class that then triggers those other actions? Or more generally, should an object be in charge of executing it's own functions/methods, something tells me not usually, but I can't be sure. Also, if there are any books on the matter, I'd be happy to do my own research. I'm currently reading "Rapid GUI Programming" but not sure if this topic is covered in the later chapters just yet.
So I guess my question is, where do I handle the customObject class, in the mainWindow class, or in some other place? If so, where?
I apologize if this question is NOT clear. I promise to update as necessary to help work through this.
Here's come simplified code examples:
class customClass(object): # this is actually in a separate file but for argv sake
def __init__(self):
self.userInput = ""
self.file1 = ""
self.file2 = ""
self.otherstuff...
def setUserInput(self, uinput):
self.userInput = uinput
def dostuffwithdata(self):
# method to execute subprocess command
class MainWindow( QMainWindow ):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.customObject = customClass.customCmdClass()
self.cmddata = dict()
self.ui.toolButton.clicked.connect(self.getformdata)
def getformdata(self):
# recurses through QWidgets and grabs QLabels and QLineEdit.Text() and updates dict()
for w in widgets:
if w is qlabel:
k = w.text()
v = w.buddy().text()
self.cmddata.update({k: v})
""" all the above works fine. what doesn't work is this part"""
# at this point I want to send the collected data to the customObject for processing
def senddatatocustomObject(self):
self.customObject.setUserInput(self.cmddata) """but this says that 'self' isn't defined.
I know it has to be because of the object in an object, or something I'm doing wrong here.
**Update**: figured this out. PyCharm was freaking out about some sort of
perceived indentation error despite there not appearing to actually be one.
Was able to correct this. """
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
In an effort to close this out, I offer this answer to my previously posted question about where to put the "logic" and flow in my code.
Given that this is a graphical application without any back-end services, it makes the most sense to put most of the user-interaction logic and flow within the MainWindow object because that is essentially the control center of my program. When a user clicks or enters something, it is going to happen on the MainWindow, when a state changes, it happens (mostly) on the MainWindow or is directly tied to the MainWindow in some way. Therefore, it makes sense to include the majority of my method calls, user-input-flow logic, and other code, in the MainWindow class/object of my program.
My other classes and objects are there to capture state and to perform actions on different sets of data, but in most cases, these auxiliary classes/objects will/should be controlled by the MainWindow of my application.
This is certainly not the only way to write this application or others, but I believe this at least answers my previously posted question(s).

Kivy add widget from thread

I am trying to add a widget to the screen via a thread. This is the function I am calling from within the thread:
#mainthread
def add_message(self, text):
message_lab = Label(text=text, markup=True)
message_lab.text_size = message_lab.width, None
self.add_widget(message_lab)
When I run this, I can see that the widget has been added into the children variable of the layout, but it does not show up. When I tried to make a small segment of code, it worked. However, I still don't know what the problem is. Any help I could get would be greatly appreciated!

Python and Tkinter: object oriented programming query

I am trying to learn python, Tkinter and oop. Below is the code that I wrote after following tutorial on effbot.org
from Tkinter import Tk, Frame, Label
class Nexus(object):
"""Top level object which represents entire app"""
def __init__(self, main_window):
self.nexus_frame = Frame(main_window)
self.nexus_frame.pack()
self.label = Label(main_window, text="Tkinter")
self.label.pack()
def main():
main_window = Tk()
nexus_app = Nexus(main_window)
main_window.wm_title("Hello World Window")
width = main_window.winfo_screenwidth()
height = main_window.winfo_screenheight()
main_window.wm_minsize(width=width-100, height=height-100)
main_window.mainloop()
if __name__ == "__main__":
main()
Here a top level window is created first and it is passed as argument to Nexus class where I am adding a frame and a label to the frame. Then I am setting the size of top level window relative to current screen size back in the main function.
My question is why was the top level window create in main function?
Could it not be created inside __init__ of Nexus class itself?
What difference would it make if main_window was create inside __init__ of Nexus class and mainloop() was started therein?
Once Tk.mainloop is entered, no further code will be executed. Instead, the Tk event loop will take over (hence the name).
What that means is that if you, eg, did something like this:
def main():
...
main_window.mainloop()
print 'Hello world!'
then that print statement would never be executed (or, at least, not while the GUI is running).
So, with that in mind, why is there a problem with creating the root window and executing main loop within the constructor (the __init__ statement)? Well, two basic reasons:
It would mean that the constructor never returns, which is unexpected. If a programmer sees this:
def main():
Nexus()
print 'Hello world!'
then he or she will expect that print statement to be executed. As a rule, you don't expect creating an instance of a class to be the kind of thing which will cause an infinite loop (as the event loop is).
Related to that is the second reason: it would not be possible to create more than one instance of Nexus, because as soon as you create one, Tk.mainloop will take over. Again, that's unexpected: a class is a description of a type of object, and you would normally expect to be able to instantiate more than one object like that.
At the moment, if you write:
def main():
...
Nexus(main_window)
Nexus(main_window)
then you'll get two copies of your Nexus window on the screen. That's expected, and sensible. The alternative would not be.
So what's the take-away message?
When you're dealing with GUI programs, entering the event loop is the last thing you want to do. Your setup might involve creating one object (as now), or it might involve creating many objects (eg, a complex GUI app might have two or three windows).
Because we want to be able to write similar code in both cases, the usual approach is to create the root window (the Tk object) once, and then pass it in as a reference to any classes that need to know about it.

Accessing GUI elements from outside the GUI class

I'm hoping someone can help me with a Qt designer question. I'm trying to modify GUI elements from outside the class calling the GUI file. I've set up example code showing the structure of my programs. My goal is to get func2, in the main program (or another class) to change the main window's statusbar.
from PyQt4 import QtCore, QtGui
from main_gui import Ui_Main
from about_gui import Ui_About
#main_gui and about_gui are .py files generated by designer and pyuic
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_Main()
self.ui.setupUi(self)
self.ui.actionMyaction.triggered.connect(self.func1)
#Signals go here, and call this class's methods, which call other methods.
#I can't seem to call other methods/functions directly, and these won't take arguments.
def func1(self):
#Referenced by the above code. Can interact with other classes/functions.
self.ui.statusbar.showMessage("This works!")
def func2(self):
StartQT4.ui.statusbar.showMessage("This doesn't work!")
#I've tried many variations of the above line, with no luck.
#More classes and functions not directly-related to the GUI go here; ie the most of the program.
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = StartQT4()
myapp.show()
sys.exit(app.exec_())
I'm trying to get func2 to work, since I don't want my whole program to be under the StartQT4 class. I've tried many variations of that line, but can't seem to access GUI items from outside of this class. I've tried sending signals as well, but still can't get the syntax right.
It's possible that my structure is bogus, which is why I posted most of it. Essentially I have a .py file created by Designer, and my main program file, which imports it. The main program file has a class to initiate the GUI, (and a class for each separate window). It has signals in this class, that call methods in the class. These methods call functions from my main program, or other classes I've created. The end of the program has the if __name__ == "__main__" code, to start the GUI. Is this structure bogus? I've read many tutorials online, all different, or outdated.
Your func1 method is a way to go - since ui is a field in StartQT4 class, you should directly manipulate with its data only within the same class. There is nothing wrong that you have all user interface functionality for one widget in one class - it is not a big issue if you have only two classes in your code, but having several classes to reference the fields directly is potential nightmare for maintentace (what if you change the name of statusbar widget?).
However, if you actually want to edit it from func2, then you need to pass the reference of StartQT4 object to it, because you need to specify for what instance of window you need to change status bar message.
def func2(qtWnd): # Self should go here if func2 is beloning to some class, if not, then it is not necessary
qtWnd.ui.statusbar.showMessage("This should work now!")
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = StartQT4()
myapp.show()
func2(myapp)
sys.exit(app.exec_())

wxpython - Binding events in external files

I am trying to bind events from a GUI file to use code from another file (effectively a "front end" and a "back end"). I can get the back end and front end working within the same file, but when I try to move them into separate files, I have issues getting the back end to see parts (labels, buttons, etc.) of the front end.
I. E. I need the back end code to change labels and do math and such, and it would need to affect the GUI.
I have provided a simple version of my program. Everything works with the exception of the error I get when I try to make the back end see the parts of the GUI.
mainfile.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
import label_changer
class foopanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, id=wx.ID_ANY)
box = wx.BoxSizer()
btn = wx.Button(self,1,"Press")
btn.Bind(wx.EVT_BUTTON,label_changer.change_label(self))
box.Add(btn)
self.lbl = wx.StaticText(self,1,"Foobar")
box.Add(self.lbl)
self.SetSizerAndFit(box)
class main_frame(wx.Frame):
"""Main Frame holding the main panel."""
def __init__(self,*args,**kwargs):
wx.Frame.__init__(self,*args,**kwargs)
sizer = wx.BoxSizer()
self.p = foopanel(self)
sizer.Add(self.p,1)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = main_frame(None,-1,)
app.MainLoop()
label_changer.py
def change_label(self):
self.p.lbl.SetLabel("barfoo")
All I want it to do is change the label of the GUI, but use an external file.
I am doing this mostly to keep my code separate and just as a learning experience.
Thanks in advance!
One solution is to modify change_label to accept an argument that identifies the label to change. For example:
def change_label(event, label):
label.SetLabel("barfoo")
Then, use lambda to create a callback that passes that argument in:
btn.Bind(wx.EVT_BUTTON, label_changer,
lambda event, label=self.p.lbl: label_changer.change_label(event, label))
Make sure you define self.lbl before you do the binding.
For more on passing arguments to callbacks see Passing Arguments to Callbacks on WxPyWiki
A common way to do this is the MVC Pattern and pubsub. See this Example.
This
btn.Bind(wx.EVT_BUTTON,label_changer.change_label(self))
needs to be
btn.Bind(wx.EVT_BUTTON,label_changer.change_label)
and this
def change_label(self):
self.p.lbl.SetLabel("barfoo")
needs to be
def change_label(event):
panel = event.GetEventObject().GetParent()
panel.lbl.SetLabel("barfoo")
To clarify, you need to pass a reference to a function to Bind that is to be called when the event occurs. wx will always pass one argument to these functions - the event. The self that you usually see in the callbacks is a byproduct of them being bound methods. Every bound method (to oversimplify, a function defined in a class) gets implicitly passed a first argument when called that is a reference to a class instance. So since you can't get to this instance the traditional way in an "external" function you have to get to it through the event object.
One more thing, you are not realy separating the gui from the logic this way. This is because the logic (label_changer in this case) needs to know about the gui and to manipulate it directly. There are ways to achieve much stronger separation (st2053 hinted at one of them) but for a relatively small program you don't need to bother if you don't want to right now, simply splitting the code in multiple files and focusing on getting the thing done is fine. You can worry about architecture later.

Categories

Resources