How to open a new window after the user clicks a button is described here:
https://stackoverflow.com/a/21414775/1898982
and here:
https://stackoverflow.com/a/13519181/1898982
class Form1(QtGui.QWidget, Ui_Form1):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setupUi(self)
self.button1.clicked.connect(self.handleButton)
self.window2 = None
def handleButton(self):
if self.window2 is None:
self.window2 = Form2(self)
self.window2.show()
class Form2(QtGui.QWidget, Ui_Form2):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setupUi(self)
I want to develop a GUI application that consists of several steps. Once the user clicks next, the current window closes and another window opens. Technically I can do this like it is described above: Each window opens a new one. After a few steps this is pretty much nested.
Is there a better way to do this?
I would like to have the control flow in my main. Something like this:
main()
window1 = win2()
window1.show()
wait until button in window1 is clicked, then
window1.close()
window2 = win2()
window2.show()
wait until button in window2 is clicked, then
window1.close()
....
I would recommend to use QWizard or QStackedWidget class to perform this task. You can easily switch between widgets or windows using either of these two classes. Refer to QWizard and QStackedWidget docs.
Qt provides special widget for such cases, which called QWidget. Look at it. It's also available in Qt4.
Related
i have a main GUI-Window from which i open a new Window (FCT-popup) with a buttonclick:
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow() # sets ui = to the main window from the ui-file
self.ui.setupUi(self)
[...]
def enter_fct_results(self):
self.FCTpopup = FCT_Window()
self.FCTpopup.show()
In the Window i have a QTable to fill and a button to submit the data and close the window:
class FCT_Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_FCT_Window()
self.ui.setupUi(self)
[...]
self.ui.pushButton_submitFCT.clicked.connect(lambda: MainWindow.store_fct_data(MainWindow, self.on_submit()[0]))
def on_submit(self): # event when user clicks
fct_nparray = np.zeros((self.ui.tableFCTinputs.rowCount(), self.ui.tableFCTinputs.columnCount()))
for j in range(self.ui.tableFCTinputs.columnCount()):
for i in range(self.ui.tableFCTinputs.rowCount()):
fct_nparray[i, j] = float(self.ui.tableFCTinputs.item(i, j).text())
return fct_nparray, lambda: self.close()
self.ui.pushButton_submitFCT.clicked.connect(lambda: MainWindow.store_fct_data(MainWindow, self.on_submit()[0]))
The receiving function iin the main window looks like ths:
def store_fct_data(self, data):
self.fct_data = data
Now i just want to understand how i can make either the mainwindow or the pushbutton which opens the 2nd window disabled. Disabling inside enter_fct_results() works, but if i want to enable it again with either store_fct_data or on_submit provides errors like this:
self.ui.pushButton_FCTresults.setEnabled(1)
self.ui.pushButton_submitFCT.clicked.connect(lambda: MainWindow.store_fct_data(MainWindow, self.on_submit()[0]))
AttributeError: type object 'MainWindow' has no attribute 'ui'
I dont think i have understood it here how to deal with multiple windows and stuff. For example how would i change a the color of a button in the main window by using a button in window2. How do i access the widgets? if i am inside the same Window i do that easily by
self.ui.pushbutton.setText("New Text")
I dont get how to access items and attributes across Windows :( Can you help me?
Access to attributes of another instance
There is a fundamental difference between disabling the button of the second window in enter_fct_results and what you tried in the lambda: in the first case, you're accessing an instance attribute (for instance, self.FCTpopup.ui.pushButton), while in the second you're trying to access a class attribute.
The self.ui object is created in the __init__ (when the class instance is created), so the instance will have an ui attribute, not the class:
class Test:
def __init__(self):
self.value = True
test = Test()
print(test.value)
>>> True
print(Test.value)
>>> AttributeError: type object 'Test' has no attribute 'value'
Provide a reference
The simple solution is to create a reference of the instance of the first window for the second:
def enter_fct_results(self):
self.FCTpopup = FCT_Window(self)
self.FCTpopup.show()
class FCT_Window(QMainWindow):
def __init__(self, mainWindow):
QMainWindow.__init__(self)
self.mainWindow = mainWindow
self.ui.pushButton_submitFCT.clicked.connect(self.doSomething)
def doSomething(self):
# ...
self.mainWindow.ui.pushButton.setEnabled(True)
Using modal windows (aka, dialogs)
Whenever a window is required for some temporary interaction (data input, document preview, etc), a dialog is preferred: the main benefit of using dialogs is that they are modal to the parent, preventing interaction on that parent until the dialog is closed; another benefit is that (at least on Qt) they also have a blocking event loop within their exec() function, which will only return as soon as the dialog is closed. Both of these aspects also make unnecessary disabling any button in the parent window. Another important reason is that QMainWindow is not intended for this kind of operation, also because it has features that generally unnecessary for that (toolbars, statusbars, menus, etc).
def enter_fct_results(self):
self.FCTpopup = FCT_Window(self)
self.FCTpopup.exec_()
class FCT_Window(QDialog):
def __init__(self, parent):
QMainWindow.__init__(self, parent)
self.ui.pushButton_submitFCT.clicked.connect(self.doSomething)
def doSomething(self):
# ...
self.accept()
The above makes mandatory to recreate the ui in designer using a QDialog (and not a QMainWindow) instead. You can just create a new one and drag&drop widgets from the original one.
i finally found my mistake: It was the place of the signal connection. It has to be right before the 2nd window is opened:
def enter_fct_results(self):
self.FCTpopup = Dialog(self.fct_data)
self.FCTpopup.submitted.connect(self.store_fct_data)
self.FCTpopup.exec_()
With this now i can send the stored data from the mainwindow to the opened window, import into the table, edit it and send it back to the main window on submit.
I have a pyqt app where I want a dialog to display when I click a menu item. If the dialog loses focus and the menu item is clicked again, it brings the dialog to the front. This is working fine so far.
The problem is that when the dialog is opened and then closed, clicking the menu item doesnt create/display a new dialog. I think I know why, but can't figure out a solution
Heres the code:
from ui import mainWindow, aboutDialog
class ReadingList(QtGui.QMainWindow, mainWindow.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
self.about = None
self.actionAbout.triggered.connect(self.showAbout)
def showAbout(self):
# If the about dialog does not exist, create one
if self.about is None:
self.about = AboutDialog(self)
self.about.show()
# If about dialog exists, bring it to the front
else:
self.about.activateWindow()
self.about.raise_()
class AboutDialog(QtGui.QDialog, aboutDialog.Ui_Dialog):
def __init__(self, parent=None):
super(self.__class__, self).__init__()
self.setupUi(self)
def main():
app = QtGui.QApplication(sys.argv)
readingList = ReadingList()
readingList.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The problem lies in the fact that when the dialog is created the first time, self.about is no longer None. This is good because the conditional in showAbout() allows me to bring the dialog to the front instead of creating a new one (the else condition)
However, when the dialog is closed, self.about is no longer None due to the previous dialog creation, which means it doesn't create a new one and just jumps to the else condition
How can I make it so that dialogs can be created after the first?
I thought about overriding the closeEvent method in the AboutDialog class but I'm not sure how to get a reference to readingList to send a message back saying the dialog has been closed. Or maybe I'm overthinking it, maybe the return from self.about.show() can be used somehow?
(I know I can probably avoid all of this using modal dialogs but want to try to figure this out)
There are probably several ways to do this, but here's one possibility:
class ReadingList(QtGui.QMainWindow, mainWindow.Ui_MainWindow):
def __init__(self):
super(ReadingList, self).__init__()
self.setupUi(self)
self.actionAbout.triggered.connect(self.showAbout)
self.about = None
def showAbout(self):
if self.about is None:
self.about = AboutDialog(self)
self.about.show()
self.about.finished.connect(
lambda: setattr(self, 'about', None))
else:
self.about.activateWindow()
self.about.raise_()
class AboutDialog(QtGui.QDialog):
def __init__(self, parent=None):
super(AboutDialog, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
(NB: you should never use self.__class__ with super as it can lead to an infinite recursion under certain circumstances. Always pass in the subclass as the first argument - unless you're using Python 3, in which case you can omit all the arguments).
Here is my class that is representing preferencies. It loads glade layout from 'preferences.glade'. btn_cancel_clicked_cb and btn_ok_clicked_cb are called when the corresponding buttons are activated. But self.destroy() doesn't do anything. Can somebody explain how to destroy this dialog when after clicking the buttons?
from gi.repository import Gtk
from common import Prefs
class ViewPrefs(Gtk.Dialog):
def __init__(self):
Gtk.Dialog.__init__(self)
self.builder = Gtk.Builder()
self.builder.add_from_file("preferences.glade")
self.builder.connect_signals(self)
self.rb_input=self.builder.get_object("rb_input")
self.rb_select=self.builder.get_object("rb_select")
def run(self, *args):
window = self.builder.get_object("window_prefs")
window.show()
window.connect('destroy', Gtk.main_quit)
Gtk.main()
def register_observer(self, controller):
self.controller = controller
def btn_cancel_clicked_cb(self,widget):
self.destroy()
def btn_ok_clicked_cb(self,widget):
active = [r for r in self.rb_input.get_group() if r.get_active()][0]
input_type=active.get_label().lower()
self.controller.set_prefs(Prefs(input_type=input_type))
self.destroy()
It starts from the main window like that:
prefsview=ViewPrefs()
prefsview.register_observer(self.controller)
prefsview.run()
self is not self.window. In fact, subclassing Gtk.Dialog for your case is useless as the dialog part of self is never used! If you are requiring a new enough version of GTK+, you can create your dialog as a composite widget template and build your class that way (I don't know how to do this with Python; sorry). Otherwise, get rid of the subclass and call window.destroy() instead (and, if window is really a Gtk.Dialog, window.run() in your self.run()).
According to the docs, dialogs created in wxPython and shown with ShowModal should handle the escape (ESC) key by searching for a button with ID_CANCEL and simulating a click (i.e. triggering its EVT_BUTTON event).
This was working in all but one of my dialogs. After a lot of debugging, I discovered that the cancel button - or any other button! - should have focus. In other words, as long as I call .SetFocus() on one of the buttons I create, the ESC key works fine.
Does anybody know what's going on here? Do dialogs displayed with ShowModal() not automatically get focus? Should they? Or am I misunderstanding something?
In the example code below, uncomment the line b.SetFocus() to see the difference:
import wx
class MainWindow(wx.Frame):
def __init__(self, parent):
super(MainWindow, self).__init__(parent)
self.Show()
d = SomeDialog(self)
d.ShowModal()
self.Destroy()
class SomeDialog(wx.Dialog):
def __init__(self, parent):
super(SomeDialog, self).__init__(parent)
button = wx.Button(self, wx.ID_CANCEL, 'Cancel')
button.Bind(wx.EVT_BUTTON, self.action_cancel)
#button.SetFocus()
def action_cancel(self, e):
self.EndModal(wx.ID_CANCEL)
if __name__ == '__main__':
app = wx.App(False)
frame = MainWindow(None)
app.MainLoop()
Update: This happens when running on linux (Fedora 20, Gnome)
You can call the dialog's SetFocus() method to set focus on itself:
import wx
class MainWindow(wx.Frame):
def __init__(self, parent):
super(MainWindow, self).__init__(parent)
self.Show()
d = SomeDialog(self)
d.ShowModal()
self.Destroy()
class SomeDialog(wx.Dialog):
def __init__(self, parent):
super(SomeDialog, self).__init__(parent)
button = wx.Button(self, wx.ID_CANCEL, 'Cancel')
button.Bind(wx.EVT_BUTTON, self.action_cancel)
self.SetFocus()
def action_cancel(self, e):
self.EndModal(wx.ID_CANCEL)
if __name__ == '__main__':
app = wx.App(False)
frame = MainWindow(None)
app.MainLoop()
This works on Kubuntu 14.04 with wxPython 2.8.12.1 and Python 2.7.6. However, I suspect that when you set focus to the dialog, it is probably passing the focus to its first child just as a wx.Panel will do. I don't know why Linux is behaving in this manner, but I would agree with #nepix32 and #VZ that this should work without the SetFocus(). You can call it anyway as a workaround, but you should probably report it as a bug. There's a link on the wxPython website where you can submit your bug report.
I have an extended main window with a QtGui.QTabWidget added to it. I am creating several widgets extended from QtGui.QWidget which I can add and remove to the tab widget.
What I would like to do is have a "pop-out" button that causes the child widget to be removed from the tab widget and come up as it's own independent window (and a "pop-in" button to put it back into the main window). The same sort of idea as Gtalk-in-Gmail has.
Note that if I close the main window, the other "tabs" or "windows" should also close, and I should be able to put all the windows side-by-side and have them all visible and updating at the same time. (I will be displaying near-realtime data).
I am new to Qt, but if I'm not mistaken, if a Widget has no parent it comes up independently. This works, but I then have no idea how I could "pop" the window back in.
class TCWindow(QtGui.QMainWindow):
.
.
.
def popOutWidget(self, child):
i = self.tabHolder.indexOf(child)
if not i == -1:
self.tabCloseRequested(i)
self.widgets[i].setParent(None)
self.widgets[i].show()
My gut says that there should still be a parent/child relationship between the two.
Is there a way to keep the parent but still have the window come up independently, or am I misunderstanding Qt's style?
Otherwise, would creating a variable in the child to hold a link to the main window (like self.parentalUnit = self.parent()) be a good idea or a hackish/kludgy idea?
Leave the parent as is. If you remove the parent, then closing main window won't close 'floating' tabs, since they are now top-level windows. windowFlags defines if a widget is window or a child widget. Basically, you need to alternate between QtCore.Qt.Window and QtCore.Qt.Widget
Below is a small but complete example:
#!/usr/bin/env python
# -.- coding: utf-8 -.-
import sys
from PySide import QtGui, QtCore
class Tab(QtGui.QWidget):
popOut = QtCore.Signal(QtGui.QWidget)
popIn = QtCore.Signal(QtGui.QWidget)
def __init__(self, parent=None):
super(Tab, self).__init__(parent)
popOutButton = QtGui.QPushButton('Pop Out')
popOutButton.clicked.connect(lambda: self.popOut.emit(self))
popInButton = QtGui.QPushButton('Pop In')
popInButton.clicked.connect(lambda: self.popIn.emit(self))
layout = QtGui.QHBoxLayout(self)
layout.addWidget(popOutButton)
layout.addWidget(popInButton)
class Window(QtGui.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__()
self.button = QtGui.QPushButton('Add Tab')
self.button.clicked.connect(self.createTab)
self._count = 0
self.tab = QtGui.QTabWidget()
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.tab)
def createTab(self):
tab = Tab()
tab.setWindowTitle('%d' % self._count)
tab.popIn.connect(self.addTab)
tab.popOut.connect(self.removeTab)
self.tab.addTab(tab, '%d' % self._count)
self._count += 1
def addTab(self, widget):
if self.tab.indexOf(widget) == -1:
widget.setWindowFlags(QtCore.Qt.Widget)
self.tab.addTab(widget, widget.windowTitle())
def removeTab(self, widget):
index = self.tab.indexOf(widget)
if index != -1:
self.tab.removeTab(index)
widget.setWindowFlags(QtCore.Qt.Window)
widget.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
In Qt, layout takes ownership over the widgets that are added to layout, so let it handle parentship.
You can create another widget (with no parent) which will be hidden until you press pop-out button and when it is pressed, you remove "pop-out widget" from its original layout and add it to layout of the hidden widget. And when pop-in button pressed - return widget to it's original layout.
To close this hidden window, when closing main window, you will need to redefine closeEvent(QCloseEvent* ev) to something like this (sorry for c++, but i bet, in python it's all the same):
void MainWindow::closeEvent(QCloseEvent* ev)
{
dw->setVisible(false); // independent of mainwindow widget
sw->setVisible(false); // independent of mainwindow widget
QWidget::closeEvent(ev); //invoking close event after all the other windows are hidden
}