Make multiple tk.Toplevel windows embedded/unified in main tk window - python-3.x

So I'm trying to create a program which uses multiple tk.Toplevel windows. The problem with this is, that all windows show up seperated as their "own App", so when you alt tab, you switch between the toplevel windows.
The pseudocode would look something like this:
import tkinter as tk
top_levels = {}
def open_toplevel():
top_level = tk.Toplevel(root)
top_level.geometry("300x200+0+0")
top_levels.update({f"toplevel{len(top_levels.keys())}" : top_level})
root = tk.Tk()
button = tk.Button(root, command= open_toplevel)
button.place(x=0, y=0)
root.mainloop()
So my question, is: is there a way to unify them into "one window"?

If you want all of them to unify into one window then tk.Frame is a better widget to use instead of tk.Toplevel
The purpose of tk.Toplevel is to create a new temporary window, not an extra part of the window. But frames are a really good way to organise stuff.
This code below creates new frame every time you click the button. This is just a simple example. You can also use grid for widgets in a frame. I also put a border so you can see where the frames are located.
from tkinter import *
def open_frame():
frame = Frame(root, highlightbackground="black", highlightthickness=2)
lbl1 = Label(frame, text=f"Frame {len(frames) + 1} label 1")
lbl2 = Label(frame, text=f"Frame {len(frames) + 1} label 2")
lbl1.pack()
lbl2.pack()
frame.pack(padx=5, pady=5)
frames.append(frame)
root = Tk()
frames = []
btn = Button(root, text="Open Frame", command=open_frame)
btn.pack()
root.mainloop()
I hope this solution is helpful
EDIT
Use this code here to move the frames:
from tkinter import *
def open_frame():
global frame, frames
frame = Frame(root, highlightbackground="black", highlightthickness=2)
lbl1 = Label(frame, text=f"Frame {len(frames) + 1} label 1")
lbl2 = Label(frame, text=f"Frame {len(frames) + 1} label 2")
lbl1.pack()
lbl2.pack()
frame.pack(padx=5, pady=5)
frame_number = len(frames)
lbl1.bind('<B1-Motion>', lambda event: MoveWindow(event, frame_number))
lbl2.bind('<B1-Motion>', lambda event: MoveWindow(event, frame_number))
frame.bind('<B1-Motion>', lambda event: MoveWindow(event, frame_number))
frames.append(frame)
labels.append(lbl1)
labels.append(lbl2)
def MoveWindow(event, frame_number):
global root, frames
root.update_idletasks()
f = frames[frame_number]
x = f.winfo_width()/2
y = f.winfo_height()*1.5
f.place(x=event.x_root-x, y=event.y_root-y)
root = Tk()
root.geometry("500x500")
frames = []
labels = []
btn = Button(root, text="Open Frame", command=open_frame)
btn.pack()
root.mainloop()

Related

How to get input from tkinter Entry widget on second window while the first window continues to run

from tkinter import *
def first():
root1 = Tk()
Button(root1, text = 'get Second', command= second).pack()
root1.mainloop()
def second():
root2 = Tk()
user_input = StringVar()
Entry(root2, text = user_input).pack()
Button(root2, text = 'submit', command = lambda : print(user_input.get(), '\t printed')).pack()
root2.mainloop()
first()
You are making a few basic mistakes in here -
You if want to use a second window, it should be Toplevel not root Tk window. There should be only one root window in the program. This should act as parent to all the windows.
Its a good practice in most of the cases to define the widgets like Button, Entry separately and then pack() them.
Entry should have 'textvariable' not 'text'
Following is the updated code which may help you -
from tkinter import *
root = Tk()
def first():
button = Button(root, text = 'get Second', command= second)
button.pack()
root.mainloop()
def second():
window2 = Toplevel(root)
user_input = StringVar()
entry = Entry(window2, textvariable=user_input)
entry.pack()
button = Button(window2, text = 'submit', command = lambda: print(user_input.get()))
button.pack()
first()

Tkinter text-widget insertion of images

