Multiple Windows in PyQt4 - python

I have a PyQt program used to visualize some python objects. I would like to do display multiple objects, each in its own window.
What is the best way to achieve multi-window applications in PyQt4?
Currently I have the following:
from PyQt4 import QtGui
class MainWindow(QtGui.QMainWindow):
windowList = []
def __init__(self, animal):
pass
def addwindow(self, animal)
win = MainWindow(animal)
windowList.append(win)
if __name__=="__main__":
import sys
app = QtGui.QApplication(sys.argv)
win = QMainWindow(dog)
win.addWindow(fish)
win.addWindow(cat)
app.exec_()
However, this approach is not satisfactory, as I am facing problems when I try to factor out the MultipleWindows part in its own class. For example:
class MultiWindows(QtGui.QMainWindow):
windowList = []
def __init__(self, param):
raise NotImplementedError()
def addwindow(self, param)
win = MainWindow(param) # How to call the initializer of the subclass from here?
windowList.append(win)
class PlanetApp(MultiWindows):
def __init__(self, planet):
pass
class AnimalApp(MultiWindows):
def __init__(self, planet):
pass
if __name__=="__main__":
import sys
app = QtGui.QApplication(sys.argv)
win = PlanetApp(mercury)
win.addWindow(venus)
win.addWindow(jupiter)
app.exec_()
The above code will call the initializer of the MainWindow class, rather than that of the appropriate subclass, and will thus throw an exception.
How can I call the initializer of the subclass? Is there a more elegant way to do this?

Why not using dialogs? In Qt you do not need to use the main window unless you want to use docks etc.. Using dialogs will have the same effect.
I can also see a problem in your logic regarding the fact that you want your super class to be calling the constructor of its children, which of course can be any type. I recommend you rewrite it like the following:
class MultiWindows(QtGui.QMainWindow):
def __init__(self, param):
self.__windows = []
def addwindow(self, window):
self.__windows.append(window)
def show():
for current_child_window in self.__windows:
current_child_window.exec_() # probably show will do the same trick
class PlanetApp(QtGui.QDialog):
def __init__(self, parent, planet):
QtGui.QDialog.__init__(self, parent)
# do cool stuff here
class AnimalApp(QtGui.QDialog):
def __init__(self, parent, animal):
QtGui.QDialog.__init__(self, parent)
# do cool stuff here
if __name__=="__main__":
import sys # really need this here??
app = QtGui.QApplication(sys.argv)
jupiter = PlanetApp(None, "jupiter")
venus = PlanetApp(None, "venus")
windows = MultiWindows()
windows.addWindow(jupiter)
windows.addWindow(venus)
windows.show()
app.exec_()
It is not a nice idea to expect the super class to know the parameter to be used in the init of its subclasses since it is really hard to ensure that all the constructor will be the same (maybe the animal dialog/window takes diff parameters).
Hope it helps.

In order to reference the subclass that is inheriting the super-class from inside the super-class, I am using self.__class__(), so the MultiWindows class now reads:
class MultiWindows(QtGui.QMainWindow):
windowList = []
def __init__(self, param):
raise NotImplementedError()
def addwindow(self, param)
win = self.__class__(param)
windowList.append(win)

Related

Why treeview's item method doesn't work in this situation?

I wrote a GUI program with Python tkinter. To achieve some function, in a Toplevel window, some event triggered a method of it which would call the Treeview widget's item(ID, tags=(some_tag)) of the Tk window to change the style of Treeview's content. But it doesn't work even if the snippet containing .item() have been run and no error occurs. My corresponding code snippet is as follows(some irrelevant part is omitted).
class Main_window(Tk):
# some_code_omitted...
def create_widgets():
# some_code_omitted...
self.tv1 = ttk.Treeview()
class A_Toplevel(Toplevel):
def __init__(self, parent):
self.parent = parent
# some_code_omitted...
def some_foo(self, event):
self.parent.tv1.item(ID, tags=(some_tag))
After some attempt, I found it seems that only when tv.item() is called in the Main_window, it works. later I wrote a method in Main_window to call tv.item(). But when the instance of A_Toplevel call it, it still doesn't work at all.
class Main_window(Tk):
# some_code_omitted...
def create_widgets():
# some_code_omitted...
self.tv1 = ttk.Treeview()
def a_foo(self, ):
self.tv1.item(ID, tags=(some_tag))
class A_Toplevel(Toplevel):
def __init__(self, parent):
self.parent = parent
# some_code_omitted...
def some_foo(self, event):
self.parent.a_foo()
What's wrong and how can I solve this problem?
Oh! Some progress.
Today, I found a way to solve it occasionally with threading module. Codes are as follows:
def a_foo_thr(self, ID, some_tag):
thr = threading.Thread(target=self.a_foo, args=(ID, some_tag))
thr.start()
def a_foo(self, ID, some_tag):
self.tv1.item(ID, some_tag)
But I have no idea why it succeeded, even whether it could make some unexpected problem.

