Tkinter全新自定义标题栏(已解决!!!)

这段时间有空,想着做一个自定义的tkinter的标题栏(原来的标题栏看着普通)

在 Tkinter 中,标题栏的背景颜色是由操作系统控制的,因此无法直接通过 Tkinter 的 API 自定义标题栏的背景颜色。
这意味着你不能直接改变窗口标题栏的颜色、字体或其他样式。

所以

我想做一个可改标题颜色,可放标题背景,可调整标题栏高度等待的一个自定义标题栏

目前,自定义标题栏的解决方法有两种思路:

1.去掉原有标题栏,自己重写。难点在缩小窗口到任务栏。(有些轻微问题,实在解决不了)

app.overrideredirect(True)

2.使用Windows的一些函数来删除标题栏。难点是移动会有黑块、阴影(已解决!!!)

本篇讲第一种,第二种下篇讲。

源码展示:

import ctypes
from ctypes import windll
import tkinter as tk
from tkinter import colorchooser
from PIL import Image, ImageTk  # 导入Pillow库

GWL_EXSTYLE = -20
WS_EX_APPWINDOW = 0x00040000
WS_EX_TOOLWINDOW = 0x00000080

# 创建主窗口
app = tk.Tk()

icon_path='小红书.ico'
title='自定义标题栏'

app.iconbitmap(icon_path)
app.title(title)

# 设置主窗口的背景颜色
app.configure(background='lightblue')  # 设置背景颜色为浅蓝色

#=======================================提高程序清晰度=======================================
# 告诉操作系统使用程序自身的dpi适配
ctypes.windll.shcore.SetProcessDpiAwareness(1)
# 获取屏幕的缩放因子
scale_factor = ctypes.windll.shcore.GetScaleFactorForDevice(0)
# 设置程序缩放
app.tk.call('tk', 'scaling', scale_factor / 75)
#=======================================提高程序清晰度=======================================

# 设置窗口大小
app_width = 800  # 宽度
app_height = 500  # 高度

# 获取屏幕宽度和高度
screen_width = app.winfo_screenwidth()
screen_height = app.winfo_screenheight()

# 计算窗口左上角位置
x = (screen_width - app_width) // 2
y = (screen_height - app_height) // 2

# 设置窗口位置
app.geometry(f"{app_width}x{app_height}+{x+200}+{y}")  # 中心位置

# 初始标题栏背景图片高度
image_height=100

# 标题栏背景图片
image_path="标题栏背景.jpg"

# 初始调整图片大小
def resize_image(width, height,image_path):
    # 加载图片
    background_image666 = Image.open(image_path)  # 替换为你的图片路径
    """根据宽高调整图片大小并返回"""
    background_image = background_image666.resize((width, height), Image.Resampling.LANCZOS)
    return ImageTk.PhotoImage(background_image)

bg_image = resize_image(app_width, image_height,image_path)


background_image666 = Image.open(image_path)  # 替换为你的图片路径
# 获取左上角像素的颜色
bg_color = background_image666.getpixel((100, 100))
bg_color_hex = f'#{bg_color[0]:02x}{bg_color[1]:02x}{bg_color[2]:02x}'  # 转换为十六进制颜色码


# 创建标题栏(这个 高度 由 按钮高度 决定)
title_bar = tk.Frame(app, bg='green', bd=2, highlightbackground='#202020', highlightthickness=0.5)
title_bar.pack(expand=0, fill='x')

# 创建画布,覆盖标题栏
canvas = tk.Canvas(title_bar, width=800,highlightthickness=0, bg='green')
canvas.place(x=0, y=0, relwidth=1, relheight=1)  # 填充整个标题栏


background_label=canvas.create_image(0, 0, anchor="nw", image=bg_image)  # 在画布的左上角绘制背景图片


# 加载 .ico 图标并显示
def   TUBIAO(icon_path,width,height):
    icon_image = Image.open(icon_path)  # 使用 PIL 打开图标
    icon_image = icon_image.resize((width, height))  # 调整图标大小
    return     ImageTk.PhotoImage(icon_image)
