[Python]代码片段分享:基于tkinter的桌面小工具(永久置底+永久显示)

最近翻到一台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()方法获取.XY就是新的位置了,在往下的两个参数则是新的大小,我们填入原来的窗口大小就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)

140 views
Comments
登录后评论
Sign In
·

为啥在我的电脑上没有任何输出?