CustomTkinter
CustomTkinter copied to clipboard
Added 2 new widgets (dividers)
I have added 2 new widgets - Horizontal and vertical dividers They are loosely based on the the CTk Frame widget, except some changes:
- width and height are called thickness and length
- radius is half of thickness for full rounded corners
- no border or border color
- no child widget allowed in the frame
image while testing:

I kindly request the maintainers to consider this pull request for addition of divider widgets.
Dividers are very simple and effective element of a powerful user interface.
Cool ! Can you consider adding some padding.
I don't understand how padding will work for multiple sides. Could you give some logic insight?
to be like this:

@felipetesc Update: I have been working on it recently, and much of the "padding" functionality has been implemented, but there one single problem: rounded corners on divider are lost.
What seems to be causing the problem?
screenshot:


here is code used to draw this window:
from ctypes import windll
from win32api import GetMonitorInfo, MonitorFromPoint
import mycustomtkinter as ctk
###############################################
################# set scaling #################
###############################################
screensize_old = windll.user32.GetSystemMetrics(0)
windll.shcore.SetProcessDpiAwareness(1)
screensize_new = windll.user32.GetSystemMetrics(0)
work_area = GetMonitorInfo(MonitorFromPoint((0, 0))).get("Work")
screen_w, screen_h = work_area[2], work_area[3]
scale = round(screensize_old / screensize_new, 2)
ctk.set_window_scaling(scale)
ctk.set_spacing_scaling(scale)
ctk.set_widget_scaling(scale)
###############################################
###############################################
root = ctk.CTk()
root.title("VerticalDivider")
root.geometry("300x300")
root.resizable(False, False)
root.bg_color = "#323232"
div = ctk.CTkVerticalDivider(root,
bg_color='#000000',
fg_color="#ff0000",
length=120,
thickness=8,
pady=70,
padx=(80, 40))
div.pack()
root.mainloop()
Update:
Rounded corners problem is solved
Question:
Is there a need to implement configure method for padding?
In my opinion, there is no such need, Just some polishing and it will be completed
Just to let you know, so you are not left in the dark. I'm trying to add padding, or border width to the divider.
I didn't post my observations and findings, which now seems unwise since we could have figured it out together. Yet, I'm debugging your code to understand what can be done. I found some issues, which are not directly related to your widget, but mostly about how canvas draw through the class DrawEngine the divider.
Perhaps borderwidth is a better word for padding, since ctk already use it for the same purpose.
I'll start posting my findings with the objective of make your widgets to be added to ctk.
configure: if you are asking my opinion, yes, I think it should be added a if clause to check if the border_width was passed as a string paremeter to the overriden configure method. So, just like any other ui element, dividers can be changed on the fly. If one day someone decides to create a gui editor for ctk we can simply pass another value and Voilà the gui shows another theme for the available widgets
Ok, I will post two sample codes, the first one for a changed CTkHorizontalDivider (we can run without your cloned ctk), and the second one contains a test:
Before looking the code though, a small explanation about its changes can save some time and help, since the changes are so small and can be easily missed):
- I added a new constructor param: border_width = 0
- Inside the the constructor I created a new class property: self.border_width = border_width
- I added inside configure method a new verification for border_width:
if "border_width" in kwargs:
self.border_width = kwargs["border_width"]
require_redraw = True
del kwargs["border_width"]
Modified CTkHorizontalDivider(it's your class):
import customtkinter
from customtkinter import CTkCanvas
from customtkinter import ThemeManager
from customtkinter import DrawEngine
from customtkinter import CTkBaseClass
class CTkHorizontalDivider(CTkBaseClass):
def __init__(self, *args,
bg_color=None,
fg_color="default_theme",
length=160,
thickness=8,
border_width = 0,
overwrite_preferred_drawing_method: str = None,
**kwargs):
# transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=length, height=thickness, border_width=0, **kwargs)
self.border_width = border_width
# determine fg_color
if fg_color == "default_theme":
if isinstance(self.master, CTkHorizontalDivider):
if self.master.fg_color == ThemeManager.theme["color"]["frame_low"]:
self.fg_color = ThemeManager.theme["color"]["frame_high"]
else:
self.fg_color = ThemeManager.theme["color"]["frame_low"]
else:
self.fg_color = ThemeManager.theme["color"]["frame_low"]
else:
self.fg_color = fg_color
# shape
self.corner_radius = corner_radius = thickness / 2
self.canvas = CTkCanvas(master=self,
highlightthickness=0,
width=self.apply_widget_scaling(self._current_width),
height=self.apply_widget_scaling(self._current_height))
self.canvas.place(x=0, y=0, relwidth=1, relheight=1)
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.draw_engine = DrawEngine(self.canvas)
self._overwrite_preferred_drawing_method = overwrite_preferred_drawing_method
self.bind('<Configure>', self.update_dimensions_event)
self.draw()
def set_scaling(self, *args, **kwargs):
super().set_scaling(*args, **kwargs)
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self._desired_height))
self.draw()
def set_dimensions(self, width=None, height=None):
super().set_dimensions(width, height)
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width),
height=self.apply_widget_scaling(self._desired_height))
self.draw()
def draw(self, no_color_updates=False):
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(
self.apply_widget_scaling(self._current_width),
self.apply_widget_scaling(self._current_height),
self.apply_widget_scaling(self.corner_radius),
self.apply_widget_scaling(self.border_width),
overwrite_preferred_drawing_method=self._overwrite_preferred_drawing_method)
if no_color_updates is False or requires_recoloring:
if self.fg_color is None:
self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.bg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.bg_color, self._appearance_mode))
else:
self.canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self.fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self.fg_color, self._appearance_mode))
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
self.canvas.tag_lower("inner_parts")
self.canvas.tag_lower("border_parts")
def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end
if "border_width" in kwargs:
self.border_width = kwargs["border_width"]
require_redraw = True
del kwargs["border_width"]
if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"]
require_redraw = True
del kwargs["fg_color"]
if "bg_color" in kwargs:
if kwargs["bg_color"] is None:
self.bg_color = self.detect_color_of_master()
else:
self.bg_color = kwargs["bg_color"]
require_redraw = True
del kwargs["bg_color"]
if "length" in kwargs:
self.set_dimensions(width=kwargs["length"])
del kwargs["length"]
if "thickness" in kwargs:
self.set_dimensions(height=kwargs["thickness"])
del kwargs["thickness"]
super().configure(*args, **kwargs)
if require_redraw:
self.draw()
Sample test:
import customtkinter
from customtkinter import CTkCanvas
from customtkinter import ThemeManager
from customtkinter import DrawEngine
from customtkinter import CTkBaseClass
from ctk_vdiv import CTkVerticalDivider
from ctk_hdiv import CTkHorizontalDivider
DARK_MODE = "dark"
customtkinter.set_appearance_mode(DARK_MODE)
customtkinter.set_default_color_theme("blue")
root = customtkinter.CTk()
root.title("Any divider with padding")
root.geometry("800x600")
root.resizable(True, True)
root.bg_color = "#323232"
frame = customtkinter.CTkFrame(root, corner_radius=10, width = 800 , height=600 )
frame.pack()
v_div = CTkVerticalDivider(frame, border_width=4,bg_color="red", fg_color="default_theme", length=380, thickness=40)
v_div.pack()
root.mainloop()
I believe that the problem I am finding is related how radius calculation happens as you can see on the image below:

