I am creating an app which will have two Tk() widgets. Is it possible to combine them side into one larger widget side by side to make the app easier to use?
from tkinter import *
tk = Tk()
canvas = Canvas(tk,width=400, height=150)
canvas.pack()
tk2 = Tk()
canvas2 = Canvas(tk2,width=400, height=150)
canvas2.pack()
tk.mainloop(), tk2.mainloop()
When I do this to make the basic windows, I obviously get two seperate windows. Can that be combined into one?
I am a beginner and am using python 3.3
Not sure if it is what you are looking for, but you can create two Frames inside your main Tk.
import tkinter as tk
class SubWindow(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
x = tk.Text(self)
x.pack()
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self)
self.win1 = SubWindow(self)
self.win1.pack(side="left")
self.win2 = SubWindow(self)
self.win2.pack(side="right")
if __name__ == "__main__":
main = MainWindow()
main.mainloop()
EDIT:
Here is the code to make Frames resize when window does:
import tkinter as tk
class SubWindow(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
x = tk.Text(self)
x.pack(expand=1, fill='both')
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self)
self.win1 = SubWindow(self)
self.win1.pack(side="left", expand=1, fill=tk.BOTH)
self.win2 = SubWindow(self)
self.win2.pack(side="right", expand=1, fill=tk.BOTH)
if __name__ == "__main__":
main = MainWindow()
main.mainloop()
You should not have two instances of Tk in the same app. If you want multiple windows, you should create instances of Toplevel. If you want everything in one window you would create instances of Frame and arrange them however you want (side by side, top to bottom, in a grid, etc).
While it's possible to have multiple instances of Tk, there are side effects that can cause unexpected problems. Tk (upon which tkinter is built) was designed to have a single root widget.
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.
So i just want to make this window a text widget thats always on top. Im trying to add this widget but it keeps creating another window...
import tkinter as tk
from tkinter import ttk
class App(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.pack()
hello="hello"
self.tb = tk.Text(self)
self.tb.pack(expand=1, fill = tk.BOTH)
self.tb.insert(tk.END, hello)
topLevelWindow = tk.Toplevel(self)
# Make topLevelWindow remain on top until destroyed, or attribute changes.
topLevelWindow.attributes('-topmost', 'true')
if __name__ == "__main__":
root = tk.Tk()
main = App(root)
root.mainloop()
If you want your text widget to be active you need to call this instead:
self.tb.focus_set()
What you're doing is instead create a Toplevel widget, which is a window that stays topmost, as in above all windows in desktop, you should remove:
topLevelWindow = tk.Toplevel(self)
# Make topLevelWindow remain on top until destroyed, or attribute changes.
topLevelWindow.attributes('-topmost', 'true')
If you also want that your entire window you can do that for your root instead in your 'main' according to this answer:
root.call('wm', 'attributes', '.', '-topmost', '1')
Finally to have:
import tkinter as tk
from tkinter import ttk
class App(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.pack()
hello="hello"
self.tb = tk.Text(self)
self.tb.pack(expand=1, fill = tk.BOTH)
self.tb.insert(tk.END, hello)
self.tb.focus_set()
if __name__ == "__main__":
root = tk.Tk()
root.call('wm', 'attributes', '.', '-topmost', '1')
main = App(root)
root.mainloop()
Also if you want to make other widgets unfocusable:
widget.config(takefocus=False)
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()
I've been trying to get to grips with OOP and tkinter in python 3. I'd really like to have sub-windows pop up during use, either for data, output, etc. However, I cannot figure out how to assign the title in my Windows class, depending on what sort of window is being made. Please find a simplified example of what I have done so far.
from tkinter import *
from tkinter import messagebox
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.master.title("ProgramName")
self.pack(fill = BOTH, expand = 1)
menu = Menu(self.master)
self.master.config(menu=menu)
prog_help = Menu(menu)
prog_help.add_command(label='Help', command=self.help_popup)
prog_help.add_command(label='About', command=self.version_popup)
menu.add_cascade(label='Help', menu=prog_help)
#Method 1: Using message.box
def version_popup(self):
messagebox.showinfo("About program", "Version 0.1")
return
#Method 2: Using another window
def help_popup(self):
helpwindow()
return
def helpwindow():
hwindow = Toplevel()
hwindow.geometry("100x100")
root = Tk()
root.geometry("400x300")
app = Window(root)
root.mainloop()
I think that I should be doing something in the definition of helpwindow() which alters a variable in self.master.title(x), but I cannot figure out what.
Many thanks for your time.
In your case, the help window isn't a subclass of anything. All you need to do is call the title method of the Toplevel
def helpwindow():
hwindow = Toplevel()
hwindow.title("I am the help window")
...
I am trying to make a on-button click event in a Tkinter window. I have a Tkinter Window on which there are buttons. Pressing one of those buttons,opens up a new Tkinter Window using Toplevel. This window would have a Scrollbar and some other buttons with images on it which can be vertically scrolled down. I could create the two functionalities separately,i.e, I could embedd a button with an image on a Tkinter window and use the Scrollbar but was unable to call the same function with the previous Tkinter window.
The code I am using is -
from Tkinter import *
from ttk import *
class VerticalScrolledFrame(Frame):
def __init__(self, parent, *args, **kw):
Frame.__init__(self, parent, *args, **kw)
# create a canvas object and a vertical scrollbar for scrolling it
vscrollbar = Scrollbar(self, orient=VERTICAL)
vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
canvas = Canvas(self, bd=0, highlightthickness=0,
yscrollcommand=vscrollbar.set)
canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
vscrollbar.config(command=canvas.yview)
# reset the view
canvas.xview_moveto(0)
canvas.yview_moveto(0)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = Frame(canvas)
interior_id = canvas.create_window(0, 0, window=interior,
anchor=NW)
def _configure_interior(event):
# update the scrollbars to match the size of the inner frame
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the canvas's width to fit the inner frame
canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
def _configure_canvas(event):
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the inner frame's width to fill the canvas
canvas.itemconfigure(interior_id, width=canvas.winfo_width())
canvas.bind('<Configure>', _configure_canvas)
#if __name__ == "__main__":
class SampleApp(Tk):
def __init__(self, *args, **kwargs):
#from Tkinter import *
print "in constructor"
import Tkinter
import ImageTk
import Image
import cv2
import numpy as np
import cv2.cv as cv
import math
import tkFileDialog
import tkMessageBox
import Tkinter as tk
root = Tk.__init__(self, *args, **kwargs)
def Print():
print "print function"
self.frame = VerticalScrolledFrame(root)
self.frame.pack()
self.label = Label(text="Shrink the window to activate the scrollbar.")
self.label.pack()
compare_button_path = "compare-button.jpg"
image_compare = Image.open(compare_button_path)
image_compare.thumbnail((70,45))
photo_compare = ImageTk.PhotoImage(image_compare)
button = tk.Button(self.frame, width=120, height=40, image=photo_compare,fg='black',bg='medium turquoise', activebackground='indian red',cursor="hand2",bd=6,relief=RAISED, compound=tk.LEFT, text="Compare",command = Print)
button.image = photo_compare
button.pack(side=LEFT)
buttons = []
for i in range(10):
buttons.append(Button(self.frame.interior, text="Button " + str(i)))
buttons[-1].pack()
app = SampleApp()
app.mainloop()
The above written function gives a pretty good result.
But how do I call this function on a button click from another Tkinter window? Changing the initial declaration of root to root = Tk() instead of root = Tk.init(self, *args, **kwargs) throws a
RuntimeError: maximum recursion depth exceeded while calling a Python object.
If I try to keep the function in some other file and import it into my original Tk file and create the object of that class on a button click, the second file gets automatically called during complilation of the original Tk file.
Can somebody please suggest a way out.
I really don't understand your question, even after asking for clarification. You finally wrote in the comments of the question "I simply want to open a tkinter window with buttons and images, on a button click from another Tkinter window".
I don't see what's preventing you from doing that. The only thing I see wrong with your code is that you're simply not creating an instance of Toplevel (well, except for a confusing set of imports). Other than that, your code should work.
For example, if you modify your sample app to look something like this, you can open as many windows as you want:
class SampleApp(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
b = Button(self, text="Open a new window", command=self.open_new)
b.pack()
def open_new(self):
top = Toplevel()
self.frame = VerticalScrolledFrame(top)
self.frame.pack()
def Print():
print "print function"
button = Button(self.frame, text="Compare",command = Print)
button.pack(side=LEFT)
buttons = []
for i in range(10):
buttons.append(Button(self.frame.interior, text="Button " + str(i)))
buttons[-1].pack()