Tkinter w.destroy() blocks/resets window resize - python-3.x

I have a Python 3.5 app using tkinter and create/remove widgets in a canvas on the fly. I noticed that when a widget is being destroyed using w.destroy() during the callback of a window resize, the callback finished correctly, but the window is being reset to its original size.
I've tried to delay the w.destroy() using self.after(1000, self.resize) and that kind off works but when dragging/holding the cursor down while the callback is being fired, the resize jumps back anyway.
Anyone having a suggestion to remove widgets on the fly and release memory?
UPDATE:
As a workaround I mark widgets to be deleted with w.delete=True and in an 'after' callback delete the widgets which are marked for deletion.
Here is a stripped down sample to illustrate the issue:
UPDATE: Simplified code to bare minimum to reproduce issue.
from tkinter import ALL, N, E, W, S, Canvas, Tk, ttk
class Resize(Tk):
def __init__(self):
super().__init__()
self.init = True
self.canvas = None
self.position_window()
self.create_canvas()
def resize(self):
if self.init:
self.init = False
return
# delete(ALL) will remove the widget from the display, but widget
# remains in memory as child of canvas. Resizing works ok.
self.canvas.delete(ALL)
# widget.destroy() will release memory of widget, but blocks/resets resizing.
# Window gets resized by 1-2 pixels and jumps back to its original size.
[child.destroy() for child in self.canvas.winfo_children()]
def create_canvas(self):
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.canvas = Canvas(self)
self.canvas.grid(row=0, column=0, sticky=(N, S, W, E))
self.canvas.bind('<Configure>', lambda *args: self.resize())
entry = ttk.Label(self.canvas, text='Some dummy text')
self.canvas.create_window(20, 20, window=entry, anchor='w')
def position_window(self):
self.resizable(1, 1)
sceenwidth = self.winfo_screenwidth()
screenheight = self.winfo_screenheight()
self.update_idletasks()
width = 600
height = 300
left = sceenwidth / 2 - width / 2
top = (screenheight / 2 - height / 2) / 3
self.geometry("%dx%d%+d%+d" % (width, height, left, top))
if __name__ == '__main__':
Resize().mainloop()

I did not try your code, but I had the same problem with my own project.
The window wasn't just resizing/resetting on destroy(); the <Configure> event was seemingly interrupted.
The window always reverted back to the very first <Configure> call back no matter how much or how little I resized. Because of that, or along with that, Button-1 was automatically releasing, preventing the window resize grip from following the mouse.
In other words, the window edge was "slipping out" from under the mouse, much like a scrollbar in Windows will reset mid-scroll when the mouse moves too far from it.
Anyways, a solution is to use ttk.Sizegrip and prohibit resizing the top level window with the window borders.

https://stackoverflow.com/a/65605907/7281120
I answered this somewhere else. I ended up polling
win32api.GetKeyState(0x01) >= 0
and storing a list of things to destroy when the mouse was released and it was safe to do so.

Related

How to get rid of titlebar without removing icon in taskbar with Tkinter?

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>")

Animate the color of rectangle

I want to change the color of rectangle after a certain period of time.
I tried root.after() method but it is not working.
import time
from tkinter import *
def task():
canvas= Canvas()
canvas.create_rectangle(0,0,100,100,fill='red')
canvas.pack()
time.sleep(2)
canvas.create_rectangle(0,0,100,100,fill='blue')
canvas.pack()
time.sleep(2)
print("Testing...")
root = Tk()
canvas = Canvas(root)
while(1):
task()
root.mainloop()
Given code running continuously and root windows get hang for certain time. Rectangle color should change after 2 seconds of delay.
Using time.sleep() hangs the window as well as while loop. To use time.sleep
in tkinter we use after(ms) (ms-milliseconds) in the functions so the GUI won't hang.
While does the same, so we use after(ms, callback, args) function.
Here is an example of what you are trying to achieve. The rectangle will change his color every 1000ms (1 sec) from red to blue - blue to red so on. Also in your code you were creating a new canvas and rectangle every 4 secs. What I did is, I defined one canvas and one rectangle outside the task() function and gave the rectangle a tag (tag='rect') for the reference through which edit that one rectangle's color without creating unnecessary items. I hope this helped you.
Example:
from tkinter import *
root = Tk()
canvas = Canvas(root)
canvas.pack()
canvas.create_rectangle(0,0,100,100,fill='red', tag='rect')
def task():
l = root.after(1000, task)
if int(l.split('#')[1]) % 2 == 0:
canvas.itemconfig('rect', fill='blue')
else:
canvas.itemconfig('rect', fill='red')
task()
root.mainloop()

