Make second PyQt4 QMainWindow "detachable" from main application - python

I have a PyQt4 application with a QMainWindow. From within that program I launch another QMainWindow that is used to draw a matplotlib plot. My approach is based on Eli Benderskys way of integrating matplotlib with PyQt.
class QtMatplotlibWindow(QtGui.QMainWindow):
"""Plot window to display data, is created by main application"""
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
If I instantiate QtMatplotlibWindow with parent=None the resulting window will be completely "independent", meaning that it has its own icon in the taskbar and is completely "detached" from the main application. So, for instance, I can bring anther application, say Internet Explorer, to the front and subsequently bring only the Matplotlib window to the front, the actual application staying in the background. However using parent=None results in the matplotlib window being thrown off the stack and closed without my willing to do so at some seemingly random point in time.
If, on the other hand, I pass the instance of the main application as the parent the two windows are "tied together", meaning that I cannot view them independently of each other.
How can I achieve the "best of both worlds"? I'd like to pass the instance of the main application as the parent, so that the generated plots will only be closed if I close the main application, but I would also like the plot windows to be entirely independent in showing and moving. I would expect there to be some property of QMainWindow that would allow me exactly that. I hope I could phrase my question clear enought, I feel like I lack the appropriate terminology.

The fact that your second window disappears at random time indicates that it has been garbage collected. You must keep a python reference to all your windows. For instance append your newly created window to a list somwhere in your application: windowlist.append(QtMatplotlibWindow())

Related

QApplication and main window connection

Considering a very basic HelloWorld PyQt5 application like:
app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle('PyQt5 app')
window.setGeometry(100, 100, 280, 80)
window.move(60, 15)
helloMsg = QLabel('<h1>Hello World!</h1>', parent=window)
helloMsg.move(60, 15)
window.show()
sys.exit(app.exec_())
It constructs a QApplication, a parent-less QWidget becoming the main window, adds a QLabel and shows it.
My question is: how does the QApplication know about the main window?
There is nothing in this code connecting the two.
Perhaps it is a naive question but just looking at this, it seems like magic.
How is the main window's paint event added to the application's event queue without telling so in the source code ? How does the QApplication instance know what is going to be added below in the source code?
tl;dr
There's no "magic" involved: sub-modules can access their "main" modules, and each module of Qt can know if a QApplication instance is running.
Long version
I think that's an interesting question, especially for those who are not that into low level programming. For instance, I've always given the QApplication as some sort of a "cartesian" assumption: «it exists».
As a premise, I'm not going to give you a very technical and low-level explanation: I don't have enough skills to do so (and I really welcome any other answer or edit to this), but I'm assuming that's not what you're looking for.
[Almost] technically speaking, you've to remember that Qt - and PyQt along with it - is an environment (the exact term is framework). As such, each one of its sub elements (classes, and eventually instances of them) "know" about that environment.
QApplication (and its base classes QGuiApplication and QCoreApplication) is a class that is internally accessible from any "sub" Qt module.
It's something like the builtin types (str, int, bool, etc.) that are accessible to any module. For example, the os.path is a python module that you can import as standalone, but it knows what the main os module is, and each function of os.path actually uses some part of that main module.
Like most frameworks, Qt has what is called called an event loop, which is usually run as soon as you call Q[*]Application.exec(). An event loop is something that generally blocks itself waiting for something to happen (an event) and eventually react to it.
Whenever a Qt class needs it, it internally calls the Q[*]Application.instance() method to ensure that an instance of the application is running, meaning that an event loop is active and running. For example Qt widgets need that to be able to show the interface and interact with it: tell the operating system that a new window has been created, therefore it has to be drawn on the screen, so the OS will say "ok, let's show it" by sending Qt an event requesting the drawing, then Qt will "send" that event to that window that will finally draw itself by telling Qt how it's being painted; finally Qt will "tell" the OS what's going to be shown. At the same time, that window might need to know if some keyboard or mouse event has been sent to it and react in some way.
You can see this in the Qt sources: whenever a new QWidget is created, it ensures that a QApplication exists by calling QCoreApplication.instance().
The same happens for other Qt objects that require an application event loop running. This is the case of QTimer (that doesn't require a graphical interface, but has to interface with the system for correct timing) and QPixmap (which needs to know about the graphical environment to correctly show its image), but in some specific cases it also depends on the platform (for example, creation of a QIcon on MacOS requires a running event loop, while that's not necessary on Linux and Windows).
So, finally, that's what (roughly) happens when you run your code:
# create an application instance; at this point the loop is not "running"
# (but that might be enough to let know most classes about the current system
# environment, such as available desktop geometries or cursor position)
app = QApplication(sys.argv)
# create a widget; an application exists and the widget can "begin" to create its
# interface using the information provided by it, like the system default font
# (it's actually a bit more complicated due to cross-platform issues, but let's
# ignore those things now)
window = QWidget()
window.setWindowTitle('PyQt5 app')
window.setGeometry(100, 100, 280, 80)
window.move(60, 15)
helloMsg = QLabel('<h1>Hello World!</h1>', parent=window)
helloMsg.move(60, 15)
# "ask Qt to prepare" the window that is going to be shown; at this point the
# widget's window is not shown yet even if it's "flagged as shown" to Qt, meaning
# that "window.isVisible()" will return True even if it's not actually visible yet
window.show()
# start the event loop by running app.exec(); sys.exit will just "wait" for the
# application to return its value as soon as it actually exits, while in the
# meantime the "exec" function will run its loop almost as a "while True" cycle
# would do; at this point the loop will start telling the OS that a new window
# has to be mapped and wait from the system to tell what to do: it will probably
# "answer" that it's ok to show that window, then Qt will tell back the widget
# that it can go on by "polishing" (use the current style and app info to finally
# "fix" its size) and begin drawing itself, then Qt will give back those drawing
# information allowing the OS to actually "paint" it on the screen; then it will
# be probably waiting for some user (keyboard/mouse) interaction, but the event
# loop might also tell the OS that the window is willing to close itself (as a
# consequence of a QTimer calling "widget.close", for instance) which could
# possibly end with ending the whole event loop, which is the case of
# https://doc.qt.io/qt-5/qguiapplication.html#quitOnLastWindowClosed-prop
# which would also cause the application to, finally, return "0" to sys.exit()
sys.exit(app.exec_())

