QMainWindow flashes and disappears when called from another QMainWindow - pyqt

This fairly minimal code creates a systray item with three right click options. One is an instance of QDialog, another QMainWindow, and also Quit. It's for a systray-driven app that will have some qdialogs and also a qmainwindow containing a table widget (or, is it possible to create a table into a qdialog?). I've been stuck on this for a week or so, read a lot of related materials and do not understand how to resolve it.
From the systray icon menu, clicking on QDialog, the dialog opens, waits for user action, and clicking the Ok or Cancel buttons will print which one was clicked. It would seem this works because QDialog has its own exec_(). Great so far.
However, clicking on the QMainWindow menu option, the main window dialog appears briefly and disappears with no chance for user input, of course. Maybe instead, the qmainwindow dialog would need to rely on the QApplication's exec_() where the object would instead be initialized just before app.exec_() perhaps? If that would work, I'm not clear on how def qmainwindow() would retrieve the information back from the user.
Hopefully a simple matter for someone who knows, a few changes, bingo.
Current environment: Windows 7 or XP, Python 2.7, Pyside
If you run this, there will be a blank place-holder in the systray that is clickable (right click), or you can also give it an actual image in place of 'sample.png'.
#!python
from PySide import QtGui, QtCore
from PySide.QtGui import QApplication, QDialog, QMainWindow
def qdialog():
qdialog_class_obj = TestClassQDialog()
qdialog_class_obj.show()
qdialog_class_obj.exec_() # wait for user
print "qdialog_user_action: ", qdialog_class_obj.qdialog_user_action
def qmainwindow():
qmainwindow_class_obj = TestClassQMainWindow()
qmainwindow_class_obj.show()
#qmainwindow_class_obj.exec_() # 'TestClassQMainWindow' object has no attribute 'exec_'
class TestClassQDialog(QDialog):
def __init__(self, parent=None):
super(TestClassQDialog, self).__init__(parent)
self.ok_cancel = QtGui.QDialogButtonBox(self)
self.ok_cancel.setStandardButtons(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel)
QtCore.QObject.connect(self.ok_cancel, QtCore.SIGNAL("accepted()"), self.button_ok)
QtCore.QObject.connect(self.ok_cancel, QtCore.SIGNAL("rejected()"), self.button_cancel)
def button_ok(self):
self.qdialog_user_action = 'ok'
self.hide()
def button_cancel(self):
self.qdialog_user_action = 'cancel'
self.hide()
class TestClassQMainWindow(QMainWindow):
def __init__(self, parent=None):
super(TestClassQMainWindow, self).__init__(parent)
self.ok_cancel = QtGui.QDialogButtonBox(self)
self.ok_cancel.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
QtCore.QObject.connect(self.ok_cancel, QtCore.SIGNAL("accepted()"), self.button_ok)
QtCore.QObject.connect(self.ok_cancel, QtCore.SIGNAL("rejected()"), self.button_cancel)
def button_ok(self):
self.hide()
def button_cancel(self):
self.hide()
class SysTrayIcon(QMainWindow):
def __init__(self, parent=None):
super(SysTrayIcon, self).__init__(parent)
self.qdialog_action = QtGui.QAction("QDialog", self, triggered=qdialog)
self.qmainwindow_action = QtGui.QAction("QMainWindow", self, triggered=qmainwindow)
self.quit_action = QtGui.QAction("Quit", self, triggered=QtGui.qApp.quit)
self.createSystrayIcon()
self.systrayIcon.show()
def createSystrayIcon(self):
self.systrayIconMenu = QtGui.QMenu(self)
self.systrayIconMenu.addAction(self.qdialog_action)
self.systrayIconMenu.addAction(self.qmainwindow_action)
self.systrayIconMenu.addSeparator()
self.systrayIconMenu.addAction(self.quit_action)
self.systrayIcon = QtGui.QSystemTrayIcon(self)
self.systrayIcon.setContextMenu(self.systrayIconMenu)
self.systrayIcon.setIcon(QtGui.QIcon('sample.png')) # point to a valid image if you want.
self.systrayIcon.setVisible(True)
if __name__ == '__main__':
app = QtGui.QApplication([])
systrayicon = SysTrayIcon()
app.exec_()