Is there a way to bind MouseWheel event on labels inside a frame [duplicate]

I have this scroll-able frame (frame inside canvas actually).
import Tkinter as tk
class Scrollbarframe():
def __init__(self, parent,xsize,ysize,xcod,ycod):
def ScrollAll(event):
canvas1.configure(scrollregion=canvas1.bbox("all"),width=xsize,height=ysize,bg='white')
self.parent=parent
self.frame1=tk.Frame(parent,bg='white')
self.frame1.place(x=xcod,y=ycod)
canvas1=tk.Canvas(self.frame1)
self.frame2=tk.Frame(canvas1,bg='white',relief='groove',bd=1,width=1230,height=430)
scrollbar1=tk.Scrollbar(self.frame1,orient="vertical",command=canvas1.yview)
canvas1.configure(yscrollcommand=scrollbar1.set)
scrollbar1.pack(side="right",fill="y")
canvas1.pack(side="left")
canvas1.create_window((0,0),window=self.frame2,anchor='nw')
self.frame2.bind("<Configure>",ScrollAll)
I would like to bind mouse wheel to the scrollbar so that user can scroll down the frame without having to use arrow buttons on the scrollbar. After looking around, i added a binding to my canvas1 like this
self.frame1.bind("<MouseWheel>", self.OnMouseWheel)
This is the function:
def OnMouseWheel(self,event):
self.scrollbar1.yview("scroll",event.delta,"units")
return "break"
But the scroll bar won't move when i use mousewheel. Can anyone help me with this? All i want is when the user use mousewheel (inside the frame area/on the scrollbar), the canvas should automatically scroll up or down.
Perhaps the simplest solution is to make a global binding for the mousewheel. It will then fire no matter what widget is under the mouse or which widget has the keyboard focus. You can then unconditionally scroll the canvas, or you can be smart and figure out which of your windows should scroll.
For example, on windows you would do something like this:
self.canvas = Canvas(...)
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
...
def _on_mousewheel(self, event):
self.canvas.yview_scroll(-1*(event.delta/120), "units")
Note that self.canvas.bind_all is a bit misleading -- you more correctly should call root.bind_all but I don't know what or how you define your root window. Regardless, the two calls are synonymous.
Platform differences:
On Windows, you bind to <MouseWheel> and you need to divide event.delta by 120 (or some other factor depending on how fast you want the scroll)
on OSX, you bind to <MouseWheel> and you need to use event.delta without modification
on X11 systems you need to bind to <Button-4> and <Button-5>, and you need to divide event.delta by 120 (or some other factor depending on how fast you want to scroll)
There are more refined solutions involving virtual events and determining which window has the focus or is under the mouse, or passing the canvas window reference through the binding, but hopefully this will get you started.
Based on #BryanOakley's answer, here is a way to scroll only the focused widget (i.e. the one you have mouse cursor currently over).
Bind to <Enter> and <Leave> events happening on your scrollable frame which sits inside a canvas, the following way (scrollframe is the frame that is inside the canvas):
...
self.scrollframe.bind('<Enter>', self._bound_to_mousewheel)
self.scrollframe.bind('<Leave>', self._unbound_to_mousewheel)
return None
def _bound_to_mousewheel(self, event):
self.canv.bind_all("<MouseWheel>", self._on_mousewheel)
def _unbound_to_mousewheel(self, event):
self.canv.unbind_all("<MouseWheel>")
def _on_mousewheel(self, event):
self.canv.yview_scroll(int(-1*(event.delta/120)), "units")
This link gives you an example as to how to use the scrollwheel.
http://www.daniweb.com/software-development/python/code/217059/using-the-mouse-wheel-with-tkinter-python
I hope this helps!
# explore the mouse wheel with the Tkinter GUI toolkit
# Windows and Linux generate different events
# tested with Python25
import Tkinter as tk
def mouse_wheel(event):
global count
# respond to Linux or Windows wheel event
if event.num == 5 or event.delta == -120:
count -= 1
if event.num == 4 or event.delta == 120:
count += 1
label['text'] = count
count = 0
root = tk.Tk()
root.title('turn mouse wheel')
root['bg'] = 'darkgreen'
# with Windows OS
root.bind("<MouseWheel>", mouse_wheel)
# with Linux OS
root.bind("<Button-4>", mouse_wheel)
root.bind("<Button-5>", mouse_wheel)
label = tk.Label(root, font=('courier', 18, 'bold'), width=10)
label.pack(padx=40, pady=40)
root.mainloop()
To get rid of the weird factor 120 we could just look at the sign of the event.delta value. This makes it easy to use the same handler under Windows, Linux and Mac OS.
# Mouse wheel handler for Mac, Windows and Linux
# Windows, Mac: Binding to <MouseWheel> is being used
# Linux: Binding to <Button-4> and <Button-5> is being used
def MouseWheelHandler(event):
global count
def delta(event):
if event.num == 5 or event.delta < 0:
return -1
return 1
count += delta(event)
print(count)
import tkinter
root = tkinter.Tk()
count = 0
root.bind("<MouseWheel>",MouseWheelHandler)
root.bind("<Button-4>",MouseWheelHandler)
root.bind("<Button-5>",MouseWheelHandler)
root.mainloop()
As an addendum to the above, the "delta" scaling factor is easy to calculate, since platform information is available through the sys and platform modules (and possibly others).
def my_mousewheel_handler(event):
if sys.platform == 'darwin': # for OS X # also, if platform.system() == 'Darwin':
delta = event.delta
else: # for Windows, Linux
delta = event.delta // 120 # event.delta is some multiple of 120
if event.widget in (widget1, widget2, ):
'do some really cool stuff...'
In case you are interested
How to scroll 2 listbox at the same time
#listbox scrollbar
from tkinter import *
root = Tk()
def scrolllistbox2(event):
listbox2.yview_scroll(int(-1*(event.delta/120)), "units")
scrollbar = Scrollbar(root)
#scrollbar.pack(side=RIGHT, fill=Y)
listbox = Listbox(root)
listbox.pack()
for i in range(100):
listbox.insert(END, i)
# attach listbox to scrollbar
listbox.config(yscrollcommand=scrollbar.set)
listbox.bind("<MouseWheel>", scrolllistbox2)
listbox2 = Listbox(root)
listbox2.pack()
for i in range(100):
listbox2.insert(END, i+100)
listbox2.config(yscrollcommand=scrollbar.set)
#scrollbar.config(command=listbox.yview)
root.mainloop()
Or...
from tkinter import *
root = Tk()
root.geometry("400x400")
def scrolllistbox(event):
''' scrolling both listbox '''
listbox2.yview_scroll(int(-1*(event.delta/120)), "units")
listbox1.yview_scroll(int(-1*(event.delta/120)), "units")
def random_insert():
''' adding some numbers to the listboxes '''
for i in range(100):
listbox1.insert(END, i)
listbox2.insert(END, i + 100)
# SCROLLBAR
scrollbar = Scrollbar(root)
#scrollbar.pack(side=RIGHT, fill=Y)
# LISTBOX 1
listbox1 = Listbox(root)
listbox1.pack()
# attach listbox to scrollbar with yscrollcommand
# listbox1.config(yscrollcommand=scrollbar.set)
# The second one
listbox2 = Listbox(root)
listbox2.pack()
listbox2.config(yscrollcommand=scrollbar.set)
# scroll the first one when you're on the second one
# listbox2.bind("<MouseWheel>", scrolllistbox)
root.bind("<MouseWheel>", scrolllistbox)
# scroll also the second list when you're on the first
listbox1.bind("<MouseWheel>", scrolllistbox)
random_insert()
#scrollbar.config(command=listbox.yview)
root.mainloop()
Mikhail T.'s answer worked really well for me. Here is perhaps a more generic set up that others might find useful (I really need to start giving things back)
def _setup_mousewheel(self,frame,canvas):
frame.bind('<Enter>', lambda *args, passed=canvas: self._bound_to_mousewheel(*args,passed))
frame.bind('<Leave>', lambda *args, passed=canvas: self._unbound_to_mousewheel(*args,passed))
def _bound_to_mousewheel(self, event, canvas):
canvas.bind_all("<MouseWheel>", lambda *args, passed=canvas: self._on_mousewheel(*args,passed))
def _unbound_to_mousewheel(self, event, canvas):
canvas.unbind_all("<MouseWheel>")
def _on_mousewheel(self, event, canvas):
canvas.yview_scroll(int(-1*(event.delta/120)), "units")
Then setting a canvas/frame up for mousewheel scrolling is just:
self._setup_mousewheel(frame, canvas)
def onmousewheel(widget, command):
widget.bind("<Enter>", lambda _: widget.bind_all('<MouseWheel>',command ))
widget.bind("<Leave>", lambda _: widget.unbind_all('<MouseWheel>'))
onmousewheel(canvas, lambda e: canvas.yview_scroll(int(-1*(e.delta)), "units"))
Compact solution for just scroll frame which you want.
Thanks for everyone who share their solution.