I made a text widget to write questions. I want to insert images when add image button is pressed. How can I do that? When I choose another image the first one is deleted. Right now I am able to add one image using the code:
import tkinter as tk
root = tk.Tk()
root.geometry('800x520+0+0')
global img
img = tk.PhotoImage(file="quiz.gif")
def add_img():
T.image_create(tk.INSERT, image=img)
tk.Button(root, text="Add Image", font=('Verdana',8),
command=add_img).place(x=690, y=0)
T = tk.Text(root, width=65, height=17, padx=10, pady=10, font=('Verdana',
14), wrap='word')
T.place(x=0, y=0)
root.mainloop()
I want to add different images when chosen using tk-listbox.
Thanks for the help.
I used dictionary to solve the problem.
import tkinter as tk, glob
root = tk.Tk()
root.geometry('800x520+0+0')
Images = {}
for infile in glob.glob('*.gif'):
img = infile[:-4]
if img not in Images:
Images[img] = tk.PhotoImage(file=infile)
def add_img():
global listbox
listbox = tk.Listbox(root, font=('Verdana',9), width=12)
listbox.place(x=60,y=2)
for infile in glob.glob('*.gif'):
listbox.insert(tk.END, infile[:-4])
listbox.bind('<<ListboxSelect>>',CurSelet)
def CurSelet(event):
fn = listbox.get(tk.ANCHOR)
listbox.destroy()
T.image_create(tk.INSERT, image=Images[fn])
tk.Button(root, text="Add Image", font=('Verdana',8),
command=add_img).place(x=690,y=0)
T = tk.Text(root, width=65, height=17, padx=10, pady=10, font=('Verdana',
14), wrap='word')
T.place(x=0, y=50)
root.mainloop()

Adding padding query insert to Listbox

I have read a number of threads and other resources to try to find the correct way to handle this but I have not found anything that works with my application.
Here is what I am trying to accomplish.
When a query is completed and the insert of the data to a Listbox is done I cannot seem to get it to margin the data insert by 1 character space.
I am using pack() and I have read the tkinter manual for this and have tried each example available along with others found on various threads here.
The widget:
output = tkinter.Listbox(window_2, height = 20, font='Times 10',
width=42, bd=1, bg = '#FFD599', fg = '#9A0615', selectmode=SINGLE)
output.pack()
output.place(x=210, y=195)
I have tried padx and pady with pack() without success, although this works successfully with the Text widget. I have also attempted to use a few alternatives that I have found here on the site but all without success in margining the Listbox when the data is inserted.
Any advice?
pack's padx/pady and ipadx/ipady options don't affect the data that is inside the listbox. The listbox itself doesn't have any options to add an internal margin.
To get a margin around the inside of the listbox, what I normally do is give it a zero borderwidth and highlightthickness, and then place it in a frame with the same background color and let the frame be the border. You can then add any padding you want between the border and the listbox.
This is also convenient because you can put a scrollbar inside the frame, giving it the appearance that it is inside the listbox without actually being inside the listbox.
Example:
import tkinter as tk
root = tk.Tk()
root.configure(background="gray")
listbox_border = tk.Frame(root, bd=1, relief="sunken", background="white")
listbox_border.pack(padx=10, pady=10, fill=None, expand=False)
listbox = tk.Listbox(listbox_border, width=20, height=10,
borderwidth=0, highlightthickness=0,
background=listbox_border.cget("background"),
)
vsb = tk.Scrollbar(listbox_border, orient="vertical", command=listbox.yview)
listbox.configure(yscrollcommand=vsb)
vsb.pack(side="right", fill="y")
listbox.pack(padx=10, pady=10, fill="both", expand=True)
for i in range(100):
listbox.insert("end", "Item #{}".format(i))
root.mainloop()
here is a variation on the much appreciated answer by Bryan Oakley.
it uses ttk widgets instead of tk widgets
the scrollbar tracks your position in the list box when you scroll with the mouse
uses the oStyle.theme_use("clam") because it may look more modern...this can be commented out
'
import tkinter as tk
from tkinter import ttk
try: # allows the text to be more crisp on a high dpi display
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)
except:
pass
root = tk.Tk()
oStyle = ttk.Style()
oStyle.theme_use("clam")
oStyle.configure('LB.TFrame', bd=1, relief="sunken", background="white")
listbox_border = ttk.Frame(root, style='LB.TFrame')
listbox_border.pack(padx=4, pady=4, fill=None, expand=False)
vsb = ttk.Scrollbar(listbox_border)
vsb.pack(side="right", fill="y")
listbox = tk.Listbox(listbox_border, width=20, height=10, borderwidth=0,
highlightthickness=0, selectmode=tk.SINGLE,
activestyle=tk.NONE)
listbox.pack(padx=6, pady=6, fill="y", expand=True)
listbox.config(yscrollcommand=vsb.set)
vsb.config(command=listbox.yview)
for i in range(100):
listbox.insert("end", "Item #{}".format(i))
root.mainloop()
'
first of all to format chars in a tkinter listbox you need to use a fixed font and .format python funcion....;
So you can do something this
Press Load to load data in the listbox and pay attention to this line code
s = '{0:>8}{1:5}'.format(i[0],i[1])
self.list.insert(tk.END, s)
import tkinter as tk
RS = (('Apple',10),
('Banana',20),
('Peack',8),
('Lemon',6),)
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello World")
self.init_ui()
def init_ui(self):
self.pack(fill=tk.BOTH, expand=1,)
f = tk.Frame()
sb = tk.Scrollbar(f,orient=tk.VERTICAL)
self.list = tk.Listbox(f,
relief=tk.GROOVE,
selectmode=tk.BROWSE,
exportselection=0,
background = 'white',
font='TkFixedFont',
yscrollcommand=sb.set,)
sb.config(command=self.list.yview)
self.list.pack(side=tk.LEFT,fill=tk.BOTH, expand =1)
sb.pack(fill=tk.Y, expand=1)
w = tk.Frame()
tk.Button(w, text="Load", command=self.on_callback).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)
def on_callback(self,):
for i in RS:
s = '{0:>8}{1:5}'.format(i[0],i[1])
self.list.insert(tk.END, s)
def on_close(self):
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()