I'm guessing this is the source of the calculation problem, but I wanted to know what do you think: self.corner_radius = corner_radius = thickness / 2
maybe:
if self.boder_width != 0:
self.corner_radius = corner_radius = (thickness / 2) - (self.boder_width*2)
else:
self.corner_radius = corner_radius = (thickness / 2)
The border_width parameter has been removed bacause a divider is just a solid, flat rounded rectangle. It only has 3 major parameter values, divided into 2 each:
- Color (bg, fg)
- dimensions (length, thickness)
- padding (x, y)
i used padx and pady insted, just as in native tkinter widget
(instead of passing these values through pack, they are passed when initializing the widget)
example:
import customtkinter as ctk
root = ctk.CTk()
root.title("VerticalDivider")
root.geometry("300x300")
root.resizable(False, False)
div = ctk.CTkVerticalDivider(root, fg_color="#eeeeee", bg_color='#00bbaa', length=150, thickness=10, pady=(50, 10), padx=40)
div.pack()
root.mainloop()
output:

because padx is passed as an integer, it is taken as same from both sides (40 pixels), and pady is a tuple with 50 pixls above and 10 pixels below reserved for padding
I'm guessing this is the source of the calculation problem, but I wanted to know what do you think: self.corner_radius = corner_radius = thickness / 2
@felipetesc I believe it can be said that this problem is half-solved, that it works after initializing the divider but configure method is still buggy. The problem was that divider size was being redirected to whole widget size including padding dimensions, so only top left rounded corner was visible while other corners were not visible. Now that is solved, and i will commit current code soon, for further debugging. There is no problem with the corner_radius value.
corner_radius
The corner_radius issue it is not related to your code, but it is related to my implementation, which would need to changed, but it won't because your padx and pady solution is many times better
The
border_widthparameter has been removed bacause a divider is just a solid, flat rounded rectangle. It only has 3 major parameter values, divided into 2 each:
- Color (bg, fg)
- dimensions (length, thickness)
- padding (x, y)
i used
padxandpadyinsted, just as in native tkinter widget (instead of passing these values through pack, they are passed when initializing the widget)example:
import customtkinter as ctk root = ctk.CTk() root.title("VerticalDivider") root.geometry("300x300") root.resizable(False, False) div = ctk.CTkVerticalDivider(root, fg_color="#eeeeee", bg_color='#00bbaa', length=150, thickness=10, pady=(50, 10), padx=40) div.pack() root.mainloop()output:
because padx is passed as an integer, it is taken as same from both sides (40 pixels), and pady is a tuple with 50 pixls above and 10 pixels below reserved for padding
After seeing the result image, your dividers seems quite nice ! The use of padx and pady it's the best idea to add spaces. I'll certainly use them several times .
corner_radius
I will test configure
@IshanJ25 How can I use the dividers? Does it require a pip installation?
@IshanJ25 How can I use the dividers? Does it require a pip installation?
@DominicCulyer no, this is just an idea for new widgets, not an external package. for now you can use frame widget to show a "divider" in your GUI, I have been hardly able to give this any attention in past months due to some reasons. I will surely get back to it and try the proper implementations of these widgets as soon as I am able.