In python3 tkinter, the wigdet frame doesn't show in interface

I use the same format of frame but it doesn't show in the interface, hope someone could tell me the solution, thanks.
class Interface(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.master.title("measurement")
self.grid()
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = Frame(self,relief=GROOVE,bg='white')
self.master.Frame1.grid(column=1,row=9)
self.can =Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = Canvas(self.master, width=150, height=120, background='snow')
ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = Tk()
window.resizable(False, False)
Interface(window).mainloop()
I can't figure out why you have 2 Canvas's, but the problem is that you aren't placing them on their respective parents. I cut out a lot of the code that seemed unnecessary and restructured your code to make it more logical:
class Interface(Frame):
def __init__(self, parent):
self.parent = parent
super().__init__(self.parent)
self.Frame1 = Frame(self, relief=GROOVE)
self.Frame1.grid()
self.canvas = Canvas(self.Frame1, bg="ivory", width=200, height=150)
self.canvas.grid()
self.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
root = Tk()
# Tk configurations are not relevant to
# the Interface and should be done out here
root.title('Measurement')
root.geometry('700x400+100+50')
root.resizable(False, False)
Interface(root).pack()
root.mainloop()
i think I don't really understand your problem, you don't see your frame because you don't have any widget in it, that's all
import tkinter as tk
class Interface(tk.Frame):
def __init__(self,parent=None):
tk.Frame.__init__(self,parent)
self.master.title("measurement")
self.grid(row=0, column=0)
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = tk.Frame(self,relief='groove',bg='white')
self.master.Frame1.grid(column=1,row=9)
labelExemple =tk.Label(self.master.Frame1, text="Exemple")
labelExemple.grid(row=0,column=0)
self.can = tk.Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = tk.Canvas(self.master, width=150, height=120, background='snow')
self.ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = tk.Tk()
window.resizable(False, False)
Interface(window).mainloop()
PS : use import tkinter as tk instead of from tkinter import *
There are several problems with those few lines of code, almost all having to do with the way you're using grid:
you aren't using the sticky option, so widgets won't expand to fill the space they are given
you aren't setting the weight for any rows or columns, so tkinter doesn't know how to allocate unused space
you aren't using grid or pack to put the canvases inside of frames, so the frames stay their default size of 1x1
The biggest problem is that you're trying to solve all of those problems at once. Layout problems are usually pretty simple to solve as long as you're only trying to solve one problem at a time.
Start by removing all of the widgets from Interface. Then, give that frame a distinctive background color and then try to make it fill the window (assuming that's ultimately what you want it to do). Also, remove the root.resizable(False, False). It's rarely something a user would want (they like to be able to control their windows), plus it makes your job of debugging layout problems harder.
Once you get your instance of Interface to appear, add a single widget and make sure it appears too. Then add the next, and the next, adding one widget at a time and observing how it behaves.

tkinter resize label width (grid_propagate not working)

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")

Resources