I'm still in my first week of Python, so I'm sorry if this is an obvious mistake...
I want to style a Frame in Tkinter with rounded corners, therefore I got a base64 encoded image, which I applied using ttk.Style following this answer (Tkinter: How to make a rounded corner text widget?) and that worked fine.
My problem is now adapting this into a Class structured program, there the frame is not showing at all.
I replicated the Problem in the code below.
The first window is showing the problem, and the second one how its supposed to look.
How do I make this work? And could there be any new conflicts coming up when I'm not packing the frame to the main window, but into another Frame?
Thanks in advance!
import tkinter as tk
from tkinter import ttk
borderImageData = '''
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
'''
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
style = ttk.Style()
style.theme_use('clam')
borderImage = tk.PhotoImage("borderImage", data=borderImageData)
style.element_create("RoundedFrame", "image", borderImage, border=16, sticky="nsew")
style.layout("RoundedFrame", [("RoundedFrame", {"sticky": "nsew"})])
frame = ttk.Frame(self, style = "RoundedFrame", padding = 10)
frame.pack()
entry = tk.Entry(frame, borderwidth = 0, width = 40)
entry.pack(fill="both", expand=True)
app = Application()
app.mainloop()
root = tk.Tk()
style = ttk.Style()
style.theme_use('clam')
borderImage = tk.PhotoImage("borderImage", data=borderImageData)
style.element_create("RoundedFrame", "image", borderImage, border=16, sticky="nsew")
style.layout("RoundedFrame", [("RoundedFrame", {"sticky": "nsew"})])
frame = ttk.Frame(root, style = "RoundedFrame", padding = 10)
frame.pack()
entry = tk.Entry(frame, borderwidth = 0, width = 40)
entry.pack(fill="both", expand=True)
root.mainloop()
The problem is pythons garbage collections destroying the PhotoImage instance. You need to keep a reference to it (this is also somewhere in the docs). You probably just wanna save most stuff as an attribute anyway:
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.style = ttk.Style()
self.style.theme_use('clam')
self.borderImage = tk.PhotoImage("borderImage", data=borderImageData)
self.style.element_create("RoundedFrame", "image", self.borderImage, border=16, sticky="nsew")
self.style.layout("RoundedFrame", [("RoundedFrame", {"sticky": "nsew"})])
self.frame = ttk.Frame(self, style="RoundedFrame", padding=10)
self.frame.pack()
self.entry = tk.Entry(self.frame, borderwidth=0, width=40)
self.entry.pack(fill="both", expand=True)
Works for me.
Related
I'm an amateur programmer and I used Gtk a long time ago and PyQt recently to create GUIs but have just returned to Tkinter for it's elegance. In Gtk and Qt the grid commands seem to resize widgets in an automatic fashion for the window size. In Tkinter I'm really confused by how to setup a window to be resizable.
Using Brian Oakley's suggestion at: How can i fit my tkinter app to any size screen? and the various sources cited I've tried to write the following simple code so it should work on any size screen. For some reason I am unable change the size of the parent column s an extend the LB listbox to the bottom of the screen. If I add width = 10, height = 75 to the LB definition line it does extend, but of course the bottom of LB will be lost when the window is resized. I realize this is really messy code but I think it explains the problem. Can someone tell me what to do to the code (and especially LB) to it make usable on various screen sizes?
Thank you very much in advance.
class Application(tk.Frame): # /5104330/
#
def __init__(self, parent, *args, **kwargs): # /17466561/
tk.Frame.__init__(self, parent, *args, **kwargs) # /17466561/
self.parent = parent # root - /17466561/
# Makes column 1 three times wider than column 2
# THIS DOES NOT HAVE ANY EFFECT. WHY NOT?:
self.parent.columnconfigure(0, weight=3)
self.parent.columnconfigure(1, weight=1)
# Setup frame
frame1 = tk.Frame(self)# /61989498/
frame1.grid(row=0, column=0, rowspan = 20, columnspan = 8, sticky='nsew')
# Create widgets
scrollbar = tk.Scrollbar(frame1, orient=tk.VERTICAL) # /10870855/, /32715745/
self.LB = tk.Listbox(frame1, yscrollcommand=scrollbar.set)#, width = 10, height = 75)
scrollbar.config(command=self.LB.yview)
# Pack the widgets
self.LB.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=1)
# insert data
for r in range(0, 50):
self.LB.insert(tk.END, str(r)+'-LB1')
# As per https://stackoverflow.com/questions/31885234/, /53073534/ and /64545856/
for i in range(0,35):
# None of these extend the self.LB down to the bottom of the window:
self.parent.grid_rowconfigure(i, weight=1)
frame1.grid_rowconfigure(i, weight=1)
self.LB.grid_rowconfigure(i, weight=1)
#
self.parent.rowconfigure(i, weight=1)
frame1.rowconfigure(i, weight=1)
self.LB.rowconfigure(i, weight=1)
#
frame2 = tk.Frame(self, bd=1, relief='flat', bg='white')# , width = 12, height = 700) # /61989498/
frame2.grid(row=1, column=9, sticky='nsew', rowspan=40, columnspan = 3, ipadx=4)
self.slbl = tk.Label(frame2, text ='Enter Search Term:'); self.slbl.pack(side=tk.TOP, padx=20)
if __name__ == "__main__": # /17466561/
root = tk.Tk()
Application(root).pack(side='top', fill='both', expand=True)
root.attributes('-zoomed', True) # maximize the window
root.mainloop()
While I dislike answering my own question, I think the solution is just to remove the pack commands and use grid on the root window to get around the problem. Simplifying I have used:
import tkinter as tk
class Application(tk.Frame):
#
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
# Create widgets
scrollbar = tk.Scrollbar(self.parent, orient=tk.VERTICAL)
self.LB = tk.Listbox(self.parent, yscrollcommand=scrollbar.set)
scrollbar.config(command=self.LB.yview)
# Pack the widgets
self.LB.grid(row=0, column=0, rowspan = 98, columnspan = 8, sticky='nsew')
scrollbar.grid(row=0, column=8, rowspan = 98, columnspan = 1, sticky='nsw')
# insert data
for r in range(0, 50):
self.LB.insert(tk.END, str(r)+'-LB1')
for i in range(0,98):
# Extend the self.LB down to the bottom of the maximized window
self.parent.grid_rowconfigure(i, weight=1)
self.slbl = tk.Label(self.parent, text ='Enter Search Term:')
self.slbl.grid(row=1, column=9, sticky='nsew', rowspan=40, columnspan = 3, ipadx=4)
if __name__ == "__main__":
root = tk.Tk()
Application(root)
root.attributes('-zoomed', True) # maximize the window
root.mainloop()
##########################################################
I'd like to position one of my python tkinter Frames (ButtonWindow) within my other Frame (MainWindow), so that when I run the app the widgets in ButtonWindow are present in MainWindow along with the MainWindow widget.
In the code below the Buttons from ButtonWindow are present along with the MainWindow Label, but the ButtonWindow Label is missing.
I looked at the answers in Frame inside another frame In python Tkinter and tried to set the background to purple to understand where the borders of ButtonWindow actually are, but I can't see any purple?
Thanks for any help!
import tkinter as tk
class ButtonWindow(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.bd = 5
self.bg = "purple"
self.label = tk.Label(text="Button window", font=12)
for i in range(3):
self.button = ttk.Button(text="button", command= lambda: button_fun())
self.button.pack(side = tk.LEFT)
def button_fun(self):
pass
class MainWindow(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.label = tk.Label(text="Main Window", font=12)
self.label.pack(pady=10,padx=10)
self.button_window = ButtonWindow()
self.button_window.pack()
app = MainWindow()
app.mainloop()
It is better to specify the parent of widgets when creating them, otherwise they will be children of root window.
Also you have never called any layout function on the "Button window" label so it is not visible.
self.bd = 5 and self.bg = "purple" will not change the border width and the background color. Use self.config(bd=5, bg="purple") instead.
import tkinter as tk
from tkinter import ttk
class ButtonWindow(tk.Frame):
def __init__(self, master=None, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.config(bd=5, bg="purple") # replace self.bd = 5 and self.bg = "purple"
self.label = tk.Label(self, text="Button window", font=12, fg='white', bg='purple') # specify parent
self.label.pack() # pack the label, otherwise it is not visible
for i in range(3):
self.button = ttk.Button(self, text="button", command=self.button_fun) # specify parent
self.button.pack(side=tk.LEFT)
def button_fun(self):
pass
class MainWindow(tk.Frame):
def __init__(self, master=None, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.label = tk.Label(self, text="Main Window", font=12) # specify parent
self.label.pack(pady=10, padx=10)
self.button_window = ButtonWindow(self) # specify parent
self.button_window.pack()
root = tk.Tk() # create root window explicitly
MainWindow(root).pack()
root.mainloop()
Also I have changed command=lambda: button_fun() to command=self.button_fun. The former one will raise exception when any of the buttons is clicked.
If I understood correctly what you want is create a Main Frame with another frame inside it which include 3 buttons.
In this case, I changed a little bit your code to do that. One of the changes was replace the tk.Label for the tk.LabelFrame (This corrects the Label that was missing in the ButtonWindow as you stated).
The second change that I suggest is to pass the MainFrame to the ButtonWindow as a parent frame. To do this I created the myCoreFrame inside the MainWindowclass. Also, for all widgets I've set a parent frame.
import tkinter as tk
class ButtonWindow():
def __init__(self, Frame, *args, **kwargs):
self.label = tk.LabelFrame(Frame, text="Button window", font=12, bg = "purple")
self.label.pack()
for i in range(3):
self.button = tk.Button(self.label, text="button", command= lambda: button_fun())
self.button.pack(side = tk.LEFT)
def button_fun(self):
pass
class MainWindow():
def __init__(self, window, *args, **kwargs):
myCoreFrame = tk.Frame(window)
myCoreFrame.pack()
self.label = tk.LabelFrame(myCoreFrame, text="Main Window", font=12, bg = "red")
self.label.pack(pady=10,padx=10)
self.button_window = ButtonWindow(self.label)
root = tk.Tk()
app = MainWindow(root)
root.mainloop()
The problem is that you aren't putting the labels and buttons inside frames. You need to explicitly set the frame as the parent of the button and label. If you don't, the widgets become children of the root window.
class ButtonWindow(tk.Frame):
def __init__(self, *args, **kwargs):
...
self.label = tk.Label(self, text="Button window", font=12)
# ^^^^^^
for i in range(3):
self.button = ttk.Button(self, text="button", command= lambda: button_fun())
# ^^^^^^
...
class MainWindow(tk.Frame):
def __init__(self, *args, **kwargs):
...
self.label = tk.Label(self, text="Main Window", font=12)
# ^^^^^
self.label.pack(pady=10,padx=10)
self.button_window = ButtonWindow(self)
# ^^^^
...
I would like to align text widgets horizontally and be able to scroll left and right the frame where are placed these widgets. The code below is almost what I want except the fact that my scrollbar doesn't work.
I found some example where it is said not to use pack or grid in Canvas. However, if I use place layout, my widgets simply disapear.
from tkinter import *
class MainView(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.canvas = Canvas(self)
self.sensorsStatsFrame = Frame(self.canvas)
myscrollbar = Scrollbar(self,orient=HORIZONTAL,command=self.canvas.xview)
myscrollbar.pack(side=BOTTOM,fill=X)
self.canvas.configure(xscrollcommand=myscrollbar.set)
self.canvas.pack(side=TOP, fill=BOTH)
test0 = Text(self.sensorsStatsFrame, bg="red", state=DISABLED, width=150)
test1 = Text(self.sensorsStatsFrame, bg="green")
test0.pack(side=LEFT)
test1.pack(side=LEFT)
self.canvas.create_window((0,0),window=self.sensorsStatsFrame,anchor='nw')
self.canvas.config(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
root = Tk()
main = MainView(root)
main.pack(fill="both", expand=1)
root.wm_geometry("1100x500")
root.wm_title("MongoDB Timed Sample Generator")
root.mainloop()
I would like to align text widgets horizontally and be able to scroll left and right the frame where are placed these widgets.
If i didn't misunderstand you, you should add an event function to your codes.
from tkinter import *
class MainView(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.canvas = Canvas(self)
self.sensorsStatsFrame = Frame(self.canvas)
myscrollbar = Scrollbar(self,orient=HORIZONTAL,command=self.canvas.xview)
myscrollbar.pack(side=BOTTOM,fill=X)
self.canvas.configure(xscrollcommand=myscrollbar.set)
self.canvas.pack(side=TOP, fill=BOTH)
test0 = Text(self.sensorsStatsFrame, bg="red", state=DISABLED, width=150)
test1 = Text(self.sensorsStatsFrame, bg="green")
test0.pack(side=LEFT)
test1.pack(side=LEFT)
self.canvas.create_window((0,0),window=self.sensorsStatsFrame,anchor='nw')
# Call the function like the below.
self.sensorsStatsFrame.bind("<Configure>", self.onFrameConfigure)
# Add below function to your codes.
def onFrameConfigure(self, event):
self.canvas.config(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
root = Tk()
main = MainView(root)
main.pack(fill="both", expand=1)
root.wm_geometry("1100x500")
root.wm_title("MongoDB Timed Sample Generator")
root.mainloop()
I'm using python2 on Windows. When I run the followig code, I get a gap between the two canvas (see picture below), although there is no padding specified when I grid them.
Is there any possibility to remove this?
import Tkinter as tk
import ttk
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.c1 = tk.Canvas(master=self, background='white', borderwidth=0,
relief=tk.FLAT)
self.c2 = tk.Canvas(master=self, background='white', borderwidth=0,
relief=tk.FLAT)
self.c1.grid(row=0, column=0, sticky=tk.NSEW)
self.c2.grid(row=1, column=0, sticky=tk.NSEW)
self.mainloop()
App()
Thanks for help!
You need to set highlightthickness to zero as well.
self.c1 = tk.Canvas(..., highlightthickness=0)
From the canvas page of effbot highlightthickness explained as:
The width of the highlight border. The default is system specific
(usually one or two pixels). (highlightThickness/HighlightThickness)
So I've passed a create_text variable to "text1"
text1 = UseCanvas(self.window, "text", width = 100, height = 100, text = "Goodbye")
Using:
self.create_text(20,25 width=100, anchor="center", text =self.text, tags= self.tag1)
after passing it to another class.
How do I edit that text widget? I want it to say "Hello" instead of "Goodbye". I've looked all over and I can do anything I want with it EXCEPT change the text.
You should use the itemconfig method of the canvas to modify the text attribute. You have to give to it an id of one or more canvas items. Here is a small working example that lets you change the text of one text object:
# use 'tkinter' instead of 'Tkinter' if using python 3.x
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.button = tk.Button(self, text="Change text", command=self.on_change_text)
self.canvas = tk.Canvas(self, width=400, height=400)
self.button.pack(side="top", anchor="w")
self.canvas.pack(side="top", fill="both", expand=True)
# every canvas object gets a unique id, which can be used later
# to change the object.
self.text_id = self.canvas.create_text(10,10, anchor="nw", text="Hello, world")
def on_change_text(self):
self.canvas.itemconfig(self.text_id, text="Goodbye, world")
if __name__ == "__main__":
root = tk.Tk()
view = Example(root)
view.pack(side="top", fill="both", expand=True)
root.mainloop()
You should assign the text to a variable(id):
then use this code edit the text:
canvas.itemconfig(your_text_id, text="New Text")