icon=TUBIAO(icon_path,50,50)
icon_item = canvas.create_image(10, 5, anchor="nw", image=icon)  # 绘制图标,左上角对齐

icon_path2='设置图标.png'
icon2=TUBIAO(icon_path2,50,50)
sz_item=canvas.create_image(450, 5, anchor="nw", image=icon2)  # 在画布的左上角绘制背景图片

# 显示下拉菜单
def show_dropdown(event):
    # 获取图标的边界框
    bbox = canvas.bbox(sz_item)  # 返回 (x1, y1, x2, y2)
    x = canvas.winfo_rootx() + bbox[0]  # 图标左上角 x 坐标
    y = canvas.winfo_rooty() + bbox[3]  # 图标底部 y 坐标

    # 在图标下方显示下拉菜单
    dropdown_menu.post(x, y)

# 创建下拉菜单
dropdown_menu = tk.Menu(app, tearoff=0)
options = ["设置软件图标", "修改标题", "祝你每天开心"]

def on_option_selected(option):
    print(f"选择了:{option}")
    if   option=='修改标题':
        # 创建一个 Toplevel 窗口
        title_window = tk.Toplevel(app)
        title_window.title("修改标题")
        title_window_width=360
        title_window_height=350
        # 获取屏幕宽度和高度
        screen_width = app.winfo_screenwidth()
        screen_height = app.winfo_screenheight()

        # 计算窗口左上角位置
        x = (screen_width - title_window_width) // 2
        y = (screen_height - title_window_height) // 2

        # 设置窗口位置
        title_window.geometry(f"{title_window_width}x{title_window_height}+{x+200}+{y}")  # 中心位置

        title_window.transient(app)  # 让 Toplevel 窗口显示在主窗口上方
        title_window.grab_set()  # 禁用其他窗口,直到关闭该窗口

        # 创建提示标签
        prompt_label = tk.Label(title_window, text="请输入新的标题:", font=("Arial", 12))
        prompt_label.pack(pady=10)

        # 创建输入框
        title_entry = tk.Entry(title_window, font=("Arial", 12))
        title_entry.pack(pady=5)

        def get_color_name(hex_code):
            color_dict = {
                "#000000": "黑色", "#FFFFFF": "白色", "#FF0000": "红色", "#00FF00": "绿色", "#0000FF": "蓝色",
                "#FFFF00": "黄色", "#00FFFF": "青色", "#FF00FF": "品红色", "#808080": "灰色", "#C0C0C0": "银色",
                "#800000": "栗色", "#808000": "橄榄绿", "#008000": "深绿色", "#800080": "紫色", "#008080": "青绿",
                "#FFA500": "橙色", "#A52A2A": "棕色", "#8B0000": "深红", "#4682B4": "钢蓝", "#708090": "暗灰",
                "#FF4500": "橙红", "#2E8B57": "海洋绿", "#6A5ACD": "岩蓝", "#FF1493": "深粉红", "#4B0082": "靛青",
                "#7FFF00": "黄绿色", "#7B68EE": "中岩蓝", "#FFD700": "金色", "#DC143C": "绯红", "#1E90FF": "道奇蓝",
                "#ADFF2F": "绿黄色", "#F08080": "淡珊瑚", "#90EE90": "淡绿色", "#20B2AA": "浅海蓝", "#6495ED": "矢车菊蓝",
                "#BDB76B": "暗卡其布", "#8FBC8F": "暗海绿", "#9932CC": "深兰花紫", "#FF69B4": "热粉红", "#F4A460": "沙棕",
                "#EEE8AA": "苍金麒麟黄", "#D2691E": "巧克力", "#00CED1": "暗绿松石", "#FFB6C1": "淡粉", "#B0C4DE": "亮钢蓝",
                "#CD5C5C": "印度红", "#B22222": "火砖红", "#2F4F4F": "暗灰蓝绿", "#5F9EA0": "军兰", "#FFDAB9": "桃色",
                "#FF6347": "番茄", "#9ACD32": "黄绿", "#DAA520": "金黄", "#40E0D0": "绿松石", "#E9967A": "深鲑肉",
                "#6B8E23": "橄榄褐", "#8A2BE2": "蓝紫", "#A0522D": "赭色", "#7CFC00": "草绿", "#4682B4": "钢蓝",
                "#FFDEAD": "纳瓦白", "#D8BFD8": "蓟色", "#FFFAFA": "雪白", "#FFFACD": "柠檬绸", "#FFE4E1": "薄雾玫瑰",
            }
            return color_dict.get(hex_code.upper(), "未知颜色")

        # 默认颜色(如果没有选择颜色)
        selected_color = ""

        # 选择颜色的函数
        def choose_color():
            nonlocal selected_color  # 使用非局部变量来更新外部作用域的变量
            color_code = colorchooser.askcolor(title="选择颜色")[1]  # 获取颜色的 HEX 值
            if color_code:  # 如果选择了颜色
                selected_color = color_code  # 更新所选颜色
                color_label.config(text=f"所选标题颜色是: {color_code}——{get_color_name(color_code)}", bg=color_code)  # 更新标签内容和背景色
                print(f"选中的颜色: {color_code} 颜色的名称是: {get_color_name(color_code)}")

        # 创建按钮用于打开颜色选择器
        choose_color_button = tk.Button(title_window, text="选择颜色", font=("Arial", 14), command=choose_color)
        choose_color_button.pack(padx=10,pady=20)
        # 创建标签用于显示所选颜色
        color_label = tk.Label(title_window, text="所选标题颜色是: 无", font=("Arial", 12), bg="white", anchor="w")
        color_label.pack(fill="x", padx=10, pady=5)  # 使标签占满行宽,左对齐




        # 提交按钮的回调函数
        def submit_title():
            new_title = title_entry.get()  # 获取输入框内容
            if new_title:
                # 更新标题文本和颜色
                canvas.itemconfig(title_label, text=new_title)  # 更新标题文本和颜色
                print(f"修改成功! 新标题: {new_title}")
            if selected_color!='':
                canvas.itemconfig(title_label,fill=selected_color)  # 更新标题文本和颜色
                print(f"修改成功! 新标题颜色: {selected_color}")
            title_window.destroy()  # 关闭窗口

        # 取消按钮的回调函数
        def cancel_title():
            print("取消修改标题")
            title_window.destroy()  # 关闭窗口

        # 创建提交和取消按钮
        button_frame = tk.Frame(title_window)
        button_frame.pack(pady=10)

        submit_button = tk.Button(button_frame, text="提交", command=submit_title, width=10)
        submit_button.pack(side="left", padx=5)

        cancel_button = tk.Button(button_frame, text="取消", command=cancel_title, width=10)
        cancel_button.pack(side="left", padx=5)