I've got it working. What I did was move your external method qmainwindow inside of your
SysTrayIcon and created the class parameter self.qmainwindow_class_obj = TestClassQMainWindow(). I've attached the working code below. Also, you're using the old style signal slot method, I take it you're coming from old school PyQt. The new method if very nice, clean and pythonic. I've also put the new style methods in the below code. Another thing I would do is move your qdialog method inside the SysTrayIcon class. I don't really understand why you have it outside the class but maybe I'm missing something. Hope this helps.
#!python
from PySide import QtGui, QtCore
from PySide.QtGui import QApplication, QDialog, QMainWindow
def qdialog():
qdialog_class_obj = TestClassQDialog()
qdialog_class_obj.show()
qdialog_class_obj.exec_() # wait for user
print "qdialog_user_action: ", qdialog_class_obj.qdialog_user_action
class TestClassQDialog(QDialog):
def __init__(self, parent=None):
super(TestClassQDialog, self).__init__(parent)
self.ok_cancel = QtGui.QDialogButtonBox(self)
self.ok_cancel.setStandardButtons(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel)
self.ok_cancel.accepted.connect(self.button_ok)
self.ok_cancel.rejected.connect(self.button_cancel)
def button_ok(self):
self.qdialog_user_action = 'ok'
self.hide()
def button_cancel(self):
self.qdialog_user_action = 'cancel'
self.hide()
class TestClassQMainWindow(QMainWindow):
def __init__(self, parent=None):
super(TestClassQMainWindow, self).__init__(parent)
self.ok_cancel = QtGui.QDialogButtonBox(self)
self.ok_cancel.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.ok_cancel.accepted.connect(self.button_ok)
self.ok_cancel.rejected.connect(self.button_cancel)
def button_ok(self):
self.hide()
def button_cancel(self):
self.hide()
class SysTrayIcon(QMainWindow):
def __init__(self, parent=None):
super(SysTrayIcon, self).__init__(parent)
self.qmainwindow_class_obj = TestClassQMainWindow()
self.qdialog_action = QtGui.QAction("QDialog", self, triggered=qdialog)
self.qmainwindow_action = QtGui.QAction("QMainWindow", self, triggered=self.qmainwindow)
self.quit_action = QtGui.QAction("Quit", self, triggered=QtGui.qApp.quit)
self.createSystrayIcon()
self.systrayIcon.show()
def createSystrayIcon(self):
self.systrayIconMenu = QtGui.QMenu(self)
self.systrayIconMenu.addAction(self.qdialog_action)
self.systrayIconMenu.addAction(self.qmainwindow_action)
self.systrayIconMenu.addSeparator()
self.systrayIconMenu.addAction(self.quit_action)
self.systrayIcon = QtGui.QSystemTrayIcon(self)
self.systrayIcon.setContextMenu(self.systrayIconMenu)
self.systrayIcon.setIcon(QtGui.QIcon('linux.jpeg')) # point to a valid image if you want.
self.systrayIcon.setVisible(True)
def qmainwindow(self):
self.qmainwindow_class_obj.show()
if __name__ == '__main__':
app = QtGui.QApplication([])
systrayicon = SysTrayIcon()
app.exec_()

Related

How to translate ui file + several questions [duplicate]

