无法使用 win32gui.DrawText 更新 Window 中的文本

Can't update text in Window with win32gui.DrawText

我正在尝试使用 Windows 中的 Python 设置非滚动文本输出 window。

我发现了一些使用 win32gui.DrawText 将文本放置在 window 中的代码示例,但其中 none 在初始文本就位后添加或更改文本。

我在文档中没有看到任何内容表明 window 文本在调用 DrawText 后已修复,但我无法在绘制 window 后进行任何更改第一次。

我缺少什么可以让我更新 windows 文本?

这是 Christophe Keller 的 "Hello World" 示例,我修改它以在 1 秒后更改 window 文本。 (我添加的代码不起作用。)

import win32api
import win32con
import win32gui
import time

#Code example modified from:
#Christophe Keller
#Hello World in Python using Win32

# New code: Define global
g_str_Text = 'Hello send by Python via Win32!'

def main():
    #get instance handle
    hInstance = win32api.GetModuleHandle()

    # the class name
    className = 'SimpleWin32'

    # create and initialize window class
    wndClass                = win32gui.WNDCLASS()
    wndClass.style          = win32con.CS_HREDRAW | win32con.CS_VREDRAW
    wndClass.lpfnWndProc    = wndProc
    wndClass.hInstance      = hInstance
    wndClass.hIcon          = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
    wndClass.hCursor        = win32gui.LoadCursor(0, win32con.IDC_ARROW)
    wndClass.hbrBackground  = win32gui.GetStockObject(win32con.WHITE_BRUSH)
    wndClass.lpszClassName  = className

    # register window class
    wndClassAtom = None
    try:
        wndClassAtom = win32gui.RegisterClass(wndClass)
    except Exception as e:
        print (e)
        raise e

    hWindow = win32gui.CreateWindow(
        wndClassAtom,                   #it seems message dispatching only works with the atom, not the class name
        'Python Win32 Window',
        win32con.WS_OVERLAPPEDWINDOW,
        win32con.CW_USEDEFAULT,
        win32con.CW_USEDEFAULT,
        win32con.CW_USEDEFAULT,
        win32con.CW_USEDEFAULT,
        0,
        0,
        hInstance,
        None)

    # Show & update the window
    win32gui.ShowWindow(hWindow, win32con.SW_SHOWNORMAL)
    win32gui.UpdateWindow(hWindow)

    # Dispatch messages
    win32gui.PumpMessages()

# New code: Attempt to change the text 1 second later
    time.sleep(1.0)
    g_str_Text = 'Something new'
    win32gui.ShowWindow(hWindow, win32con.SW_SHOWNORMAL)
    win32gui.UpdateWindow(hWindow)
    win32gui.PumpMessages()

def wndProc(hWnd, message, wParam, lParam):

    if message == win32con.WM_PAINT:
        hDC, paintStruct = win32gui.BeginPaint(hWnd)

        rect = win32gui.GetClientRect(hWnd)
        win32gui.DrawText(
            hDC,
            g_str_Text,
            -1,
            rect,
            win32con.DT_SINGLELINE | win32con.DT_CENTER | win32con.DT_VCENTER)

        win32gui.EndPaint(hWnd, paintStruct)
        return 0

    elif message == win32con.WM_DESTROY:
        print ('Being destroyed')
        win32gui.PostQuitMessage(0)
        return 0

    else:
        return win32gui.DefWindowProc(hWnd, message, wParam, lParam)

if __name__ == '__main__':
    main()

您的代码有 3 个问题:

  1. [ActiveState.Docs]: win32gui.PumpMessages 阻塞 直到 window 收到 WM_QUIT,这意味着代码它后面的(本来应该重绘的)只会在 你关闭 window 之后执行,并且还会产生异常,因为那时 hWindow 句柄将无效。请注意,此问题还阻止了代码 运行 进入下一个
  2. win32gui.ShowWindowwin32gui.UpdateWindow 都不会触发 window 重绘,因为从 window 的角度来看,没有变化(没有 window 区域无效)。强制失效的典型用户操作是 window 调整大小(包括最小化/最大化)。请注意,移动 window 也会 而不是 重绘它
  3. g_str_Text = 'Something new' - 这并不像您认为的那样,它是 Python 陷阱 的(变体):它将创建一个new g_str_Textlocal 改为 main,而不是修改 global wndProc 用来绘制 window

