wxPython Paint Damaged, Clipped area - python

I have the following simple code (click the pink box and you can move it around with your mouse while holding down the left mouse button).
import wx
class AppPanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
p = MovablePanel(self, -1)
self.i = 0
self.Bind(wx.EVT_PAINT, self.OnPaint, self)
def OnPaint(self, event):
dc = wx.PaintDC(self)
self.i = self.i+10
c = self.i % 255
c = (0, 0, c)
dc.SetPen(wx.Pen(c))
dc.SetBrush(wx.Brush(c))
dc.DrawRectangle(0, 0, 10000,10000)
class MovablePanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.SetMinSize((500,500))
self.SetSize((500,500))
self.SetBackgroundColour("PINK")
self.LEFT_DOWN = False
self.Bind(wx.EVT_MOTION, self.OnMove, self)
self.Bind(wx.EVT_LEFT_DOWN,
self.OnClickDown,
self)
self.Bind(wx.EVT_LEFT_UP,
self.OnClickUp,
self)
def OnClickUp(self, event):
self.LEFT_DOWN = False
self.Refresh()
def OnClickDown(self, event):
self.LEFT_DOWN = True
self.Refresh()
def OnMove(self, event):
if self.LEFT_DOWN:
p = self.GetTopLevelParent().ScreenToClient(wx.GetMousePosition())
self.SetPosition(p)
if __name__ == "__main__":
app = wx.App(False)
f = wx.Frame(None, -1, size = (700, 700))
p = AppPanel(f, -1)
f.Show()
f.Maximize()
app.MainLoop()
and it is suppose to look like the following (simply resize the frame)
However after moving the pink box around you will see it really looks like this
I have tried the following
dc.Clear()
dc.DestroyClippingRegion()
wx.FULL_REPAINT_ON_RESIZE
wx.EVT_ERASE_BACKGROUND
I'm pretty sure it has to do with it being a panel, and therefore the PaintEvent only marking it partially damaged. This part is colored differently making the 'ghosting' or 'smearing' obvious. Perhaps I'm using the wrong words because I was unable to find a solution (and I this seems to be a non complex issue simply having to do with the 'damaged' region).

