PyGI window not destroying - python

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

Related

Programmatically press the `X` button on the toobar?

I know I can intercept pressing the X button with protocol("WM_DELETE_WINDOW", do_something) however I am having a hard time figuring out how to activate this button or at least the protocol that triggers when this button is pressed.
Here is the situation. I have 2 classes. My main Tk class and my Menu class. When I am setting up the command to close the program with an exit button from the menu I want this button to do exactly the same thing as the X button on the Tk class.
Now I know I could simply call the controller that was passed to the menu class and then call the method I built to handle the close event however I am trying to build this menu class in such a way that I do not need to do this from the menu class. This will allow me to use the menu class on any app I build with little to no editing.
I have not been able to find a post or some documentation that tells me how I can programmatically activate the "WM_DELETE_WINDOW" protocol.
Here is an image if it is unclear what I want. Simply I want the exit button to do exactly what the X button does.
Main class:
import tkinter as tk
import PIP_MENU
class PIP(tk.Tk):
def __init__(self):
super().__init__()
PIP_MENU.start(self)
self.protocol("WM_DELETE_WINDOW", self.handle_close)
def handle_close(self):
print("Closing")
self.quit()
if __name__ == '__main__':
PIP().mainloop()
Menu class on separate .py file:
import tkinter as tk
class Menu(tk.Menu):
def __init__(self, controller):
super().__init__()
self.controller = controller
controller.config(menu=self)
file_menu = tk.Menu(self, tearoff=0)
self.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Exit", command=self.handle_exit)
def handle_exit(self):
# What can I do here that will be handled by
# protocol "WM_DELETE_WINDOW" of the main class?
# All I can find is destroy() and quit()
# But both of these do not get handled by "WM_DELETE_WINDOW".
def start(controller):
Menu(controller)
I have not been able to find a post or some documentation that tells me how I can programmatically active the "WM_DELETE_WINDOW" protocol.
You can't. By definition, the WM_DELETE_WINDOW protocol comes from the window manager.
Catching the protocol handler is designed to give you an opportunity to override its behavior. It is not designed to be a way to trigger some code no matter how the application is destroyed. If you want to run some code when the window is destroyed, whether that is by the user clicking the control on the window frame or through some other way, the correct way to do that is to bind to the <Destroy> event on the root window.
You have to be careful, in that any binding on the root window will be triggered for every widget. Therefore, your binding should only run when event.widget is the same as the root window.
The following example illustrates the technique. There is a method handle_close which is called whenever the window is destroyed. Whether you close the window by clicking on the control on the window frame, or whether you click on the "Close me!" button, the code still runs.
import tkinter as tk
class Example(tk.Tk):
def __init__(self):
super().__init__()
self.bind("<Destroy>", self.handle_close)
button = tk.Button(self, text="Close me!", command=self.destroy)
button.pack()
def handle_close(self, event):
if event.widget == self:
print("Closing")
self.quit()
example = Example()
example.mainloop()
I don't believe there's a method that invokes a specific protocol, since protocol seems to be a specific event watch. Here's a snippet from the module's class Tk:
class Tk(Misc, Wm):
"""Toplevel widget of Tk which represents mostly the main window
of an application. It has an associated Tcl interpreter."""
def _loadtk(self):
...
self.protocol("WM_DELETE_WINDOW", self.destroy)
As you can see, by default the module itself sets the protocol to destroy(). The protocol() method only seeks to replace the default function (at the absence of a function, it just removes the function):
def wm_protocol(self, name=None, func=None):
"""Bind function FUNC to command NAME for this widget.
Return the function bound to NAME if None is given. NAME could be
e.g. "WM_SAVE_YOURSELF" or "WM_DELETE_WINDOW"."""
if callable(func):
command = self._register(func)
else:
command = func
return self.tk.call(
'wm', 'protocol', self._w, name, command)
protocol = wm_protocol
but to achieve what you want you should be able to reference back to the same handling method with this:
def handle_exit(self):
self.controller.handle_close()
Of course, this is not as versatile since you must explicitly know the handler in your main window.
Thought I have accepted Bryan's answer I did manage to come to a workaround I think is fine here.
If I pass the method that is being used to deal with window closing to my menu class and then check if something has been passed I can then decide on weather or not to use the exit method I made or self.controller.destroy() with an if statement.
Here is my solution.
Main file:
import tkinter as tk
from tkinter import messagebox
import PIP_MENU
class PIP(tk.Tk):
def __init__(self):
super().__init__()
PIP_MENU.start(self, self.handle_close)
self.protocol("WM_DELETE_WINDOW", self.handle_close)
def handle_close(self):
x = messagebox.askquestion("DERP", "Do you want to close without saving?")
if x == "yes":
self.destroy()
if __name__ == '__main__':
PIP().mainloop()
Menu file:
import tkinter as tk
class Menu(tk.Menu):
def __init__(self, controller, exit_handler=None):
super().__init__()
self.controller = controller
self.exit_handler = exit_handler
controller.config(menu=self)
file_menu = tk.Menu(self, tearoff=0)
self.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Exit", command=self.handle_exit)
def handle_exit(self):
if self.exit_handler != None:
self.exit_handler()
else:
self.controller.quit()
def start(controller, exit_handler=None):
return Menu(controller, exit_handler)

