Accessing GUI elements from outside the GUI class - python

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_())

Related

Making a cleaner code in PyQt6 development

I have recently opted to learn programming using PyQt(6). I am trying to build a multi-tab main window where each tab is going to have their own functionality.
What I'm doing is that I designed my QMainWindow in QtDesigner and then used pyqt6.uic to import the .ui file, consequently accessing the widgets and views with findchild.
It is all going well If code like this, but the problem is that the great number of signals and slots in the code make the code very hard to debug and read after a short time.
I thought about defining each of these functionalities with their own class, but I could not make the connection between these sub classes and the main class.
I am not sure if that the correct approach to it, and if it is, then I probably miss a note or two with using multiple classes in PyQt.
Many thanks!
P.S.: I have updated the topic with a simple code to make my intention clearer Say I have main window with a tab widget consisting N tabs.
class UI(QMainWindow):
def __init__(self):
super(UI, self).__init__()
uic.loadUi('SO.ui', self)
#Instead of writing all the codes from tab1 to tabN. Use them in a clearer
#such as having their functionality in a different .py file.
#Maybe a way to inherit tab1 and tab2?
#Window runs via this method then.
class tab1(QMainWindow):
def __init__(self):
super(tab1, self).__init__()
uic.loadUi('SO.ui', self)
#signals and slots in Tab1 placed in a seperate .py file
class tab2(QMainWindow):
def __init__(self):
super(tab2, self).__init__()
uic.loadUi('SO.ui', self)
#signals and slots in Tab2 placed in a seperate .py file

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).

Initialization completed event in QT4 GUI

I have a QT4 and python 2.4 based GUI. The user starts using it by opening a file. In addition to explicit browsing, I would like to allow the user to specify a file to open as a command line argument. I am looking for some event in the QMainWindow (or wherever) that would allow me to detect when the application completed its initialization and is ready for user interaction at which point I could automatically open the file and populate the widgets. So far I could not find anything better than overriding showEvent which is not exactly it because the main window is still not visible at this point. It might be okay but I am looking for a proper way to do this. In some other UI toolkits that I have used in the past that would be something like “main form layout completed” event that would signal that the UI is safe to deal with. Is there something similar in QT4? I am running this on Linux if that matters.
You insights are greatly appreciated.
You don't need an event here. It is guaranteed that everything is loaded after __init__() and show() have run, so you can just put your code for the file opening after that.
import sys
from PyQt4 import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.setupUI()
self.show()
# normal __init__ done
if len(sys.argv) > 1:
with open(sys.argv[1]) as f:
# do stuff with file
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

PyQt Multiple Windows - how to pass functions between modules Qt Designer

I'll start with the question and then try to explain:
Is there a way for an imported module to call a function in the module that imports it?
I am just learning to use Qt and am starting with Qt Designer to get some fundamentals worked out.
I have figured out how to create more than one ".ui" file in order to get the code for multiple windows and have managed to work out how to call the multiple windows from a main application by importing the code for the two windows.
For example, starting with win1.ui and win2.ui I create win1.py and win2.py - from my main application I import win1 and win2...
Note - I got this far by following this simple tutorial : http://www.youtube.com/watch?v=bHsC6WJsK-U&list=PLF4575388795F2531&index=10&feature=plpp_video
OK - now the question. If I have a button in win2, I know how to link that button to a function in the win2.py code. What I don't know how to do is link the button in win2 to a function in my main application.
My only thought would be to add a function as an argument to the class that sets up the second window but if I do that then any changes to win2.ui will wreck the code that I have changed.
Thus, Is there a way for an imported module to call a function in the module that imports it?
I hope this is clear without adding a bunch of code that isn't really relevant...
Qt is based on event-driven programming. Generally when you start building up your widgets, what you are going to be wanting to do is providing information to receiver widgets via signals that are then processed. You don't want to explicitly have a child widget know or require to call methods on a parent widget (this is not always the case, but it is good to avoid when possible).
I'm gonna post some examples that don't have UI files for ease here, but just assume you can build the same widget with designer and have it work the same way...
testwidget.py
from PyQt4 import QtGui, QtCore
class TestWidget(QtGui.QWidget):
textSaved = QtCore.pyqtSignal(str)
def __init__( self, parent = None ):
super(TestWidget, self).__init__(parent)
# create the ui (or load it)
self.__edit = QtGui.QTextEdit(self)
self.__button = QtGui.QPushButton(self)
self.__button.setText('Save')
layout = QtGui.QVBoxLayout()
layout.addWidget(self.__edit)
layout.addWidget(self.__button)
self.setLayout(layout)
# create connections
self.__button.clicked.connect(self.emitTextSaved)
def emitTextSaved( self ):
# allow Qt's blocking of signals paradigm to control flow
if ( not self.signalsBlocked() ):
self.textSaved.emit(self.__edit.toPlainText())
testwindow.py
from PyQt4 import QtGui, QtCore
import testwidget
class TestWindow(QtGui.QMainWindow):
def __init__( self, parent == None ):
super(TestWindow, self).__init__(parent)
# create the ui (or load it)
self.__editor = testwidget.TestWidget(self)
self.setCentralWidget(self.__editor)
# create connections
self.__editor.textSaved.connect(self.showMessage)
def showMessage( self, message ):
QtGui.QMessageBox.information(self, 'Message', message)
So, here you can see that instead of thinking about it like - "when I click the button in TestWidget, I want to show a message in TestWindow" and explicitly link the two methods, you expose a signal that the TestWidget will emit out when the user performs an action, then connect that signal to the showMessage slot of the TestWindow. This way, your smaller widgets become more independent, and its more a matter of how you connect to each event that drives your application.
I could have done something like self.parent().showMessage(self.__edit.toPlainText()) within the TestWidget's emitTextSaved method to call the method directly - but this is not a good design.

PyQt4 TextEdit.clear causes crash

I have a deque type list (a queue) which I'd like to show and update in QTextEdit.
There is a function uuenda_kama in class MyForm which should do this (and some other s*** too). First pass of this function when textEdit is empty, it works like a charm, all necessary fields are updated. But on second pass as there has some text added to it, it crashes throwing me a Visual Studio debugger in face.
Tried commenting different parts out and came out that line "self.ui.textEdit.clear()" is causing this. What is wrong with it and why is it working on first pass? What can I do to fix it?
Code I have right now:
class MyForm(QtGui.QMainWindow):
...
def uuenda_kama(self):
while True:
...
if vana_que != list(que):
self.ui.textEdit.clear()
for i in que:
self.ui.textEdit.append(i)
vana_que = list(que)
sleep(1)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MyForm()
uuendamine = Thread(target=myapp.uuenda_kama)
uuendamine.start()
myapp.show()
sys.exit(app.exec_())
You should not be creating a standard python thread outside of the entire app that runs methods on your qwidgets. Instead you should have a QThread that runs non-gui related logic and then emits a signal when it wants the main thread to affect the GUI
Refer to this other question for a good example: Howto change progress by worker thread
You should never call gui methods directly outside of the main thread.

Categories

Resources