for option in options:
    dropdown_menu.add_command(label=option, command=lambda opt=option: on_option_selected(opt))

# 为图标绑定鼠标点击事件
canvas.tag_bind(sz_item, "<Button-1>", show_dropdown)

# 添加标题文字到画布
title_label=canvas.create_text(60, 30, text=title, fill="white", font=("KaiTi", 20), anchor="w")


# 通用的 on_enter 函数,接受按钮、文本和背景色
def on_enter(e, button, enter_text, enter_bg_color):
    button.configure(text=enter_text, bg=enter_bg_color)


def on_leave(e, button, leave_text, leave_bg_color):
    button.configure(text=leave_text, bg=leave_bg_color)



# 自定义工具提示类 (ToolTip)
class ToolTip:
    def __init__(self, widget, text, tip_x, tip_y,enter_text,enter_bg_color,leave_text,leave_bg_color):
        self.enter_text=enter_text
        self.enter_bg_color=enter_bg_color
        self.leave_text=leave_text
        self.leave_bg_color=leave_bg_color
        self.tip_x = tip_x
        self.tip_y = tip_y
        self.widget = widget
        self.text = text
        self.tooltip_window = None

        # 绑定鼠标进入和离开事件
        # 使用 lambda 将事件绑定到两个回调
        self.widget.bind('<Enter>', lambda event: self.show_tooltip(event) or on_enter(event, self.widget, self.enter_text, self.enter_bg_color))
        self.widget.bind('<Leave>', lambda event: self.hide_tooltip(event) or on_leave(event, self.widget, self.leave_text, self.leave_bg_color))

    def show_tooltip(self, event=None):
        if self.tooltip_window is not None:
            return

        # 使用实例变量 self.tip_x 和 self.tip_y
        x = self.widget.winfo_rootx() + self.tip_x
        y = self.widget.winfo_rooty() + self.tip_y  # 此处是相对于鼠标的位置

        # 创建工具提示窗口
        self.tooltip_window = tk.Toplevel(self.widget)
        self.tooltip_window.wm_overrideredirect(True)
        self.tooltip_window.wm_geometry(f'+{x}+{y}')  # 设置提示框的位置

        label = tk.Label(self.tooltip_window, text=self.text, background='lightyellow', borderwidth=1, relief='solid')
        label.pack()

    def hide_tooltip(self, event=None):
        if self.tooltip_window is not None:
            self.tooltip_window.destroy()
            self.tooltip_window = None


