How to ceate child Modal window in Python tkinter? - python

Python version is 3.7.
Tried to use tkinter.TopLevel(), the second window is created behind the main window.
I need a true child window which is also modal: can't do anything on the main window before the child is closed.
tkinter.messagebox is very similar but I need my own customized window.
Thank you.

Use the Toplevel widget to set a parent modal dialog
class topDialog:
def __init__(self, parent):
top = self.top = Toplevel(parent)
Label(top, text="Text").pack()
Then follow that up with all of your widgets and such.

Related

Binding closing of toplevel window

How can I create a binding in tkinter for when someone closes a toplevel window or if it is closed with toplevel1.destroy() or something similar. I am trying to make a small pop-up and when the user closes the main window or toplevel I want to prompt the user to save a file. I have figured out that I can set the actual close button to the function but cannot figure out how to get .destroy() and the closing of the main window to call the function. What should I do to bind the destroy function or window closing function?
Tested code:
import tkinter as tk
class TestWidget(tk.toplevel):
def __init__(self, *args, **kwargs):
tk.Toplevel.__init__(*args, **kwargs)
self.protocol("WM_DELETE_WINDOW", self.close)
def close(self):
print("Closed")
self.destroy()
if __name__ == "__main__":
root = tk.Tk()
TestWidget()
root.mainloop()
So I figured out that if you make a toplevel widget integrated into a class that you can find all the classes with the code below:
for child in root.winfo_children():
print(child)
This returns all the widgets and classes used:
.!testwidget
.!testwidget2
With this I can set up a function in the main window to call the child's close function one by one and this allows me to gain access to all the needed pieces

Can the auto-placement of tkinter windows be turned off?

I have this very basic code
from tkinter import *
class GUI(Tk):
def __init__(self):
super().__init__()
self.geometry('600x400')
Button(self, text="Show new window", command=self.show_window).pack()
def show_window(self):
smallwin = display()
class display(Toplevel):
def __init__(self):
super().__init__()
self.geometry('300x300+30+30')
self.attributes('-topmost',True)
root = GUI()
root.mainloop()
When you click on the button, a child window appears. When you press it again, a second child window appears etc etc, BUT each new window is to the right and further down from the last one.
I would like to know if this automatic behavior can be turned off?
If you explicitly set the geometry for each window, they will go wherever you tell them to go.
You seem to be setting a geometry, but you aren't using it. If you pass that value to the geometry method, the window will go to that exact location.
class display(Toplevel):
def __init__(self):
super().__init__()
self.defaultgeometry='300x300+30+30'
self.wm_geometry(self.defaultgeometry)
...
you can just set the location of the window. Then all windows will open a this exact location.
E.g.
root.geometry('250x150+0+0')
More detailed solutions are described here:
How to specify where a Tkinter window opens?

How to put a toplevel window in front of the main window in tkinter?

Is there any way to put a toplevel window in front of the main window?
Here's the code:
from tkinter import *
root = Tk()
root.geometry('1280x720')
def create_new_window():
root2 = Toplevel()
root2.geometry('500x500')
create_new_window()
mainloop()
Here, I want the root2 window to always stay in front of the root window.
I tried using root2.attributes('-topmost' , 1), but the problem is that this line puts the window on top of all the other programs as well.
What I want is that the toplevel window should only be in front of the main window, and it should never go back when I click on the main window.
Is there any way to achieve this in tkinter?
It would be great if anyone could help me out.
What you want, i think, is a transient window, you nedd to do:
root2.wm_transient(root)
From the manual:
wm transient window ?master?
If master is specified, then the window manager is informed that window is a transient window (e.g. pull-down menu) working on behalf of master (where master is the path name for a top-level window). If master is specified as an empty string then window is marked as not being a transient window any more. Otherwise the command returns the path name of window's current master, or an empty string if window isn't currently a transient window. A transient window will mirror state changes in the master and inherit the state of the master when initially mapped. It is an error to attempt to make a window a transient of itself.
So you could do something like this, but it seems buggy for me.
What I have done is to bind the FocusOut event to the toplevel that was created, so every time it looses the focus it triggers the event stackingorder to put the windos in the right order. You may need to expire this code for several events of your choice, but to get you the idea..
Here is the code:
import tkinter as tk
def add_toplevel(idx, toplevel):
if idx == 'end':
idx = len(toplevels)
toplevels.insert(idx,toplevel)
def create_new_window():
root2 = tk.Toplevel()
root2.geometry('500x500')
add_toplevel('end',root2)
root2.bind('<FocusOut>', stackingorder)
def stackingorder(event):
for toplevel in toplevels:
toplevel.lift()
toplevel.update_idletasks()
toplevels = [] #stacking order by index
root = tk.Tk()
create_new_window()
root.mainloop()
You are maybe also intrested in this:
https://stackoverflow.com/a/10391659/13629335

Object oriented Tkinter, best way to communicate between widgets in gui with many frames

