拖动期间无法使用 wxPython 正确绘制矩形

cannot paint rectangles correctly with wxPython during dragging

我是 wxPython 的新手,我想制作一个具有以下功能的可重用 Panel

当我用鼠标移动矩形时,我在 windows 上看到的是这样的:

  1. 当我按下鼠标按钮时,矩形消失(它应该保持可见)
  2. 当我开始拖动矩形时,另一个绘制在原始位置(不正确),另一个跟随鼠标(正确行为)
  3. 当我松开鼠标按钮时没有问题,矩形只在用户想要的地方绘制

代码简历

这是对代码的简单解释:

self.Bind(wx.EVT_PAINT, self.on_paint)

在绘画方法中我重画了所有背景和矩形

self.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)

在鼠标按下处理程序中,如果鼠标悬停在一个矩形上,该矩形将被暂时移除,然后 Panel 将被强制重新绘制

self.Bind(wx.EVT_LEFT_UP, self.on_mouse_up)

当鼠标按钮被释放并且我们处于拖动模式时,所选的矩形被重新插入到列表中并且面板被强制重新绘制

self.Bind(wx.EVT_MOTION, self.on_mouse_motion)

当鼠标移动并且我们处于拖动模式时,选中的矩形被绘制在覆盖对象中

完整代码

from mouse_tracker import MouseTracker
import wx
from rect import Rect
from enum import Enum


class TrackMode(Enum):
    NONE = 0
    DRAWING = 1
    MOVING = 2


class DrawPanel(wx.Panel):

    def __init__(self, *args, **kw):

        super().__init__(*args, **kw)

        self.bitmap = None
        self.rectangles = []
        self.track_mode = TrackMode.NONE

        self.Bind(wx.EVT_PAINT, self.on_paint)
        self.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
        self.Bind(wx.EVT_LEFT_UP, self.on_mouse_up)
        self.Bind(wx.EVT_MOTION, self.on_mouse_motion)

    def on_mouse_down(self, event):
        x = event.Position.x
        y = event.Position.y
        self.tracker = MouseTracker(x, y)

        hover_rects = (r for r in self.rectangles[::-1] if r.contains(x, y))
        self.moving_rect = next(hover_rects, None)

        self.tmp_dc = wx.ClientDC(self)
        self.overlay = wx.Overlay()
        self.overlay_dc = wx.DCOverlay(self.overlay, self.tmp_dc)

        if self.moving_rect:
            self.track_mode = TrackMode.MOVING
            self.rectangles.remove(self.moving_rect)
            self.Parent.Refresh()
        else:
            self.track_mode = TrackMode.DRAWING
    

    def on_mouse_motion(self, event):
        if self.track_mode is TrackMode.NONE:
            return

        x = event.Position.x
        y = event.Position.y
        self.tracker.set_position(x, y)

        if self.track_mode is TrackMode.DRAWING:
            self.overlay_dc.Clear()
            x, y, w, h = self.tracker.get_rect()
            self.tmp_dc.DrawRectangle(x, y, w, h)
        elif self.track_mode is TrackMode.MOVING:
            self.overlay_dc.Clear()
            dx, dy = self.tracker.get_delta()
            x, y, w, h = self.moving_rect
            self.tmp_dc.DrawRectangle(x + dx, y + dy, w, h)

    def on_mouse_up(self, event):

        if self.track_mode is TrackMode.DRAWING:
            self.add_rect(self.tracker.get_rect())
        elif self.track_mode is TrackMode.MOVING:
            dx, dy = self.tracker.get_delta()
            self.moving_rect.move(dx, dy)
            self.add_rect(self.moving_rect)
            self.Parent.Refresh()

        self.tracker = None
        self.track_mode = TrackMode.NONE
        del self.tmp_dc, self.overlay, self.overlay_dc

    def on_paint(self, event):
        self.dc = wx.PaintDC(self)

        if self.bitmap:
            memory_dc = wx.MemoryDC(self.bitmap)
            w, h = self.bitmap.GetSize()
            self.dc.Blit(0, 0, w, h, memory_dc, 0, 0)
            del memory_dc

        for rect in self.rectangles:
            x, y, w, h = rect
            self.dc.DrawRectangle(x, y, w, h)

        del self.dc

    def set_background(self, bitmap):
        self.bitmap = bitmap

    def add_rect(self, rect):
        self.rectangles.append(rect)


def main():
    app = wx.App()
    example = wx.Frame(None)
    panel = DrawPanel(example)
    bitmap = wx.Bitmap('screenshot.png')
    panel.set_background(bitmap)
    example.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

from rect import Rect


class MouseTracker:

    def __init__(self, x, y) -> None:
        self.start = (x, y)
        self.end = (x, y)

    def set_position(self, x, y):
        self.end = (x, y)

    def get_delta(self):
        dx = self.end[0] - self.start[0]
        dy = self.end[1] - self.start[1]

        return dx, dy

    def get_rect(self):
        rect = Rect()
        rect.set_points(
            self.start[0],
            self.start[1],
            self.end[0],
            self.end[1])

        return rect
from core import between


class Rect:

    def __init__(self) -> None:
        self.x = None
        self.y = None
        self.w = None
        self.h = None

    def set_points(self, x0, y0, x1, y1):
        self.x = min(x0, x1)
        self.y = min(y0, y1)
        self.w = abs(x0 - x1)
        self.h = abs(y0 - y1)

    def __iter__(self):
        yield self.x
        yield self.y
        yield self.w
        yield self.h

    def contains(self, x, y):
        test_x = between(x, self.x, self.get_right())
        test_y = between(y, self.y, self.get_bottom())
        return test_x and test_y

    def get_bottom(self):
        return self.y + self.h

    def get_right(self):
        return self.x + self.w

    def move(self, dx, dy):
        self.x += dx
        self.y += dy
def between(x, start, end):
    return x >= start and x <= end

我的问题

拖动过程中有2个矩形,一个在移动,一个在原来的位置。原来位置的矩形应该不存在

我可以看到那里有一个严重的问题,我认为在我们修复它之前继续下去是没有意义的。所有绘图都必须出现在 on_paint 处理程序中。您正在从列表中绘制矩形,但当前正在绘制或拖动的矩形从未被绘制。您在 on_motion 中执行的这些叠加操作不会执行任何操作。

我建议你检查on_paint方法中是否有任何矩形正在绘制或拖动,并在那里绘制更新

屏幕仅在需要时更新,因此您必须在每只鼠标 up/down/motion 中调用 self.Refresh()。无需调用 self.Parent.Refresh().

此外,我建议不要以这种方式使用 del。它实际上并没有对该变量背后的对象做任何事情,对我来说,这样看它,相当令人困惑。我在问自己:这里调用del的原因是什么?