Ok I found the problem, but I'll try to post more details later.
Basically the goal of this code is to move a panel around and then update the parent panel. SetPosition calls Move which going through the wxWidget code calls DoMoveWindow, all of this leads to a change in position and a repaint call (not sure what calls the repaint yet). Great. However the repaint only marks a certain 'area' as it tries to be efficient. That is why some of the issue can be solved by having the panel go over the 'ghosted' area. What you have to do is after the SetPosition, call GetParent().Refresh(), which will send a 'full' paint without any excluded area.
Another thing to note is there are TWO terms for this 'damaged' or 'clipped' area. One is 'damage' however there is another, 'dirty'. Damage is used in the wx PaintDC information
Using wx.PaintDC within EVT_PAINT handlers is important because it
automatically sets the clipping area to the damaged area of the
window. Attempts to draw outside this area do not appear.
Trusting the documentation you will be mostly lost. However in one of the wxPython DoubleBuffer how to's the lingo changes (but it is the same thing as 'damage')
Now the OnPaint() method. It's called whenever ther is a pain event
sent by the system: i.e. whenever part of the window gets dirty.
Knowing this if you Google wx Window dirty you will get the following
Mark the specified rectangle (or the whole window) as "dirty" so it
will be repainted. Causes an EVT_PAINT event to be generated and sent
to the window.
Take the following three print cycles where an EVT_PAINT was fired after a SetPosition call (this is WITHOUT the GetParent().Refresh() call)
# first EVT_PAINT
Drawing
Panel Size (1440, 851)
Clipping Rect (0, 0, 1440, 851)
Client Update Rect (x=0, y=6, w=500, h=501) # the only place getting update is
# directly below the panel
# (that is (500, 500) )
# second
Drawing
Panel Size (1440, 851)
Clipping Rect (0, 0, 1440, 851)
Client Update Rect (x=0, y=6, w=910, h=845) # however this time the update area is
# bigger, this is also right before
# the move
# i believe what it is doing is
# drawing from (0,6) to (910, 851)
# why? because the panel is moving to
# (410, 390) and the bottom right
# corner of the panel (after moved)
# is (410+500, 390+461) = (910, 851)
# or about where the edge of the panel
# will be
# third
Drawing
Panel Size (1440, 851)
Clipping Rect (0, 0, 1440, 851)
Client Update Rect (x=410, y=390, w=500, h=461)
Here is the update code to play around with, hopefully this will help others.
import wx
instructions = """
How to use.
1) Hover your mouse over the pink panel.
2) Click down (left click)
3) While holding down drag mouse around
4) Release mouse button to stop.
"""
class AppPanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.settings_sizer = wx.BoxSizer(wx.HORIZONTAL)
p = MovablePanel(self, -1)
self.c = wx.CheckBox(self, -1, label = "Ghosting On?")
self.p = p
self.i = 0
self.settings_sizer.Add(self.c)
self.sizer.Add(self.settings_sizer)
self.sizer.Add(self.p)
self.SetSizer(self.sizer)
self.Layout()
self.Bind(wx.EVT_CHECKBOX, self.OnCheck, self.c)
self.Bind(wx.EVT_PAINT, self.OnPaint, self)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase, self)
def OnCheck(self, event):
print "CHECK\n\n\n\n\n"
v = self.c.GetValue()
self.p.r = v
print v
def OnErase(self, event):
pass
def OnPaint(self, event):
print "Drawing"
dc = wx.PaintDC(self)
print "Panel Rect, ", self.p.GetPosition(),
print self.p.GetSize()
print "Clipping Rect", dc.GetClippingBox()
print "Client Update Rect", self.GetUpdateClientRect()
print "----------------------------"
self.i = self.i+10
c = self.i % 255
c = (0, 0, c)
dc.SetPen(wx.Pen(c))
dc.SetBrush(wx.Brush(c))
dc.DrawRectangle(0, 0, 10000,10000)
self.SetBackgroundColour(c)
dc.SetPen(wx.Pen("WHITE"))
dc.SetBrush(wx.Brush("WHITE"))
dc.DrawRectangle(0, 0, self.GetSize()[0], self.c.GetSize()[1])
class MovablePanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.SetMinSize((300,300))
self.SetSize((300,300))
txt = wx.StaticText(self, -1, label = "CLICK AND DRAG ME!")
inst = wx.StaticText(self, -1, label = instructions)
font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)
txt.SetFont(font)
inst.SetFont(font)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, flag = wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_CENTRE_HORIZONTAL)
sizer.Add(inst, flag = wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_CENTRE_HORIZONTAL)
self.SetSizer(sizer)
self.SetBackgroundColour("PINK")
self.LEFT_DOWN = False
self.r = False
self.Bind(wx.EVT_MOTION, self.OnMove, self)
self.Bind(wx.EVT_LEFT_DOWN,
self.OnClickDown,
self)
self.Bind(wx.EVT_LEFT_UP,
self.OnClickUp,
self)
def OnClickUp(self, event):
self.LEFT_DOWN = False
self.Refresh()
def OnClickDown(self, event):
self.LEFT_DOWN = True
self.Refresh()
def OnMove(self, event):
if self.LEFT_DOWN:
p = self.GetTopLevelParent().ScreenToClient(wx.GetMousePosition())
self.SetPosition(p)
if not self.r:
self.GetParent().Refresh()
if __name__ == "__main__":
app = wx.App(False)
f = wx.Frame(None, -1, size = (700, 700))
p = AppPanel(f, -1)
f.Show()
app.MainLoop()

Related

wxPython Manual Scroll of panel ScrolledPanel failing with wx.Grid

