How to organize classes in pyqt GUI programming - python

I have constructed a main window GUI using qt designer and pyqt. As the program grows more complex, using only one class may result in too many methods and attributes to manage. I figured that I should construct new classes in order to make things more manageable.
My first question is, how do I know when do I add a new class for my application? Is there any rule of thumb as a general guide? Is it a good idea to add a new class for new windows/tabs?
My second question is, if I added new classes for my application, how do my new class gain access to the Ui_MainWindow design file that I designed in Qt designer? Below is the sample code for my main window. Its a simple clock which displays the current time. Lets say if I would like to create a class for the clock display itself, how can I rewrite the code using OOP efficiently?
from PyQt4 import QtGui
from myMainWindowUI import Ui_MainWindow
class MyMainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
super(MyMainWindow, self).__init__(*args, **kwargs)
self.setupUi(self)
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.Time)
self.timer.timeout.connect(self.Date)
self.timer.start(1000)
self.lcdNumber_time.setDigitCount(8)
self.lcdNumber_time.display(strftime("%H"+":"+"%M"+":"+"%S"))
self.label_dynamic_date.setText(strftime("%Y"+" "+"%B"+" "+"%d"+", "+"%A"))
def Time(self):
self.lcdNumber_time.display(strftime("%H"+":"+"%M"+":"+"%S"))
def Date(self):
self.label_dynamic_date.setText(strftime("%Y"+" "+"%B"+" "+"%d"+", "+"%A"))
app = QtGui.QApplication(sys.argv) # A new instance of QApplication
form = MyMainWindow()
form.show()
app.exec_()

