最近翻到一台win7 32位电脑,闲着没事儿就想美化一下它的界面,拿tkinter写一些桌面小工具.本来想用C直接调用windows api写的,但是看了看那一大堆粗犷的代码,我就是想写个小工具而已好吧......又看了看gtk--不直接支持win7了......还是用tkinter吧!
但是发现tkinter原生支持真的有点烂,就混用了部分windows api,正好发现了一点小坑,就来水一片文章吧!
OS: win7 32bit Python: 3.8
先上代码:
from tkinter import *
from tkinter.ttk import *
import ctypes
winuser = ctypes.windll.user32 # 这里的方法似乎与 winuser.h 类似
class TkDesktopTool(Tk):
"""
一个基于Tk的windows桌面小工具窗口类.
所有参数与方法均与Tk类相同,
本类仅实现了一些纯Tk难以实现的功能.
"""
def __init__(self, screenName=None, baseName=None, className="Tk", useTk=1):
super().__init__(screenName, baseName, className, useTk)
# 设置窗口为无边框
self.overrideredirect(True)
# 将窗口设置为桌面的子窗口
def set_TkTool_on_desktop():
winuser.SetParent(
self.winfo_id(), winuser.FindWindowW(ctypes.c_wchar_p("Progman"), None)
)
self.wm_withdraw()
self.after(10, lambda: self.wm_deiconify())
self.after(10, lambda: set_TkTool_on_desktop())
self.tk_move()
def tk_move(self):
"""无边框窗体拖动实现"""
mous_x, mous_y = None, None
def mouse_down(event):
nonlocal mous_x, mous_y
mous_x, mous_y = event.x, event.y
def mouse_move(event):
winuser.MoveWindow(
self.winfo_id(),
event.x_root - mous_x,
event.y_root - mous_y,
self.winfo_width(),
self.winfo_height(),
True,
)
self.bind("<Button-1>", mouse_down)
self.bind("<B1-Motion>", mouse_move)
接下来是解析:
先说无边框窗口拖动部分:
def tk_move(self):
"""无边框窗体拖动实现"""
mous_x, mous_y = None, None
def mouse_down(event):
nonlocal mous_x, mous_y
mous_x, mous_y = event.x, event.y
def mouse_move(event):
winuser.MoveWindow(
self.winfo_id(),
event.x_root - mous_x,
event.y_root - mous_y,
self.winfo_width(),
self.winfo_height(),
True,
)
self.bind("<Button-1>", mouse_down)
self.bind("<B1-Motion>", mouse_move)
核心在于其中调用的windows api函数MoveWindow
,下面是其在C语言中的定义:
BOOL MoveWindow(
[in] HWND hWnd,
[in] int X,
[in] int Y,
[in] int nWidth,
[in] int nHeight,
[in] BOOL bRepaint
);
其中hWnd
是窗口句柄,其实就是窗口的指针,tkinter实例的句柄使用.winfo_id()
方法获取.X
和Y
就是新的位置了,在往下的两个参数则是新的大小,我们填入原来的窗口大小就OK了.最后的布尔值bRepaint
则代表是否重新绘制窗口,我们设置为True
就是发送一个重绘的消息.
另一部分难懂的代码是__init__
方法中的将窗口设置为桌面的子窗口:
def set_TkTool_on_desktop():
winuser.SetParent(
self.winfo_id(), winuser.FindWindowW(ctypes.c_wchar_p("Progman"), None)
)
self.wm_withdraw()
self.after(10, lambda: self.wm_deiconify())
self.after(10, lambda: set_TkTool_on_desktop())
tkinter的.after
方法是在程序循环中延迟一定毫秒执行函数,windows api的SetParent
就是将第一个参数(一个句柄)对应的窗口设置为第二个参数(也是句柄)对应的窗口的子窗口FindWindowW
则是根据类名和标题(标题设置为空就会匹配所有符合类名的窗口)查找一个顶层窗口并返回其句柄,使用ctypes的c_wchat_p
转换一下正确的数据类型.而tkinter的.wm_withdraw()
和self.wm_deiconify()
整体实现了窗口的重绘,我也不清楚为什么必须这样做才能正常显示,反正能跑就行.
另外需要注意的是,如果你使用有边框窗口 这部分实在不想写了,真的不要太恶心,能看到这里的还是自己翻windows api文档吧,挺详细的.
最后,顺带挖个坑,过几天更新一个完美的tkinnter无边框窗口(还是用api,但是我发现了一些被一大堆csdn水文遮盖住的好文章,找到一些好用且不太好找的api)
为啥在我的电脑上没有任何输出?