PyQt4: Adding QtMessageBox.information functionality to custom window - python

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
...

Related

How to get custom signals from a Qt dialog box

I have a QDialog with 3 buttons - Apply, OK and Cancel. In the __init__ method of the Dialogbox, I am connecting the OK and Cancel using the following:
buttonBox.accepted.connect( self.accept )
buttonBox.rejected.connect( self.reject )
In my main form, I am able to run a method (addNameToSandbox) for the OK signal using
self.__nameDialog.accepted.connect(self.__addNameToSandbox)
However, I want the Apply button to do the same but keep the child Dialog box open (as opposed to the OK button which closes it). How can I get that signal on the main window?
I have a method within the child dialog that I am able to run when Apply is clicked, but how to trigger an action in the main form with that, I have no idea.
buttonBox.button( QtGui.QDialogButtonBox.Apply ).clicked.connect( self.add )
I've tried using some of the other signals like finished, but I can't figure that one out either.
Create a signal in the dialog and connect it to the clicked of the apply button, and then use a signal to connect it in your main form:
class YourDialog(QtGui.QDialog):
applyClicked = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(YourDialog, self).__init__(parent):
# ...
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
apply_button = buttonBox.button(QtGui.QDialogButtonBox.Apply)
apply_button.clicked.connect(self.applyClicked)
# ...
self.__nameDialog.accepted.connect(self.__addNameToSandbox)
self.__nameDialog.applyClicked.connect(self.__applyfunc)
You need to declare QtCore.pyqtSignal applied as a class variable and then fire it up with self.applied.emit()
Then you'll be able to use it:
self.__nameDialog.applied.connect(self.__applyPressed)

Pyqt check when other window is closed

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.

What event should I bind to before enabling a button in wxPython?

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/

Simulate user clicking in QSystemTrayIcon

Even through the activated slot is being executed, the menu is still not showing. I traced through manually clicking the tray icon and the simulated click, and its going through the same execution logic.
Currently I have
class MyClass(QObject):
def __init__():
self._testSignal.connect(self._test_show)
self.myTrayIcon.activated.connect(lambda reason: self._update_menu_and_show(reason))
def show():
self._testSignal.emit()
#pyqtSlot()
def _test_show():
self._trayIcon.activated.emit(QtWidgets.QSystemTrayIcon.Trigger)
#QtCore.pyqtSlot()
def _update_menu_and_show(reason):
if reason in (QtWidgets.QSystemTrayIcon.Trigger):
mySystemTrayIcon._update_menu()
...
class MySystemTrayIcon(QSystemTrayIcon):
def _update_menu(self):
# logic to populate menu
self.setContextMenu(menu)
...
MyClass().show()
Here is how I made the context menu associated with the tray icon pop up
class MyClass(QObject):
def __init__():
self._testSignal.connect(self._test_show)
self.myTrayIcon.activated.connect(lambda reason: self._update_menu_and_show(reason))
def show():
self._testSignal.emit()
#pyqtSlot()
def _test_show():
self._trayIcon.activated.emit(QSystemTrayIcon.Context)
#QtCore.pyqtSlot()
def _update_menu_and_show(reason):
if reason in (QSystemTrayIcon.Trigger, QSystemTrayIcon.Context):
mySystemTrayIcon._update_menu()
# Trigger means user initiated, Context used for simulated
# if simulated seems like we have to tell the window to explicitly show
if reason == QSystemTrayIcon.Context:
mySystemTrayIcon.contextMenu().setWindowFlags(QtCore.Qt.WindowStaysOnTopHint|QtCore.Qt.FramelessWindowHint)
pos = mySystemTrayIcon.geometry().bottomLeft()
mySystemTrayIcon.contextMenu().move(pos)
mySystemTrayIcon.contextMenu().show()
...
class MySystemTrayIcon(QSystemTrayIcon):
def _update_menu(self):
# logic to populate menu
self.setContextMenu(menu)
...
MyClass().show()
It seems you have to set the WindowStaysOnTopHint on the context menu so that it will appear.
This solution is specific to mac since it assumes the taskbar is on the top.
One side effect is that the context menu is always on top, even if the user clicks somewhere else. I placed an event filter on the context menu, the only useful event that it registered was QEvent.Leave

How can I pass variables between two classes/windows in PyGtk?

I am starting out with PyGtk and am having trouble understanding the interaction of windows.
My very simple question is the following.
Suppose I have a class that simply creates a window with a text-entry field. When clicking the "ok" button in that window, I want to pass the text in the entry field to another window, created by another class, with a gtk menu and create a new entry with the content of the text field.
How do I implement this?
Let's call A the Menu, and B the window with the text-entry field.
If I understood correctly A calls B and when Ok button is pressed in B, A needs to update its menu.
In this scenario you could create a callback function in A, meant to be called when B's ok button is pressed. When you create B you can pass this callback, here's an example:
class B(gtk.Window):
def __init__(self, callback):
gtk.Window.__init__(self)
self.callback = callback
# Create components:
# self.entry, self.ok_button ...
self.ok_button.connect("clicked", self.clicked)
def clicked(self, button):
self.callback(self.entry.get_text())
class A(gtk.Window):
def create_popup(self):
popup = B(self.popup_callback)
popup.show()
def popup_callback(self, text):
# Update menu with new text
# ...

Categories

Resources