如何使用Python在屏幕上绘制一个空矩形

Jos*_*rro 6 python drawing updating pywin32 win32gui

我不是专家,我试图在屏幕上显示一个矩形,该矩形从固定起点开始跟随鼠标移动,就像您在文字或绘画中选择某些内容时一样。我带着这个代码:

import win32gui
m=win32gui.GetCursorPos()
while True:
    n=win32gui.GetCursorPos()
    for i in range(n[0]-m[0]):
        win32gui.SetPixel(dc, m[0]+i, m[1], 0)
        win32gui.SetPixel(dc, m[0]+i, n[1], 0)
    for i in range(n[1]-m[1]):
        win32gui.SetPixel(dc, m[0], m[1]+i, 0)
        win32gui.SetPixel(dc, n[0], m[1]+i, 0)
Run Code Online (Sandbox Code Playgroud)

如您所见,代码将绘制矩形,但之前的矩形将保留,直到屏幕更新。

我唯一的解决方案是在将它们设置为黑色之前获取我要绘制的像素值,然后每次都重新绘制它们,但这使我的代码非常慢。有没有一种简单的方法可以更快地更新屏幕来防止这种情况发生?

...

用解决方案编辑。

正如@Torxed 所建议的,使用 win32gui.InvalidateRect 解决了更新问题。但是,我发现仅设置需要设置的点的颜色比要求矩形便宜。第一个解决方案渲染得相当干净,而第二个解决方案仍然有点问题。最后,最适合我的代码是:

import win32gui

m=win32gui.GetCursorPos()
dc = win32gui.GetDC(0)

while True:
    n=win32gui.GetCursorPos()
    win32gui.InvalidateRect(hwnd, (m[0], m[1], GetSystemMetrics(0), GetSystemMetrics(1)), True)
    back=[]
    for i in range((n[0]-m[0])//4):
        win32gui.SetPixel(dc, m[0]+4*i, m[1], 0)
        win32gui.SetPixel(dc, m[0]+4*i, n[1], 0)
    for i in range((n[1]-m[1])//4):
        win32gui.SetPixel(dc, m[0], m[1]+4*i, 0)
        win32gui.SetPixel(dc, n[0], m[1]+4*i, 0)
Run Code Online (Sandbox Code Playgroud)

为了避免闪烁,必须进行除法和乘法四,但在视觉上与使用 DrawFocusRect 相同。

只有当你保持在初始位置的下方和右侧时,这才有效,但这正是我所需要的。改进它以接受任何次要职位并不困难。

Tor*_*xed 9

为了刷新旧的绘制区域,您需要调用win32gui.UpdateWindow或类似的方法来更新您的特定窗口,但因为从技术上讲您不是在表面上绘制,而是在整个监视器上绘制。您需要使监视器的整个区域无效,以便告诉 Windows 在其上重新绘制任何内容(或者我是这样理解的)

为了克服速度慢的问题,您可以一次性绘制它,而不是使用 for 循环来创建边界(在完成矩形之前需要 X 个循环来迭代)win32ui.Rectangle

import win32gui, win32ui
from win32api import GetSystemMetrics

dc = win32gui.GetDC(0)
dcObj = win32ui.CreateDCFromHandle(dc)
hwnd = win32gui.WindowFromPoint((0,0))
monitor = (0, 0, GetSystemMetrics(0), GetSystemMetrics(1))

while True:
    m = win32gui.GetCursorPos()
    dcObj.Rectangle((m[0], m[1], m[0]+30, m[1]+30))
    win32gui.InvalidateRect(hwnd, monitor, True) # Refresh the entire monitor
Run Code Online (Sandbox Code Playgroud)

可以在这里进行进一步的优化,例如不更新整个显示器,仅更新您绘制的部分等等。但这是基本概念:)

例如,要创建一个没有填充的矩形,您可以交换RectangleDrawFocusRect或者为了更多的控制,甚至使用win32gui.PatBlt

显然setPixel是最快的,所以这是我关于颜色和速度的最后一个例子,尽管它并不完美,因为它RedrawWindow强制重绘,它只是要求 Windows 执行此操作,然后由 Windows 决定是否遵守它。InvalidateRect性能更好一点,因为它要求事件处理程序在有空闲时间时清除矩形。但我还没有找到比 更激进的方法RedrawWindow,尽管它仍然相当温和。一个例子是,隐藏桌面图标,下面的代码将不起作用。

import win32gui, win32ui
from win32api import GetSystemMetrics

dc = win32gui.GetDC(0)
dcObj = win32ui.CreateDCFromHandle(dc)
hwnd = win32gui.WindowFromPoint((0,0))
monitor = (0, 0, GetSystemMetrics(0), GetSystemMetrics(1))

while True:
    m = win32gui.GetCursorPos()
    dcObj.Rectangle((m[0], m[1], m[0]+30, m[1]+30))
    win32gui.InvalidateRect(hwnd, monitor, True) # Refresh the entire monitor
Run Code Online (Sandbox Code Playgroud)

立场和决议有问题吗?请注意,高 DPI 系统往往会导致一系列问题。除了使用 OpenGL 解决方案或使用wxPython或 OpenCV 等框架之外,我还没有找到很多解决此问题的方法,除了这篇文章:Marking Your Python Program as High DPI Aware Seamlessly Windows

或者将 Windows 显示比例更改为100%

100%

这会导致定位问题消失,也许可以通过查询操作系统的比例和补偿来考虑这一点。


我能找到的关于“清除旧图纸”的唯一参考是这篇文章:win32 content Changed but does not show update except window is moving tagged c++win winapi。希望这可以帮助一些人在找到一个好的例子之前不必进行搜索。