Lets say I have 2 windows, one of which opens the other on a menu item click:
class ProjectWindow(QtWidgets.QMainWindow, project_window_qt.Ui_ProjectWindow):
def __init__(self):
super(ProjectWindow, self).__init__()
# Setup the main window UI
self.setupUi(self)
self.new_project_window = None
# Handle menu bar item click events
self.actionNewProject.triggered.connect(self.new_project)
def new_project(self):
self.new_project_window = project_new_window.NewProjectWindow()
self.new_project_window.show()
def refresh_projects(self):
with open(os.path.join(self.directory, 'projects.txt'), 'r') as f:
projects = json.load(f)
return projects
and
class NewProjectWindow(QtWidgets.QDialog, project_new_window_qt.Ui_NewProjectWindow):
def __init__(self,):
super(NewProjectWindow, self).__init__()
# Setup the main window UI
self.setupUi(self)
Once the user closes new_project_window, I want the refresh_projects method to be called in the ProjectWindow class.
I thought about setting up an event listener to check when new_project_window is closed, and then call refresh_projects once that happens, but the window just closes immediately after it opens:
def new_project(self):
self.new_project_window = project_new_window.NewProjectWindow(self.directory, self.project_list)
self.new_project_window.onClose.connect(self.refresh_projects)
self.new_project_window.show()
Is that the correct approach? Or is there a way to call refresh_projects directly from within the new_project_window object?
If you are using QDialog you should call exec_() instead of show(), this will return a value when the user closes the window, and just call refresh project.
def new_project(self):
self.new_project_window = project_new_window.NewProjectWindow(self.directory, self.project_list)
code = self.new_project_window.exec_()
"""code: gets the value of the QDialog.Accepted, or QDialog.Rejected
that you can connect it to some accept button
using the accept() and reject() functions.
"""
self.refresh_projects()
exec_() is blocking, ie the next line is not executed unless the QDialog has been closed.
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'm learning GTK3 under Python3 and made an app that has only one AppWindow so far.
I'm using Gtk.Application.
I can't figure out how to proper handle opening a second window.
I can open it directly from my main Window but I don't know how to pass Application object to it (I googled and "duckduckgoed" without any success).
Do I need to call Gtk.Application to open second window?
How do I let Gtk.Application track this new window?
Application is like this:
Main window with an objects list from a database.
Second window to edit a single item selected from the main window's
list.
Thanks.
My code (I stripped out the unnecesary code):
# ################################################################
# MAIN APP WINDOW
class AppWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Here all the widgets and a button to open the second window
self.show_all()
# ################################################################
# MAIN APP CLASS
class Application(Gtk.Application):
def __init__(self, *args, **kwargs):
super().__init__(*args, application_id=MIKUNA_ID,
**kwargs)
self.window = None
def do_startup(self):
Gtk.Application.do_startup(self)
action = Gio.SimpleAction.new("quit", None)
action.connect("activate", self.on_quit)
self.add_action(action)
def do_activate(self):
# We only allow a single window and raise any existing ones
if not self.window:
# Windows are associated with the application
# when the last one is closed the application shuts down
self.window = AppWindow(application=self, title="Mikuna")
self.window.present()
def on_quit(self, action, param):
self.quit()
if __name__ == "__main__":
app = Application()
app.run(sys.argv)
Second window to edit a single item selected from the main window's list.
You just create the second window from your main window then, probably a Gtk.Dialog that is transient to the main window. You only need to make the Application track it if it is a toplevel window you expect to out-live your main window.
I'm trying to launch a dialog window inside of an active application window. The difficulty I am facing is being able to interact with the active application window once the dialog window is launched.
Here is an example of my python script:
class select_output_UI(QtGui.QDialog):
def __init__(self, *args, **kwargs):
super(select_output_UI, self).__init__(*args, **kwargs)
# BUILD UI FROM FILE
ui_file = QtCore.QFile("./select_output.ui")
ui_file.open(QtCore.QFile.ReadOnly)
self.myWidget = QtUiTools.QUiLoader().load(ui_file, self)
ui_file.close()
# SIGNALS
self.myWidget.cancel_button.clicked.connect(self.cancel_button_pressed)
def cancel_button_pressed(self):
self.button_pressed = "CANCEL"
self.close()
dialog = select_output_UI(QtGui.QApplication.activeWindow())
There are 2 options I am familiar with to launch this dialog window:
dialog.show()
This option allow's me to interact with the active application window, but this option will not wait for the dialog window to close before continuing to run whatever code is underneath.
dialog.exec_()
This option does not allow me to interact with the active application window. But what it does do is wait for the dialog window to close before continuing with the rest of the code.
Is there a way to interact with the application window while the dialog window has launch and have python wait till the dialog window is closed before continuing to read the rest of my code?
Sounds like you want to connect your dialog's "OK" (or "proceed", "continue", etc.) button to a method or function containing the rest of the code you want to run. Chances are you'll want it to be a method, since I imagine the rest of the code will need access to some of the widget values on the dialog.
For example:
class select_output_UI(QtGui.QDialog):
def __init__(self, *args, **kwargs):
super(select_output_UI, self).__init__(*args, **kwargs)
# Load .ui file, etc...
self.myWidget.ok_button.clicked.connect(self.do_work)
self.myWidget.cancel_button.clicked.connect(self.reject)
def do_work(self):
print "I'm doing work!"
# Do the work...
self.accept()
dialog = select_output_UI(QtGui.QApplication.activeWindow())
dialog.show()
Alternatively, you could hook your "OK" and "Cancel" buttons up to .accept() and .reject(), respectively, and then attach your do_work() function/method to the dialog's accepted signal. However, if you approach it that way, your code will execute after the dialog is closed, rather than allowing you to close it when you see fit (or, say, leave it open if something goes wrong in the rest of your code).
I have a simple app that requires 4 file inputs before proceeding to generate an output.
Consider btnFile1 btnFile2 btnFile3 and btnFile4 and btnOutput.
btnOutput is disabled on init.
btnFile1...4 all link to four similar, yet different, methods, call them OnBtn1..4 that assign a variable (call them file1..file4) to the file selected using the wx.FileDialog method.
I have a helper method, EnableButton() with a simple test:
if self.file1 and self.file2 and self.file3 and self.file4:
self.btnGenerate.Enable()
e.Skip()
What is the best way to call this method when all four variables are assigned? The four buttons can be clicked in any order, so we can't assign it only to the 4th button.
One obvious option is to add a call to EnableButton in every OnBtn method, but is this the ideal solution? What if there were 50 buttons?
Another option was to add a new event to each button like so:
for btn in (btn1, btn2, btn3, btn4):
btn.Bind(wx.EVT_BUTTON, self.EnableButton)
However, this doesn't work with the method above because when the 4th button is clicked, the variable is not yet assigned. It is only assigned after the file is selected from wx.FileDialog.
I am sure there's a third, more ideal, solution that I am not seeing.
The event EVT_UPDATE_UI is an event handler to look at the state of the application and change UI elements accordingly. You can use it to enable or disable your button depending on the state of the files, heres an example using check boxes to represent the file states, the button will only be enabled while all four check boxes are ticked.
import wx
from wx.lib import sized_controls
class TestFrame(sized_controls.SizedFrame):
def __init__(self, *args, **kwargs):
super(TestFrame, self).__init__(*args, **kwargs)
contents_pane = self.GetContentsPane()
self.files = {}
for number in range(1, 5):
label = 'file{}'.format(number)
ctrl = wx.CheckBox(contents_pane, label=label)
self.files[label] = ctrl
btn_output = wx.Button(contents_pane, label='btn_output')
btn_output.Bind(wx.EVT_UPDATE_UI, self.btn_update)
def btn_update(self, event):
enable = all(ctrl.IsChecked() for ctrl in self.files.values())
event.Enable(enable)
app = wx.App(False)
test_frame = TestFrame(None)
test_frame.Show()
app.MainLoop()
I would use a wx.Timer and set the timer to fire once a second. Every second it fires and calls your EnableButton event handler. The code would look something like this:
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.EnableButton, self.timer)
self.timer.Start(1000)
In the event handler, I would add the following inside the if statement:
self.timer.Stop()
You may also want to add some logic to stop the timer when the user closes the application as timer objects can cause the application to not close correctly if they are still active. You can read more about timers here:
http://www.blog.pythonlibrary.org/2009/08/25/wxpython-using-wx-timers/
what I need is something very alike QtMessageBox.information method, but I need it form my custom window.
I need a one window with few labels, one QtTreeViewWidget, one QButtonGroup … This window will be called from main window. If we call class that implements called window as SelectionWindow, than what I need is:
class MainWindow(QtGui.QMainWindow):
...
def method2(self):
selWin = SelectionWindow()
tempSelectionValue = selWin.getSelection()
# Blocked until return from getSelection
self.method1(tempSelectionValue)
...
class SelectionWindow(QtGui.QMainWindow):
...
def getSelection(self):
...
return selectedRow
...
Method getSelection from SelectionWindow should pop up selection window and at the end return row selected in QTreeViewWidget. I want that main window remains blocked until user selects one row in selection window and confirms it by button. I hope that you will understand what I need.
I will appreciate any help!
Thanks,
Tiho
I would do something like this:
dialog window with buttonbox ->
events connected to accept() and
reject() slots of the dialog itself
set the dialog modality to something like application modal
call the exec_() method of the dialog to keep it blocking until the user chooses ok/cancel
after the execution of the exec_() method terminates, you can read what you need from the dialog widgets.
Something like this should fit your needs:
class SelectionWindow(QtGui.QMainWindow):
...
def getSelection(self):
result = self.exec_()
if result:
# User clicked Ok - read currentRow
selectedRow = self.ui.myQtTreeViewWidget.currentIndex()
else:
# User clicked Cancel
selectedRow = None
return selectedRow
...