Accessing the main window from Python
So I'm newish to both Pyton & Qt (& PyQt). This is probably a question more about Python than Qt/PyQt themselves, though depends on solution.
I wish to access my
QMainWindowfrom another class. Some dialog/window. The main window will of course be open, but not necessarily the parent of the other dialog class, so I can't just take the "parent/owner" of the dialog. I will be wanting to call a function in the main window class (to provide some information to outside world).
I'm happy to follow the simple pattern of https://stackoverflow.com/a/5921561/489865, viz. "global variable". But cannot figure how to do this in Python!
So code skeleton of
main.pystarts out looking like
# Global variable for Main window # (note that this is declared outside of any class) mainWindow = None class Main(QtWidgets.QMainWindow): ... def someFunc(self): ... if __name__ == '__main__': # global mainWindow mainWindow = Main() sys.exit(app.exec_())
Now I want other dialog class to be able to call
I know (believe) this is to do with declaring
global mainWindowin the caller (else
mainWindowwill just be an [uninitialized] local variable), but e.g.
global mainWindow mainWindow.someFunc()
gives me "mainWindow is None" error. I've tried the
main.py(e.g. just above where I assign it) as well as/instead of in the other module, dotted it around everywhere, but nothing wants to play ball.
So what are the necessary patterns, in
main.pyand in the calling module, to get this darn thing to work, please??
I know I could (and probably should) use a pattern more like https://stackoverflow.com/a/46456214/489865, which iterates
qApp->topLevelWidgets(), but don't feel like looping, and would now like to understand just how to use these global variables in Python, just like https://stackoverflow.com/a/5921561/489865 shows how simple it is in C++.
Global variables would be the wrong approach here. That's typically a signal/slot situation.
Where's you dialog declared/used ?
Global variables would be the wrong approach here. That's typically a signal/slot situation
Aarrghh, why?? Really this question could have nothing to do with Qt, it's very, very simply:
- The main window has some internal data it would like to expose, via a member function call, to the outside world.
- Another window/dialog/page/class would like to call the function in the main window (you can assume the main window exists). In Python.
However, since you ask I'll state my actual usage case.
Where's you dialog declared/used ?
No idea. Maybe a direct dialog off the main window. Maybe six dialogs down, from a window, who cares, it should not matter.
- My main window has a toolbar on it, with various actions. These might change over time as the code changes.
- I have a "settings" dialog, available somewhere. The administrator is allowed to selectively mark main-window-toolbar-items by name as unavailable, and save the settings. (Next time the app is started, those items will no longer be shown on the main toolbar.)
- The code for creating the toolbar items is all inside the main window module, in the
QMainWindow-derived object that is my main window.
- To offer the various items for disablement, the settings dialog wants to ask the main window for a list of (the text of) its toolbar items.
- So it wants to call a function in the main window, which by whatever means decides what the list of items to be offered is and returns it.
- The simplest way of accessing the main window is via a global variable set upon creation, rather than iterating top-level Qt widgets to identify which one is the main window.
I don't see any place for signal-slot-type code. Given the C++ code link I showed, it's really just a question about how Python can call a function in the main window from another class. Which somehow involves
globalbut I can't make the darn thing work. That's how I see it.
Then that's typically a dialog that is launched from within your MainWindow since it's your application main interface. Therefore you can configure that dialog before showing it.
This also has the advantage of removing the tight coupling you are currently creating.
So in my case from the main window the user clicks a menu action to bring up a "Utilities Page" dialog, and from there he clicks a button to bring up the "Settings" dialog.
So following your suggestion the Main Window must pass its information to the Utilities dialog, and then the Utilities dialog must pass on that information to the Settings dialog. And when I later change that architecture so that there is an intervening "Utilities Subset" dialog after Utilities and before Settings, or I remove the Utilities dialog and go straight to Settings, or I allow Settings dialog to be reached via "Utilities 2" dialog as well as via "Utilities" dialog, in all these cases I must change code.
The "removing the tight coupling you are currently creating" you claim instead introduces a "tight coupling" between the architecture used to reach the Settings dialog and the code. I'd far rather have the "tight coupling" involved in allowing the Settings dialog to access the Main Window directly....
So that's my choice, and we seem to have a difference of opinion here. I'd like to say that I always read your posts with interest and welcome your comments, so please don't take that as a dismissal/disrespect of your suggestions.
Cutting a long story short, reading around some more my attempt to use Python "global" variables is not going to work.
Given first a
# Top-level "global variable" mainWindow = None class MainWindow(QtWidgets.QMainWindow): ... def createMainWindow(): global mainWindow mainWindow = MainWindow() if __name__ == '__main__': createMainWindow() ...
This in itself works OK, correctly setting the "global"
Now attempt to access it in another module. Attempt #1:
class SettingsDialog(QDialog): def func(self): global mainWindow w = mainWindow
This is the wrong way, as Python
globalturns out to mean "global to defining module only" instead of the "global to all modules/application" which is what I had expected.
mainWindowis thus simply not defined in this module.
Attempt #2. Taken from https://stackoverflow.com/a/13034602/489865, which "claims" to work:
class SettingsDialog(QDialog): def func(self): from main import mainWindow w = mainWindow
Here we use
globalas the way to access the variable. However, when executed
None--- or whatever it was initialized to in my module-level statement
mainWindow = None. In that stackoverflow post there is an intriguing comment:
Your global variables will no longer remain in sync. Each module receives its own copy. Changing the variable in one file will not reflect in another.
So although in
main.pyI have executed
mainWindow = MainWindow()(and that changes its value in that module), for whatever reason attempting to import it elsewhere gives me some copy of its original (module-level-statement?) value, not its altered current value.
The above explains why my various attempts to access it from another module simply did not work.
I will now have to look at alternative ways for accessing the main window from another module, without using
import. I will report back... :)
In that case, and for the sake of cleanliness (and longterm avoidance of maintenance hell) may I suggest another alternative ?
Since you are likely to access these settings from several places (or maybe more than one settings widget), what about having a class that is responsible for your application settings and that could be a singleton ? It would be cleaner than a singleton widget and keep responsibilities separated.
Well, for right or for wrong architecturally, I ended up writing a function (Python, but you get the drift :) ):
def findMainWindow() -> typing.Union[QMainWindow, None]: # Global function to find the (open) QMainWindow in application app = QApplication.instance() for widget in app.topLevelWidgets(): if isinstance(widget, QMainWindow): return widget return None
globalvariables --- which I now understand, are about as "global" as the parochial village I live in, are misnamed really, and are brain-dead in the way they work :)