I am trying to figure out what the best way to communicate between different widgets is, where the widgets are custom classes inheriting from tkinter widgets and I have several frames present (to help with layout management). Consider for example the following simple gui (written for python 3, change tkinter to Tkinter for python 2):
from tkinter import Frame,Button,Tk
class GUI(Frame):
def __init__(self, root):
Frame.__init__(self,root)
self.upper_frame=Frame(root)
self.upper_frame.pack()
self.lower_frame=Frame(root)
self.lower_frame.pack()
self.upper_btn1 = Button(self.upper_frame, text="upper button 1")
self.upper_btn2 = Button(self.upper_frame, text="upper button 2")
self.upper_btn1.grid(row=0,column=0)
self.upper_btn2.grid(row=0,column=1)
self.lower_btn = CustomButton(self.lower_frame, "lower button 3")
self.lower_btn.pack()
class CustomButton(Button):
def __init__(self,master,text):
Button.__init__(self,master,text=text)
self.configure(command=self.onClick)
def onClick(self):
print("here I want to change the text of upper button 1")
root = Tk()
my_gui = GUI(root)
root.mainloop()
The reason I put them in different frames is because I want to use different layout managers in the two different frames to create a more complicated layout. However, I want the command in lower_btn to change properties of eg upper_btn1.
However, I can not immediately access upper_btn1 from the instance lower_btn of the customized class CustomButton. The parent of lower_btn is frame2, and the frame2 parent is root (not the GUI instance). If I change the parent of lower_btn to the GUI instance, ie
self.lower_btn = CustomButton(self, "lower button")
it will not be placed correctly when using pack(). If I change the parent of frame1/frame2 to the GUI instance, ie
self.upper_frame=Frame(self)
self.upper_frame.pack()
self.lower_frame=Frame(self)
self.lower_frame.pack()
I could in principle access the upper_btn1 from lower_btn by self.master.master.upper_btn1, BUT then the frame1/frame2 are not placed correctly using pack() (this I don't understand why). I can of course pass the GUI instance as separate variable, on top of master, to CustomButton, ie something like
class CustomButton(Button):
def __init__(self,master,window,text):
Button.__init__(self,master,text=text)
self.window=window
self.configure(command=self.onClick)
def onClick(self):
self.window.upper_btn1.configure(text="new text")
and then change the construction of lower_btn to
self.lower_btn = CustomButton(self.lower_frame,self, "lower button 3")
but is that the "correct" way of doing it?
So, what is the best way to reorganize this gui? Should I pass GUI as a separate variable on top of the master/parent argument? Should I change the master/parent of the buttons and/or the frames (and somehow fix the issues with the layout management)? Or should I make the commands of the buttons as methods of the GUI instance so they can communicate with the widgets in that GUI, although this does not really feel like object oriented programming? I can't seem to find a working object oriented design that feels "correct". Also, an explanation why pack() does not work for frame1/frame2 if I make the GUI instance (self) the master, would also be appreciated, as that would have been my intuitive, most object oriented, approach.
Edit: Maybe the best way is to not use frames inside frames at all, and use only grid() and then use colspan/rowspan to give the layout management more flexibility.
A year late, but: One way to communicate between widgets is to instantiate some kind of control center object that receives knowledge about the state of your widgets and then compels other widgets to act on that information. This 'manager' functionality will be independent of the layout of your widgets.
Here's an implementation that's customized to your example. The idea is to add a .manager attribute to the lower button, and to notify this manager when clicked. The GUI class remains unchanged.
from tkinter import Frame,Button,Tk
class Manager(object):
def __init__(self, gui):
self.gui = gui
self.gui.lower_btn.manager = self
def onClick(self):
self.gui.upper_btn2.configure(text="changed text")
class GUI(Frame):
def __init__(self, root):
Frame.__init__(self,root)
self.upper_frame=Frame(root)
self.upper_frame.pack()
self.lower_frame=Frame(root)
self.lower_frame.pack()
self.upper_btn1 = Button(self.upper_frame, text="upper button 1")
self.upper_btn2 = Button(self.upper_frame, text="upper button 2")
self.upper_btn1.grid(row=0,column=0)
self.upper_btn2.grid(row=0,column=1)
self.lower_btn = CustomButton(self.lower_frame, "lower button 3")
self.lower_btn.pack()
class CustomButton(Button):
def __init__(self,master,text):
Button.__init__(self,master,text=text)
self.configure(command=self.onClick)
self.manager = None
def onClick(self):
if self.manager:
self.manager.onClick()
else:
print("here I want to change the text of upper button 1")
root = Tk()
my_gui = GUI(root)
Manager(my_gui)
root.mainloop()

PyQt - Do not want modeless dialog always on top

I have a main window that creates modeless dialogs. That's working well, but the dialogs are always in front of the main window. Even if I go back to the main window and use it to give it focus, the dialogs always remain on top. I cannot slide the main window on top of the dialogs.
I'm passing the main window's self as the parent to the dialog.
#In my main window
self.beacon_dlg = dialog_beacon.BeaconDialog(self)
#In the dialog class
class BeaconDialog(QDialog, ui_dialog_beacon.Ui_Dlg_beacon_soh):
def __init__(self, parent):
super(BeaconDialog, self).__init__(parent)
self.setupUi(self)
Any idea how to allow the main window to be in front of the dialogs, and still close the dialog when the main window is closed (parent control)?
(I'm using PyQt 4.10 and Python 2.7 on Windows)
Thanks.
I ended up using the following and it seems to work, but not sure if it's the best method. Instead of using:
def __init__(self, parent):
super(BeaconDialog, self).__init__(parent)
I used:
def __init__(self, parent):
super(BeaconDialog, self).__init__()
thus not making the dialog a child of the main window. (I still passed the main window as an argument to the class for other reasons)
However then in order to have the dialog shutdown correctly I had to overload the main window's closeEvent() and shut down the dialog myself with:
def closeEvnet(self):
if (self.beacon_dlg) : self.beacon_dlg.reject()
From QDialog Class Reference: "A dialog is always a top-level widget, but if it has a parent, its default location is centered on top of the parent's top-level widget (if it is not top-level itself). It will also share the parent's taskbar entry."
You could try to use QWidget instead.

Categories

Resources