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
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 am designing an application using PyQt that will manage multiple instances of Selenium. Each instance has a QFrame with unique information and controls and can be tabbed through from the main window.
class Instance(QFrame):
def __init__(self):
super().__init__()
self.username = "whatever"
...
self.startButton = QPushButton('Start')
self.startButton.clicked.connect(lambda: self.engineStart())
self.exitButton = QPushButton('Exit')
self.exitButton.clicked.connect(lambda: self.engineExit())
...
How it looks
Users should be able to create and delete instances at will.
Creating a tab is no problem. I have a "+" button set as the QTabWidget's cornerWidget. It is connected to a simple method to add the tab.
class App(QFrame):
def __init__(self):
...
def addNewTab(self):
t = Instance()
self.tabs.addTab(t, t.username)
The problem is, how can I use the "Exit" button from the "inside" Instance class to remove the tabs that are managed from the main window's "outside" class? I need some way to call removeTab()
To do what you want you must create a slot in the main window, and connect it to the clicked signal of the button as shown below:
class App(QFrame):
def __init__(self):
...
def addNewTab(self):
t = Instance()
self.tabs.addTab(t, t.username)
t.exitButton.clicked.connect(self.slot)
def slot(self):
self.tabs.removeTab(your_index)
I'm using python and PyGObjects (the introspection lib) for Gtk 3 here.
Consider the following code:
from gi.repository import Gtk
class InternalWidget(Gtk.Button):
def __init__(self):
super(InternalWidget, self).__init__()
self.set_size_request(100,100)
self.connect("button-press-event", self.on_press)
def on_press(self, *args):
print "The Internal Widget was clicked."
class ExternalEventBox(Gtk.EventBox):
def __init__(self):
super(ExternalEventBox, self).__init__()
self.fixed = Gtk.Fixed()
self.add(self.fixed)
self.internal_widget = InternalWidget()
self.set_size_request(200, 200)
self.connect("button-press-event", self.on_press)
self.connect("enter-notify-event", self.on_enter)
self.connect("leave-notify-event", self.on_leave)
def on_enter(self, *args):
self.fixed.put(self.internal_widget, 50,50)
self.show_all()
def on_leave(self, *args):
self.fixed.remove(self.internal_widget)
def on_press(self,*args):
print "The External Event Box was clicked."
w = Gtk.Window(Gtk.WindowType.TOPLEVEL)
w.connect("delete-event", Gtk.main_quit)
w.add(ExternalEventBox())
w.show_all()
Gtk.main()
Above, whenever the mouse enters the ExternalEventBox, a button (InternalWidget) is added to it as a child. When the mouse leaves the ExternalEventBox, the button is removed as a child of the ExternalEventBox.
Now, if you run the code (which you can), the button appears and disappears properly. However, clicking on the button, contrary to what is expected, only sends a signal to the containing ExternalEventBox, whereas the button receives no signal.
Interestingly, the expected behavior (clicking on the button actually clicks it) happens when the button, rather than being dynamically added and removed, is added once in the constructor of the event box, and never removed.
Is this a bug, or am I just missing something?
Edit: In a nutshell, I only get "The External Event Box was clicked.", but never "The Internal Widget was clicked.".
Update: I filed a bug report.
You need to set the EventBox event window to be below it's children using .set_above_child(false)
Here's the docs for it: GtkEventBox
If the window is above, all events inside the event box will go to the event box. If the window is below, events in windows of child widgets will first got to that widget, and then to its parents.
It must be something very simple, but I didn't manage to find a way...
I have a GUI with MainWindow (QMainWindow), where I added Help menu and actionAbout QAction via Qt Designer. Now I want a small new window with text "Program... Version... etc" to show up when I press About item in Help menu.
A triggered signal seem to work and I get NotImplementedError when I press on About. But no idea how to show a new window now from this signal...
class MainWindow(QMainWindow, Ui_MainWindow):
"""
My main GUI window
"""
def __init__(self, db, parent = None):
QMainWindow.__init__(self, parent)
...
#pyqtSlot(QAction)
def on_menuAbout_triggered(self, action):
"""
Slot documentation goes here.
"""
# TODO: not implemented yet
raise NotImplementedError
Ok, it was indeed easy:
#pyqtSlot(QAction)
def on_menuAbout_triggered(self, action):
"""
Show about page
"""
about_box = QMessageBox(self)
about_box.about(self, 'About', 'This is a GUI...\n\nwww.google.com')
# about_box.setText('This is a GUI')
# about_box.exec()
What remains is to insert a link to the page (here google just as an example). The example above is not working. Any ideas how to make a link out the text?
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
...