I've seen many examples of grab_set() being used for modal windows for tkinter but I can't get it to work for my application.
I am creating a second window as my 'Settings' window which is called from the Menu of the main application.
example:
import tkinter as tk
class Main(tk.Tk):
def __init__(self,*args, **kwargs):
tk.Tk.__init__(self,*args, *kwargs)
button = tk.Button(self,text="second window", command=lambda:Settings())
button.pack()
class Settings(tk.Tk):
def __init__(self,*args, **kwargs):
tk.Tk.__init__(self,*args, *kwargs)
button = tk.Button(self,text="quit", command=lambda: quit())
button.pack()
self.grab_set()
if __name__ == "__main__":
app = Main()
app.mainloop()
Right now I can still click the 'Settings' button to create as many instances of Settings as the pc would allow. How do I restrict clickability to the main application window until the second one is closed first?
Here is a super simple example of how you can open another window using Toplevel and how you can edit stuff on the main window from the Toplevel window.
Its very basic but it should be a good enough example to illustrate what is required in tkinter to open new window.
UPDATE: Added the grab_set() method as pointed out by Bryan in the comments.
The grab_set() method according to the documentation routes all events for this application to this widget.
Note: This would be along the lines of a Minimal, Complete, and Verifiable example. It is the smallest possible bit of code to get the point across while also being testable.
from tkinter import *
class GUI(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.my_frame = Frame(self.master)
self.my_frame.pack()
self.button1 = Button(self.master, text="Open New Window", command = self.open_toplevel_window)
self.button1.pack()
self.text = Text(self.master, width = 20, height = 3)
self.text.pack()
self.text.insert(END, "Before\ntop window\ninteraction")
def open_toplevel_window(self):
self.top = Toplevel(self.master)
#this forces all focus on the top level until Toplevel is closed
self.top.grab_set()
def replace_text():
self.text.delete(1.0, END)
self.text.insert(END, "Text From\nToplevel")
top_button = Button(self.top, text = "Replace text in main window",
command = replace_text)
top_button.pack()
if __name__ == "__main__":
root = Tk()
app = GUI(root)
root.mainloop()
Here is an example when using a separate class for the Toplevel:
from tkinter import *
class GUI(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.my_frame = Frame(self.master)
self.my_frame.pack()
self.button1 = Button(self.master, text="Open New Window",
command = open_toplevel_window)
self.button1.pack()
self.text = Text(self.master, width = 20, height = 3)
self.text.pack()
self.text.insert(END, "Before\ntop window\ninteraction")
class open_toplevel_window(Toplevel):
def __init__(self, *args, **kwargs):
Toplevel.__init__(self, *args, **kwargs)
self.grab_set()
def replace_text():
app.text.delete(1.0, END)
app.text.insert(END, "Text From\nToplevel")
top_button = Button(self, text = "Replace text in main window",
command = replace_text)
top_button.pack()
if __name__ == "__main__":
root = Tk()
app = GUI(root)
root.mainloop()
I figured out my problem
import tkinter as tk
class Main(tk.Tk):
def __init__(self,*args, **kwargs):
tk.Tk.__init__(self,*args, *kwargs)
self.button = tk.Button(self,text="second window", command=lambda: SecondWindow())
self.button.pack()
class SecondWindow(tk.Toplevel):
def __init__(self,*args, **kwargs):
tk.Toplevel.__init__(self,*args, *kwargs)
self.button = tk.Button(self,text="quit", command=lambda: quit())
self.button.pack()
self.grab_set()
if __name__ == "__main__":
app = Main()
app.mainloop()
as per Sierra Mountain Tech and Bryan Oakley's suggestion. I have changed my Settings class to Toplevel and it does exactly what I want.
My acutal application has the two in different modules but yield the same results.
Try adding the following line after the line containing the grab_set method:
self.wait_window(self)
You need to allow focus with takefocus = True and you give the focus to it with focus_set()
def __init__(self, *args, **kwargs):
Toplevel.__init__(self, *args, **kwargs)
self.takefocus = True
self.focus_set()
Related
I have an app with multiple windows. I use pack_forget to eliminate the login window and invoke the main window. However this main window loses the default centered position of tkinter. The window is created at position (0 , 0).
Is there any simple way to make this main window be created in the default centered position?
example code, 3 files ->
main.py
#!/usr/bin/env python3
from tkinter import *
from frm_login import Wlogin
class Mainframe(Tk):
def __init__(self):
Tk.__init__(self)
self.frame = Wlogin(self)
self.frame.pack()
def change(self, frame):
self.frame.pack_forget() # delete currrent frame
self.frame = frame(self)
self.frame.pack() # make new frame
if __name__== '__main__':
app = Mainframe()
app.mainloop()
frm_login.py
from tkinter import *
from frm_default import Wmain
class Func(Frame):
def check(self, event=None):
if self.pwd.get() == '1':
self.master.change(Wmain)
else:
self.status.config(text='wrong password')
class Wlogin(Func):
def __init__(self, master=None, **kwargs):
Frame.__init__(self, master, **kwargs)
master.title('Enter password')
master.geometry('300x200')
self.status = Label(self, fg='red')
self.status.pack()
self.lbl = Label(self, text='Enter password')
self.lbl.pack()
self.pwd = Entry(self, show='*')
self.pwd.insert(-1, '1')
self.pwd.pack()
self.pwd.focus()
self.pwd.bind('<Return>', self.check)
self.pwd.bind('<KP_Enter>', self.check)
self.btn = Button(self, text='Done', command=self.check)
self.btn.pack()
self.btn = Button(self, text='Cancel', command=self.quit)
self.btn.pack()
frm_default.py
from tkinter import *
class Wmain(Frame):
def __init__(self, master=None, **kwargs):
Frame.__init__(self, master, **kwargs)
master.title('Main application')
master.geometry('600x400')
There is nothing about your forget / repack code that makes this unique. You can use the same commands you would otherwise. So either define the position yourself:
master.geometry('600x400+300+400')
Or use tk PlaceWindow function:
master.eval('tk::PlaceWindow . center')
Or calculate the position from the window size and monitor size:
master.geometry("600x400")
master.update_idletasks()
x = (master.winfo_screenwidth() - master.winfo_reqwidth()) // 2
y = (master.winfo_screenheight() - master.winfo_reqheight()) // 2
master.geometry(f"+{x}+{y}")
FWIW, my experience tells me that setting the window size yourself instead of letting tkinter calculate it will lead to bugs down the road.
I am creating an tkinter app. For now I just want to get a very basic menubar to work, with a file section, and an exit button in the sub menu. Here is my object oriented code, which may be where I am going wrong:
import tkinter as tk
class MainApplication(tk.Frame):
def __init_(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.menubar = tk.Menu(self)
self.filemenu = tk.Menu(self.menubar, tearoff=0)
self.filemenu.add_command(label="Exit", command=self.quit)
self.menubar.add_cascade(label="File", menu=self.filemenu)
self.config(menu=self.menubar)
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
However, this only creates a blank tkinter window. This usually works for me when I use procedural programming so I think I am doing something wrong with OOP. I am trying to say self.config() as root.config(), but this does not work.
2 big issues there. The first is that you misspelled __init__, so none of your custom code is being run. The second is that you need to apply the menu to the root window, aka self.master (default name) or self.parent (your name). Try like this:
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
self.menubar = tk.Menu(self)
self.filemenu = tk.Menu(self.menubar, tearoff=0)
self.filemenu.add_command(label="Exit", command=self.quit)
self.menubar.add_cascade(label="File", menu=self.filemenu)
self.master.config(menu=self.menubar)
if __name__ == "__main__":
root = tk.Tk()
root.geometry('200x200') # remove once you've added window content
win = MainApplication(root)
win.pack(side="top", fill="both", expand=True)
root.mainloop()
I also moved you to a python3 inheritance style, and defined a size so that you actually see something.
I am creating a GUI using tkinter based on the structure described here. I have some tabs that look identical but with different variables. So I decided to define a class for tabs and add them to the main window. I am going to configure some widgets in one tab from another tab. In line 11, a function is defined that when a button in tab_2 is clicked, tab_1's button background color changes to green. Whereas its working, I have two question:
Is it possible not to define channel_1 as an attribute of main_window? I think there must be better way to do so, specifically, if the GUI is going to be used as module (then main_window will not be defined).
Is it possible to know which tab is open, so when button in each tab is clicked, configurations in the other one changes only?
import tkinter as tk
from tkinter import ttk
class Channel(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.btn = tk.Button(self.parent, text = 'click me', command = self.change_green)
self.btn.pack()
def change_green(self):
main_window.channel_1.btn.config(bg = 'green') # line 11
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.tab_control = ttk.Notebook(self.parent)
self.tab_1 = ttk.Frame(self.tab_control)
self.tab_2 = ttk.Frame(self.tab_control)
self.tab_control.add(self.tab_1, text = 'tab 1')
self.tab_control.add(self.tab_2, text = 'tab 2')
self.tab_control.pack(fill = 'both', expand = 1)
self.channel_1 = Channel(self.tab_1)
self.channel_2 = Channel(self.tab_2)
if __name__ == "__main__":
root = tk.Tk()
main_window = MainApplication(root) # <<<< here defined main_window
main_window.pack(side="top", fill="both", expand=True)
root.mainloop()
I would create class MyTab and keep its widgets in this class, not in channel. It can also keep access to other tab(s) to button in one tab can change color in other tab.
Using tab's parent (self.master) I can get active tab, list of all tabs and activate other tab.
import tkinter as tk
from tkinter import ttk
class MyTab(tk.Frame):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
#self.master = master # super() already set it
self.btn = tk.Button(self, text='click me', command=self.change_green)
self.btn.pack()
self.other_tab = None # default value at start
def change_green(self):
if self.other_tab:
# change color in other tab
self.other_tab.btn.config(bg = 'green')
# get active tab ID
print('active tab ID:', self.master.select())
# get button in active tab
active_tab = root.nametowidget(self.master.select())
print('active tab - btn text:', active_tab.btn['text'])
# get all tabs
print('all tabs:', self.master.children.items())
# set other tab as active
self.master.select(self.other_tab)
class MainApplication(tk.Frame):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
#self.master = master # super() already set it
self.tab_control = ttk.Notebook(self.master)
self.tab_1 = MyTab(self.tab_control)
self.tab_2 = MyTab(self.tab_control)
self.tab_1.other_tab = self.tab_2
self.tab_2.other_tab = self.tab_1
self.tab_control.add(self.tab_1, text = 'tab 1')
self.tab_control.add(self.tab_2, text = 'tab 2')
self.tab_control.pack(fill = 'both', expand = 1)
if __name__ == "__main__":
root = tk.Tk()
main_window = MainApplication(root)
main_window.pack(side="top", fill="both", expand=True)
root.mainloop()
To keep this as short as possible - In my program I start with Page1 and when I press a button I want to open Page2 and close Page1, I have managed to open Page2 but I cant close Page1, I have tried using .destroy() but it closes everything not just the page. I looked around some questions here on SO but couldn't find much in the same layout as my code so I wasnt sure how to apply it to mine. This is my first tkinter project so I am still getting to grips with it.
My code is;
class Page1:
def __init__(self,master):
self.master = master
#lots of labels and buttons
self.BTNNextPage = ttk.Button(master, text = "Proceed",
command = self.NextPage)
self.BTNNextPage.place(x=450, y=420)
def NextPage(self):
self.newWindow = tk.Toplevel(self.master)
self.app = Page2(self.newWindow)
self.master.destroy()
class Page2():
def __init__(self,master):
self.master = master
#tried Page1.destroy() here but Page1 has no attibute destroy
#more labels and buttons
def main():
widthpixels=690
heightpixels=500
root = tk.Tk()
root.resizable(width=False, height=False)
root.configure(background='black')
root.iconbitmap("Image")
root.wm_title("Title")
root.geometry('{}x{}'.format(widthpixels, heightpixels))
app = Page1(root)
root.mainloop()
if __name__ == "__main__":
main()
If you destroy root, it destroys all the widgets contained, including Page2. To destroy only page 1, one possibility is to make the page classes inherit from tk.Frame, so that they have a destroy method:
import tkinter as tk
from tkinter import ttk
class Page1(tk.Frame):
def __init__(self, master, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.pack(fill='both', expand=True) # display page 1
#lots of labels and buttons:
tk.Label(self, text='Page 1').place(relx=0.5, rely=0.5)
self.BTNNextPage = ttk.Button(self, text="Proceed", command=self.NextPage)
self.BTNNextPage.place(x=450, y=420)
def NextPage(self):
self.app = Page2(self.master) # create page 2
self.destroy() # remove page 1
class Page2(tk.Frame):
def __init__(self, master, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.pack(fill='both', expand=True) # display page 2
# create widgets on page 2
tk.Label(self, text='Page 2').pack()
tk.Button(self, text='Quit', command=self.master.destroy).pack(side='bottom')
def main():
widthpixels=690
heightpixels=500
root = tk.Tk()
root.resizable(width=False, height=False)
root.configure(background='black')
root.wm_title("Title")
root.geometry('{}x{}'.format(widthpixels, heightpixels))
app = Page1(root)
root.mainloop()
if __name__ == "__main__":
main()
I am creating 2 window in my program and i am using two class, since the code is complex, i separate it in 2 different python file. After i imported the second window file, how can i make sure it open without having this error which show in this picture
The original result should look like this after the new window button clicked:
Coding for Main Window:
from tkinter import *
import classGUIProgram
class Window(Tk):
def __init__(self, parent):
Tk.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.geometry("600x400+30+30")
self.wButton = Button(self, text='newWindow', command = self.OnButtonClick)
self.wButton.pack()
def OnButtonClick(classGUIProgram):
classGUIProgram.top = Toplevel()
master = Tk()
b = classGUIProgram.HappyButton(master)
master.mainloop()
if __name__ == "__main__":
window = Window(None)
window.title("title")
window.mainloop()
Coding for Second Window:
from tkinter import *
class HappyButton:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.printButton = Button(frame, text="Print message", command=self.printMessage)
self.printButton.pack(side=LEFT)
self.quitButton = Button(frame, text="Quit", command= quit)
self.quitButton.pack(side=LEFT)
self.downloadHistoryCB=Checkbutton(frame, text="Download History")
self.downloadHistoryCB.pack(side=LEFT)
def printMessage(self):
print("Wow this actually worked!")
master = Tk()
b = HappyButton(master)
master.mainloop()
You're creating extra Tk windows. Here is an example of using Toplevel widgets and another file.
mainWindow.py
import tkinter as tk
import secondWindow as sW
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
self.title("Main Window")
self.geometry("600x400+30+30")
tk.Button(self, text = "New Window", command = self.new_window).pack()
tk.Button(self, text = "Close Window", command = self.close).pack()
self._second_window = None
def new_window(self):
# This prevents multiple clicks opening multiple windows
if self._second_window is not None:
return
self._second_window = sW.SubWindow(self)
def close(self):
# Destory the 2nd window and reset the value to None
if self._second_window is not None:
self._second_window.destroy()
self._second_window = None
if __name__ == '__main__':
window = MainWindow()
window.mainloop()
secondWindow.py
import tkinter as tk
class SubWindow(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.title("Sub Window")
self.geometry("400x300+30+30")
# Change what happens when you click the X button
# This is done so changes also reflect in the main window class
self.protocol('WM_DELETE_WINDOW', master.close)
tk.Button(self, text = "Print", command = self.printMessage).pack()
def printMessage(self):
print("Wow this actually worked!")
When using another file be sure to not have any global code you don't want running. Your classes don't have to inherit from Tk and Toplevel, this is just an example. But you need to ensure you only ever have one instance of Tk otherwise you get the behaviour you encountered