【问题标题】:cannot paint rectangles correctly with wxPython during dragging拖动期间无法使用 wxPython 正确绘制矩形
【发布时间】:2021-09-09 14:15:32
【问题描述】:

我是 wxPython 的新手,我想用这些功能制作一个可重复使用的 Panel

  • 设置背景(完成)
  • 绘制矩形(完成)
  • 移动矩形(通过错误完成)

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

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

代码简历

这是对代码的简要说明:

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

在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)

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

完整代码

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 个矩形,一个在移动,一个在原始位置。原位置的矩形不应该出现

【问题讨论】:

    标签: wxpython


    【解决方案1】:

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

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

    屏幕仅在需要时更新,因此您必须在每次鼠标向上/向下/移动时调用 self.Refresh()。无需调用 self.Parent.Refresh()。

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

    【讨论】:

    • 有些教程使用del,而有些则没有。但我会停止以这种方式使用它。感谢您的建议,因为我对 wxPython 的使用感到困惑。
    猜你喜欢
    • 1970-01-01
    • 2012-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-12
    • 2020-10-21
    • 2011-01-14
    相关资源
    最近更新 更多