How to use 2nd Tkinter window to change image in 1st? - python

Quick summary: The green button is supposed to change when an image is selected, but it doesn't:
I have a Tkinter window-A with a button that when pressed will use a separate Python file to create a new window-B. Window-B has two buttons: new screen-snip or select image from folder. The method used for this is supposed to then change self.image_selected so that it can be used to update the button in window-A to have this new image.
I've tried both lines of code below, and neither are working. I'm not getting any errors either:
self.button.configure(image = img.image_selected) # first try
self.button['image'] = img.image_selected # second try
Here is my code (simplified for clarity):
import tkinter as tk
import get_image_or_snip
class ButtonImg:
def __init__(self, master):
self.newWindow = None
self.master = master
self.title = "My Minimum Reproducible Example"
self.fontA = ("arial", 20, "bold")
self.canvas = tk.Canvas(height = 5)
self.canvas.pack()
self.button = tk.Button(bg="#93d9cc", height = 5, text = "Select Image",
font = self.fontA, command = self.changeImage)
self.button.pack(fill="both")
def changeImage(self):
self.newWindow = tk.Toplevel(self.master)
img = get_image_or_snip.AcquireImage(self.newWindow)
self.button.configure(image = img.image_selected)
#self.button['image'] = img.image_selected
root = tk.Tk()
app = ButtonImg(root)
root.mainloop()
Here is the aforementioned get_image_or_snip.py code:
import tkinter as tk
from tkinter import filedialog
from PIL import ImageTk, Image
import snipping_tool
class AcquireImage:
def __init__(self, master):
self.master = master
self.fontA = ("arial", 20, "bold")
self.frame = tk.Frame(master, bg="#1B2631")
self.frame.pack(fill="both", expand=True)
self.button1 = tk.Button(self.frame, text="Select Image File", padx=10, pady=10, bg="#d9a193",
font = self.fontA, command =lambda: self.show_dialogs(1))
self.button1.grid(row=0, column=0, sticky="nsew")
self.button2 = tk.Button(self.frame, text="Get Screen Snip", padx=10, pady=10, bg="#d9a193",
font = self.fontA, command=lambda: self.show_dialogs(2))
self.button2.grid(row=0, column=1, sticky="nsew")
self.image_selected = None
self.master.mainloop()
def show_dialogs(self, method):
if method == 1:
ret = filedialog.askopenfilename()
load_img = Image.open(ret)
self.image_selected = ImageTk.PhotoImage(load_img)
if method == 2:
ret = snipping_tool.get_snip()
self.image_selected = ret
def main():
root = tk.Tk()
AcquireImage(root)
root.mainloop()
if __name__ == '__main__':
main()