I've created a panel thats derived from wx.lib.scrolledpanel. I could scroll on it fine with my mousewheel until I put a grid into the panel. Now when the mouse cursor is on top of the grid, the scroll stopped working, and would start working again if i moved the cursor outside of the grid.
I figured the easiest solution after searching and searching was to just manually capture the mousewheel event and scroll the panel manually. I bound this handler to wx.EVT_MOUSEWHEEL inside my wx.App object
class Wx_app(wx.App):
def __init__(self):
super().__init__(clearSigInt=True)
self.frame = MyFrame(None, pos=(0,0), size=(1900, 1100))
self.Bind(wx.EVT_MOUSEWHEEL, self.on_mouse_wheel)
def on_mouse_wheel(self, e):
# get the current scroll pos, is tuple with x as first val, y as second val
pos = self.frame.panel.CalcUnscrolledPosition(0, 0)
y_pos = pos[1]
# detemrine if user is scrolling up or down
if e.GetWheelRotation() > 0:
# user is scrolling up
print("UP")
self.frame.panel.Scroll(0, y_pos + 10)
else:
# user is scrolling down
print("DOWN")
self.frame.panel.Scroll(0, y_pos - 10)
This code works when i try to scroll down, but when i try to scroll back up with the the mousewheel nothing happens, even though "UP" registers in my terminal. Also i would think up should be y_pos - 10 and not y_pos + 10, but then the wheel scrolls in the opposite direction you would expect. What am I doing wrong? Perhaps i'm not getting the correct existing position in the first place, but CalcUnscrolledPosition is the only thing I could find that could maybe do that. I'm new to Python please explain it like I'm a 5 year old. thanks
Your immediate issue is that Up would be Y - 10 and Down would be Y + 10.
You need to go further up (less y) or further down (more y).
You also may well be comparing Apples with Oranges, when relying on the position as returned from the mouse event and how that relates to the position within the scrolled window.
The crux of this is that both the scrolledpanel and the grid are scrollable widgets.
Your issue seems to be that your grid is not of sufficent size to show the grid's scrollbars, thus causing confusion.
You can force the scrollbars on the grid and size it so that it is obvious that there are two sets of scrollbars and hopefully your users will work it out.
Here is a sample grid in a scrolled panel to play with (ignore the bound functions, they were there because initially I misunderstood your issue, thinking that you wanted manual scrolling within the grid)
import wx
import wx.lib.scrolledpanel
import wx.grid
class MyPanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.fileFormPanel = FileFormPanel(self)
self.sizer.Add(self.fileFormPanel, 1, wx.EXPAND)
self.SetSizer(self.sizer)
class FileFormPanel(wx.lib.scrolledpanel.ScrolledPanel):
def __init__(self, parent):
wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)
# Create a wxGrid object
self.grid = wx.grid.Grid(self, -1, size=(300,480))
self.grid.CreateGrid(5, 10)
self.grid.SetRowLabelSize(1)
self.grid.SetDefaultColSize(120)
self.grid.SetColSize(0, 50)
self.grid.ShowScrollbars(True, True)
for j in range(5):
for k in range(10):
self.grid.SetCellValue(j, k, str(k))
text = wx.TextCtrl(self, wx.ID_ANY, value="Some text")
sizer.Add(self.grid)
sizer.Add(text)
self.SetSizer(sizer)
self.SetupScrolling()
#self.grid.Bind(wx.EVT_MOUSEWHEEL, self.OnGrid)
#self.Bind(wx.EVT_MOUSEWHEEL, self.OnScroll)
#self.grid.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnSelect)
# def OnGrid(self, event):
# obj = event.GetEventObject()
# if event.WheelRotation > 0:
# self.grid.MoveCursorUp(False)
# else:
# self.grid.MoveCursorDown(False)
#
# def OnSelect(self, event):
# obj = event.GetEventObject()
# r = event.GetRow()
# c = event.GetCol()
# self.grid.MakeCellVisible(r,c)
# event.Skip()
#
# def OnScroll(self, event):
# print("window scroll")
# event.Skip()
class DemoFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent = None, size = (400, 400))
MyPanel(self)
class App(wx.App):
def OnInit(self):
self.frame = DemoFrame()
self.frame.Show()
self.SetTopWindow(self.frame)
return True
app = App()
app.MainLoop()

wxPython using a sizer with panels