When changing screens with QStackedWidget, how to call a function in the changed screen?

I'm trying to call the init function of the screen I'm changing my screen index to
For an example, i have this code:
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from sys import argv as sysArgv
from sys import exit as sysExit
arialLarge = qtg.QFont("Arial", 18)
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# Current screen label;
mainWindowLabel = qtw.QLabel("This is the main window", self)
mainWindowLabel.setFont(arialLarge)
mainWindowLabel.move(20, 40)
# Button for going to the HelloWindow screen;
gotoHelloWindowButton = qtw.QPushButton("Go to hello window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()+1))
gotoHelloWindowButton.move(100, 100)
class HelloWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# EG: print hello world when I visit this page
print("hello world")
# Current screen label;
helloWindowLabel = qtw.QLabel("This is the hello window", self)
helloWindowLabel.setFont(arialLarge)
helloWindowLabel.move(20, 40)
# Button for going to the MainWindow screen;
gotoMainWindowButton = qtw.QPushButton("Go to main window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()-1))
gotoMainWindowButton.move(100, 100)
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = qtw.QStackedWidget()
appStack.addWidget(MainWindow())
appStack.setFixedSize(300, 300)
appStack.show()
appStack.addWidget(HelloWindow())
sysExit(app.exec())
If im visiting the HelloWindow from the MainWindow, how can i run the init function of the HelloWindow screen so I can run whatever code I want in there?
I need to be able to do this as on the app im working on as on the mainpage i have dynamically created buttons that all have functions parameters with different indexes to my server, and i need to be able to fetch the data from server based off the clicked button's data index so on the other page I can view the desired data.
The __init__ of a python class is what is called when an instance is created (using SomeClass()), so you should not try (or even think) to call it again, as it could create serious problems and bugs that are hard to track.
I strongly suggest you to read the documentation about classes in Python, as you cannot ignore that aspect in object oriented programming.
If you need to call something everytime the index is changed, then you should better subclass QStackedWidget and control everything from there.
A good solution is to create a standardized function that will be called everytime the page is presented, and ensure that the stack widget correctly calls it.
class FirstPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.nextButton = QtWidgets.QPushButton('Next')
self.doSomething()
def doSomething(self):
...
class SecondPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.prevButton = QtWidgets.QPushButton('Previous')
self.doSomething()
def doSomething(self):
...
class Stack(QtWidgets.QStackedWidget):
def __init__(self):
super().__init__(self)
self.first = FirstPage()
self.first.nextButton.clicked.connect(self.goNext)
self.addWidget(self.first)
self.second = SecondPage()
self.second.prevButton.clicked.connect(self.goPrev)
self.currentChanged.connect(self.initCurrent)
def goNext(self):
self.setCurrentIndex(1)
def goPrev(self):
self.setCurrentIndex(0)
def initCurrent()
if self.currentWidget():
self.currentWidget().doSomething()
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = Stack()
appStack.setFixedSize(300, 300)
appStack.show()
sysExit(app.exec())
Note that adding a QMainWindow to a parent is not a good idea, as Qt main windows are intended to be used as top level windows; also note that using fixed geometries (positions and sizes) is often considered bad practice, and you should use layout managers instead.

How to get the list of all QPushButton from a specific tab in Python Pyqt5? [duplicate]

I'm using Python3 and PyQt5, make my widgets and windows in Qt Designer. What is more, I do not generate .py files from a .ui. I simply load it using next code:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
uic.loadUi('UI/Qt/source/MainWindow.ui', self)
So, I wanted to know, how do I bind menu bar actions to functions.
Is there any way I can do something like this?
self.getActionByName("actionTest_Action").connect(self.do_something)
It is not necessary to use findChild when using loadUi since this method adds the object to the attributes of the class using the objectName as a name, for example in this particular case a cleaner code than the other answer is:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
uic.loadUi('UI/Qt/source/MainWindow.ui', self)
self.actionTest_Action.triggered.connect(self.test)
def test(self):
print("Test")
So, answering my own question..
One way to do this, is to find an action by using FindChild(QAction, "ActionName") function, and then bind a function using connect() function
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
uic.loadUi('UI/Qt/source/MainWindow.ui', self)
action = self.findChild(QAction, "actionTest_Action")
action.triggered.connect(self.test)
def test(self):
print("Test")

Changed to options listed in PyQt4 combo_boxes defined in a different class not reflected

I am working on a small GUI project using PyQt4. I have defined one class (inside a separate file) defining the basic functionality of combo_boxes that I have to use and another class to use the functionality for all the combo_boxes.
The code looks something like
class core:
def __init__(self, default_value, window_name):
self.combo_box = QtGui.QComboBox(window_name)
self.combo_box.addItem(default_value)
self.combo_box.addItem("some other value")
self.combo_box.addItem("a third value")
self.combo_box.activated[str].connect(self.set_text)
self.text = default_value
def set_text(self, text):
print text
The main class is something like:
from file import *
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(200, 100, 820, 700)
combo_box_one = core("first", self)
combo_box_two = core("second", self)
#some other methods follow defining the geometry for each combo_box and other functions
def main():
app = QtGui.QApplication(sys.argv)
gui = Window()
sys.exit(app.exec_())
main()
The GUI is working as expected. All the combo_boxes appear as per the defined geometry. However, when on selecting different options, nothing seems to happen. Ideally, I would expect the text on the option to be printed. In fact, when I return the combo_box object to the main class and set it connections there, the change in options is reflected. But when the same thing is done in the coreclass, the changes are not reflected as printed text. Is it a scope related thing? Please help me understand what's happening.
Slots can only be implemented in classes that inherit from QObject, a simple solution is that the core class inherits from QComboBox, since QComboBox inherits from QObject.
class core(QtGui.QComboBox):
def __init__(self, default_value, window_name):
QtGui.QComboBox.__init__(self, window_name)
self.addItem(default_value)
self.addItem("some other value")
self.addItem("a third value")
self.activated[str].connect(self.set_text)
def set_text(self, text):
print(text)

Open a second window in PyQt

I'm trying to use pyqt to show a custom QDialog window when a button on a QMainWindow is clicked. I keep getting the following error:
$ python main.py
DEBUG: Launch edit window
Traceback (most recent call last):
File "/home/james/Dropbox/Database/qt/ui_med.py", line 23, in launchEditWindow
dialog = Ui_Dialog(c)
File "/home/james/Dropbox/Database/qt/ui_edit.py", line 15, in __init__
QtGui.QDialog.__init__(self)
TypeError: descriptor '__init__' requires a 'sip.simplewrapper' object but received a 'Ui_Dialog'
I've gone over several online tutorials, but most of them stop just short of showing how to use a non built-in dialog window. I generated the code for both the main window and the dialog using pyuic4. What I think should be the relevant code is below. What am I missing here?
class Ui_Dialog(object):
def __init__(self, dbConnection):
QtGui.QDialog.__init__(self)
global c
c = dbConnection
class Ui_MainWindow(object):
def __init__(self, dbConnection):
global c
c = dbConnection
def launchEditWindow(self):
print "DEBUG: Launch edit window"
dialog = QtGui.QDialog()
dialogui = Ui_Dialog(c)
dialogui = setupUi(dialog)
dialogui.show()
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
conn = sqlite3.connect('meds.sqlite')
c = conn.cursor()
self.ui = Ui_MainWindow(c)
self.ui.setupUi(self)
def main():
app = QtGui.QApplication(sys.argv)
program = StartQT4()
program.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Bonus question: since it looks like you can't pass arguments in pyqt function callbacks, is setting something which would otherwise be passed as an argument (the poorly named "c") to be global the best way to get information into those functions?
I've done like this in the past, and i can tell it works.
assuming your button is called "Button"
class Main(QtGui.QMainWindow):
''' some stuff '''
def on_Button_clicked(self, checked=None):
if checked==None: return
dialog = QDialog()
dialog.ui = Ui_MyDialog()
dialog.ui.setupUi(dialog)
dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
dialog.exec_()
This works for my application, and I believe it should work with yours as well. hope it'll help, it should be pretty straight forward to do the few changes needed to apply it to your case.
have a good day everybody.
Ui_Dialog should inherent from QtGui.QDialog, not object.
class Ui_Dialog(QtGui.QDialog):
def __init__(self, dbConnection):
QtGui.QDialog.__init__(self)
global c
c = dbConnection
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
Why QtGui.QWidget.__init___ ???
Use insted:
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
You must call __init__ methon from base class (name in parenthesis '()')
QDialog have two useful routins:
exec_()
show()
First wait for closing dialog and then you can access any field form dialog. Second show dialog but don't wait, so to work properly you must set some slot/signals connections to respond for dialog actions.
eg. for exec_():
class Dialog(QDialog):
def __init__(self, parent):
QDialog.__init__(parent)
line_edit = QLineEdit()
...
dialog = Dialog()
if dialog.exec_(): # here dialog will be shown and main script will wait for its closing (with no errors)
data = dialog.line_edit.text()
Small tip: can you change your ui classes into widgets (with layouts). And perhaps problem is that your __init__ should be __init__(self, parent=None, dbConnection)
Because when you create new widget in existing one PyQt may try to set it as children of existing one. (So change all init to have additional parent param (must be on second position)).

Categories