# 图标 ToolTip 类定义
class ToolTip2:
    def __init__(self, canvas, item, text, tip_x, tip_y):
        self.canvas = canvas
        self.item = item
        self.text = text
        self.tip_x = tip_x
        self.tip_y = tip_y
        self.tooltip_window = None

        # 绑定鼠标进入和离开事件
        self.canvas.tag_bind(self.item, '<Enter>', self.show_tooltip)
        self.canvas.tag_bind(self.item, '<Leave>', self.hide_tooltip)

    def show_tooltip(self, event=None):
        if self.tooltip_window is not None:
            return

        # 计算提示框位置
        x = self.canvas.winfo_rootx() + self.tip_x
        y = self.canvas.winfo_rooty() + self.tip_y

        # 创建工具提示窗口
        self.tooltip_window = tk.Toplevel(self.canvas)
        self.tooltip_window.wm_overrideredirect(True)
        self.tooltip_window.wm_geometry(f'+{x}+{y}')  # 设置提示框的位置

        label = tk.Label(self.tooltip_window, text=self.text, background='lightyellow', borderwidth=1, relief='solid')
        label.pack()

    def hide_tooltip(self, event=None):
        if self.tooltip_window is not None:
            self.tooltip_window.destroy()
            self.tooltip_window = None
# 为图标添加 ToolTip
sz_tooltip = ToolTip2(canvas, sz_item, text="设置", tip_x=470, tip_y=-40)
icon_tooltip = ToolTip2(canvas, icon_item, text="只是软件图标", tip_x=10, tip_y=-40)


# 保持应用在任务栏中
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
    windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, style)
    root.wm_withdraw()
    root.after(10, lambda: root.wm_deiconify())

app.o_flag=False

# 缩小窗口
def minimize_win():
    app.overrideredirect(False)
    app.o_flag = False
    app.state("iconic")

#检查 窗口去了标题栏没
def check():
    if app.state() != "iconic" and app.o_flag == False:
        app.overrideredirect(True)
        app.o_flag = True
        set_appwindow(app)  # 确保显示在任务栏中
    app.after(500, check)   #这种方式对应用程序的性能影响非常小,因为 after() 是异步执行的,不会阻塞主事件循环。

#第一次程序启动调用check()函数  去除标题栏
check()


#全局变量
is_maximized = False  # 用于跟踪窗口是否被最大化