I'm trying to make a GUI with on the left side a camera input with some data on the camera stream. On the right side I want some buttons and other widgets. The code a have so far: (the functions get_image() and pil_to_wx() work fine, they're just not shown in the code below)
class HUDPanel(wx.Panel):
def __init__(self, parent):
super(HUDPanel, self).__init__(parent, -1)
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
self.Bind(wx.EVT_PAINT, self.on_paint)
self.update()
def update(self):
self.Refresh()
self.Update()
wx.CallLater(15, self.update)
def create_bitmap(self):
image = get_image()
bitmap = pil_to_wx(image)
return bitmap
def on_paint(self, event):
bitmap = self.create_bitmap()
dc = wx.AutoBufferedPaintDC(self)
dc.DrawBitmap(bitmap, 0, 0)
class ExtraPanel(wx.Panel):
def __init__(self, parent):
super(ExtraPanel, self).__init__(parent, -1)
My_Button = wx.Button(self,label="TEST")
class Frame(wx.Frame):
def __init__(self):
style = wx.DEFAULT_FRAME_STYLE & ~wx.RESIZE_BORDER & ~wx.MAXIMIZE_BOX
super(Frame, self).__init__(None, -1, 'Camera Viewer', style=style)
my_sizer = wx.BoxSizer(wx.HORIZONTAL)
campanel = HUDPanel(self)
my_sizer.Add(campanel, 0, wx.ALL | wx.CENTER, 5)
widgetpanel = ExtraPanel(self)
my_sizer.Add(widgetpanel, 0, wx.ALL | wx.CENTER, 5)
self.SetSizer(my_sizer)
self.Fit()
def main():
app = wx.App()
frame = Frame()
frame.Show()
app.MainLoop()
if __name__ == '__main__':
main()
When I run this code, all I get is a small window with only a button named "TEST" (the panel that should be on the right side). The previous version with only the camera panel worked fine, so that's not the problem. What am I doing wrong?
UPDATE:
The sizes of the sub-panels are fixed, i see the panel with the camera show up but only a small line on the screen. The part with the button show perfectly.
First of all, you don't give any size to your HUDPanel, so I'm not sure how do you expect it to appear.
Second, you're recursively calling update all the time (well every 15ms), which is most definitely a bad idea as this will consume close to 100% of (one) CPU and may prevent your application from dispatching other events.

Using wx.EVT_PAINT and wx.PaintDC

You use wx.EVT_PAINT and wx.PaintDC to draw shapes, so that when window is resized (redrawn) shapes will not be lost. This works when the window is created. But, how will I preserve the shapes that I create after window is created?
Below, I present you a code, when the app first starts, a rectangle is drawn on the window. When user double clicks somewhere on the window, another rectangle is created. The initial rectangle is always preserved because it is bind to wx.EVT_PAINT event, so that it will be redrawn every time the window is redrawn.
But the second rectangle is not associated to the wx.EVT_PAINT, therefore it is lost when window is redrawn. How do I preserve the second rectangle as well?
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_LEFT_DCLICK, self.on_left_double_click)
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawRectangle(50, 60, 90, 40)
def on_left_double_click(self, evt):
x = evt.GetX()
y = evt.GetY()
dc = wx.ClientDC(self)
dc.SetBrush(wx.Brush("yellow"))
dc.DrawRectangle(x, y, 90, 40)
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Test",style=wx.DEFAULT_FRAME_STYLE,size=wx.Size(400, 300))
self.main_panel = MyPanel(self)
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
There is no universal solution to this except handling every drawing operation in a wx.PaintDC. You would do something along the lines of the following:
def __init__(self, parent):
# ...
self.show_yellow_box = False
self.box_pos = None
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawRectangle(50, 60, 90, 40)
if self.show_yellow_box:
x, y = self.box_pos
dc.SetBrush(wx.Brush("yellow"))
dc.DrawRectangle(x, y, 90, 40)
def on_left_double_click(self, evt):
x = evt.GetX()
y = evt.GetY()
self.box_pos = (x, y)
self.show_yellow_box = True
self.Refresh() # important, to trigger EVT_PAINT on panel
If the operations in the paint event are more expensive, you probably will end up collecting the expensive drawing operations on the DC in a wx.MemoryDC and blit the bitmap content back onto the panel in the MyPanel.OnPaint.
There is a temporary DC (wx.Overlay/wx.OverlayDC), which is however only useful to apply temporary changes between paint events.

wxpython - Erase background erases non-background components

