[Python]用tkinter的Canvas重绘Scale

import tkinter as tk


class IkScale(tk.Canvas):
    def __init__(
            self, master,
            variable: tk.IntVar | tk.DoubleVar,
            from_=0, to=100, precision: int = 0,
            length: int = 150, height: int = 5,
            scale_width: int = 15, scale_height: int = 20,
            show_value: bool = True, font: tuple = (None, 10), fg: str = "black", padx: int = 10,
            line_bg: str = "white", select_bg: str = "red",
            rect_bg: str = "#CDCDCD", focus_bg: str = "#A6A6A6", click_bg: str = "#606060",
            wheel_step: int | float = 1,
            command=None,
    ):
        """
        :param master: 父窗口
        :param variable: 绑定变量
        :param from_: 起始值
        :param to:  结束值
        :param precision: 精度,小数点后几位
        :param length: 直线长度
        :param height: 直线高度
        :param scale_width: 滑块宽度
        :param scale_height: 滑块高度,且决定画布的高度
        :param show_value: 是否显示值
        :param font: 字体,show_value=True时生效
        :param fg: 字体颜色,show_value=True时生效
        :param padx: 文字与直线的距离,show_value=True时生效
        :param line_bg: 直线背景色
        :param select_bg: 经过的背景色
        :param rect_bg: 滑块背景色
        :param focus_bg: 焦距背景色
        :param click_bg: 点击背景色
        :param wheel_step: 鼠标滚动加减的步长
        :param command: 绑定事件
        """
        super().__init__(master)
        self.config(height=scale_height, bg=master["bg"], highlightthickness=0)
        if show_value:
            text_length = max(len(str(from_)), len(str(to))) + precision  # 文本长度
            self.config(width=length + font[1] * text_length + padx)
        else: self.config(width=length)
        # 参数
        self.variable = variable
        self.length, self.height = length, height
        self.scale_width, self.scale_height = scale_width, scale_height
        self.show_value = show_value
        self.line_bg, self.select_bg, self.rect_bg = line_bg, select_bg, rect_bg
        self.focus_bg, self.click_bg = focus_bg, click_bg
        self.from_, self.to = from_, to
        self.precision = precision
        self.wheel_step = wheel_step
        self.command = command
        # 变量
        self.InCanvas = False   # 是否在画布上
        self.Click = False      # 是否点击
        self.InRect = False     # 是否在矩形区域
        # 滑块参数
        self.rectangle_line_height_range = (
                (self.scale_height - self.height) // 2,
                (self.scale_height - self.height) // 2 + self.height
        )  # 直线高度范围
        value_get = self.variable.get()
        pos_x = (value_get - self.from_) / (self.to - self.from_) * (self.length - self.scale_width)
        # 直线
        self.create_rectangle(
            0, self.rectangle_line_height_range[0], self.length, self.rectangle_line_height_range[1],
            fill=self.line_bg, width=0
        )
        # 选中区域
        self.rectagnle_selection = self.create_rectangle(
            0, self.rectangle_line_height_range[0], pos_x, self.rectangle_line_height_range[1],
            fill=self.select_bg, width=0
        )
        # 滑块
        self.rectangle_scale = self.create_rectangle(
            pos_x, 0, self.scale_width + pos_x, self.scale_height,
            fill=self.rect_bg, width=0
        )
        # 显示值
        if show_value:
            self.text_value = self.create_text(
                self.length + padx, self.scale_height // 2, anchor="w",
                text=f"{value_get:.{self.precision}f}", font=font, fill=fg,
            )
        # 绑定事件
        self.bind("<Enter>", self.__enter)  # 鼠标进入
        self.bind("<Motion>", self.__move)  # 鼠标移动
        self.bind("<Leave>", self.__leave)  # 鼠标离开
        self.bind("<Button-1>", self.__click)  # 鼠标点击
        self.bind("<ButtonRelease-1>", self.__release)  # 鼠标释放
        self.bind("<MouseWheel>", self.__wheel)  # 鼠标滚动
        self.bind("<Destroy>", self.__destroy)  # 销毁
        # 绑定variable是否发生变化
        self.fun_value = self.variable.trace_add("write", self.__bind_value)

    # 关闭
    def __destroy(self, event):
        self.unbind("<Enter>")
        self.unbind("<Motion>")
        self.unbind("<Leave>")
        self.unbind("<Button-1>")
        self.unbind("<ButtonRelease-1>")
        self.unbind("<MouseWheel>")
        self.variable.trace_remove("write", self.fun_value)
        self.unbind("<Destroy>")

    # 鼠标进入
    def __enter(self, event):
        self.InCanvas = True

    # 鼠标移动
    def __move(self, event):
        if not self.InCanvas: return  # 鼠标未进入画布
        if self.Click:  self.__update(event.x)
        else:
            pos_1, pos_2, pos_3, pos_4 = self.coords(self.rectangle_scale)
            if pos_1 <= event.x <= pos_3 and pos_2 <= event.y <= pos_4:
                self.InRect = True
                self.itemconfig(self.rectangle_scale, fill=self.focus_bg)
            else:
                self.InRect = False
                self.itemconfig(self.rectangle_scale, fill=self.rect_bg)

    # 鼠标离开
    def __leave(self, event):
        if not self.Click:
            self.InRect = False
            self.InCanvas = False
            self.itemconfig(self.rectangle_scale, fill=self.rect_bg)

    # 鼠标点击
    def __click(self, event):
        self.Click = True
        if self.InRect:
            self.itemconfig(self.rectangle_scale, fill=self.click_bg)
            self.config(cursor="sb_h_double_arrow")
        else:
            if self.InCanvas:
                self.__update(event.x)

    # 鼠标释放
    def __release(self, event):
        if self.Click:
            self.Click = False
            self.itemconfig(self.rectangle_scale, fill=self.focus_bg)
            self.config(cursor="")

    # 鼠标滚动
    def __wheel(self, event):
        if not self.InCanvas: return
        value = self.variable.get()
        if event.delta > 0:   # 向上滚动
            value += self.wheel_step
        else:   # 向下滚动
            value -= self.wheel_step
        pos_x = (value - self.from_) / (self.to - self.from_) * (self.length - self.scale_width)
        self.__update(pos_x)

    # 绑定variable是否变化
    def __bind_value(self, *args):
        value = self.variable.get()
        pos_x = (value - self.from_) / (self.to - self.from_) * (self.length - self.scale_width)
        self.__update(pos_x)

    # 更新滑块与选中区域
    def __update(self, pos_x):
        pos_x = max(0, min(self.length - self.scale_width, pos_x))  # 限制滑块位置在直线内
        self.coords(
            self.rectangle_scale,
            pos_x, 0,
            pos_x + self.scale_width, self.scale_height
        )
        self.coords(
            self.rectagnle_selection,
            0, self.rectangle_line_height_range[0],
            pos_x, self.rectangle_line_height_range[1]
        )
        Percen = (pos_x / (self.length - self.scale_width))
        value = round(self.from_ + (self.to - self.from_) * Percen, self.precision)  # 计算滑块位置对应的数值
        if self.precision == 0:   # 整数
            value = int(value)
        self.variable.set(value)# 计算滑块位置对应的数值
        self.variable.set(value)
        if self.show_value:
            # 改变显示值
            self.itemconfig(
                self.text_value,
                text=f"{self.variable.get():.{self.precision}f}",
            )
        if self.command is not None: self.command()

继承了Canvas,所以可当Canvas使用

运行代码:

root = tk.Tk()
root.geometry("300x160")
var = tk.DoubleVar(value=150)
scale1 = InkWnScale(root, variable=var, from_=0, to=255, precision=1, show_value=True)
scale1.pack(side="top", pady=10)
scale2 = tk.Scale(root, variable=var, from_=0, to=255, orient=tk.HORIZONTAL, length=150,)
scale2.pack(side="top", pady=10)
scale3 = ttk.Scale(root, variable=var, from_=0, to=255, orient=tk.HORIZONTAL, length=150,)
scale3.pack(side="top", pady=10)
root.mainloop()


2024年11月2日:

1.修复了有多个控件时全局绑定导致的问题

2.修复了销毁控件时调用别的控件时报错的问题

3.修复了精度为0时设置variable时不为int的问题

2024年11月9日:

1.修复了精度为0时值卡在28与29之间的问题

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值