拖动期间无法使用 wxPython 正确绘制矩形
cannot paint rectangles correctly with wxPython during dragging
我是 wxPython 的新手,我想制作一个具有以下功能的可重用 Panel
:
- 设置背景(完成)
- 绘制矩形(完成)
- 移动矩形(有一个错误)
当我用鼠标移动矩形时,我在 windows 上看到的是这样的:
- 当我按下鼠标按钮时,矩形消失(它应该保持可见)
- 当我开始拖动矩形时,另一个绘制在原始位置(不正确),另一个跟随鼠标(正确行为)
- 当我松开鼠标按钮时没有问题,矩形只在用户想要的地方绘制
代码简历
这是对代码的简单解释:
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的原因是什么?
我是 wxPython 的新手,我想制作一个具有以下功能的可重用 Panel
:
- 设置背景(完成)
- 绘制矩形(完成)
- 移动矩形(有一个错误)
当我用鼠标移动矩形时,我在 windows 上看到的是这样的:
- 当我按下鼠标按钮时,矩形消失(它应该保持可见)
- 当我开始拖动矩形时,另一个绘制在原始位置(不正确),另一个跟随鼠标(正确行为)
- 当我松开鼠标按钮时没有问题,矩形只在用户想要的地方绘制
代码简历
这是对代码的简单解释:
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的原因是什么?