PyQt4: Where can you make menus? - pyqt4

I'm using Python 3 and PyQt4
I'm trying to make a simple main window with a menubar. It doesn't work if I try to set up the menubar in the MainWindow initialization but does work if I set it up in some external function. That is, the following does NOT work:
import sys
from PyQt4 import QtGui
class MyMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
centralWidget = QtGui.QWidget()
menubar = QtGui.QMenuBar()
menu = QtGui.QMenu("File")
menu.addAction("New")
menubar.addMenu(menu)
self.setMenuBar(menubar)
self.setCentralWidget(centralWidget)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mainWindow = MyMainWindow()
mainWindow.show()
sys.exit(app.exec_())
While if I simply move the menu setup down to the main routine:
class MyMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mainWindow = MyMainWindow()
centralWidget = QtGui.QWidget()
menubar = QtGui.QMenuBar()
menu = QtGui.QMenu("File")
menu.addAction("New")
menubar.addMenu(menu)
mainWindow.setMenuBar(menubar)
mainWindow.setCentralWidget(centralWidget)
mainWindow.show()
sys.exit(app.exec_())
Then the menu is created (I know I don't have any actions hooked up, this is stripped down to show the oddity). I thought it might have to do with being in init but moving it to another class routine, e.g. setup(self), and then calling that after creating the mainWindow doesn't solve the problem.
I seem to be continually baffled by what works and what doesn't in PyQt4. If anyone could point me at some good resources I'd also appreciate it (I've read Rapid GUI Programming but find I'm still lost).
Thanks in advance for your help

PyQt can be confusing because there are two object ownership models in use together. Here for example you are creating a QMenu which can be owned either by your Python program or by the Qt object hierarchy.
QMenus have two typical use cases: in one you create a pop-up menu and use QMenu.exec_ to await a response modally. In this case you naturally "own" the menu: you will hold a reference to it until it has finished and closed.
But your code illustrates the other case. Here you have no interest in holding a reference to the object: you have added one just as a workaround. But you don't need to do this.
Qt has its own object ownership hierarchy which typically starts at the top-level windows which have no parent and then goes down through all their component widgets. This parenting is typically established by assigning the parent upon creation of each child QObject. Once this is done, the object's lifetime is tied to that of their parent unless they are explicitly deleted sooner.
So that's what you want in this case:
menu = QtGui.QMenu("File",self)
The other widgets such as the central widget and the menu bar could have the same issue. But in each case they are being assigned a parent as they are being added to the main window.
Giving QObjects parents is generally the right thing to do. It soon becomes second nature to just add a parent parameter to every creation call.

Related

Do class in python need parameter when we call it?

I am new to python, actually new to programming too, and I am recently working on a simple project with Tkinter. In this project, I was trying to create a force attention window and I finally got the answer from this page. The main codes are as follows:
import tkinter as tk
class App(tk.Tk):
TITLE = 'Application'
WIDTH, HEIGHT, X, Y = 800, 600, 50, 50
def __init__(self):
tk.Tk.__init__(self)
tk.Button(self, text="open popout 1", command=self.open1).grid()
tk.Button(self, text="open popout 2", command=self.open2).grid()
def open1(self):
PopOut1(self)
def open2(self):
# .transient(self) ~
# flash PopOut if focus is attempted on main
# automatically drawn above parent
# will not appear in taskbar
PopOut2(self).transient(self)
if __name__ == '__main__':
app = App()
app.title(App.TITLE)
app.geometry(f'{App.WIDTH}x{App.HEIGHT}+{App.X}+{App.Y}')
# app.resizable(width=False, height=False)
app.mainloop()
It worked though, one thing that I am still concerning is that why he specified a parameter in a class APP:
class App(tk.Tk):
However, there is nothing pass in the class when he called it:
app = App()
Can anyone answer it, or just give me some keyword that I can go search specifically. I have read some of the tutorials about class in python, but none of them mention it.
This is called inheritance. All parameters for creating an object of the class, like you did here: app = App(), are in the __init__ method.
class App(tk.Tk):
This part is not a parameter. Instead, this indicates that the App class inherits methods from the tk.Tk class. In essence, this is what turns your App class into a Tkinter application. Without it, you wouldn't have any of the functionality that Tkinter provides. Observe how at the bottom of your code, you create the app and then call app.mainloop(). Note that your App class has no mainloop method. It actually comes from the inherited tk.Tk class.
That said, this is a major topic in most languages so I don't doubt you'll find tons of resources to learn further if you simply search for "Python inheritance".

Pyside2 trigger messagebox from button click closes whole application

i want to trigger a messagebox from a button click without closing the entire application, I managed to do this in my previous project, but this time, the behavior is really unexpected. as soon as I clicked ok or the cross sign on the messagebox, the app also closing too without any error. here's the minimal example
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.set_and_load_ui()
def set_and_load_ui(self):
self.ui = QUiLoader().load('main.ui', self)
def trigger_messagebox(self):
self.messagebox()
def messagebox(self,x=""):
msg = QMessageBox(self)
msg.setIcon(QMessageBox.Information)
msg.setText("{}".format(x))
msg.setInformativeText("test")
msg.setWindowTitle("test")
msg.exec_()
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.ui.show()
# mainWindow.trigger_messagebox() #this is fine
mainWindow.ui.mainmenuButton.clicked.connect(lambda: mainWindow.trigger_messagebox()) #this is not fine
exit_code = (app.exec_())
just to check, if the messagebox itself is causing the problem, I tried to call it directly on my code, but turns out it just fine, but not when I tried to trigger it by a button click
The problem is related to the fact that QUiLoader loads the UI using the parent given as argument, and since the UI is already a QMainWindow, you're practically loading a QMainWindow into another, which is unsupported: QMainWindows are very special types of QWidgets, and are intended to be used as top level widgets.
It's unclear to me the technical reason for the silent quit, but it's certainly related to the fact that the loaded window actually has a parent (the MainWindow instance), even if that parent is not shown (since you're showing the ui).
Unfortunately, PySide doesn't provide a way to "install" a UI file to an existing instance unlike PyQt does (through the uic.loadUi function), so if you want to keep using PySide there are only two options:
do not use a main window in Designer, but a plain widget ("Widget" in the "New Form" dialog of Designer), load it using QUiLoader and use setCentralWidget() (which is mandatory, since, as the documentation also notes, "creating a main window without a central widget is not supported"):
self.ui = QUiLoader().load('main.ui', self)
self.setCentralWidget(self.ui)
The downside of this approach is that you cannot use the main window features anymore in Designer (so, no menus, status bar, dock widgets or toolbars).
use pyside-uic to generate the python code and use the multiple inheritance method to "install" it; the following assumes that you exported the ui file as mainWindow.py:
from mainWindow import ui_mainWindow
class MainWindow(QtWidgets.QMainWindow, ui_mainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
# no "set_and_load_ui"
self.mainmenuButton.clicked.connect(self.trigger_messagebox)
# ...
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
exit_code = (app.exec_())
As you can see, now you can access widgets directly (there's no ui object). The downside of this approach is that you must remember to always generate the python file everytime you modify the related ui.

PyQT5 - how can i get the QApplication to call a method once it is loaded?

I would like to write in a QTextEdit as soon as the main window is loaded, how can I do that efficiently ?
I tried changing a boolean value once app.exec() is called but since that's the main application loop, it does not work.
The only current solutions I have ( and that I would like to avoid ) are doing a timer or asking the user to press a button that I link to the method.
I tried looking into the signals sent by QApplication, QGuiApplication and the parents but could not find a signal related to the main window having loaded anything.
If something has to happen as soon as a widget is [going to be] shown, you can do it in the showEvent() method:
class MainWindow(QtWidgets.QMainWindow):
firstShown = False
def showEvent(self, event):
super().showEvent(event)
if not self.firstShown:
self.firstShown = True
self.textEdit.setPlainText('hello')
Note that this does not exactly happen as soon as the window is shown (there's a moltitude of reason for this, including the fact that the system's "window manager" might need some time to actually show the widget); In such cases, it is safe enough to use a singleshot QTimer set to 0:
class MainWindow(QtWidgets.QMainWindow):
firstShown = False
def showEvent(self, event):
super().showEvent(event)
if not self.firstShown:
self.firstShown = True
QtCore.QTimer.singleShot(0, self.doStartupEvents)
def doStartupEvents(self):
self.textEdit.setPlainText('hello')
Another theoretical possibility is to do those events in the paintEvent (ensuring that they happen only the first time), but I wouldn't suggest it.

Cannot execute custom code on pressure of Qt button

In QT Designer I have defined a button loadValues_btn and associated the signal pressed() to the custom slot loadValues()
Then in Python, after having loaded the .ui file with the PyQt library, I have defined the method loadValues() in the MainApplication class, in order to execute custom code when the same button is clicked. But it does not execute the code (I need simply to open a File chooser dialog)
I have already tried with clicked() and it didn't work either
Here is the MainApplication class code:
class Main(QMainWindow, Ui_MainWindow):
def __init__(self, ):
super(Main, self).__init__()
self.setupUi(self)
def loadValues():
filePath = QtGui.QFileDialog.getOpenFileName(parent=self, caption='Open file', directory='.')
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
Thank you all for your support. Actually the File dialog was not appearing simply because... it was behind the Spyder editor window!! So it was hidden and it did not pop up in the main window of the Python application I was developing!!! :( Useless to tell you that I have lost one day to understand where the problem was, f***ing Python!!!

PyQt4 QSpinBox.selectAll() not working as expected

Python 2.5.4
PyQt4
I sub-classed a QDoubleSpinBox to emit a signal on a focusIn event:
#Custom widgets for DPL GUI
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class DPLDoubleSpinBox(QDoubleSpinBox):
__pyqtSignals__ = ("valueChanged(double)", "focusIn()")
def __init__(self, *args):
QDoubleSpinBox.__init__(self, *args)
def event(self, event):
if(event.type()==QEvent.FocusIn):
self.emit(SIGNAL("focusIn()"))
#self.clear() Works as expected
self.selectAll() #See below
return QDoubleSpinBox.event(self, event)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
widget = DPLDoubleSpinBox()
widget2 = DPLDoubleSpinBox()
widget.show()
widget2.show()
sys.exit(app.exec_())
If you click inside one box, then kill the other window, it works. If you click inside one, then the other, then focus any other window on the desktop, it seems to work.
I think it's a focus problem, but can't track it down. I just need it to select all when clicked on. I tried doing it through its line edit pointer, but I get the same results. Tried forcing focus to other widgets, but still same result.
You can connect a custom slot to fire when it emits "focusIn()". You can then anyQSpinBox.selectAll(), and it works, just not on itself.
I know this question is more than two years old, but since it is one of the first results when googling "qspinbox select on focus", I would like to leave a solution for future generations.
The problem is the behavior of the QSpinBox.lineEdit(). With the focusInEvent, you can call selectAll(), but for some reason, the mousePressEvent of QLineEdit clears the selection right after the focus event. See here for an explanation.
The solution is to install an event filter for the QSpinBox.lineEdit() widget or subclass QLineEdit and call QSpinBox.setLineEdit(). Either way, the link above will show you how to achieve the desired behavior by keeping a boolean flag around and filtering both focusInEvent and mousePressEvent.
According to this, put a QTimer.singleShot call to selectAll inside the overriden focusInEvent, and then magic happens.
class SpinBox(QSpinBox):
def focusInEvent(self, event: QFocusEvent) -> None:
QTimer.singleShot(0, self.selectAll)
or like this (not recommended):
b = QSpinBox()
b.focusInEvent = lambda _: QTimer.singleShot(0, b.selectAll)
I changed the event to QEvent.Enter
Now it will self.selectAll()
I can get away with this because it's for a touch screen application, so it wouldn't be obvious to the user that something is amiss. I'd still love to know what I'm missing, or if this is just a bug.

Resources