This question is not a duplicate: see below.
I want to create a modal dialog box in tkinter. In other words, a dialog box which, while it is active, prevents the user from interacting with the parent window. There is an existing question on SO answering this, and there is a full example illustrating the concept.
However, making the dialog box a Toplevel and calling grab_set() on it simply doesn't work, on either Windows 7 or Ubuntu 16.04.4 LTS. The user is still able to close, resize, and in general interact with the parent window.
Is there a way of creating a modal dialog box in Tkinter that actually works?
Here is a minimal usage example of dialog.grab_set() failing to prevent interaction with the parent window:
import os
try:
import Tkinter as tkinter
except ImportError:
import tkinter
class MyToplevel(tkinter.Toplevel, object):
def __init__(self, parent):
tkinter.Toplevel.__init__(self, parent)
self.title("Main window")
MyDialog(self)
self.protocol("WM_DELETE_WINDOW", parent.destroy)
class MyDialog(tkinter.Toplevel, object):
def __init__(self, parent):
tkinter.Toplevel.__init__(self, parent)
self.transient(parent)
self.title("Dialog")
self.grab_set()
self.protocol("WM_DELETE_WINDOW", self.destroy)
if __name__ == "__main__":
root = tkinter.Tk()
root.withdraw()
app = MyToplevel(root)
app.mainloop()
I should point out that using grab_set_global() (as in this answer) does work, but is not a viable solution because it blocks access to all windows, for the entire system.
This is (at least on windows) a version specific problem. To make it work on python2.7, just add self.focus_force() before self.grab_set():
def __init__(self, parent):
tkinter.Toplevel.__init__(self, parent)
self.transient(parent)
self.title("Dialog")
self.focus_force() # added
self.grab_set()
self.protocol("WM_DELETE_WINDOW", self.destroy)
Related
when I opened my Top Level window, I always saw an annoying and weird behaviour.. at the end I realized that it was because of my custom icon.
below an exaple code:
from tkinter import *
from tkinter import ttk
class MainWindow:
def __init__(self):
self.parent=Tk()
self.parent.geometry("494x410+370+100")
self.parent.title("My Software - WITH ICON")
self.parent.iconbitmap("icon.ico")
Button = ttk.Button(self.parent, text="open a new widnow", command=self.OpenNewWindow)
Button.place(x=16, y=16)
def OpenNewWindow(self):
self.obj = NewWindow(self)
class NewWindow:
def __init__(self, mw):
self.window, self.mw = Toplevel(mw.parent), mw
self.window.geometry("200x150+360+200")
self.window.title("New Window")
self.window.iconbitmap("icon.ico") # it creates the issue..
self.window.protocol("WM_DELETE_WINDOW", self.on_close)
self.window.focus()
self.mw.parent.attributes('-disabled', 1)
self.window.transient(mw.parent)
self.window.grab_set()
self.mw.parent.wait_window(self.window)
def on_close(self):
self.mw.parent.attributes('-disabled', 0)
self.window.destroy()
def main():
app=MainWindow()
app.parent.mainloop()
if __name__=="__main__":
main()
to make it clear where is the issue, I create a GIF:
here we have two softwares, "without icon.py" and "with icon.py". they are the same, but the first one doesn't use my custom icon for his second window.
as you can see, if I run "with icon.py" the second window will be always affected by something when I will open it, but for "without icon.py" this "something" doesn't exist.
what is the issue? the software opens his second window, focus the root one (it's the issue), and then focus the second window again. you can see it clearly from the GIF.
how can I solve the issue? and why with the default icon this weird behaviour doesn't happen?
I've been using the code from this example PyQt: How to hide QMainWindow:
class Dialog_02(QtGui.QMainWindow):
def __init__(self, parent):
super(Dialog_02, self).__init__(parent)
# ensure this window gets garbage-collected when closed
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
...
def closeAndReturn(self):
self.close()
self.parent().show()
class Dialog_01(QtGui.QMainWindow):
...
def callAnotherQMainWindow(self):
self.hide()
self.dialog_02 = Dialog_02(self)
self.dialog_02.show()
It works, however when opening a second window, the window's task bar icon doesn't show. I've tried using QtGui.QDialog for the Dialog_02 as well but that gives me the same result.
How do I go about solving this?
Edit: I'm on Windows 10
Just guessing (because I don't know what platform you're on, and I don't use a task-bar myself, so I cant really test it), but try getting rid of the parent:
class Dialog_02(QtGui.QMainWindow):
def __init__(self, other_window):
super(Dialog_02, self).__init__()
# ensure this window gets garbage-collected when closed
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self._other_window = other_window
...
def closeAndReturn(self):
self.close()
self._other_window.show()
How do you remove the border of a TopLevel without using overrideredirect?
TopLevel.overrideredirect(True)
It would be great if a sample code can be provided.
Python 2.7.3, Linux, Tkinter version $Revision: 81008 $
With the help of Bryan Oakley, I have realized a solution that would allow me to use 'overrideredirect' while solving my problem, and that is using 'Unmap' event.
The following example code shows that when the additional window can be minimized with the main window when using 'Map' and 'Unmap':
import Tkinter
class App:
def __init__(self):
self.root = Tkinter.Tk()
Tkinter.Label(self.root, text="main window").pack()
self.window = Tkinter.Toplevel()
self.window.overrideredirect(True)
Tkinter.Label(self.window, text="Additional window").pack()
self.root.bind("<Unmap>", self.OnUnMap)
self.root.bind("<Map>", self.OnMap)
self.root.mainloop()
def OnMap(self, e):
self.window.wm_deiconify()
def OnUnMap(self, e):
self.window.wm_withdraw()
app=App()
I use wxpython altogether with matplotlib backend on an ubuntu machine. I would like to connect my matplotlib canvas to a button_press_event that pops up a wxpython modal dialog. When the modal dialog pops up, the whole application gets frozen. This problem does not occur on a windows machine. Here is a snippet that typically reproduces the problem.
import wx
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure
class SettingDialog(wx.Dialog):
def __init__(self, parent=None):
wx.Dialog.__init__(self, parent, wx.ID_ANY, title="Modal dialog")
class PlotterFrame(wx.Frame):
def __init__(self, parent, title="Frame with matplotlib canvas"):
wx.Frame.__init__(self, parent, wx.ID_ANY, title)
self.figure = Figure(figsize=(5,4), dpi=None)
self.canvas = FigureCanvasWxAgg(self, -1, self.figure )
self.canvas.mpl_connect("button_press_event", self.on_click)
def on_click(self, event=None):
d = SettingDialog(self)
d.ShowModal()
d.Destroy()
if __name__ == "__main__":
app = wx.App(False)
f = PlotterFrame(None)
f.Show()
app.MainLoop()
Would you have any idea of what is wrong with my code ?
PS0 : The problem is that the dialog window is also frozen, like all the applications in the desktop wich do not react anymore. the only way to escape is to switch to another desktop using the keyboard
PS1 : with a very common example like http://eli.thegreenplace.net/files/prog_code/wx_mpl_bars.py.txt
the problem also appear, I conclude so that, this issue is a bug on linux (here ubuntu 12.04) for the following libs version :
wx.version : '2.8.12.1'
matplotlib.version : '1.1.1rc'
The whole point of a modal dialog is that it freezes the application while the dialog is in its modal state. If you don't want the application to freeze, then don't show the dialog modally.
I ran into this problem too, on several different Linux systems. None of the various mentioned resources seem to be describing exactly the same as this problem. After some investigation, it seems that something is locking up when you try to show a modal dialog before the mouse release event fires in the Matplotlib FigureCanvas.
Once I realized that, the solution is very simple. Your event handler should become:
def on_click(self, event=None):
try:
event.guiEvent.GetEventObject().ReleaseMouse()
except:
pass
d = SettingDialog(self)
d.ShowModal()
d.Destroy()
One issue that could complicate the code is that not all matplotlib events have the same structure. So if this had been a 'pick_event' handler, you would instead do
event.mouseevent.guiEvent.GetEventObject().ReleaseMouse()
Check http://matplotlib.org/users/event_handling.html for the key to which Event types are passed in by which matplotlib events.
I'm trying to make a GUI using Tkinter and have come to implementing a menu bar. I've looked at a few tutorials and written some code for it, but a menu bar never seems to appear - just a blank frame with a white background. This doesn't just happen for my code though; on copying and pasting the code of one of the aforementioned tutorials into a new script, the same behaviour is exhibited.
I'd appreciate it if anyone could shed any light on what's causing this. My system is OS X 10.5, Python 2.7, Tk 8.4. Here's the code from the tutorial that doesn't appear to work:
#!/usr/local/bin/python2.7
from Tkinter import *
from ttk import *
class App(Frame):
def __init__(self):
Frame.__init__(self)
self.master.geometry('400x300')
self.master.title(__file__)
self.pack()
self.menu = Menu(tearoff=False)
self.master.config(menu = self.menu)
fm = self.file_menu = None
fm = Menu(self.menu, tearoff=False)
self.menu.add_cascade(label='File', menu = fm)
fm.add_command(label='Say Hello', command = self.say_hello)
fm.add_separator()
fm.add_command(label='Quit', command = self.quit)
self.mainloop()
def say_hello(self, *e):
self.label = Label(self.master, text='Hello there!')
self.label.pack(anchor=CENTER, fill=NONE, expand=YES, side=LEFT)
if __name__ == '__main__':
App()
and my code is here:
from Tkinter import *
class App(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
parent.title("Cluedo Solver 1.0")
menubar = Menu(root)
menubar.add_command(label="File")
menubar.add_command(label="Quit", command=root.quit())
root.config(menu=menubar)
root=Tk()
root.geometry("300x250+300+300")
app=App(root)
root.mainloop()
Based on some comments you made to one of the answers, you are apparently running this on a Macintosh. The code works fine, but the menu appears in the mac menubar rather than on the window like it does on Windows and Linux. So, there's nothing wrong with your code as far as the menubar is concerned.
Code with Explanation
From personal experience, I have found that it is usually easier to manage all widgets in a widgets method. That is what I did here, and it worked. Also, instead of parent, I used master. I will now walk you through the code step-by-step.
from Tkinter import *
We import Tkinter (GUI stuff)
class App(Frame):
We create a class called App, which is the Frame where widgets are held.
def __init__(self, master):
Frame.__init__(self, master)
self.grid()
self.widgets()
We create a method called __init__. This initializes the class, and runs another method called widgets.
def widgets(self):
menubar = Menu(root)
menubar.add_command(label="File")
menubar.add_command(label="Quit", command=root.quit())
root.config(menu=menubar)
We create the widgets method. This is where the widget, menubar is added. If we were to create anymore widgets, they would also be here.
root=Tk()
root.title("Menubar")
app=App(root)
root.mainloop()
Lastly, we give the entire window some properties. We give it a title, Menubar, and run the App class. lastly, we start the GUI's mainloop with root.mainloop.
I am trying the code as above, but all I get is "Python" on the macOS menubar and it's usual pulldown. tkinter just doesn't seem to work menus on macOS 10.14.1
I think what is happening is that there are mac specific fixups that are changing the event codes so certain menu items will end up under the Python menu item instead of where expected, I saw some of this in my own experiments. When I expanded my code and used some of the reserved FILE event codes instead of the standard ones, things worked better.
#!/Library/Frameworks/Python.framework/Versions/3.7/bin/python3.7
# -*- coding: utf-8 -*-# -*- coding: utf-8 -*-
from tkinter import *
class App(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.grid()
self.widgets()
def widgets(self):
menubar = Menu(root)
menubar.add_command(label = 'File')
menubar.add_command(label = 'quit', command = root.quit())
root.config(menu = menubar)
root = Tk()
root.title('Menubar')
app = App(root)
root.mainloop()
Check your mac menu bar if you are doing any GUI that involves menubar which you will like to view or test. Its subtle and you may think you code is not to working. Click on the app(in this case python window), it will show a drop down menubar.
guys to solve the problem
look the file part that's where the menubar for mac is located