要解决这些问题,重绘 window 的代码应该从新的 线程 执行。为此:

  • 它被放置在一个新函数中(customDraw)
  • 线程在之前win32gui.PumpMessages启动,休眠一秒然后改变windowText值(注意 全局)。
  • 它通过 [ActiveState.Docs]: win32gui.RedrawWindow (which is a wrapper over [MS.Docs]: RedrawWindow function 强制 window 重绘):
    • 向 window 发出其客户区已失效的信号 (win32con.RDW_INVALIDATE)
    • 它还表示应该擦除客户区 (win32con.RDW_ERASE),以避免在旧文本上绘制新文本
    • 发送 WM_PAINT 消息到 window

这是经过上述更改的脚本 - 请注意,我还做了一些其他小改动(例如,将 g_str_Text 重命名为 window Text) 只有一个命名约定 - camelCaseBTW,是 not Python推荐一个):

#!/usr/bin/env python3

import win32api
import win32con
import win32gui
import time
import threading

#Code example modified from:
#Christophe Keller
#Hello World in Python using Win32

# New code: Define global
windowText = 'Hello send by Python via Win32!'

def main():
    #get instance handle
    hInstance = win32api.GetModuleHandle()

    # the class name
    className = 'SimpleWin32'

    # create and initialize window class
    wndClass                = win32gui.WNDCLASS()
    wndClass.style          = win32con.CS_HREDRAW | win32con.CS_VREDRAW
    wndClass.lpfnWndProc    = wndProc
    wndClass.hInstance      = hInstance
    wndClass.hIcon          = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
    wndClass.hCursor        = win32gui.LoadCursor(0, win32con.IDC_ARROW)
    wndClass.hbrBackground  = win32gui.GetStockObject(win32con.WHITE_BRUSH)
    wndClass.lpszClassName  = className

    # register window class
    wndClassAtom = None
    try:
        wndClassAtom = win32gui.RegisterClass(wndClass)
    except Exception as e:
        print (e)
        raise e

    hWindow = win32gui.CreateWindow(
        wndClassAtom,                   #it seems message dispatching only works with the atom, not the class name
        'Python Win32 Window',
        win32con.WS_OVERLAPPEDWINDOW,
        win32con.CW_USEDEFAULT,
        win32con.CW_USEDEFAULT,
        win32con.CW_USEDEFAULT,
        win32con.CW_USEDEFAULT,
        0,
        0,
        hInstance,
        None)

    # Show & update the window
    win32gui.ShowWindow(hWindow, win32con.SW_SHOWNORMAL)
    win32gui.UpdateWindow(hWindow)

    # New code: Create and start the thread
    thr = threading.Thread(target=customDraw, args=(hWindow,))
    thr.setDaemon(False)
    thr.start()

    # Dispatch messages
    win32gui.PumpMessages()


# New code: Attempt to change the text 1 second later
def customDraw(hWindow):
    global windowText
    time.sleep(1.0)
    windowText = 'Something new'
    win32gui.RedrawWindow(hWindow, None, None, win32con.RDW_INVALIDATE | win32con.RDW_ERASE)


def wndProc(hWnd, message, wParam, lParam):

    if message == win32con.WM_PAINT:
        hDC, paintStruct = win32gui.BeginPaint(hWnd)

        rect = win32gui.GetClientRect(hWnd)
        win32gui.DrawText(
            hDC,
            windowText,
            -1,
            rect,
            win32con.DT_SINGLELINE | win32con.DT_CENTER | win32con.DT_VCENTER)

        win32gui.EndPaint(hWnd, paintStruct)
        return 0

    elif message == win32con.WM_DESTROY:
        print('Being destroyed')
        win32gui.PostQuitMessage(0)
        return 0

    else:
        return win32gui.DefWindowProc(hWnd, message, wParam, lParam)

if __name__ == '__main__':
    main()