无闪烁的可绘制滚动窗口
我正在尝试创建一个可以用鼠标绘画的滚动窗口,这个功能是可以用的,但当用户在窗口上绘画时,如果滚动条没有回到“初始”位置,就会出现很烦人的闪烁现象。
要重现这个问题,运行附带的程序,稍微向下(或向右)滚动一下,然后按住左键“涂鸦”。你会发现偶尔会有闪烁的情况出现。
import wx
class MainFrame(wx.Frame):
""" Just a frame with a DrawPane """
def __init__(self, *args, **kw):
wx.Frame.__init__(self, *args, **kw)
s = wx.BoxSizer(wx.VERTICAL)
s.Add(DrawPane(self), 1, wx.EXPAND)
self.SetSizer(s)
########################################################################
class DrawPane(wx.PyScrolledWindow):
""" A PyScrolledWindow with a 1000x1000 drawable area """
VSIZE = (1000, 1000)
def __init__(self, *args, **kw):
wx.PyScrolledWindow.__init__(self, *args, **kw)
self.SetScrollbars(10, 10, 100, 100)
self.prepare_buffer()
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
self.Bind(wx.EVT_MOTION, self.on_motion)
def prepare_buffer(self):
self.buffer = wx.EmptyBitmap(*DrawPane.VSIZE)
dc = wx.BufferedDC(None, self.buffer)
dc.Clear()
dc.DrawLine(0, 0, 999, 999) # Draw something to better show the flicker problem
def on_paint(self, evt):
dc = wx.BufferedPaintDC(self, self.buffer, wx.BUFFER_VIRTUAL_AREA)
def on_mouse_down(self, evt):
self.mouse_pos = self.CalcUnscrolledPosition(evt.GetPosition()).Get()
def on_motion(self, evt):
if evt.Dragging() and evt.LeftIsDown():
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
newpos = self.CalcUnscrolledPosition(evt.GetPosition()).Get()
coords = self.mouse_pos + newpos
dc.DrawLine(*coords)
self.mouse_pos = newpos
self.Refresh()
if __name__ == "__main__":
app = wx.PySimpleApp()
wx.InitAllImageHandlers()
MainFrame(None).Show()
app.MainLoop()
我尝试过使用 SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
,或者绑定 EVT_ERASE_BACKGROUND
,还有用 RefreshRect
代替 Refresh
,但闪烁的问题依然存在。你有什么建议可以让我尝试的吗?
我的环境是:Xubuntu 9.04,wxPython 2.8.9.1(但在Ubuntu 10.04上也测试过)
非常感谢你的时间!
2 个回答
根据Joril的建议,去掉了Refresh()这个函数后,界面不再闪烁了(即使放大窗口也没有问题)。
import wx
class MainFrame(wx.Frame):
""" Just a frame with a DrawPane """
def __init__(self, *args, **kw):
wx.Frame.__init__(self, *args, **kw)
s = wx.BoxSizer(wx.VERTICAL)
s.Add(DrawPane(self), 1, wx.EXPAND)
self.SetSizer(s)
########################################################################
class DrawPane(wx.PyScrolledWindow):
""" A PyScrolledWindow with a 1000x1000 drawable area """
VSIZE = (1000, 1000)
def __init__(self, *args, **kw):
wx.PyScrolledWindow.__init__(self, *args, **kw)
self.SetScrollbars(10, 10, 100, 100)
self.prepare_buffer()
cdc = wx.ClientDC(self)
self.PrepareDC(cdc)
dc = wx.BufferedDC(cdc, self.buffer)
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
self.Bind(wx.EVT_MOTION, self.on_motion)
def prepare_buffer(self):
self.buffer = wx.EmptyBitmap(*DrawPane.VSIZE)
cdc = wx.ClientDC(self)
self.PrepareDC(cdc)
dc = wx.BufferedDC(cdc, self.buffer)
dc.Clear()
dc.DrawLine(0, 0, 999, 999) # Draw something to better show the flicker problem
def on_paint(self, evt):
dc = wx.BufferedPaintDC(self, self.buffer, wx.BUFFER_VIRTUAL_AREA)
def on_mouse_down(self, evt):
self.mouse_pos = self.CalcUnscrolledPosition(evt.GetPosition()).Get()
def on_motion(self, evt):
if evt.Dragging() and evt.LeftIsDown():
newpos = self.CalcUnscrolledPosition(evt.GetPosition()).Get()
coords = self.mouse_pos + newpos
cdc = wx.ClientDC(self)
self.PrepareDC(cdc)
dc = wx.BufferedDC(cdc, self.buffer)
dc.DrawLine(*coords)
self.mouse_pos = newpos
if __name__ == "__main__":
app = wx.PySimpleApp()
wx.InitAllImageHandlers()
MainFrame(None).Show()
app.MainLoop()
这是Robin Dunn自己说的:
首先,默认情况下,
Refresh()
会在发送绘图事件之前清空背景(不过你可以通过设置背景样式或者捕捉清空事件来解决这个问题)。其次,可能最明显的问题是,在你的on_motion
处理函数中,你没有根据滚动的偏移量来调整ClientDC的位置,而只是根据你在缓冲区中绘制线段的位置来处理。所以当缓冲区的内容被刷新到客户端DC时,它是以物理坐标(0,0)来绘制的,而不是虚拟坐标(0,0)。换句话说,你看到的闪烁是因为每次鼠标拖动事件后,缓冲区在错误的位置被绘制,然后又在on_paint
中被触发的Refresh()
中立即在正确的位置被绘制。你可以通过在使用客户端DC之前调用
PrepareDC
来解决这个问题,像这样:cdc = wx.CLientDC(self) self.PrepareDC(cdc) dc = wx.BufferedDC(cdc, self.buffer)
不过,由于你已经在使用
Refresh
或RefreshRect
,所以这里根本不需要使用客户端DC,只需在on_paint
中处理缓冲区内容的刷新即可:dc = wx.BufferedDC(None, self.buffer)