Related
I am trying to create an own ttk Theme based on my company's CI. I took the Sun Valley theme as starting point and swapped out graphics, fonts and colors.
However I am stuck on the Label frame. I am trying to position the Label within the frame, kind of like a heading. I.e. there should be some margin between top edge and label, and appropriate top-padding for the content (child widgets).
Now:
+-- Label ------
| ...
Desired:
+---------------
| Label
| ...
I tried to set the padding option:
within the Layout
on TLabelframe itself
on TLabelframe.Label
but the label did not move a pixel. How to achieve this?
Generally I am very confused about what identifiers and options are legal within ttk:style layout, ttk:style element and ttk:style configure, because documentation is hazy and scattered all over the 'net, and there are no error messages whatsoever. Any helpful tips?
Edit: What I found out since posting:
The Labelframe label is a separate widget altogether, with the class TLabelframe.Label.
It is possible to override its layout and add a spacer on top, shifting the text down.
However, the label widget is v-centered on the frame line. If its height increases, it pushes "upward" as much as downward. I found no way to alter the alignment w.r.t. to the actual frame.
It might be possible to replace Labelframe altogether with a custom Frame subclass with the desired layout. But that means changing the "client" code in many places. :-/
This can be done by changing the layout definitions so that the text element is held by the Labelframe layout and the Layoutframe.Label no longer draws the text element. Adding a bit of padding ensures the contained widgets leave the label clear.
Example code:
import sys
import tkinter as tk
import tkinter.ttk as ttk
class CustomLabelframe(ttk.Labelframe):
def __init__(self, master, **kwargs):
"""Initialize the widget with the custom style."""
kwargs["style"] = "Custom.Labelframe"
super(CustomLabelframe, self).__init__(master, **kwargs)
#staticmethod
def register(master):
style = ttk.Style(master)
layout = CustomLabelframe.modify_layout(style.layout("TLabelframe"), "Custom")
style.layout('Custom.Labelframe.Label', [
('Custom.Label.fill', {'sticky': 'nswe'})])
style.layout('Custom.Labelframe', [
('Custom.Labelframe.border', {'sticky': 'nswe', 'children': [
('Custom.Labelframe.text', {'side': 'top'}),
('Custom.Labelframe.padding', {'side': 'top', 'expand': True})
]})
])
if (style.configure('TLabelframe')):
style.configure("Custom.Labelframe", **style.configure("TLabelframe"))
# Add space to the top to prevent child widgets overwriting the label.
style.configure("Custom.Labelframe", padding=(0,12,0,0))
style.map("Custom.Labelframe", **style.map("TLabelframe"))
master.bind("<<ThemeChanged>>", lambda ev: CustomLabelframe.register(ev.widget))
#staticmethod
def modify_layout(layout, prefix):
"""Copy a style layout and rename the elements with a prefix."""
result = []
for item in layout:
element,desc = item
if "children" in desc:
desc["children"] = HistoryCombobox.modify_layout(desc["children"], prefix)
result.append((f"{prefix}.{element}",desc))
return result
class App(ttk.Frame):
"""Test application for the custom widget."""
def __init__(self, master, **kwargs):
super(App, self).__init__(master, **kwargs)
self.master.wm_geometry("640x480")
frame = self.create_themesframe()
frame.pack(side=tk.TOP, fill=tk.BOTH)
for count in range(3):
frame = CustomLabelframe(self, text=f"Frame {count}", width=160, height=80)
frame.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
button = ttk.Button(frame, text="Test")
button.pack(side=tk.LEFT)
self.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
def create_themesframe(self):
frame = ttk.Frame(self)
label = ttk.Label(frame, text="Theme: ")
themes = ttk.Combobox(frame, values=style.theme_names(), state="readonly")
themes.current(themes.cget("values").index(style.theme_use()))
themes.bind("<<ComboboxSelected>>", lambda ev: style.theme_use(ev.widget.get()))
label.pack(side=tk.LEFT)
themes.pack(side=tk.LEFT)
return frame
def main(args=None):
global root, app, style
root = tk.Tk()
style = ttk.Style(root)
CustomLabelframe.register(root)
app = App(root)
try:
import idlelib.pyshell
sys.argv = [sys.argv[0], "-n"]
root.bind("<Control-i>", lambda ev: idlelib.pyshell.main())
except Exception as e:
print(e)
root.mainloop()
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
It is relatively easy to place ttk.Labelframe text below, on or above the relief graphic. This example uses the text attribute but labelwidget can also be used.
In order for the relief to be visible the background color of Labelframe.Label must be set to "".
import tkinter as tk
from tkinter import font
from tkinter import ttk
message = "Hello World"
master = tk.Tk()
style = ttk.Style(master)
style.theme_use(themename = "default")
actualFont = font.Font(
family = "Courier New", size = 20, weight = "bold")
style.configure(
"TLabelframe.Label", background = "", font = actualFont)
frame = ttk.LabelFrame(
master, labelanchor = "n", text = message)
frame.grid(sticky = tk.NSEW)
frame.rowconfigure(0, weight = 1)
frame.columnconfigure(0, weight = 1)
def change_heading():
if frame["text"][0] == "\n":
frame["text"] = f"{message}\n"
else:
frame["text"] = f"\n{message}"
button = tk.Button(
frame, text = "Change", command = change_heading)
button.grid(sticky = "nsew")
master.mainloop()
Looking through the source of the Labelframe widget, I found that:
The label is either placed vertically-centered on the frame's border, or flush above it, depending on the -labeloutside config option. (for default NW anchor)
i.e. by adding whitespace on top of the text by any means, the label box will extend upwards the same amount as downwards, creating a "dead space" above the frame.
There might still be a way to get it "inside" by increasing the border width, but I couldn't get it to work.
I now used the labeloutside option to make a "tab-like" heading.
# ... (define $images array much earlier) ...
ttk::style element create Labelframe.border image $images(card2) \
-border 6 -padding 6 -sticky nsew
ttk::style configure TLabelframe -padding {8 8 8 8} -labeloutside 1 -labelmargins {2 2 2 0}
ttk::style element create Label.fill image $images(header2) -height 31 -padding {8 0 16 0} -border 1
With suitable images, this is nearly what I was aiming for, only that the header does not stretch across the full frame width. Tkinter elements use a "9-patch"-like subdivision strategy for images, so you can make stretchable frames using the -border argument for element create.
Result is approximately this:
+-------------+
| Heading |
+-------------+----------------+
| ... |
So, I was recently checking out VsCode, and I noticed an interesting feature. Although there was a taskbar icon, there was no titlebar; instead, VsCode implements its own. I looked at some other programs from Microsoft, and they do the same thing. I think this is a very cool feature.
I make a lot of productivity apps with Tkinter*, so I looked at how to do this in my apps. Unfortunately, the standard way to get rid of the titlebar in Tkinter is to disable the Window Manager (using overridedirect(1)). This also gets rid of the taskbar icon, which I want to keep.
In other words, what I am trying to get is
while still keeping this: .
* For reference I am using Python 3.8 and TkInter 8.6.
You can create your own buttons titlebar using frames. Here take a look at this. I also worked on a tkinter based app and created this along with using what #Hruthik Reddy has given.
I make a lot of productivity apps with Tkinter*, so I looked at how to do this in my apps.
Assuming that you may have used classes at some point in those apps, I have created this inherited Tk subclass, and explained in comments:
import tkinter as tk
import tkinter.ttk as ttk
from ctypes import windll
class TestApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# set overrideredirect to True to remove the windows default decorators
self.overrideredirect(True)
self.geometry('700x500+10+10') # you may or may not want to initialize the geometry of the window
self.minsize(193, 109)
# (x, y) coordinates from top left corner of the window
self.x = None
self.y = None
# Create a frame that will contain the title label of the window
self.frame = tk.Frame(self, bg='gray38')
self.frame.pack(side=tk.TOP, fill=tk.X)
# Label `name` for the window
# Since buttons are on the right side and the name of the window is on the left side, the label will be packed towards LEFT side
self.name = tk.Label(self.frame, text='Simple Text Box', font='Consolas 11',
bg=self.frame.cget('background'), fg='white')
self.name.pack(side=tk.LEFT, fill=tk.X, anchor=tk.CENTER)
# Pack the close button to the right-most side
self.close = tk.Button(self.frame, text='✕', bd=0, width=3, font='Consolas 13',
command=self.destroy, bg=self.frame.cget('background'))
self.close.pack(side=tk.RIGHT)
# Pack the maximize button second from the right
# The unicode string as the value of the keyword `text` below, is taken from the internet, it contains the maximize icon as unicode character
self.maximize = tk.Button(self.frame, text=u"\U0001F5D6", bd=0, width=3, font='Consolas',
command=self.maximize_win, bg=self.frame.cget('background'))
self.maximize.pack(side=tk.RIGHT)
# Pack the minimize button third from the right
self.minimize = tk.Button(self.frame, text='—', bd=0, width=3, font='Consolas 13',
command=self.minimize_win, bg=self.frame.cget('background'))
self.minimize.pack(side=tk.RIGHT)
# -------------------
# NOW YOU CAN PUT WHATEVER WIDGETS YOU WANT AFTER THIS BUT FOR THIS EXAMPLE I
# HAVE TAKEN A TEXTBOX WITH HORIZONTAL AND VERTICAL SCROLLBARS AND A SIZEGRIP
# -------------------
# The frame below contains the vertical scrollbar and the sizegrip (sizegrip helps in resizing the window
self.scroll_frame = tk.Frame(self)
v_scroll = tk.Scrollbar(self.scroll_frame, orient=tk.VERTICAL)
h_scroll = tk.Scrollbar(self, orient=tk.HORIZONTAL)
self.grip = ttk.Sizegrip(self.scroll_frame)
# I am directly putting the textbox in the window, you may add frames and other stuff
self.text = tk.Text(self, wrap=tk.NONE, yscrollcommand=v_scroll.set, xscrollcommand=h_scroll.set,
font='Consolas 14', width=1, height=1)
# set the scrollbar for y and x views of the textbox respectively
v_scroll.config(command=self.text.yview)
h_scroll.config(command=self.text.xview)
# Packing scrollbar frame, the scrollbars and the grip according to the arrangement I want
self.scroll_frame.pack(side=tk.RIGHT, fill=tk.Y)
v_scroll.pack(side=tk.TOP, fill=tk.Y, expand=tk.Y)
self.grip.pack(side=tk.BOTTOM)
self.text.pack(side=tk.TOP, expand=tk.TRUE, fill=tk.BOTH)
h_scroll.pack(side=tk.BOTTOM, fill=tk.X)
self.grip.bind("<B1-Motion>", self.onmotion)
# Bind the motion of mouse after mouse click to the onmotion function for window resizing
self.call('encoding', 'system', 'utf-8')
# Binding `<Enter>` and `<Leave>` mouse event to their respective functions
# `<Enter>` event is called when the mouse pointer enters any widget
# `<Leave>` event is called when the mouse pointer leaves any widget
# Here when the mouse pointer enters or leaves the buttons their color will change
self.close.bind('<Enter>', lambda _: self.close.config(bg='red'))
self.close.bind('<Leave>', lambda _: self.close.config(bg=self.frame.cget('background')))
self.minimize.bind('<Enter>', lambda _: self.minimize.config(bg='gray58'))
self.minimize.bind('<Leave>', lambda _: self.minimize.config(bg=self.frame.cget('background')))
self.maximize.bind('<Enter>', lambda _: self.maximize.config(bg='gray58'))
self.maximize.bind('<Leave>', lambda _: self.maximize.config(bg=self.frame.cget('background')))
# Now you may want to move your window (obviously), so the respective events are bound to the functions
self.frame.bind("<ButtonPress-1>", self.start_move)
self.frame.bind("<ButtonRelease-1>", self.stop_move)
self.frame.bind("<B1-Motion>", self.do_move)
self.frame.bind('<Double-1>', self.maximize_win)
self.name.bind("<ButtonPress-1>", self.start_move)
self.name.bind("<ButtonRelease-1>", self.stop_move)
self.name.bind("<B1-Motion>", self.do_move)
self.name.bind('<Double-1>', self.maximize_win)
def start_move(self, event):
""" change the (x, y) coordinate on mousebutton press and hold motion """
self.x = event.x
self.y = event.y
def stop_move(self, event):
""" when mouse button is released set the (x, y) coordinates to None """
self.x = None
self.y = None
def do_move(self, event):
""" function to move the window """
self.wm_state('normal') # if window is maximized, set it to normal (or resizable)
self.maximize.config(text=u"\U0001F5D6") # set the maximize button text to the square character of maximizing window
deltax = event.x - self.x
deltay = event.y - self.y
x = self.winfo_x() + deltax
y = self.winfo_y() + deltay
self.geometry(f"+{x}+{y}")
def onmotion(self, event):
""" function to change window size """
self.wm_state('normal')
self.maximize.config(text=u"\U0001F5D6")
x1 = self.winfo_pointerx()
y1 = self.winfo_pointery()
x0 = self.winfo_rootx()
y0 = self.winfo_rooty()
self.geometry("%sx%s" % ((x1-x0), (y1-y0)))
return
def minimize_win(self, event=None):
""" function to iconify or minimize window as an icon """
self.overrideredirect(False)
self.wm_iconify()
self.bind('<FocusIn>', self.on_deiconify)
def maximize_win(self, event=None):
""" function to maximize window or make it normal (exit maximize) """
if self.maximize.cget('text') == u"\U0001F5D7":
self.wm_state('normal')
self.maximize.config(text=u"\U0001F5D6")
return
self.wm_state('zoomed')
self.maximize.config(text=u"\U0001F5D7")
def on_deiconify(self, event):
""" function to deiconify or window """
self.overrideredirect(True)
set_appwindow(root=self)
def set_appwindow(root):
hwnd = windll.user32.GetParent(root.winfo_id())
style = windll.user32.GetWindowLongPtrW(hwnd, GWL_EXSTYLE)
style = style & ~WS_EX_TOOLWINDOW
style = style | WS_EX_APPWINDOW
res = windll.user32.SetWindowLongPtrW(hwnd, GWL_EXSTYLE, style)
# re-assert the new window style
root.wm_withdraw()
root.after(10, lambda: root.wm_deiconify())
if __name__ == '__main__':
GWL_EXSTYLE = -20
WS_EX_APPWINDOW = 0x00040000
WS_EX_TOOLWINDOW = 0x00000080
app = TestApp()
# print(app.tk.call('tk', 'windowingsystem'))
# # Here root.tk.call('tk', 'windowingsystem') calls tk windowingsystem in Tcl, and that returns 'win32',
# # 'aqua' or 'x11' as documented in tk
app.after(10, lambda: set_appwindow(root=app))
app.text.insert(1.0, 'Drag the window using the title or the empty area to the right of the\ntitle.'
' Try maximizing / minimizing.\n\n-- YOU MAY HAVE A PROBLEM WITH RESIZING --\n'
'-- ALSO IF YOU REMOVE `height` AND `width` KEYWORDS FROM THE TEXTBOX DECLARATION'
' AND FONT SIZE IS TOO BIG THE SCROLLBAR MAY DISAPPEAR --\nSO KEEP THOSE KEYWORDS THERE!')
app.mainloop()
When window is maximized you may not be able to see the taskbar. But you can still resize it using sizegrip. I don't yet know how to make window resizing possible from window borders with overrideredirect but Sizegrip works just fine.
Now regarding the set_appwindow function, this is what MSDN says:
The Shell creates a button on the taskbar whenever an application
creates a window that isn't owned. To ensure that the window button is
placed on the taskbar, create an unowned window with the
WS_EX_APPWINDOW extended style. To prevent the window button from
being placed on the taskbar, create the unowned window with the
WS_EX_TOOLWINDOW extended style. As an alternative, you can create a
hidden window and make this hidden window the owner of your visible
window.
Complete reference here
This may seem like a very long answer but I hope it covers all what you need and helps you.
Check out the following code it worked for me :-
import tkinter as tk
import tkinter.ttk as ttk
from ctypes import windll
GWL_EXSTYLE=-20
WS_EX_APPWINDOW=0x00040000
WS_EX_TOOLWINDOW=0x00000080
def set_appwindow(root):
hwnd = windll.user32.GetParent(root.winfo_id())
style = windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
style = style & ~WS_EX_TOOLWINDOW
style = style | WS_EX_APPWINDOW
res = windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, style)
root.wm_withdraw()
root.after(10, lambda: root.wm_deiconify())
def main():
root = tk.Tk()
root.wm_title("AppWindow Test")
button = ttk.Button(root, text='Exit', command=lambda: root.destroy())
button.place(x=10,y=10)
root.overrideredirect(True)
root.after(10, lambda: set_appwindow(root))
root.mainloop()
if __name__ == '__main__':
main()
In the function on_deiconify() we need to unbind the previous event <FocusIn> to avoid from blitting of the window we write like this
def on_deiconify(self, event):
""" function to deiconify or window """
self.overrideredirect(True)
set_appwindow(root=self)
self.unbind("<FocusIn>")
I'm currently working on a little project on python-3.x including some tkinter ressources. My program is made to display on a screen a list of pictures included in a directory, each picture is put on a button that is a sixth of the original image, and if we click on it, it display the image on his original size on a new window. The original window is set by the amount of pictures i put in the columns (i can choose in the code) and i ve made a scrollbar because i have to work with a lot of pictures.
But here is my problem, it's works fine except that if i change the window size, like reduce it for example, the buttons don't follow, they just vanish behind the window, and with the scrollbar.
I'm not particularly good in python so i was wondering that maybe by doing like a threading we could get the window size in live and then if the window size is inferior/superior of our columns of buttons, we could resize it and change the amount of columns then reload the page, but i will have to work with multiple image so it will take a lot of time.
from tkinter import *
from tkinter.filedialog import *
from tkinter.messagebox import *
from PIL import Image, ImageTk
import tkinter as tk
import glob
import os
import cv2
import copy
import _thread
import time
folder = 'X:/users/Robin/data/dataset-valid/visu/*.jpg'
a=glob.glob(folder)
fic = "../data/list.txt"
fichObj=open(fic,"w")
p = []
for f in a:
fichObj.write(f+"\n")
fichObj.close()
class SuperPhoto(object):
def __init__(self, photo , image):
self.photo = photo
temp = cv2.resize(image, (int((self.photo.width())/6) , int((self.photo.height())/6)))
red = temp[:,:,2].copy()
blue = temp[:,:,0].copy()
temp[:,:,0] = red
temp[:,:,2] = blue
temp = Image.fromarray(temp)
self.miniature = ImageTk.PhotoImage(temp)
def agrandir(self):
Newfen=Toplevel()
Newfen.geometry("+60+60")
#self.photo.resize((500,500))
print(type(self.photo))
label = Label(Newfen, image=self.photo, width=self.photo.width(), height=self.photo.height())
label.image = self.photo # keep a reference!
label.pack()
if os.path.exists (fic): #os.path utile
count = len(open(fic).readlines())
print(count)
#lin = open(fic).readlines()
#print(lin)
class ScrollableCanvas(Frame):
def __init__(self, parent, *args, **kw):
Frame.__init__(self, parent, *args, **kw)
canvas=Canvas(self,bg='#FFFFFF',width=300,height=300,scrollregion=(0,0,500,500))
canvas.update_idletasks()
vbar=Scrollbar(self,orient=VERTICAL)
vbar.pack(side=RIGHT, fill=Y)
vbar.config(command=canvas.yview)
canvas.config(width=1200,height=700)
canvas.config(yscrollcommand=vbar.set)
canvas.pack(side=LEFT,expand=True,fill=BOTH)
# 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 )
# track changes to the canvas and frame width and sync them,
# also updating the scrollbar
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)
class Main_frame(Frame):
# Init
def __init__(self, fenetre_principale=None):
Frame.__init__(self, fenetre_principale)
self.grid()
self.scrollable_canvas = ScrollableCanvas(self)
self.scrollable_canvas.grid(row=1,column=1)
nbCol = 4
for file in a:
image = Image.open(file)
photo = ImageTk.PhotoImage(image)
w = photo.width()
L.append(int(w/6))
#print(L)
sumL = int(sum(L)/nbCol)
print(sumL)
p.append(SuperPhoto(photo, cv2.imread(file)))
for ligne in range(int(count/nbCol)):
for colonne in range(nbCol):
photo = p[ligne * nbCol + colonne]
button = Button(self.scrollable_canvas.interior, image=photo.miniature, command=photo.agrandir)
button.grid(row=ligne, column=colonne)
if __name__ == "__main__":
root = Tk()
root.title("VISU")
root.geometry("+0+0")
L= []
interface = Main_frame(fenetre_principale=root)
root.update_idletasks()
print(root.winfo_width())
print(root.geometry())
interface.mainloop()
So, I except this program to work like a classic directory display, with the columns that change automatically when we resize the window and with the scrollbar that follow it.
If you have any solutions it will really help me ..
You can try it, just put some jpeg pictures in a directory and change the folder variable with the link of your directory.
Thanks in advance for your help, if you have any questions to understand more clearly what i've said don't hesitate.
Each time the root window is resized, a <Configure> event is triggered. Catch it as follows:
def resize(event):
root.update_idletasks()
#update all image sizes here if needed
#all widgets can be 're-grided' here based on new width and height of root window
root.bind('<Configure>', resize)
If you want to ensure that your window cannot be resized, use the following:
root.resizable(False, False)
I already have a set of code that is similarly formatted as the one below, and that seems to work. But somehow, the image for this one isn't popping up. And they're in the same folder as the code. Def small is the code that has the image working, and def stripes is the one that is giving me an error.
from tkinter import *
import tkinter as tk
from tkinter import ttk
def small():
s = Tk()
s.title('Small Preset Shirt (Not fit to scale)')
canvas = Canvas(s, width = 800, height = 100)
canvas.pack()
b1=ttk.Button(s,text='Click to Start', command = questions)
b1.pack()
photo = PhotoImage(file = 'small.png')
b1.config(image=photo,compound=RIGHT)
s.mainloop()
def stripes():
stripes = Tk()
stripes.title('Black Shirt with Stripes')
canvas = Canvas(stripes, width = 800, height = 100)
canvas.pack()
b2=ttk.Button(stripes,text='Click to See Final Price', command = final)
b2.pack()
photo = PhotoImage(file = 'stripes.png')
b2.config(image=photo,compound=RIGHT)
stripes.mainloop()
Here is the full Traceback:
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/tkinter /__init__.py", line 1705, in __call__
return self.func(*args)
File "/Users/Business/Documents/Python/small.py", line 159, in stripes
b2.config(image=photo,compound=RIGHT)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/tkinter. /__init__.py", line 1485, in configure
return self._configure('configure', cnf, kw)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/tkinter. /__init__.py", line 1476, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: image "pyimage2" doesn't exist
When you get an error message "_tkinter.TclError: image "pyimage2" doesn't exist" or something like that then it means tkinter can't decide which window's photo it is. This is due to of more than one Tk() windows. There are few other things that create problems when you use more than one Tk, that is why Tkinter have another type of window Toplevel and it refers to the main window like a child window.
Lets get to your code..
Here I see few other problems other than just that error.
Like I said not more than one Tk() window. I believe you probably have more than two.
If you have a main window and decide to open few more with Toplevel then please don't use another mainloop() one is sufficient to open as many Toplevel windows but remember to use at least one mainloop() at the end of your code.
Sometimes when you define a Photoimage in a function which stored an image in a local variable the image is cleared by python even if it’s being displayed by the Label or Canvas. So always create a reference in that case.
As your code is not runnable so I added necessary things to run and test it.
from tkinter import *
from tkinter import ttk
Main_window = Tk() # Make only one Tk main window
Main_window.geometry('300x150')
Main_window.title("Get Shirts (Buy 1 get 1 Free)")
def small():
s = Toplevel() # For secondary window use Toplevel
s.title('Small Preset Shirt (Not fit to scale)')
canvas = Canvas(s, width = 800, height = 100)
canvas.pack()
b1=ttk.Button(s,text='Click to Start', command = None)
b1.pack()
photo = PhotoImage(file = 'logo.png')
b1.img_ref = photo # Create a reference
b1.config(image=photo,compound=RIGHT)
# s.mainloop() # Don't use mainloop more than once
def stripes():
stripes = Toplevel() # For secondary window use Toplevel
stripes.title('Black Shirt with Stripes')
canvas = Canvas(stripes, width = 800, height = 100)
canvas.pack()
b2=ttk.Button(stripes,text='Click to See Final Price', command = None)
b2.pack()
photo = PhotoImage(file = 'logo.png')
b2.img_ref = photo # Sometimes images in functions becomes garbage value.
b2.config(image=photo,compound=RIGHT)
# stripes.mainloop() # Using two of these will do nothnig.
Category_Lb = Label(Main_window, text='Category', font=('',25))
Category_Lb.pack()
Cate_1 = ttk.Button(Main_window, text='Small Preset Shirt', command=small)
Cate_1.pack()
Cate_2 = ttk.Button(Main_window, text='Black Shirt with Stripes', command=stripes)
Cate_2.pack()
Main_window.mainloop()
I'm having problem with adjusting the width of the label to reflect current width of the window. When the window size changes I'd like label to fill the rest of the width that is left after other widgets in row consume width they need.
Putting the label in a Frame and using grid_propagate(False) does not seem to work.
Consider following code:
import tkinter as tk
import tkinter.ttk as ttk
class PixelLabel(ttk.Frame):
def __init__(self,master, w, h=20, *args, **kwargs):
'''
creates label inside frame,
then frame is set NOT to adjust to child(label) size
and the label keeps extending inside frame to fill it all,
whatever long text inside it is
'''
ttk.Frame.__init__(self, master, width=w, height=h,borderwidth=1)
#self.config(highlightbackground="blue")
self.grid_propagate(False) # don't shrink
self.label = ttk.Label(*args, **kwargs)
self.label.grid(sticky='nswe')
def resize(self,parent,*other_lenghts):
'''
resizes label to take rest of the width from parent
that other childs are not using
'''
parent.update()
new_width = parent.winfo_width()
print(new_width)
for lenght in other_lenghts:
new_width -= lenght
print(new_width)
self.configure(width = new_width)
root = tk.Tk()
master = ttk.Frame(root)
master.grid()
label = ttk.Label(master,text='aaa',borderwidth=1, relief='sunken')
label.grid(row=0,column=0)
label1_width = 7
label1 = ttk.Label(master,text='bbbb',borderwidth=1, relief='sunken',width=label1_width)
label1.grid(row=0,column=1)
label2 = ttk.Label(master,text='ccccccccccccccccccccccccccccccccccccc',borderwidth=1, relief='sunken')
label2.grid(row=0,column=2)
label3_width = 9
label2 = ttk.Label(master,text='ddddd',borderwidth=1, relief='sunken',width=label2_width)
label2.grid(row=0,column=3)
label4 = ttk.Label(master,text='ee',borderwidth=1, relief='sunken')
label4.grid(row=1,column=0)
label5 = ttk.Label(master,text='f',borderwidth=1, relief='sunken')
label5.grid(row=1,column=1,sticky='we')
nest_frame = ttk.Frame(master)
nest_frame.grid(row=2,columnspan=4)
label8_width = 9
label8 = ttk.Label(nest_frame,text='xxxxx',borderwidth=1, relief='sunken',width=label8_width)
label8.grid(row=0,column=0)
label9 = PixelLabel(nest_frame, 5, text='should be next to xxxxx but is not?',borderwidth=1, relief='sunken')
label9.grid(row=0,column=1)
label9.resize(root,label2_width)
root.mainloop()
Why label9 does not appear next to label8
How to make label9 resize to meet current window size (this code is just a sample, I would like to be able to resize label9 as the window size changes dynamically when functions are reshaping the window)
It's not clear why you are using a label in a frame. I suspect this is an XY problem. You can get labels to consume extra space without resorting to putting labels inside frames. However, since you posted some very specific code with very specific questions, that's what I'll address.
Why label9 does not appear next to label8
Because you are creating the label as a child of the root window rather than a child of the frame. You need to create the label as a child of self inside PixelLabel:
class PixelLabel(...):
def __init__(...):
...
self.label = ttk.Label(self, ...)
...
How to make label9 resize to meet current window size (this code is just a sample, I would like to be able to resize label9 as the window size changes dynamically when functions are reshaping the window)
There are a couple more problems. First, you need to give column zero of the frame inside PixelFrame a non-zero weight so that it uses all available space (or, switch to pack).
class PixelLabel(...):
def __init__(...):
...
self.grid_columnconfigure(0, weight=1)
...
Second, you need to use the sticky attribute when placing nest_frame in the window so that it expands to fill its space:
nest_frame.grid(..., sticky="ew")