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
# ...
Related
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)
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
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.
I'm creating some dialogs using TkInter and need to be able to open a child sub-window (modal or modeless) on clicking a button in the parent. The child would then allow a data record to be created and this data (either the record or if the operation was cancelled) needs to be communicated back to the parent window. So far I have:
import sel_company_dlg
from Tkinter import Tk
def main():
root = Tk()
myCmp = sel_company_dlg.SelCompanyDlg(root)
root.mainloop()
if __name__ == '__main__':
main()
This invokes the top level dialog which allows the user to select a company. The company selection dialog looks like this:
class SelCompanyDlg(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent_ = parent
self.frame_ = Frame( self.parent_ )
// .. more init stuff ..
self.btNew_ = Button( self.frame_, text="New ...", command=self.onNew )
def onNew(self):
root = Toplevel()
myCmp = company_dlg.CompanyDlg(root)
On clicking the New ... button, a Create Company dialog is displayed which allows the user to fill in company details and click on create or cancel. Here's the opening bit of that:
class CompanyDlg(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
// etc.
I'm struggling with the best way of invoking the child dialog in onNew() - what I have works but I'm not convinced it's the best approach and also, I can't see how to communicate the details to and from the child dialog.
I've tried looking at online tutorials / references but what I've found is either too simplistic or focuses on things like tkMessageBox.showinfo() which iss not what I want.
There are at least a couple ways to solve your problem. Either your dialog can directly send information to the main application, or your dialog can generate an event that tells the main application that data is really to be pulled from the dialog. If the dialog simply changes the appearance of something (for example, a font dialog) I usually generate an event. If the dialog creates or deletes data I typically have it push information back to the application.
I typically have an application object that acts as the controller for the GUI as a whole. Often this is the same class as the main window, or it can be a separate class or even defined as a mixin. This application object has methods that dialogs can call to feed data to the application.
For example:
class ChildDialog(tk.Toplevel):
def __init__(self, parent, app, ...)
self.app = app
...
self.ok_button = tk.Button(parent, ..., command=self.on_ok)
...
def on_ok(self):
# send the data to the parent
self.app.new_data(... data from this dialog ...)
class MainApplication(tk.Tk):
...
def on_show_dialog(self):
dialog = ChildDialog(self)
dialog.show()
def new_data(self, data):
... process data that was passed in from a dialog ...
When creating the dialog, you pass in a reference to the application object. The dialog then knows to call a specific method on this object to send data back to the application.
If you're not into the whole model/view/controller thing you can just as easily pass in a function rather than an object, effectively telling the dialog "call this function when you want to give me data".
In one of my projects I was trying to check within a child tk.Toplevel window (child1) of my root window (self), if a tk.Toplevel window (child2) was created by the user from within the root window, and if this window (child2) is present at the users screen at the moment.
If this wouldn't be the case, the new tk.Toplevel window should gets created by the child window (child1) of the root window, instead of the root window itself. And if it was already created by the root window and is currently present at the users screen, it should get focus() instead of getting reinitialized by "child1".
The root window was wrapped inside a class called App() and both "children" windows were created by methods inside the root class App().
I had to initialize "child2" in a quiet mode if an argument given to the method was True. I suppose that was the entangled mistake. The problem occurred on Windows 7 64 bit, if that's significant.
I tried this (example):
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
top = self.winfo_toplevel()
self.menuBar = tk.Menu(top)
top['menu'] = self.menuBar
self.menuBar.add_command(label='Child1', command=self.__create_child1)
self.menuBar.add_command(label='Child2', command=lambda: self.__create_child2(True))
self.TestLabel = ttk.Label(self, text='Use the buttons from the toplevel menu.')
self.TestLabel.pack()
self.__create_child2(False)
def __create_child1(self):
self.Child1Window = tk.Toplevel(master=self, width=100, height=100)
self.Child1WindowButton = ttk.Button(self.Child1Window, text='Focus Child2 window else create Child2 window', command=self.CheckForChild2)
self.Child1WindowButton.pack()
def __create_child2(self, givenarg):
self.Child2Window = tk.Toplevel(master=self, width=100, height=100)
if givenarg == False:
self.Child2Window.withdraw()
# Init some vars or widgets
self.Child2Window = None
else:
self.Child2Window.TestLabel = ttk.Label(self.Child2Window, text='This is Child 2')
self.Child2Window.TestLabel.pack()
def CheckForChild2(self):
if self.Child2Window:
if self.Child2Window.winfo_exists():
self.Child2Window.focus()
else:
self.__create_child2(True)
else:
self.__create_child2(True)
if __name__ == '__main__':
App().mainloop()
Here comes the problem:
I wasn't able to check if "child2" is already present. Got error: _tkinter.TclError: bad window path name
Solution:
The only way to get the right 'window path name' was, instead of calling the winfo_exists() method directly onto the "child2" window, calling the master of the "child1" window and adding the according attributes followed by the attributes of the master window you want to use.
Example (edit of the method CheckForChild2):
def CheckForChild2(self):
if self.Child2Window:
if self.Child1Window.master.Child2Window.winfo_exists():
self.Child1Window.master.Child2Window.focus()
else:
self.__create_child2(True)
else:
self.__create_child2(True)
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
...