If you add print() before and after get_image_or_snip.AcquireImage(self.newWindow) in changeImage() then you should see only first text because you run second mainloop() and it never ends this loop and never go back to changeImage() and never runs self.button.configure(image=img.image_selected)
You should use only one mainloop() and eventually use
root.wait_window(self.newWindow)
to wait until you close second window and then it will run self.button.configure(image=img.image_selected)
But there are other problems.
It removes image from memory memory when second windows is destroyed so you have to assign it to variable in first window.
Button use height in chars when you sent text but when you assign image then it use height in pixels and you have to change it from 5 to image.height()`
All code in one file
import tkinter as tk
from tkinter import filedialog
from PIL import ImageTk
class AcquireImage:
def __init__(self, master):
self.master = master
self.fontA = ("arial", 20, "bold")
self.frame = tk.Frame(master, bg="#1B2631")
self.frame.pack(fill="both", expand=True)
self.button1 = tk.Button(self.frame, text="Select Image File", padx=10, pady=10, bg="#d9a193",
font=self.fontA, command=lambda:self.show_dialogs(1))
self.button1.grid(row=0, column=0, sticky="nsew")
self.button2 = tk.Button(self.frame, text="Get Screen Snip", padx=10, pady=10, bg="#d9a193",
font=self.fontA, command=lambda:self.show_dialogs(2))
self.button2.grid(row=0, column=1, sticky="nsew")
self.image_selected = None
def show_dialogs(self, method):
if method == 1:
ret = filedialog.askopenfilename(initialdir='/home/user/images/')
if ret:
self.image_selected = ImageTk.PhotoImage(file=ret)
self.master.destroy()
elif method == 2:
self.image_selected = snipping_tool.get_snip()
class ButtonImg:
def __init__(self, master):
self.newWindow = None
self.master = master
self.title = "My Minimum Reproducible Example"
self.fontA = ("arial", 20, "bold")
self.canvas = tk.Canvas(height=5)
self.canvas.pack()
self.button = tk.Button(bg="#93d9cc", height=5, text="Select Image",
font=self.fontA, command=self.changeImage)
self.button.pack(fill="both")
def changeImage(self):
print('open second window')
self.newWindow = tk.Toplevel(self.master)
img = AcquireImage(self.newWindow)
self.master.wait_window(self.newWindow)
print('close second window')
if img.image_selected: # check if image was selected
self.image = img.image_selected
self.button.configure(image=self.image, height=self.image.height())
root = tk.Tk()
app = ButtonImg(root)
root.mainloop()
BTW: If you want to change image without closing second window then you should send first window (or button from first window) as argument to second window and change image in show_dialogs()
import tkinter as tk
from tkinter import filedialog
from PIL import ImageTk
class AcquireImage:
def __init__(self, master, first_window):
self.master = master
self.first_window = first_window
self.fontA = ("arial", 20, "bold")
self.frame = tk.Frame(master, bg="#1B2631")
self.frame.pack(fill="both", expand=True)
self.button1 = tk.Button(self.frame, text="Select Image File", padx=10, pady=10, bg="#d9a193",
font=self.fontA, command=lambda:self.show_dialogs(1))
self.button1.grid(row=0, column=0, sticky="nsew")
self.button2 = tk.Button(self.frame, text="Get Screen Snip", padx=10, pady=10, bg="#d9a193",
font=self.fontA, command=lambda:self.show_dialogs(2))
self.button2.grid(row=0, column=1, sticky="nsew")
self.image_selected = None
def show_dialogs(self, method):
if method == 1:
ret = filedialog.askopenfilename(initialdir='/home/user/images/')
if ret:
self.image_selected = ImageTk.PhotoImage(file=ret)
self.first_window.image = self.image_selected
self.first_window.button.configure(image=self.first_window.image, height=self.first_window.image.height())
elif method == 2:
self.image_selected = snipping_tool.get_snip()
class ButtonImg:
def __init__(self, master):
self.newWindow = None
self.master = master
self.title = "My Minimum Reproducible Example"
self.fontA = ("arial", 20, "bold")
self.canvas = tk.Canvas(height=5)
self.canvas.pack()
self.button = tk.Button(bg="#93d9cc", height=5, text="Select Image",
font=self.fontA, command=self.changeImage)
self.button.pack(fill="both")
def changeImage(self):
self.newWindow = tk.Toplevel(self.master)
img = AcquireImage(self.newWindow, self) # first window as `self` but you can send `self.button`
root = tk.Tk()
app = ButtonImg(root)
root.mainloop()
EDIT: Doc about creating Dialog Windows

Related

creating new window from inside a class is merging content to the parent window in tkinter python

I am trying to create a new window from within a window in Tkinter but all my new content is added to the parent window, not in the new window.
the first window
the second window
Here is my code:
from tkinter import *
from tkinter import font
import tkinter as tk
NORMAL_PADDING = 3
class AllPages(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.pack()
self.font = font.Font(family='JetBrains Mono', name='jbm', size=12, weight='normal')
self.posts = [
{
"title": "huhu"
}
]
self.posts_container = tk.Frame()
for post in self.posts:
postf = tk.Frame(self.posts_container, bg="#ffffff", borderwidth=1, relief="solid")
post_t = tk.Label(postf, text=post["title"], bg="#ffffff", borderwidth=0, font=self.font)
post_edit = tk.Button(postf, text="Edit", bg="#ffffff", borderwidth=0, relief="solid", highlightbackground="#ffffff", highlightcolor="#ffffff", highlightthickness=1, font=self.font, command= lambda: self.modify_post_form(post))
post_t.pack(side="left")
post_edit.pack(side="right")
postf.pack(anchor=W, fill='x', pady=NORMAL_PADDING)
self.posts_container.pack(fill='both', ipadx=NORMAL_PADDING, padx=NORMAL_PADDING)
def modify_post_form(self, post):
newW = Toplevel()
app = CreatePostForm(newW)
class CreatePostForm(tk.Frame):
def __init__(self, master, post=None):
super().__init__(master)
self.pack()
if not ("jbm" in font.names()):
self.font = font.Font(family='JetBrains Mono', name='jbm', size=12, weight='normal')
else:
self.font = font.nametofont("jbm")
self.label_width = 20
self.input_width = 40
self.title_container = tk.Frame()
self.title_label = tk.Label(self.title_container, text="Title:", font=self.font, width=self.label_width, anchor=W)
self.title_input = tk.Entry(self.title_container, width=self.input_width, borderwidth=1, relief="solid", font=self.font)
self.title_label.pack(side="left")
self.title_input.pack(side="right")
self.title_container.pack(anchor=W, pady=NORMAL_PADDING)
self.title = tk.StringVar()
self.title_input["textvariable"] = self.title
root = tk.Tk()
myapp = AllPages(root)
myapp.pack()
myapp.mainloop()
The first window is created by the AllPages() class. then AllPages().modify_post_form() creates a new window using Toplevel() and pass it to CreatePostForm() class. CreatePostForm() should then populates the new window with some new content but whatever CreatePostForm() creates is going to the parent window, not to the new window created using Toplevel().
How can I fix this?
Thank you.
The problem in the above code is that both classes didn't specify the window it should create content into. Here is the working code:
from tkinter import *
from tkinter import font
import tkinter as tk
NORMAL_PADDING = 3
class AllPages(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.pack()
self.font = font.Font(family='JetBrains Mono', name='jbm', size=12, weight='normal')
self.posts = [
{
"title": "huhu"
}
]
self.posts_container = tk.Frame(self)
for post in self.posts:
postf = tk.Frame(self.posts_container, bg="#ffffff", borderwidth=1, relief="solid")
post_t = tk.Label(postf, text=post["title"], bg="#ffffff", borderwidth=0, font=self.font)
post_edit = tk.Button(postf, text="Edit", bg="#ffffff", borderwidth=0, relief="solid", highlightbackground="#ffffff", highlightcolor="#ffffff", highlightthickness=1, font=self.font, command= lambda: self.modify_post_form(post))
post_t.pack(side="left")
post_edit.pack(side="right")
postf.pack(anchor=W, fill='x', pady=NORMAL_PADDING)
self.posts_container.pack(fill='both', ipadx=NORMAL_PADDING, padx=NORMAL_PADDING)
def modify_post_form(self, post):
newW = Toplevel()
app = CreatePostForm(newW)
class CreatePostForm(tk.Frame):
def __init__(self, master, post=None):
super().__init__(master)
self.pack()
if not ("jbm" in font.names()):
self.font = font.Font(family='JetBrains Mono', name='jbm', size=12, weight='normal')
else:
self.font = font.nametofont("jbm")
self.label_width = 20
self.input_width = 40
self.title_container = tk.Frame(self)
self.title_label = tk.Label(self.title_container, text="Title:", font=self.font, width=self.label_width, anchor=W)
self.title_input = tk.Entry(self.title_container, width=self.input_width, borderwidth=1, relief="solid", font=self.font)
self.title_label.pack(side="left")
self.title_input.pack(side="right")
self.title_container.pack(anchor=W, pady=NORMAL_PADDING)
self.title = tk.StringVar()
self.title_input["textvariable"] = self.title
root = tk.Tk()
myapp = AllPages(root)
myapp.pack()
myapp.mainloop()
Thank you to all the people who commented and helped me.

Filling frame with color

I'm trying to fill the frame with the buttons so it extends the background-color to the end of the window both sides, this worked when doing .pack() but not .grid().
My code:
from tkinter import *
class App:
def __init__(self):
self.root = Tk()
self.width = 800
self.height = 400
self.root.geometry("{}x{}".format(self.width, self.height))
self.root.resizable(False, False)
self.menu_bar()
self.tool_bar()
self.name = Label(self.root, text="Tester", bg="black", fg="white")
self.root.mainloop()
def menu_bar(self):
self.menu = Menu(self.root)
self.root.config(menu=self.menu)
self.subMenu = Menu(self.menu)
self.menu.add_cascade(label="File", menu=self.subMenu)
self.subMenu.add_command(label="New Project...")
self.subMenu.add_command(label="Properties")
self.subMenu.add_separator()
self.subMenu.add_command(label="Do nothing")
def tool_bar(self):
self.toolbar = Frame(self.root, bg="#555555")
self.insert_button = Button(self.toolbar, text="Insert", bg="#555555", fg="white", activeforeground="white",
activebackground="#008CBA", borderwidth=0)
self.insert_button.grid(row=0, column=0)
self.print_buttom = Button(self.toolbar, text="Print", bg="#555555", fg="white", activeforeground="white",
activebackground="#008CBA", borderwidth=0,
command=self.root.quit)
self.print_buttom.grid(row=0, column=1)
self.toolbar.grid(row=0, column=0, sticky=EW)
if __name__ == '__main__':
App()
Thanks for answers in advance.
You can set a weight to your column.
class App:
def __init__(self):
self.root = Tk()
...
self.root.columnconfigure(0,weight=1)
...

Tkinter using append to print to a label

I have been trying to return the fuction printLabel to print "Hello
world!", but I am not too sure how to progress further:
I would like to use lambda in order to print my append strings in the label when the button is clicked but this displays without the button being clicked. My
code is as follows:
from tkinter import *
class Example(Frame):
def printLabel(self):
self.hello = []
self.hello.append('Hello\n')
self.hello.append('World!')
print(self.hello)
return(self.hello)
def __init__(self, root):
Frame.__init__(self, root)
self.buttonA()
self.viewingPanel()
def buttonA(self):
self.firstPage = Button(self, text="Print Text", bd=1, anchor=CENTER, height = 13, width = 13, command=lambda: self.printLabel())
self.firstPage.place(x=0, y=0)
def viewingPanel(self):
self.panelA = Label(self, bg='white', width=65, height=13, padx=3, pady=3, anchor=CENTER, text="{}".format(self.printLabel()))
self.panelA.place(x=100, y=0)
def main():
root = Tk()
root.title("Tk")
root.geometry('565x205')
app = Example(root)
app.pack(expand=True, fill=BOTH)
root.mainloop()
if __name__ == '__main__':
main()
I made a few modifications to your code, and it should work the way you want:
from tkinter import *
class Example(Frame):
def printLabel(self):
self.hello.append('Hello\n')
self.hello.append('World!')
return(self.hello)
# Added 'updatePanel' method which updates the label in every button press.
def updatePanel(self):
self.panelA.config(text=str(self.printLabel()))
# Added 'hello' list and 'panelA' label in the constructor.
def __init__(self, root):
self.hello = []
self.panelA = None
Frame.__init__(self, root)
self.buttonA()
self.viewingPanel()
# Changed the method to be executed on button press to 'self.updatePanel()'.
def buttonA(self):
self.firstPage = Button(self, text="Print Text", bd=1, anchor=CENTER, height = 13, width = 13, command=lambda: self.updatePanel())
self.firstPage.place(x=0, y=0)
# Changed text string to be empty.
def viewingPanel(self):
self.panelA = Label(self, bg='white', width=65, height=13, padx=3, pady=3, anchor=CENTER, text="")
self.panelA.place(x=100, y=0)
def main():
root = Tk()
root.title("Tk")
root.geometry('565x205')
app = Example(root)
app.pack(expand=True, fill=BOTH)
root.mainloop()
if __name__ == '__main__':
main()

Tkinter progess bar using Frame

I have a progress bar in Tkinter but i can figure out how to set the maximum (= number of files loaded) before to load the file with def open
from Tkinter import *
import tkFileDialog
import ttk
class MainWindow(Frame):
def __init__(self):
Frame.__init__(self)
self.master.title("FOO progress bar")
self.grid(sticky=W+N+S+E)
top = self.winfo_toplevel()
top.rowconfigure(0, weight=1)
top.columnconfigure(0, weight=1)
top_frame = Frame(self)
frame_1 = Frame(self)
top_frame.grid(row=0, sticky=W+N+S+E)
frame_1.grid(row=1, sticky=W+N+S+E)
top_frame.grid(row=0, sticky=W+N+S+E)
self.open = Button(top_frame, text="Input file(s)",
command=self.open,
activeforeground="red", width=20)
self.open.grid(row=1, column=0, pady=2, padx=2, sticky=W)
self.progressbar = ttk.Progressbar(top_frame, orient=HORIZONTAL, length=228, mode='determinate')
self.progressbar.grid(row=1, column=1, pady=2, padx=2, sticky=W)
self.process = Button(frame_1, text="process",
command=self.process,
activeforeground="red", width=20)
self.process.grid(row=2, column=0, sticky=W+N+S+E)
def open(self):
self.filename_open = tkFileDialog.askopenfilenames(defaultextension='*.*')
return self.filename_open
def process(self):
for index, image_name in enumerate(self.filename_open.split()):
self.progressbar.step(index)
self.update()
if __name__ == "__main__":
d = MainWindow()
d.mainloop()
There is a maximum setting that you can change via configure. I also fixed two or three mistakes.
import time
#in __init__
self.progressvar = IntVar()
self.progressbar = ttk.Progressbar(top_frame, orient=HORIZONTAL, length=228, mode='determinate', variable = self.progressvar)
def open(self):
self.filename_open = tkFileDialog.askopenfilenames(defaultextension='*.*')
self.progressvar.set(0)
def process(self):
self.progressbar.configure(maximum = len(self.filename_open) + 0.001) #0.001 needed to avoid progressbar empty at the end
for index, image_name in enumerate(self.filename_open):
self.progressbar.step(1)
self.update_idletasks()
time.sleep(0.5) # replace with the real process function

Tkinter custom frame not working

I'm trying to create a custom frame in tkinter, Python v2.7. I have done this just fine once (a frame with a scrollbar), but my second attempt isn't working. I compare it to the Frame that does work, and I can't understand what I have done differently.
What I want is a frame that has a little separator line underneath it, so I'm creating a "normal" frame, a thin frame to use as a separator under it, and a bigFrame to hold it.
Everything I create in the class works, except the frame itself. Hopefully my comments explain what is and isn't showing.
from Tkinter import *
class FunFrame(Frame):
def __init__(self, master, lbl, **kwargs):
self.bigFrame = Frame(master)
Frame.__init__(self, self.bigFrame, width=280, height=200, bg="red", **kwargs)
self.grid(row=0, column=0, pady=3) #this is in bigFrame, and doesn't display
#however the padding is still respected
self.separator = Frame(self.bigFrame, height=2, bd=1, width=280, relief = SUNKEN)
self.separator.grid(row=1, column=0) #this is in bigFrame, and displays
self.l = Label(self, text=lbl) #this is in self and doesn't display
self.l.grid(row=0, column=0)
def grid(self, **kwargs):
self.bigFrame.grid(**kwargs)
if __name__ == "__main__":
root=Tk()
Frame1=FunFrame(root, "hello")
Frame2=FunFrame(root, "world")
Frame1.grid(row=0, column=0)
Frame2.grid(row=1, column=0)
root.mainloop()
If you call self.grid in __init__, it calls your own grid, not Tkinter's version.
Try following (renamed grid to grid_):
from Tkinter import *
class FunFrame(Frame):
def __init__(self, master, lbl, **kwargs):
self.bigFrame = Frame(master)
Frame.__init__(self, self.bigFrame, width=280, height=200, bg="red", **kwargs)
self.grid(row=0, column=0, pady=3)
self.separator = Frame(self.bigFrame, height=2, bd=1, width=280, relief=SUNKEN)
self.separator.grid(row=1, column=0)
self.l = Label(self, text=lbl)
self.l.grid(row=0, column=0)
def grid_(self, **kwargs): ######## grid -> grid_
self.bigFrame.grid(**kwargs)
if __name__ == "__main__":
root=Tk()
Frame1 = FunFrame(root, "hello")
Frame2 = FunFrame(root, "world")
Frame1.grid_(row=0, column=0) ######## grid -> grid_
Frame2.grid_(row=1, column=0) ######## grid -> grid_
root.mainloop()
I'd rather code as follow (if '....' was used to represent hierarchy visually):
from Tkinter import *
class FunFrame(Frame):
def __init__(self, master, lbl, **kwargs):
Frame.__init__(self, master)
if 'inside outer frame (self)':
innerFrame = Frame(self, width=280, height=200, bg="red", **kwargs)
innerFrame.grid(row=0, column=0, pady=3)
if 'inside inner frame':
self.l = Label(innerFrame, text=lbl)
self.l.grid(row=0, column=0)
separator = Frame(self, height=2, bd=1, width=280, relief=SUNKEN)
separator.grid(row=1, column=0)
if __name__ == "__main__":
root = Tk()
Frame1 = FunFrame(root, "hello")
Frame2 = FunFrame(root, "world")
Frame1.grid(row=0, column=0)
Frame2.grid(row=1, column=0)
root.mainloop()

Categories