# 放大函数
def maximize_window():
    # 获取屏幕宽高
    screen_width = app.winfo_screenwidth()
    screen_height = app.winfo_screenheight()

    # 切换窗口状态(放大/还原)
    global is_maximized
    if not is_maximized:
        fd_button.configure(text="❐")
        # background_label.configure(bg='lightblue')
        close_button.configure(font=('KaiTi', 20))
        fd_button.configure(font=('KaiTi', 20))
        sx_button.configure(font=('KaiTi', 20))
        fd_tooltip.enter_text='还原'
        fd_tooltip.text='还原窗口'
        canvas.itemconfig(title_label, font=('KaiTi', 30))
        canvas.coords(icon_item, 10, 20)
        canvas.coords(title_label, 60, 45)
        app_width=1600
        image_height=200
        bg_image = resize_image(app_width, image_height,image_path)
        canvas.itemconfig(background_label, image=bg_image)
        canvas.image=bg_image

        app.geometry(f"{screen_width}x{screen_height}+200+100")  # 放大到全屏
        is_maximized = True
    else:
        fd_button.configure(text="□")
        app_width=800
        image_height=100
        bg_image = resize_image(app_width, image_height,image_path)
        canvas.itemconfig(background_label, image=bg_image)

        canvas.coords(icon_item, 10, 5)
        canvas.coords(title_label, 60, 30)
        canvas.image=bg_image

        close_button.configure(width=5,height=2,font='楷体')
        fd_button.configure(width=5,height=2,font='楷体')
        fd_tooltip.enter_text='放大'
        fd_tooltip.text='放大窗口'
        sx_button.configure(width=5,height=2,font='楷体')

        canvas.itemconfig(title_label, font=('KaiTi', 20))
        app.geometry(f"{app_width}x{app_height}+{x+200}+{y}")  # 中心位置
        is_maximized = False

# 关闭按钮
close_button = tk.Button(title_bar, width=5, height=2, text="X", font='楷体', command=app.destroy, bg='#F5F5F5', fg='#000000', highlightthickness=0, bd=0)
close_button.pack(side='right', padx=(11, 11), pady=5)  # padx=(11, 11)——表示按钮两侧的填充间距分别为11px。

# 关闭提示(提示位置:tip_x、tip_y)
close_tooltip = ToolTip(close_button, text="关闭窗口", tip_x=5, tip_y=-35,enter_text="关闭", enter_bg_color="red",leave_text='X',leave_bg_color='#F5F5F5')


#放大按钮
fd_button = tk.Button(title_bar, width=5, height=2, text="□",font='楷体', command=maximize_window, bg='#F5F5F5', fg='#000000', highlightthickness=0, bd=0)
fd_button.pack(side='right', padx=(11, 11), pady=5)

#放大提示(提示位置:tip_x、tip_y)
fd_tooltip = ToolTip(fd_button, text="放大窗口", tip_x=-20, tip_y=-35,enter_text="放大", enter_bg_color="yellow",leave_text='□',leave_bg_color='#F5F5F5')


#缩小按钮
sx_button = tk.Button(title_bar, width=5, height=2, text="—", font='楷体',command=minimize_win, bg='#F5F5F5', fg='#000000', highlightthickness=0, bd=0)
sx_button.pack(side='right', padx=(11, 11), pady=5)

#缩小提示(提示位置:tip_x、tip_y)
sx_tooltip = ToolTip(sx_button,text= "缩小窗口", tip_x=-5, tip_y=-35,enter_text="缩小", enter_bg_color="green",leave_text='—',leave_bg_color='#F5F5F5')


# 拖拽功能实现
def start_drag(event):
    global drag_data
    drag_data = {"x": event.x, "y": event.y}

def do_drag(event):
    global drag_data
    x = app.winfo_x() - drag_data["x"] + event.x
    y = app.winfo_y() - drag_data["y"] + event.y
    app.geometry(f"+{x}+{y}")

# 将控件放入列表中
widgets_to_bind = [canvas, title_bar]

# 为每个控件绑定鼠标左键点击事件
for widget in widgets_to_bind:
    widget.bind("<Button-1>", start_drag)  # 按下鼠标左键开始拖拽
    widget.bind("<B1-Motion>", do_drag)  # 鼠标左键移动时拖拽

# 设置边框(使用highlight参数)
frame = tk.Frame(app, highlightbackground='#202020', highlightthickness=0.5,bg='lightblue')
frame.pack(fill=tk.BOTH, expand=True, padx=0, pady=(0, 0))  # 设定下边距为0,上边距为0



#首次出现在任务栏
app.after(10, lambda: set_appwindow(app))
# 运行应用程序
app.mainloop()

演示gif

蓝奏云链接是:

Tkinter自定义标题栏1.zip - 蓝奏云文件大小:284.8 K|icon-default.png?t=O83Ahttps://wwbh.lanzn.com/ii52S2gduqbe下载解压!!!

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是神哥

谢谢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值