I am trying to translate my small application written in pyside2/pyqt5 to several languages, for example, Chinese. After googling, I managed to change the main window to Chinese after select from the menu -> language -> Chinese. However, the pop up dialog from menu -> option still remains English version. It seems the translation info is not transferred to the dialog. How do I solve this?
Basically, I build two ui files in designer and convert to two python files:One mainui.py and one dialogui.py. I then convert the two python file into one *.ts file using
pylupdate5 -verbose mainui.py dialogui.py -ts zh_CN.ts
after that, in linguist input the translation words. I can see the items in the dialog, which means this information is not missing. Then I release the file as zh_CN.qm file. All this supporting file I attached below using google drive.
Supporting files for the question
The main file is as
import os
import sys
from PySide2 import QtCore, QtGui, QtWidgets
from mainui import Ui_MainWindow
from dialogui import Ui_Dialog
class OptionsDialog(QtWidgets.QDialog,Ui_Dialog):
def __init__(self,parent):
super().__init__(parent)
self.setupUi(self)
self.retranslateUi(self)
class MainWindow(QtWidgets.QMainWindow,Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.actionConfigure.triggered.connect(self.showdialog)
self.actionChinese.triggered.connect(self.change_lang)
def showdialog(self):
dlg = OptionsDialog(self)
dlg.exec_()
def change_lang(self):
trans = QtCore.QTranslator()
trans.load('zh_CN')
QtCore.QCoreApplication.instance().installTranslator(trans)
self.retranslateUi(self)
if __name__=='__main__':
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
ret = app.exec_()
sys.exit(ret)
I think it should be a typical task because almost no application will only have a mainwindow.
You have to overwrite the changeEvent() method and call retranslateUi() when the event is of type QEvent::LanguageChange, on the other hand the QTranslator object must be a member of the class but it will be deleted and it will not exist when the changeEvent() method is called.
Finally assuming that the Language menu is used to establish only translations, a possible option is to establish the name of the .qm as data of the QActions and to use the triggered method of the QMenu as I show below:
from PySide2 import QtCore, QtGui, QtWidgets
from mainui import Ui_MainWindow
from dialogui import Ui_Dialog
class OptionsDialog(QtWidgets.QDialog,Ui_Dialog):
def __init__(self,parent):
super().__init__(parent)
self.setupUi(self)
def changeEvent(self, event):
if event.type() == QtCore.QEvent.LanguageChange:
self.retranslateUi(self)
super(OptionsDialog, self).changeEvent(event)
class MainWindow(QtWidgets.QMainWindow,Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.m_translator = QtCore.QTranslator(self)
self.actionConfigure.triggered.connect(self.showdialog)
self.menuLanguage.triggered.connect(self.change_lang)
# set translation for each submenu
self.actionChinese.setData('zh_CN')
#QtCore.Slot()
def showdialog(self):
dlg = OptionsDialog(self)
dlg.exec_()
#QtCore.Slot(QtWidgets.QAction)
def change_lang(self, action):
QtCore.QCoreApplication.instance().removeTranslator(self.m_translator)
if self.m_translator.load(action.data()):
QtCore.QCoreApplication.instance().installTranslator(self.m_translator)
def changeEvent(self, event):
if event.type() == QtCore.QEvent.LanguageChange:
self.retranslateUi(self)
super(MainWindow, self).changeEvent(event)
if __name__=='__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
ret = app.exec_()
sys.exit(ret)

slot to right click menu action does does not work

I have written the below code to which I finally managed to add menu but connecitn menu to a function doesnt seem to work:
import os
from PyQt5 import uic
from PyQt5 import QtWidgets
from PyQt5 import QtCore
FILE_LOCATION = os.path.dirname(os.path.realpath(__file__))
class MainDialogWindow(QtWidgets.QDialog):
def __init__(self):
super(MainDialogWindow,self).__init__()
ui_file = os.path.join(FILE_LOCATION, "example.ui")
self._ui = uic.loadUi(ui_file, self)
self.registerCallbacks()
self.initUI()
def initUI(self):
"""Initialize the UI.
"""
self.textBrowser.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
def registerCallbacks(self):
self.textBrowser.customContextMenuRequested.connect(self.context_menu)
# self.connect(self.textBrowser, QtCore.Signal('customContextMenuRequested(const QPoint &)'), self.context_menu)
def context_menu(self, pos):
menu = QtWidgets.QMenu(self)
action = menu.addAction("clear")
menu.exec_(self.mapToGlobal(pos))
action.trigered.connect(self.clear)
def clear(self):
"""Slot to claer text.
"""
print("clear")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainDialogWindow()
window.show()
window.setGeometry(500, 300, 300, 300)
sys.exit(app.exec_())
please helpp,, I want call the clear function from the right click menu
I don't seem to understand how the menu.exec_() method works, that method blocks the execution of sequential tasks until the user selects a QAction from the QMenu. In your case, for example, until when you press "clear" and the triggered signal is emitted (note: you have a typo), but at that moment there is no connection, so the clear method will not be called. The solution is to make the connection before invoking the QMenu exec_():
def context_menu(self, pos):
menu = QtWidgets.QMenu(self)
action = menu.addAction("clear")
action.triggered.connect(self.clear)
menu.exec_(self.mapToGlobal(pos))

Why second python (PyQt4) GUI window shuts down as soon as it opens? [duplicate]

I have written python pyqt code to open a new window with a label from another window on a button click. The issue is ,new window exits as soon as it opens.How do i fix this.
The code I wrote is
import sys
from PyQt4 import QtGui,QtCore
class Window(QtGui.QWidget):
def __init__(self):
super(Window,self).__init__()
self.btn=QtGui.QPushButton('button',self)
self.btn.clicked.connect(display)
self.show()
class display(QtGui.QWidget):
def __init__(self):
super(display,self).__init__()
self.lab=QtGui.QLabel()
self.lab.setText("hi")
self.show()
def main():
App=QtGui.QApplication(sys.argv)
Gui=Window()
sys.exit(App.exec_())
main()
You need to keep a reference to the QWidget object for your second window. Currently when you click the button, the clicked signal is fired and it calls disp1. That creates the widget, but then it is immediately garbage collected.
Instead do this to keep a reference:
import sys
from PyQt4 import QtGui,QtCore
class Window(QtGui.QWidget):
def __init__(self):
super(Window,self).__init__()
self.btn=QtGui.QPushButton('button',self)
self.btn.clicked.connect(self.open_new_window)
self.show()
def open_new_window(self):
# creates the window and saves a reference to it in self.second_window
self.second_window = disp1()
class displ(QtGui.QWidget):
def __init__(self):
super(displ,self).__init__()
self.lab=QtGui.QLabel()
self.lab.setText("hello")
self.show()
def main():
App=QtGui.QApplication(sys.argv)
Gui=Window()
sys.exit(App.exec_())
main()
When passing a function as parameter, maybe it's better not to include the parentheses? Try
sys.exit(App.exec_)
Instead of
sys.exit(App.exec_())

PyQt keyPressEvent not triggered from QDialog

I have a simple example of of a dialog window that has the keyPressEvent method. However, no matter what is typed when the sub window has focus, the event is not triggered.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import PyQt4.Qt
class KpeWindow(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
main = QVBoxLayout(self)
label = QLabel(self)
label.setText('Test the keyPressEvent')
self.adjustSize()
self.setLayout(main)
def keyPressEvent(self, event):
QMessageBox.warning(self, 'MDI', 'keyPressEvent')
super().keyPressEvent(event)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('KeyPressEvent Test')
child = KpeWindow()
self.setCentralWidget(child)
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
The following code works:
class KpeWindow(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self,parent)
main = QVBoxLayout(self)
label = QLabel(self)
label.setText('Test the keyPressEvent')
main.addWidget(label)
self.adjustSize()
self.setLayout(main)
def keyPressEvent(self, event):
QMessageBox.warning(self, 'MDI', 'keyPressEvent')
self.parent().keyPressEvent(event)
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle('KeyPressEvent Test')
main = QVBoxLayout(self)
child = KpeWindow(self)
child.setFocusPolicy(Qt.StrongFocus)
self.setFocusProxy(child)
main.addWidget(child)
child.setFocus(True)
self.adjustSize()
self.setLayout(main)
I am not sure which of my changes work, I suspect setFocusProxy. In general I would recommend using QWidget as the child, and putting things into layouts even when there are no siblings.
The keyPressEvent is sensitive to the focus policy. In your example, the event is going to the QMainWindow (if you move the keyPressEvent to there, it does receive key events).
Is there any reason to have a dialog within a window? If you launch the dialog in the usual way, using child.show(), child.exec_() instead of setCentralWidget, it shows in a separate window and captures the key event.

How to see signals from QWidgets inside dynamically created QTabWidget pages?

EDIT : I've come up with a solution, and it's much more straightforward than I thought. Original code and question at the top. My solution after "The Question" below..
The Example
from PyQt4 import QtGui, QtCore
from example_Ui import Ui_MainWindow
from filler_Ui import Form
class TabFiller(Form):
def __init__(self, parent=None):
Form.__init__(self, parent)
def TabButtonClicked(self):
print("Tab button pressed.")
def LineEditChanged(self):
print("LineEdit contents edited in tab page!")
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
tab_filler = [] # create empty list for tab contents
tab_page = [] # create empty list for tab page
tab_count = 0
def CreateNewTab(self):
tab_title = "New Tab : " + str(self.tab_count)
self.tab_filler.append(TabFiller())
self.tab_filler[self.tab_count].label.setText(tab_title)
self.tab_page.append(self.tab_filler[self.tab_count])
self.tabWidget.addTab(self.tab_page[self.tab_count], tab_title)
self.tab_count += 1
def MainButtonPressed(self):
self.CreateNewTab()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
MainWindow contains a QTabWidget, which is a Button. clicked() signal has been defined in QtDesigner to be sent to the MainButtonPressed() function inside the MainWindow class.
Form widget also created in QTdesigner. Used to fill additional Tab Pages.
This contains a Button widget, and a LineEdit Widget.
The Question
I can't get my head around how I can tell which widget has been clicked or edited in each tab.
I know that each Tab Page is stored in the list called tab_page.
Within the MainWindow class, how would I receive a clicked() or finishedEditing() signal for a given widget in a currently active tab?
A Solution
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4 import QtGui
from example_Ui import Ui_MainWindow
from filler_Ui import Form
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
tab_index = 1 # 1 because we already made a default tab in QtDesigner
def LineEditChanged(self):
findWidget = self.tabWidget.widget(self.tabWidget.currentIndex()).findChildren(QtGui.QLineEdit, "lineEdit")
if findWidget[0].isModified() == True:
print("LineEdit contents edited in tab page!")
print("Name of page edited :", "'", self.tabWidget.tabText(self.tabWidget.currentIndex()),"'")
def TabButtonPressed(self):
print("YOU DID IT!")
print("Current Tab Index = ", self.tabWidget.currentIndex())
def CreateNewTab(self, tabNum):
tab_title = "New Tab : " + str(self.tab_index)
self.tabWidget.addTab(Form(), tab_title)
def MainButtonPressed(self):
self.CreateNewTab(self.tab_index)
findWidget = self.tabWidget.widget(self.tab_index).findChildren(QtGui.QPushButton, "tabButton")
findWidget[0].clicked.connect(self.TabButtonPressed)
findWidget = self.tabWidget.widget(self.tab_index).findChildren(QtGui.QLineEdit, "lineEdit")
findWidget[0].editingFinished.connect(self.LineEditChanged)
self.tab_index += 1
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Using this there's no need for storing each tab page object in a list. You basically use the QTabWidget to index your pages, and off you go.
If anyone has a more elegant way than this, please inform ;)
As outlined in my edited question, I did find the solution to this, which is to use the QTabWidget to "index" each dynamically created tab page.
In QtDesigner I created a main window with one QTabWidget and one button thusly;
Here's the object tree for that;
NOTE: I added a signal/slot for the "Click Me!" button in QtDesigner, so that when that button is clicked, the MainButtonPressed function is called.
To fill the tab pages, I also created a Form in QtDesigner, with a button and a QLineEdit widget;
And the object tree for that;
I'll reproduce the code here. NOTE: I've now updated this answer to use findChild rather than findChildren above:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4 import QtGui
from example_Ui import Ui_MainWindow
from filler_Ui import Form
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
tab_index = 1 # 1 because we already made a default tab in QtDesigner
def LineEditChanged(self):
findWidget = self.tabWidget.widget(self.tabWidget.currentIndex()).findChild(QtGui.QLineEdit, "lineEdit")
if findWidget.isModified() == True:
print("LineEdit contents edited in tab page!")
print("Name of page edited :", "'", self.tabWidget.tabText(self.tabWidget.currentIndex()),"'")
def TabButtonPressed(self):
print("YOU DID IT!")
print("Current Tab Index = ", self.tabWidget.currentIndex())
def CreateNewTab(self, tabNum):
tab_title = "New Tab : " + str(self.tab_index)
self.tabWidget.addTab(Form(), tab_title)
def MainButtonPressed(self):
self.CreateNewTab(self.tab_index)
findWidget = self.tabWidget.widget(self.tab_index).findChild(QtGui.QPushButton, "tabButton")
findWidget.clicked.connect(self.TabButtonPressed)
findWidget = self.tabWidget.widget(self.tab_index).findChild(QtGui.QLineEdit, "lineEdit")
findWidget.editingFinished.connect(self.LineEditChanged)
self.tab_index += 1
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
When run, pressing the "Click Me!" button on the main tab page creates a new tab, and adds the contents of the "filler" page to it.
The variable tab_index keeps track of how many tabs there are and allows you to reference the contents of each tab.
To find a widget in a tab, you use the findChild function of Qt;
findWidget = self.tabWidget.widget(self.tab_index).findChild(QtGui.QPushButton, "tabButton")
Finding a specific widget is straightforward. You specify the type of widget you're looking for (QtGui.QPushButton) , and the name you assigned it in QtDesigner (tabButton)
In this case the found widget can be referenced by the variable findWidget.
You can then connect signals to function slots as usual;
findWidget.clicked.connect(self.TabButtonPressed)
In this case I used the new-style signal connection method to connect the clicked() signal to a function named TabButtonPressed in my program.
Rinse and repeat for each widget on the Tab Page you wish to do something with.
After that, it really is plain sailing ;)
I hope this information helps others in their GUI endeavours. You can probably use the same technique with the QToolBox widget.

Resources