Is it possible to create to create a Tkinter subwindow? [duplicate]

I wanted to ask if it is possible to draw a child window inside a parent window so that it will only be able to move inside the parent window and won't be able to move out of the parent window bounds.
If you mean having actual windows with title bar, menu, status bar etc. inside the parent window then the answer is:
No, Tcl/Tk and by extension Tkinter does not support this with its standard widgets.
There have been efforts in the past to implement widgets which emulate MDI as you can see on the TCL wiki, but most of them are over a decade old. You will probably have to implement it yourself or choose a different GUI toolkit if you really need to implement this kind of UI design.
If you do it yourself, you can use the Frame widget as the subwindow, and use place to put it in the containing window. Or, you can create it as an object on a canvas. You'll have to write all of the code to give the inner window borders and a title bar, and to manage moving it around, iconifying it, etc.

PyQT application - where to hold data that is then processed by other widgets?

I'm writing an application using PyQt4 to analyze data as it acquired from our data acquisition software. The application will consist of a few widgets:
a widget to display what files have been imported (and what data channels are in those files); this widget will also contain the file watcher that is looking for data files as they are generated
a widget that will plot certain data channels as they are imported
multiple other widgets that perform different analysis and display the results of that analysis
All of these widgets would be contained within a QMainWindow
So basically, the main routine will be: file generated (external to application) -> file imported into pandas dataframe -> data plotted / analyzed
What I'm unclear about is where in the application would be the best place to actually run the import function (and therefore where the actual pandas dataframes will be held).
The two places that make sense to me are either in the first widget mentioned (that one that is doing the file watching / displaying of what has been imported) or the actual QMainWindow itself.
I understand how to tell all the widgets currently in the QMainWindow that a file has been imported using signals / slots. So, for example, the file watcher widget will emit a signal every time a file is imported. This would be connected to slots in the other widgets that would tell them to update the current plot, or run some analysis, or whatever.
But the point of uncertainty I'm running in to how to make the data that is imported available to all of the widgets currently in the QMainWindow. Which is what I mean by asking where the import function should actually be run.
Hopefully what I'm asking is clear; if not let me know.
Edit:
Reading a bit more about MVC design in Qt, it's unclear to me whether it's appropriate here. I would like to make the parts of the application as independent as possible, so the idea behind using MVC is very appealing, but I'm having trouble understanding how it would be implemented in my case. All of the examples that I have been able to find seem to deal with updating, for example, a TreeWidget and a ComboBox so that when the data for one is edited it remains in sync with the data for the other (i.e. they both change since the data is held in one place).
In my case the data itself is never modified by anything after it has been imported. Analysis may be run on it, but that would be independent to each widget and would have no effect on the actual raw data that has been imported into the application. Each widget is essentially independent, with anything occurring within that widget having no effect on any other widget in the application. Every widget currently active in the main application simply needs to have access to the data that has been imported, and needs to know when a new piece of data has been added (this is envisioned as a dictionary of file names matched to pandas dataframes)
As suggested, keeps the data in the main application and connecting to each of the widgets to indicate that a new piece of data is now available is straightforward, so I get how that would work.
I'm just very unclear about what I would gain by going to MVC route.
You essentially only have one event firing, which is the new file found, and every other process is kicked off in turn. Therefore you could just put all of the code within your main window class given that your process at least on the surface sounds somewhat simple. The data would be shared via a class variable and a timer is used to check for files (and is the only event trigger). This would give you a main window class with a basic structure something like this (pseudocode):
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.build_ui()
self.imported_data = {}
timer = QTimer()
timer.timeout.connect(check_for_files)
timer.start(10000) # periodically check for new files
def build_ui(self):
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
def check_for_files(self):
# check for new files here
if new_file:
self.import_data(file_name)
self.ui.filename_list.addItem(file_name) # add file name widget
self.plot_data(file_name)
def import_data(self, file_name):
# open file and read data to file_data
self.ui.imported_data[file_name] = file_data
def plot_data(self, file_name):
self.ui.graph.add_plot(self.imported_data[file_name])
That could get ugly if your program grows larger but should work. If you wanted a more scalable design where the main window, processing, and data are in separate classes you could check out a model-view-controller design and place the processing in a controller class and the data in the model class.