ESC key in wx.Dialog doesn't trigger button with ID_CANCEL unless it has focus

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.

Good way of implementing more windows in PyQt

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.

PyQt - open only one child window and minimize it with parent window

The idea is to open a child window from parent window menu and when I minimize the parent window, the child window must be minimized also and only one child window can be opened.
I have the solution for minimizing the child when parent is minimized, but I can open child window multiple-times (although the child is already opened) and I would like to disable opening of multiple child windows.
The parent window is MainWindow.py:
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle('Parent window')
self.flags = QtCore.Qt.Window
self.ControlPanel = Control_panel_window()
self.createActions()
self.createMenus()
def createActions(self):
# window - menu
self.windowShowControlPanelAction = QtGui.QAction(self.tr("&Control panel"), self, statusTip='Control panel')
self.connect(self.windowShowControlPanelAction, QtCore.SIGNAL("triggered()"), self.ShowControlPanel)
def createMenus(self):
# window
self.windowMenu = QtGui.QMenu(self.tr("&Window"), self)
self.windowMenu.addAction(self.windowShowControlPanelAction)
self.menuBar().addMenu(self.windowMenu)
def ShowControlPanel(self):
self.ControlPanel = Control_panel_window(self)
self.ControlPanel.setWindowFlags(QtCore.Qt.Window)
self.ControlPanel.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindow()
win.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
win.show()
sys.exit(app.exec_())
The child window is ChildWindow.py:
class Control_panel_window(QWidget):
def __init__(self, parent = None):
super(Control_panel_window, self).__init__(parent)
self.setFixedSize(200, 300)
def setWindowFlags(self, flags):
print "flags value in setWindowFlags"
print flags
super(Control_panel_window, self).setWindowFlags(flags)
The problem is: how can I set that only one child window is opened?
In your ShowControlPanel function you are creating a new control panel each time the signal is triggered. Since you already have an instance available why don't you use that instead?
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle('Parent window')
self.flags = QtCore.Qt.Window
self.control_panel = ControlPanelWindow(self)
self.control_panel.setWindowFlags(self.flags)
#...
def create_actions(self):
self.show_control_panel_action = QtGui.QAction(
self.tr("&Control panel"),
self,
statusTip='Control panel'
)
self.show_control_panel_action.triggered.connect(self.show_control_panel)
#...
def show_control_panel(self):
self.control_panel.show()
Some stylistic notes:
Try to follow PEP8 official python coding-style guide. This include using CamelCase for classes, lowercase_with_underscore for almost everything else. In this case, since Qt uses halfCamelCase for methods etc you may use it too for consistency.
Use the new-style signal syntax:
the_object.signal_name.connect(function)
instead of:
self.connect(the_object, QtCore.SIGNAL('signal_name'), function)
not only it reads nicer, but it also provides better debugging information. Using QtCore.SIGNAL you will not receive an error if the signal doesn't exist (e.g. you wrote a typo like trigered() instead of triggered()). The new-style syntax does raise an exception in that case you will be able to correct the mistake earlier, without having to guess why something is not working right and searching the whole codebase for the typo.

How to pop out a separate window from a tabWidget in PySide Qt

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
}

Categories

Resources