In general, this isn't necessarily a Qt-specific problem. It isn't necessarily a python-specific problem either. You could extend this question to any language that supports class-based programming, or even any language with functions.
It makes sense to write a class when you want to encapsulate behavior. A class should usually provide a single purpose, and expose an interface to the outside that allows users of the class to interact with exactly the behavior you have designed. Once you have this single-purpose class, you now have reusable behavior. You could apply part of this reasoning to functions, where you say a given function has a specific purpose and once you design it to do the one bit of behavior, you now have a reusable function.
Applying this to Qt, it would make sense to move code into its own class when its logic is no longer trivial. An example would be where you are first creating and showing a QMessageBox from your main window. But then at some point you need to collect more types of information, and even pass some initial information in when you construct the dialog. While you could construct this on the fly in your main window, as needed, it would be better to move it into its own dialog class, with its own private logic of how to be constructed. Then you just instantiate one as needed, passing it the expected parameters to its constructor. Now your main window no longer has to also be concerned with constructing special dialogs.
Another point (which wasn't exactly clear from your question) is that Qt Designer UI files each represent a single class. It is expected that this UI definition will be applied to a single class in code. It would be bad design to have ClassA(UI_mainWindow) and ClassB, and have ClassB access members of ClassA or know anything about the internal implementation of ClassA. Back to our topic of "separation of concerns", ClassB should have its own purpose and interface, and be used by the ClassA(UI_mainWindow) to achieve that purpose. You don't want ClassB knowing anything about the main window or being able to do more than its designed purpose.
Let's assume your timer example was actually more trivial than you have shown it to be. If you moved it to another class, you should rely on custom signals to communicate intentions back to other classes like the main window. Or you could move each of the lcd and label widgets into their own custom classes with their own timer logic that made them self contained. Either way, signal/slot lets custom widgets connect with each other without needing to know anything about the other widgets.
In summary, you could say that it would be good to create individual classes in Qt when you are constructing any non-trivial UI elements in another class (when it requires many lines of code, or it requires a bunch of SLOT functions and wiring for internal logic). This will keep you from repeating your logic, and allow you to have reusable code. It will also keep your main window smaller and easier to debug, since you can keep a smaller mental model in your brain of what the main window is doing. If you try to keep your UI elements as single-purpose focused and generic as possible, you will end up having a lot of classes that can be reused.

Related

Subclass & override PySide2 widget method; where do I find source to reference?

When inheriting a class and overriding a method you don't always know what's inside the original class. And if I understand correctly, various aspects of how this is handled in Python are intentionally designed to accommodate this. But I have to think that all or most of the Qt code is documented somewhere. I've found the Qt for Python documentation to be refreshing (compared to PyQt) and lists all methods for a class with a fair amount of detail but I've not been able to find the actual code for specific methods and descriptions are not always very complete. For instance, consider if I want to override the mousePressEvent for a combobox.
class mycombo(QtWidgets.QComboBox):
def __init__(self, parent = None):
super(mycombo, self).__init__()
self.setAcceptDrops(True)
self.setFocusPolicy(QtCore.Qt.NoFocus)
def mousePressEvent(self,event):
In the Qt for Python documentation I see that QComboBox inherits from QWidget
which has this to say about mousePressEvent:
https://doc.qt.io/qtforpython/PySide2/QtWidgets/QWidget.html#PySide2.QtWidgets.PySide2.QtWidgets.QWidget.mousePressEvent
There are some useful things written there but, how do I determine what's actually happening in the 'original' mousePressEvent? I don't want to interfere with other parts of the operation that are actually desired. In this case, perhaps the handling of popup widgets like the description mentions, is desired. But I also might not want to super the original method. Is there more documentation for this that I've somehow missed? It seems that some of the widgets are subclassed and modified in Python and others are only in C++?
TL; DR; There is no documentation or need as the source code can change without notifying developers.
Actually you should not know what each method does since that can change in a new version. If your goal is to add functionalities then do not override the implementation of the parent class by calling it to super().foo_method(), so you can add logic before or after the original implementation
class MyCombo(QtWidgets.QComboBox):
def __init__(self, parent = None):
super(MyCombo, self).__init__(parent)
self.setAcceptDrops(True)
self.setFocusPolicy(QtCore.Qt.NoFocus)
def mousePressEvent(self,event):
# stuff
super(MyCombo, self).mousePressEvent(event)
# another stuff
If you still want to know what happens in that function then you should check the source code:
void QComboBox::mousePressEvent(QMouseEvent *e)
{
Q_D(QComboBox);
if (!QGuiApplication::styleHints()->setFocusOnTouchRelease())
d->showPopupFromMouseEvent(e);
}
Qt has 2 types of API since it implements PIMPL/D-Pointer so analyzing the source code of the class will not be enough if you want to understand the logic, the public API rarely changes for what it is, but instead the private API has changes continuously.
In conclusion, if you want to add functionalities then just analyze the documentation as this is respected even when the code changes. Another way of understanding how it works is by modifying and analyzing the changes that occur in the GUI.
What PyQt/Qt For Python does is a wrapper to handle C++ classes, so in general what can be done in C++ can be done in Python except for some minor things that can be done in one and not in another.

Accessing a common data object in a PyQt application

I have a GUI programmed in PyQt with many widgets and different windows, etc. The data for the GUI is stored in a python object. This object should be reachable from every widget in the program. Where should I put this object?
Right now I have it in the QMainWindow instance that is used for the program's main window. The problem is that it is hard to reach the QMainWindow object from deeply nested widgets. It seems much simpler to use the QApplication instance for that, because you can get it with QtCore.QCoreApplication.instance() (as shown in this answer). However, I couldn't find any other examples encouraging you to change the QApplication class, so I wonder if it really should be used that way.
What approach would you suggest?
The correct approach is to put this data/settings object in a separate module. This can then be simply imported wherever it is needed. Ideally, the module (and the code which creates the data/settings object) should be largely independent from the rest of the application.
There is no real value in tying the data/settings object to the application instance, since you would still have to import either QApplication or qApp to access it anyway. And the same thing applies to the QMainWindow - it just moves the problem to a different location, and adds an unnecessary layer of indirection. Another big problem with this approach is that the data/settings object cannot be accessed until an instance of the application or main window becomes available. Very often, the data/settings object will be required during the initialisation of various parts of the application, so tying it to specific GUI elements can easily lead to circular dependencies, or other ordering problems.
I suppose the key design principle here is Loose Coupling: once you've decoupled your data/settings object from the GUI, the rest of the application can access it wherever and whenever it is required.

Only one Class possible for GUI programming

I'm new to GUI-programming and using now tkinter for python.
In the past my "non-GUI" programs always consisted out of a few classes but if I look to the examples with a GUI it appears that only one class is used. All functions are included in this one class. Is this the normal way or is it possible to write a gui class which "calls" functions from other classes?
As I look at it now it seems the concept of object oriented programming dissapears by implementing the GUI in an OOP manner
It is definitely possible to use multiple classes in GUI apps.
For example you can have one class which defines and layouts GUI elements (like buttons, text fields, scrollbars etc.) and the second class would subclass it adding some functionality on top of it.

Synchronized Qt TreeWidgets

I'm new to Python and PyQt. What is the best way to keep 4 QtTreeWidgets synchronized so that the items are the same as well as all the attributes of all the items? These widgets appear in different dialog boxes at different times during a session. For a number of reasons, I need to keep as much of the existing code, signals and layout as intact as possible. The Model/View division would be the obvious first place to go, but I don't want to touch any of the methods that are used to access or update the tree. I'm planning to refactor the whole thing in a few months, but I need something quickly to carry me until then.
Since each QTreeWidget is a convenience class, each has its own data. The UI is maintained in Qt Designer and I don't want to keep it that way.
When each dialog is initialized, the tree appears. The application has a singleton class that all dialogs can use to reference its variables/attributes.
In the initialization of each parent dialog, couldn't I check to see if a 'locationTree' attribute exists in the singleton. If not, I would need to populate it with its initial state and have the tree in the dialog use it or a copy of it. Any time the state of the dialog tree is altered in ways that I can trap, I'd like to update the singleton 'locationTree' to mirror the change. Although there's a clone method on a QTreeWidgetItem, I didn't see a corresponding method for the entire QTreeWidget.
How can I accomplish this with the least amount of change to the existing code base and GUI layout?
John
Yes using the MVC facilities is the way to go ...
Even though you are using QTreeWidget you are still working with a class derived from QAbstractItemView therefore the model() and setModel() calls are available. Take a model from one of the widgets that you are creating and then set it in the other widgets. Whenever you change the data in one of the widgets the other widgets will follow suit as they are using the same instance of model.
If you need to maintain the same selection state in all for widgets (which parts of the tree are open or close) that might be a little bit harder but it might actually work by using the same selectionModel selectionModel() and setSelectionModel()
I'm sure you're right that using Model/View is the best approach.
But without an idea of roughly how many items your tree widgets will have, and how frequently they'll be updated, it's hard to weigh up alternative approaches. Also, what version of Qt are you using?
If the number of updates and items are not huge, one approach is to introduce a class that inherits QObject (so it has signals and slots), and make it responsible for keeping all your QTreeWidgets in sync.
By connecting signals and slots for each QTreeWidget to a single other object, you avoid the nightmare of having every tree widget know about every other one.

Accessing methods of nested widgets

Im working on optimizing my design in terms of mvc, intent on simplifying the api of the view which is quite nested even though Iv built composite widgets(with there own events and/ pubsub messages) in an attempt to simpify things.
For example I have a main top level gui class a wxFrame which has a number of widgets including a notebook, the notebook contains a number of tabs some of which are notebooks that contain composite widgets. So to call the methods of one of these composite widgets from the controller I would have
self.gui.nb.sub_nb.composite_widget.method()
To create a suitable abstraction for the view I have created references to these widgets (whose methods need to be called in the controller) in the view like so
self.composite_widget = self.nb.sub_nb.composite_widget()
so that in the controller the call is now simplified to
self.gui.composite_widget.method()
Is this an acceptable way to create an abstraction layer for the gui?
Well that's definitely one way to handle the issue. I tend to use pubsub to call methods the old fashioned way though. Some people like pyDispatcher better than pubsub. The main problem with using multi-dot method calling is that it's hard to debug if you have to change a method name.

Categories

Resources