Matplotlib canvas doesn't embed in wx panel

I'm writing a class to embed some common configurations of graphs in a wx Notebook tab, but I'm running into a strange issue. When I try to add wx.Panel with the FigureCanvas, instead it floats the figure in another window entirely.
The odd thing is, the graph window resizes when I resize the main window. The figure comes out the correct size, just not in the right window.
My code is here. I can't see what I'm doing wrong, I've embedded matplotlib in wx before, but never in a Notebook. I can get it to embed on a simple GUI by itself just fine, just not in the tabs.
Try:
Make GraphTab a wxPanel rather than a wxFrame
Set all GraphTab to have nb as the parent (currently your first one has self as the parent.
I'm not sure whether this is everything, but it's a start.

Qt - Temporarily disable all events or window functionality?

I have a Qt program with many buttons, user-interactable widgets, etc.
At one stage in the program, I would like all the widgets to temporarily 'stop working'; stop behaving to mouse clicks and instead pass the event on to one function.
(This is so the User can select a widget to perform meta operations. Part explanation here: Get variable name of Qt Widget (for use in Stylesheet)? )
The User would pick a widget (to do stuff with) by clicking it, and of course clicking a button must not cause the button's bound function to run.
What is the correct (most abstracted, sensible) method of doing this?
(which doesn't involve too much new code. ie; not subclassing every widget)
Is there anything in Qt designed for this?
So far, I am able to retrieve a list of all the widgets in the program (by calling
QObject.findChildren(QtGui.QWidget)
so the solution can incorporate this.
My current horrible ideas are;
Some how dealing with all the applications events all the time in one
function and not letting through the events when I need the
application to be dormant.
When I need dormancy, make a new transparent widget which recieves
mouse clicks and stretch it over the entire window. Take coordinates
of click and figure out the widget underneath.
Somehow create a new 'shell' instance of the window.
THANKS!
(Sorry for the terrible write-up; in a slight rush)
python 2.7.2
PyQt4
Windows 7
You can intercept events send to specific widgets with QObject::installEventFilter.
graphite answered this one first so give credit where credit is due.
For an actual example in PySide, here's an example you might draw some useful code from:
my_app.py
from KeyPressEater import KeyPressEater
if __name__ == "__main__":
app = QApplication(sys.argv)
eater = KeyPressEater()
app.installEventFilter(eater)
KeyPressEater.py
class KeyPressEater(QObject):
# subclassing for eventFilter
def eventFilter(self, obj, event):
if self.ignore_input:
# swallow events
pass
else:
# bubble events
return QObject.eventFilter(self,obj,event)

Categories

Resources