这段时间有空,想着做一个自定义的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|https://wwbh.lanzn.com/ii52S2gduqbe下载解压!!!