python3 & tkinter - creating a zoom button to zoom on the loaded image

I would like to create a zoom button. On clicking on that zoom button, the image would be zoomed in by a factor represented by an integer (1,2,3,4,5...). With this piece of code, by clicking on the zoom button, another panel is created underneath the already loaded picture. Inside it is blank. What would be needed is to:
1. kill the first (non-zoomed window) and 2. load the zoomed image on the updated panel
from tkinter import *
from tkinter.filedialog import askopenfilename
import tkinter as tk
event2canvas = lambda e, c: (c.canvasx(e.x), c.canvasy(e.y))
root = Tk()
#setting up a tkinter canvas with scrollbars
frame = Frame(root, bd=2, relief=SUNKEN)
frame.grid_rowconfigure(0, weight=1)
frame.grid_columnconfigure(0, weight=1)
xscroll = Scrollbar(frame, orient=HORIZONTAL)
xscroll.grid(row=1, column=0, sticky=E+W)
yscroll = Scrollbar(frame)
yscroll.grid(row=0, column=1, sticky=N+S)
canvas = Canvas(frame, bd=0, xscrollcommand=xscroll.set,yscrollcommand=yscroll.set)
canvas.grid(row=0, column=0, sticky=N+S+E+W)
xscroll.config(command=canvas.xview)
yscroll.config(command=canvas.yview)
frame.pack(fill=BOTH,expand=1)
#adding the image
image_str="image.png"
image = tk.PhotoImage(file=image_str)
image = image.zoom(1,1)
canvas.create_image(0,0,image=image,anchor="nw")
canvas.config(scrollregion=canvas.bbox(ALL))
def zoomin():
root = Tk()
frame = Frame(root, bd=2, relief=SUNKEN)
frame.grid_rowconfigure(0, weight=1)
frame.grid_columnconfigure(0, weight=1)
xscroll = Scrollbar(frame, orient=HORIZONTAL)
xscroll.grid(row=1, column=0, sticky=E+W)
yscroll = Scrollbar(frame)
yscroll.grid(row=0, column=1, sticky=N+S)
canvas = Canvas(frame, bd=0, xscrollcommand = xscroll.set, yscrollcommand = yscroll.set)
canvas.grid(row=0, column=0, sticky=N+S+E+W)
xscroll.config(command=canvas.xview)
yscroll.config(command=canvas.yview)
frame.pack(fill=BOTH,expand=1)
image = tk.PhotoImage(file=image_str)
image = image.zoom(1,1)
canvas.create_image(0,0,image=large_img,anchor="nw")
canvas.config(scrollregion=canvas.bbox(ALL))
toolbar = Frame(root, bg="blue")
insertButt = Button(toolbar, text="zoomin", command=lambda:zoomin())
insertButt.pack(side = LEFT, padx=2, pady=2)
toolbar.pack(side=TOP, fill = X)
#function to be called when mouse is clicked
def printcoords(event):
#outputting x and y coords to console
print (event.x,event.y)
#mouseclick event
canvas.bind("<Button 1>",printcoords)
#mouseclick event
canvas.bind("<ButtonPress-1>",printcoords)
canvas.bind("<ButtonRelease-1>",printcoords)
root.mainloop()
I would like to thank #Symon for his stackoverflow question. I largely inspired myself from his code
Well, the reason that the function zoomin(img) does not work properly is that it returns in the first line:
def zoomin(img):
return # Function returns here
... rest of code is never executed
I suspect this is due to the function being run when you create the button, not when you press it. Try cretating the button in this way instead:
insertButt = Button(toolbar, text="zoomin", command=lambda:zoomin(img))
Now the button will call zoomin(img) when it's pressed and not when the button is created.
Zooming with Tkinter
PhotoImage zoom only allows integer values, wich makes it a bit limited. But here's an example:
from tkinter import *
root = Tk()
root.geometry('300x200')
field = Canvas(root, bg='tan1', highlightthickness=0)
field.grid(sticky='news')
photo = PhotoImage(file='test.gif')
field.create_image(0, 0, image=photo, anchor='nw')
scale = 1
def zoom(event=None):
global scale, photo
scale = scale * 2
field.delete('all')
photo = photo.zoom(x=scale, y=scale)
field.create_image(0, 0, image=photo, anchor='nw')
field.image = photo
root.bind('z', zoom) # Bind "z" to zoom function
root.mainloop()
If you want to zoom by float you'll have to import a module for that. Pillow seems popular. But I haven't worked with any of them so you'll have to research them yourself.

