CustomTkinter
CustomTkinter copied to clipboard
Can not bind with CTK widgets
Hello, I using CTk widgets but they don't bind with other CTK widgets. One example is that I tired to bind with CTKFrame but it didn't work and when I binded with normal frame it worked. Could please quickly fix this as I needed this for a project. Thank you.
Please provide a minimal example code of what's not working, I don't know what you mean.
So, I was using bind with CTK widgets in class but it doesn't work and doesn't responded back to it. For example: This is the code:
# All the Moduels
from tkinter import *
from tkinter import ttk
from PIL import ImageTk
import customtkinter
import Intro_Frames
customtkinter.set_appearance_mode('light')
#customtkinter.set_appearance_mode('light')
# Frame for the delivery and pickup page on order list frame
frame_for_delivery_pickup = customtkinter.CTkFrame(Intro_Frames.order_list_frame, fg_color='white')
frame_for_delivery_pickup.place(x=0, y =0, width=1066, height=600)
# background image for the delivery image.
image_fordelivery_pixkup_page = ImageTk.PhotoImage(file = r"C:\Users\ajayb\PycharmProjects\Henderson_Pizza_Palace\button,background,etc\delivery_pixkup image.jpg")
putting_delivery_image = customtkinter.CTkLabel(frame_for_delivery_pickup, image=image_fordelivery_pixkup_page, anchor='nw')
putting_delivery_image.place(x = -8, y = 0)
# Delivery boyt and pickup photo
image_for_delivery = ImageTk.PhotoImage(file = r"C:\Users\ajayb\PycharmProjects\Henderson_Pizza_Palace\button,background,etc\delivery boy editedresziednow.png")
image_for_pick_up = ImageTk.PhotoImage(file = r"C:\Users\ajayb\PycharmProjects\Henderson_Pizza_Palace\button,background,etc\pickupeditedcroppedresized.png")
# alsoing putting the chocie frame in function so user can go back when want
def back_to_choice():
hiding_option_frames()
frame_for_delivery_pickup.place(x=0, y=0, width=1066, height=600)
def pickup_function():
hiding_option_frames()
pickup_frame.place(x=0, y=0, width=1066, height=600)
def delivery_finction():
hiding_option_frames()
delivery_frame.place(x=0, y=0, width=1066, height=600)
def hiding_option_frames():
delivery_frame.place_forget()
frame_for_delivery_pickup.place_forget()
pickup_frame.place_forget()
# Pickup and delivery buttons wiht images
delivery_button = customtkinter.CTkButton(frame_for_delivery_pickup, text=' Delivery', text_font=('robot', 30), cursor='hand2', image=image_for_delivery, corner_radius=10, height=80, width=340, text_color='black', hover_color ='#0C9FD6', command=delivery_finction)
delivery_button.place(y = 375, x = 363)
pickup_button = customtkinter.CTkButton(frame_for_delivery_pickup, text=' Pick Up', text_font=('robot', 30), cursor='hand2', image=image_for_pick_up, corner_radius=10, height=80, width=340, text_color='black', fg_color='orange', hover_color='#DE940C', command=pickup_function)
pickup_button.place(y = 480, x = 363)
# frame for both options
pickup_frame = customtkinter.CTkFrame(Intro_Frames.order_list_frame, corner_radius=0, fg_color ="#F2F2F2")
delivery_frame = Frame(Intro_Frames.order_list_frame, bg ="#F2F2F2")
# time detail
delivery_label_time = customtkinter.CTkLabel(delivery_frame, text="DELIVERY TIME:", text_color='black', border = 0, text_font=('Bahnschrift SemiBold', 18))
delivery_label_time.grid(row =0, column=0,sticky= 'nw', padx=300, pady=(15, 5) )
# time frame
time_frame = customtkinter.CTkFrame(delivery_frame, width=450, height=100, fg_color='white')
time_frame.grid(row=1, column=0,sticky= 'nw', padx=300, pady=(15, 5))
# Label for the deliveryd detail
delivery_label = customtkinter.CTkLabel(delivery_frame, text="DELIVERY DETAIL: ($3 Charge)", text_color='black', border = 0, text_font=('Bahnschrift SemiBold', 18))
delivery_label.grid(row =2, column=0,sticky= 'nw', padx=300, pady=(15, 5) )
# mini frame in delivery frame
mini_delivery_frame = customtkinter.CTkFrame(delivery_frame, height=250, width=450, fg_color = 'white')
mini_delivery_frame.grid(row=3, column=0,sticky=N, padx=300, pady=(0,30))
# confirm button
confirm_button = customtkinter.CTkButton(delivery_frame, text = 'Confirm', text_font=('Bahnschrift SemiBold', 20), cursor='hand2', corner_radius = 6, text_color='white', hover_color ='#858585', fg_color= '#9B9B9B', height=40, width=250, state=DISABLED)
confirm_button.grid(row=4, column=0)
# adding entry boxes in the mini_delivery frame
class Entry_generator():
# when the user in not on the entry place
**def back_normal_entry(self, e, *messages_in_entry):
if len(self.entry_box.get()) > 0:
pass
elif len(self.entry_box.get()) == 0:
self.title_of_requirment_outside.grid_forget()
delivery_frame.focus_set()
time_frame.focus_set()
for message in messages_in_entry:
self.entry_box.placeholder_text = message**
# when user entres something in the entry place a function takes place
def entring(self,e):
self.title_of_requirment_outside.grid(row=0, column=0, sticky='W', padx=2)
delivery_frame.bind('<Button-1>', self.back_normal_entry)
#mini_delivery_frame.bind('<Button-1>', self.back_normal_entry)
_**time_frame.bind('<Button-1>', self.back_normal_entry)**_
#confirm_button.bind('<Button-1>', self.back_normal_entry)
def __init__(self, requirment_title, requirment_row, requirment_column, requirement_columnspan, warning_message, outside_title):
self.mini_labelframe_for_each_requirment = LabelFrame(mini_delivery_frame, border=0, bg = 'white')
self.mini_labelframe_for_each_requirment.grid(row = requirment_row, column= requirment_column, pady=8, padx=6, columnspan=requirement_columnspan,sticky='n' )
self.title_of_requirment_outside = Label(self.mini_labelframe_for_each_requirment, text= outside_title, fg='blue', font=('robot', 8), bg= 'white')
self.entry_box = customtkinter.CTkEntry(self.mini_labelframe_for_each_requirment, placeholder_text = requirment_title.title(), corner_radius = 5, fg_color = 'white', border_color='blue', border_width=2, width = 210)
self.entry_box.grid(row=1, column=0, sticky='N')
self.entry_box.bind("<Button-1>", self.entring)
self.warning = Label(self.mini_labelframe_for_each_requirment, text = warning_message, foreground='red', font=('robot',7), background='white', border=0)
# Name entry box and all the error telling detail
name_entry_box = Entry_generator('Full Name (Max 26 Characters)', 0, 0, 1, "Proper Full Name Please.", "Full Name")
name_message_list = ['Full Name (Max 26 Characters)']
name_entry_box.back_normal_entry(Event, *name_message_list)
# Function that take of errors in the name entry box
def errors_telling(e):
# This the lneght error that could happen in the entry box. So the min is -1, and max is 26
length_of_name = len(name_entry_box.entry_box.get())
if length_of_name <= -1:
name_entry_box.title_of_requirment_outside.configure(foreground='red')
elif length_of_name >= 0 and length_of_name < 24:
name_entry_box.title_of_requirment_outside.configure(foreground='blue')
name_entry_box.warning.grid_forget()
elif length_of_name > 25:
name_entry_box.title_of_requirment_outside.configure(foreground='red')
name_entry_box.warning.grid(row=2,column=0, sticky=W, padx=2)
def errors_for_backspace_and_delete(e):
# This the lneght error that could happen in the entry box. So the min is -1, and max is 26
length_of_name = len(name_entry_box.entry_box.get())
if length_of_name <= -1:
name_entry_box.title_of_requirment_outside.configure(foreground='red')
elif length_of_name >= 0 and length_of_name < 27:
name_entry_box.title_of_requirment_outside.configure(foreground='blue')
name_entry_box.warning.grid_forget()
elif length_of_name > 26:
name_entry_box.title_of_requirment_outside.configure(foreground='red')
name_entry_box.warning.grid(row=2,column=0, sticky=W, padx=2)
name_entry_box.entry_box.bind("<Key>", errors_telling)
name_entry_box.entry_box.bind_all('<BackSpace>', errors_for_backspace_and_delete)
name_entry_box.entry_box.bind_all('<Delete>', errors_for_backspace_and_delete)
name_entry_box.entry_box.bind("<Control-c>", lambda _: "break")
name_entry_box.entry_box.bind("<Control-v>", lambda _: "break")
# Unit entry box and all the error telling detail
unit_entry_box = Entry_generator('Unit: Eg 4: (Max 9#)', 0, 1, 1, "Number only please.", "Unit")
unity_message_list = ['Unit: Eg 4: (Max 9#)']
unit_entry_box.back_normal_entry(Event, *unity_message_list)
S0, The words that are in bold is where I am using bind and that where is doesn't work. With simple explaintion if put print "yes" in the binding function it will print is out but before even me clciking on that specific frame. So, could please fix that.
Try this example you will know what I mean.
# All the Moduels
from tkinter import *
from tkinter import ttk
from PIL import ImageTk
import customtkinter
customtkinter.set_appearance_mode('light')
root = Tk()
root.title("Binding")
# with CTK frmae
pickup_frame = customtkinter.CTkFrame(root, corner_radius=0, fg_color ="red")
pickup_frame.place(x=0, y=0, width=400, height=600)
pickup_frame.bind('<Button-1>', lambda e: print('with CKT FRAME'))
# Without CKTFrame
delivery_frame = Frame(root, bg ="white")
delivery_frame.place(x=500, y=0, width=400, height=600)
delivery_frame.bind('<Button-1>', lambda e: print('withOUt CKT FRAME'))
root.mainloop()
You need to format the code like this in your question:
```python
your python code
```
Otherwise it's not readable
just try the code in a python app. Sorry I don't how fromat it like that
Ok, I know what you mean, I never tried to do this. You can do it like this as a quick and dirty fix and it will work:
pickup_frame.canvas.bind('<Button-1>', lambda e: print('with CKT FRAME'))
I will see if I can change this, so that it will work the normal way, but I will not upload a new version in the next days, because there is other work in progress and I want to finish it first.
thank you so much
Also, one more then when you have a button with text it will bind but it is glitch as the edges work but when you click on the text of a button it will not work.
Ok, but why do you want to use .bind() on a button. You can assign a click event with the command attribute.
But I want use bind when the button is disabled.
I have also encountered with the same bug. I wanted to get the value of CTkSlider when changed manually. For that I used .bind('<Button-1>', Click with the CTkSlider but it failed to bind.
from tkinter import *
import customtkinter
def Click(event=None):
print(slider_1.get())
root = customtkinter.CTk()
customtkinter.set_appearance_mode("Dark") # Modes: "System" (standard), "Dark", "Light"
customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
frame_right = customtkinter.CTkFrame(master=root)
frame_right.grid(row=0, column=1, sticky="nswe", padx=20, pady=20)
slider_1 = customtkinter.CTkSlider(master=frame_right, from_=0, to=1)
slider_1.grid(row=0, column=0, columnspan=2, pady=10, padx=20, sticky="we")
slider_1.bind('<Button-1>', Click)
root.mainloop()
Ok, I know what you mean, I never tried to do this. You can do it like this as a quick and dirty fix and it will work:
pickup_frame.canvas.bind('<Button-1>', lambda e: print('with CKT FRAME'))I will see if I can change this, so that it will work the normal way, but I will not upload a new version in the next days, because there is other work in progress and I want to finish it first.
The mention dirty fix works but the widget does not function as it should.
Any update for this issue @TomSchimansky ?
I've been looking at the ctk_slider file and found that the method doesn't work because inside the CTKSlider class, the event occurs in the self.canvas object. I added a new method inside CTKSlider:
def bind(self, seq, callback):
self.canvas.bind(seq, callback)
Then it worked.
I will commit it. ghanteyyy Can you test it ?
PS: And by the way the implementation for this isn't final
Almost forgot I tested using this code:
import sys
from functools import partial
from tkinter import *
import customtkinter
customtkinter.set_appearance_mode("Dark") # Modes: "System" (standard), "Dark", "Light"
customtkinter.set_default_color_theme("sweetkind") # Themes: "blue" (standard), "green", "dark-blue"
def move(event=None):
print("Mov1")
print(slider_1.get())
def move2(event=None):
print("Mov2")
print(slider_1.get())
ctk_app = customtkinter.CTk()
frame = customtkinter.CTkFrame(master=ctk_app)
frame.grid(row=0, column=0, sticky="nswe", padx=20, pady=20)
slider_1 = customtkinter.CTkSlider(master=frame, from_=0, to=1) #, command=move)
slider_1.grid(row=0, column=0, columnspan=2, pady=10, padx=20, sticky="we")
slider_1.bind("<B1-Motion>", move2)
ctk_app.mainloop()
And if we find more widgets with the same behavior, perhaps the same workaround can be used
I've been looking at the ctk_slider file and found that the method doesn't work because inside the CTKSlider class, the event occurs in the self.canvas object. I added a new method inside CTKSlider:
def bind(self, seq, callback): self.canvas.bind(seq, callback)Then it worked.
I will commit it. ghanteyyy Can you test it ?
PS: And by the way the implementation for this isn't final
TL;DR: Same as the dirty fix above mentioned by @TomSchimansky .
It overrides the default binding of the respective widget. In your example the slider does not move when used manually but it did get the value of the slider. The default behavior of the slider is to move the slider where user wants and then get the value of the current position of the slider.
I also tried with other sequences like <Button-1>, <Button-3>, etc but the resulting behavior was the same.
I've been looking at the ctk_slider file and found that the method doesn't work because inside the CTKSlider class, the event occurs in the self.canvas object. I added a new method inside CTKSlider:
def bind(self, seq, callback): self.canvas.bind(seq, callback)Then it worked. I will commit it. ghanteyyy Can you test it ? PS: And by the way the implementation for this isn't final
TL;DR: Same as the dirty fix above mentioned by @TomSchimansky .
It overrides the default binding of the respective widget. In your example the slider does not move when used manually but it did get the value of the slider.
The default behavior of the slider is to move the slider where user wants and then get the value of the current position of the slider.I also tried with other sequences like
<Button-1>, <Button-3>, etcbut the resulting behavior was the same.
You are correct
About CTk Frame: it doesn't have any of these lines, so we can't acces on enter , on leave, ..., (directly), but Slider does:
Slider has these lines:
self.canvas.bind("<Enter>", self.on_enter)
self.canvas.bind("<Leave>", self.on_leave)
self.canvas.bind("<Button-1>", self.clicked)
self.canvas.bind("<B1-Motion>", self.clicked)
With the same line added to slider, I called bind on CTkFrame.
def bind(self, sequence=None, func=None, add=None):
self.canvas.bind(sequence, func, add)
I tested:
from tkinter import *
import tkinter
from PIL import ImageTk
import customtkinter
customtkinter.set_appearance_mode('dark')
def on_click(event=None):
print('on click')
def on_enter(event=None):
print('on enter')
def on_leave(event=None):
print('on leave')
def on_move(event=None):
print('on move')
root = customtkinter.CTk()
root.title("Binding")
# with CTKFrame
pickup_frame = customtkinter.CTkFrame(root, corner_radius=0, fg_color ="red")
pickup_frame.grid(column=0, row=0, sticky=tkinter.W, padx=5, pady=5)
#pickup_frame.bind('<Button-1>', lambda e: print('with CKT FRAME'))
pickup_frame.bind('<Button-1>', on_click)
# Without CKTFrame
delivery_frame = customtkinter.CTkFrame(root, bg ="blue")
delivery_frame.grid(column=1, row=0, sticky=tkinter.E, padx=5, pady=5)
#delivery_frame.bind('<Button-1>', lambda e: print('withOUt CKT FRAME'))
delivery_frame.bind('<Button-1>', on_click)
delivery_frame.bind('<Leave>', on_leave)
delivery_frame.bind('<Enter>', on_enter)
delivery_frame.bind('<B1-Motion>', on_move)
root.mainloop()
The widgets below have, in some extent, access to the bind method for the canvas component:
CtkButton, CTkCheckbox, CTkRadioButton, CTkSlider, CTkSwitch
The ones below doesn't have direct access to bind: CTkEntry, CTkFrame, CTkLabel, CTkProgressBar, To use the bind one needs to do like this: my_widget_instance.canvas.bind() i.e.
@TomSchimansky Is there any update for this issue ?
ghanteyyy you don't need to bind to get your results...... just use the command funtion to do it.....
slider_1 = customtkinter.CTkSlider(master=frame_right, from_=0, to=1) slider_1.grid(row=0, column=0, columnspan=2, pady=10, padx=20, sticky="we",command=click)
felipetesc you can't accomplish binding any labels or frames in CTk because, there is a layer in front of those widgets that prevents your mouse pointer from touching the main widget...... try "bind_all" instead of "bind" and see what I'm talking about......
@Think-IT-think-gui
ghanteyyy you don't need to bind to get your results...... just use the command funtion to do it.....
slider_1 = customtkinter.CTkSlider(master=frame_right, from_=0, to=1) slider_1.grid(row=0, column=0, columnspan=2, pady=10, padx=20, sticky="we",command=click)
So you can use command = ... to make the bind of a label?
ghanteyyy you don't need to bind to get your results...... just use the command funtion to do it.....
slider_1 = customtkinter.CTkSlider(master=frame_right, from_=0, to=1) slider_1.grid(row=0, column=0, columnspan=2, pady=10, padx=20, sticky="we",command=click)
command does not work as expected in customtkinter.CTkSlider
In default Slider widget: command triggers only when user clicks to slider widget but not when its value changes automatically by a function (without user interactions).
In customtkinter Slider widget: command gets trigger when user clicks to slider widget but also when value changes automatically by a function (without user interactions).
Following code demonstrates the problem
from customtkinter import *
def change(event=None):
print('I got change')
def auto_change(event=None):
num = var.get() + 1
var.set(num)
root.after(1000, auto_change)
root = CTk()
var = IntVar()
var.set(1)
slider = CTkSlider(root, from_=0, to=100, variable=var, command=change)
slider.pack(pady=10, padx=10)
auto_change()
root.mainloop()
My Scenario
I am building a music player where I want the slider to update automatically without triggering command function. But I want command function only to trigger when user manually clicks to it like skipping an audio.
so it's basically a music player, then you are going about it all wrong... you have to get the full length of the music and then relate it to the spider's starting point and ending point... Then you will need a function that will calculate the second needed before the spider moves in relation to any music you choose, then since the slider and the music are in phase, you can now make the music follow the slider instead... This will move smoothly since the slider already knows the exact length of the music.. And if you seek(move the slider) the song will also skip to that current time stump...
so it's basically a music player, then you are going about it all wrong... you have to get the full length of the music and then relate it to the spider's starting point and ending point... Then you will need a function that will calculate the second needed before the spider moves in relation to any music you choose, then since the slider and the music are in phase, you can now make the music follow the slider instead... This will move smoothly since the slider already knows the exact length of the music.. And if you seek(move the slider) the song will also skip to that current time stump...
How can you make a slider follow the music instead? I mean how can you determine to which point is user is going to skip audio if the slider is following the music. As per my knowledge, you can make the slider widget to get the position of the skip point and tell the music to play from that skip point.
You will need to get the length of the song, for that you need mutigen it will return the length of a given song in seconds... Then you will need a function that will configure the slider's values to the song length every time you select a new song .... Then we need a loop that will move the slider according to the length of the song, so we divide the song length by 1000 or an absolute number you want.. Then that value will be used as delay time before we move the slider... So the slider will move fast or slow depending on the length of the song.. For the seeking, when the user moves the slider to a point since the slider is calibrated with the length of the song that exact value would be passed to a function that will set the song position to that value...
You will need to get the length of the song, for that you need mutigen it will return the length of a given song in seconds... Then you will need a function that will configure the slider's values to the song length every time you select a new song .... Then we need a loop that will move the slider according to the length of the song, so we divide the song length by 1000 or an absolute number you want.. Then that value will be used as delay time before we move the slider... So the slider will move fast or slow depending on the length of the song.. For the seeking, when the user moves the slider to a point since the slider is calibrated with the length of the song that exact value would be passed to a function that will set the song position to that value...
I have already implemented all the things that you have mentioned since making my music player.
The main problem lies when the slider is moving along with audio i.e. The command function gets called automatically as the CTkSlider updates its value with the audio position.
The code below demonstrates my problem. If you run the below code then you will notice that change function gets called itself when the value of CTkSlider changes each time. But in native ttk.Scale the command, function only gets called when user manually clicks to the Slider widget. Note: Following code is a prototype (kind of) for music player.
Conclusion: There is a bug in CTkSlider that makes command function to call automatically when its values gets changed. @TomSchimansky Please prioritize this issue.
from tkinter import *
import tkinter.ttk as ttk
from tkinter.font import Font
from customtkinter import *
class Native:
def __init__(self):
self.count = 1
self.root = Tk()
self.var = IntVar()
self.lbl_var = StringVar()
self.var.set(1)
self.Slider = ttk.Scale(self.root, from_=0, to=100, variable=self.var, command=self.Change)
self.Slider.pack()
self.ChangeLabel = Label(self.root, textvariable=self.lbl_var, font=Font(size=13, weight='bold'), fg='red')
self.ChangeLabel.pack()
self.AutoChange()
self.Slider.bind('<Button-1>', self.left_click)
self.root.mainloop()
def left_click(self, event=None):
self.Slider.event_generate('<Button-3>', x=event.x, y=event.y)
def Change(self, event=None):
self.lbl_var.set(f'I got change: {self.count}')
self.count += 1
def AutoChange(self, event=None):
num = self.var.get() + 1
self.var.set(num)
self.root.after(1000, self.AutoChange)
class Custom:
def __init__(self):
self.count = 1
self.root = Tk()
self.var = IntVar()
self.lbl_var = StringVar()
self.var.set(1)
self.Slider = CTkSlider(self.root, from_=0, to=100, variable=self.var, command=self.Change)
self.Slider.pack()
self.ChangeLabel = Label(self.root, textvariable=self.lbl_var, font=Font(size=13, weight='bold'), fg='red')
self.ChangeLabel.pack()
self.AutoChange()
self.root.mainloop()
def Change(self, event=None):
self.lbl_var.set(f'I got change: {self.count}')
self.count += 1
def AutoChange(self, event=None):
num = self.var.get() + 1
self.var.set(num)
self.root.after(1000, self.AutoChange)
if __name__ == '__main__':
Native()
Custom()
Oh OK, I know what you mean.. The reason why it call's the change function every time it moves I that, when you add a command to a slider it will be called any time there is a change in position .. So in your change function let it check if the movement is less than a value let's say 1 it should just pass, but when greater it should take that position and set it to the song position... What this will do is that, since the change function will be called every time the slider moves so by default the slider is to be moved a certain distance less than 1 so it will pass and if you move the slider since the value is greater than 1 it will be applied.
Oh OK, I know what you mean.. The reason why it call's the change function every time it moves I that, when you add a command to a slider it will be called any time there is a change in position .. So in your change function let it check if the movement is less than a value let's say 1 it should just pass, but when greater it should take that position and set it to the song position... What this will do is that, since the change function will be called every time the slider moves so by default the slider is to be moved a certain distance less than 1 so it will pass and if you move the slider since the value is greater than 1 it will be applied.
I did not understand what you meant by passing 1. Can you please elaborate it ?
I mean when the slider moves with the music, it moves per 1 pixel..... But when the user seek's, it will be moved more than 1 pixel since 1 pixel is very small... So the function will always check if the movement is less or Equals to 1, then it will do nothing but when its greater than 1 it will pick that value and set the song to that position level..