In wxpython, I want to have a window with a picture that changes based on use of toolbar buttons with text controls on top of the picture. When I click the toolbar buttons, I am posting an erase background event, then capturing the erase event, and redrawing the new background from there (base on this).
Mostly works well, except that the text controls cease to be drawn once I redraw the background. They're still there, just not drawn.
Here is a simplified code that demonstrates the problem. If you run this code and click the button to toggle drawing the background image or not, the text controls disappear.:
import wx
import wx.lib.inspection
class PanelWithDrawing(wx.Panel):
def __init__(self, parent):
super(PanelWithDrawing, self).__init__(parent, size=(100, 40))
self.showbmp = False
self.txt = wx.TextCtrl(self, pos=(10, 10))
def onErase(self, dc):
if self.showbmp:
# dc.DrawBitmap(wx.Bitmap('background.png', 0, 0)
dc.DrawRectangle(0, 0, 40, 40) # use a drawing instead so you don't have to find a png
class Toolbar(wx.ToolBar):
def __init__(self, parent):
super(Toolbar, self).__init__(parent, -1)
self.AddLabelTool(wx.ID_SAVE, "Record", wx.Bitmap("picture.png", wx.BITMAP_TYPE_ANY), wx.NullBitmap, wx.ITEM_NORMAL, "", "")
class Example(wx.Frame):
def __init__(self, parent, title):
super(Example, self).__init__(parent, title=title)
self.toolbar = Toolbar(self)
self.SetToolBar(self.toolbar)
self.toolbar.Realize()
self.panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
self.panel1 = PanelWithDrawing(self.panel)
vbox.Add(self.panel1)
# self.panel2 = PanelWithText(self.panel)
# vbox.Add(self.panel2)
self.panel.SetSizer(vbox)
self.Centre()
self.Show()
self.toolbar.Bind(wx.EVT_TOOL, self.onButton)
self.panel1.Bind(wx.EVT_ERASE_BACKGROUND, self.onErase)
def onErase(self, evt):
try:
dc = evt.GetDC()
except:
dc = wx.ClientDC(self)
rect = self.GetUpdateRegion().GetBox()
dc.SetClippingRect(rect)
dc.Clear()
self.panel1.onErase(dc)
def onButton(self, evt):
self.panel1.showbmp = not self.panel1.showbmp
wx.PostEvent(self.panel1, wx.PyCommandEvent(wx.wxEVT_ERASE_BACKGROUND))
if __name__ == '__main__':
app = wx.App()
Example(None, title='Example')
wx.lib.inspection.InspectionTool().Show() # use this for debugging GUI design
app.MainLoop()
How do I tell wxpython to draw all the non-background stuff again? Alternatively, how do I not un-draw it in the first place?
After working on it for a few days, I got it! And the answer is trivially simple (as usual).
wx.PostEvent(self.panel1, wx.PyCommandEvent(wx.wxEVT_ERASE_BACKGROUND)) should be replaced with self.Refresh() to refresh the whole frame and not just force a specific (and apparently unsafe) redraw.

Flicker-free drawable ScrolledWindow

I'm trying to build a ScrolledWindow that you can draw on using the mouse, and it's working too, but I'm getting a nasty flicker when the user is drawing on the window while the scrollbars aren't in the "home" position..
To reproduce, run the attached program, scroll a bit down (or to the right) and "doodle" a bit by keeping the left mouse button pressed. You should see a flickering now and then..
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()
I tried using SetBackgroundStyle(wx.BG_STYLE_CUSTOM), or binding EVT_ERASE_BACKGROUND, or using RefreshRect instead of Refresh, but the flicker is still there.. Any idea on what I might try next?
My environment: Xubuntu 9.04, wxPython 2.8.9.1
(but tested on Ubuntu 10.04 too)
Many thanks for your time!
From Robin Dunn himself:
First, a Refresh() by default will
erase the background before sending
the paint event (although setting the
BG style or catching the erase event
would have taken care of that.) The
second and probably most visible
problem in this case is that in your
on_motion handler you are not
offsetting the ClientDC by the scroll
offsets, just the position in the
buffer that you are drawing the line
segment at. So when the buffer is
flushed out to the client DC it is
drawn at the physical (0,0), not the
virtual (0,0). In other words, the
flicker you are seeing is coming from
drawing the buffer at the wrong
position after every mouse drag event,
and then it immediately being drawn
again at the right position in the
on_paint triggered by the
Refresh().
You should be able to fix this by
calling PrepareDC on the client DC
before using it, like this:
cdc = wx.CLientDC(self)
self.PrepareDC(cdc)
dc = wx.BufferedDC(cdc, self.buffer)
However since you are doing a
Refresh or RefreshRect anyway,
there is no need to use a client DC
here at all, just let the flushing of
the buffer to the screen be done in
on_paint instead:
dc = wx.BufferedDC(None, self.buffer)
Using Joril recomendations and removing Refresh(), there is no flicker anymore (even enlarging the frame).
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()

Categories