tkinter error calling function through entries in listbox

Recently, I tried to make a full application window with a side panel menu with separate frames running some functions and submitting forms in the canvas frame.
But I found that every time I click on any entry in listbox it runs the function or method without clearing the existing one .
I tried destroy() and forget() didn't work for me (maybe I didn't know exactly how to use it?!, and the destroy() function prevent using the function again till I close the whole application and run it again!) this is a photo of my problem
this is my code :
import tkinter as tk
from tkinter import ttk
class MainWindow() :
def __init__(self,root):
# menu left
self.menu_upper_frame = tk.Frame(root, bg="#dfdfdf")
self.menu_title_label = tk.Label(self.menu_upper_frame, text="menu title", bg="#dfdfdf")
self.menu_title_label.pack()
self.menu_left_container = tk.Frame(root, width=150, bg="#ababab")
self.menu_left_upper = tk.Frame(self.menu_left_container, width=150, height=150, bg="red")
self.menu_left_upper.pack(side="top", fill="both", expand=True)
# create a listbox of items
self.Lb1 = tk.Listbox(self.menu_left_upper,bg ="red", borderwidth=0, highlightthickness=0 )
self.Lb1.insert(1, "Python")
self.Lb1.insert(2, "Perl")
self.Lb1.insert(3, "C")
self.Lb1.insert(4, "PHP")
self.Lb1.insert(5, "JSP")
self.Lb1.insert(6, "Ruby")
self.Lb1.bind("<<ListboxSelect>>", self.OnClick ) #return selected item
self.Lb1.pack(fill="both", expand=True, pady=50 )
# right area
self.inner_title_frame = tk.Frame(root, bg="#dfdfdf")
self.inner_title_label = tk.Label(self.inner_title_frame, text="inner title", bg="#dfdfdf")
self.inner_title_label.pack()
self.canvas_area = tk.Canvas(root, width=500, height=400, background="#ffffff")
self.canvas_area.grid(row=1, column=1)
# status bar
self.status_frame = tk.Frame(root)
self.status = tk.Label(self.status_frame, text="this is the status bar")
self.status.pack(fill="both", expand=True)
self.menu_upper_frame.grid(row=0, column=0, rowspan=2, sticky="nsew")
self.menu_left_container.grid(row=1, column=0, rowspan=2, sticky="nsew")
self.inner_title_frame.grid(row=0, column=1, sticky="ew")
self.canvas_area.grid(row=1, column=1, sticky="nsew")
self.status_frame.grid(row=2, column=0, columnspan=2, sticky="ew")
root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(1, weight=1)
def OnClick(self,event):
widget = event.widget
selection = widget.curselection()
value = widget.get(selection)
if value == 'Python':
self.tabtop()
def tabtop(self):
self.tabControl = ttk.Notebook(self.canvas_area, width=400) # Create Tab Control
self.tab1 = ttk.Frame(self.tabControl) # Create a tab
self.tab2 = ttk.Frame(self.tabControl)
self.tab3 = ttk.Frame(self.tabControl)
self.tab4 = ttk.Frame(self.tabControl)
self.tab5 = ttk.Frame(self.tabControl)
self.tabControl.add(self.tab1, text='Login data' ) # Add the tab
self.tabControl.add(self.tab2, text='Permission')
self.tabControl.add(self.tab3, text='Roles')
self.tabControl.add(self.tab4, text='Personal data')
self.tabControl.add(self.tab5, text='Business data')
self.tabControl.pack(expand=1, fill="both") # Pack to make visible
self.l2 = tk.Label(self.tab2, text="label 2").pack()
self.l3 = tk.Label(self.tab3, text="label 3").pack()
root = tk.Tk()
root.title("Control Panel")
root.style = ttk.Style()
root.style.theme_use("clam")
user = MainWindow(root)
root.mainloop()
If what you're really asking is how to replace an existing notebook with a new notebook, all you need to do is call destroy() on the old notebook before creating the new one.
First, define self.tabControl to None somewhere in MainWindow.__init__. Then, in tabtop you can delete the old notebook before creating the new one:
def tabtop(self):
if self.tabControl is not None:
self.tabControl.destroy()
...

Resources