How do I draw on the screen

To draw on the screen, we use a wxPython object called a device context. A device context abstracts a display device, giving each device a common set of draw methods, so that your draw code is the same no matter what kind of device you are targeting. A device context is represented by the abstract wxPython class wx.DC and its subclasses. Since wx.DC is abstract, you'll need to use one of its subclasses for your application.

Using a device context

Table 6.1 displays a field guide to the subclasses of wx.DC and their usage. Device contexts, which are used to draw to a wxPython widget, should always be locally created, temporary objects, and should not be kept between method calls in an instance variable, global variable, or other manner. On some platforms device contexts are a limited resource and so holding references to a wx.DC could cause your program to be unstable. Because of the way wxPython uses device contexts internally, there are several subtly different wx.DC subclasses used for drawing in a widget. Chapter 12 will explain these differences in more detail.

Table 6.1 A brief guide to the device context subclasses of wx.DC

Device Context

Usage

wx.BufferedDC

Used to buffer a set of drawing commands until they are complete and ready to draw to screen. This prevents unwanted flicker in the display.

wx.BufferedPaintDC

As wx.BufferedDC but only used within the processing of a wx.PaintEvent. Only create instances of this class temporarily.

Table 6.1 A brief guide to the device context subclasses of wx.DC (continued)

Device Context

Usage

wx.ClientDC

Used to draw on a window object. Use this when you want to draw on the main area of the widget—not the border or any other decoration. The main area is sometimes called the client area, hence the name of this DC. The wx.ClientDC class should only be created temporarily. This class is only used outside of the processing of a wx.PaintEvent.

wx.MemoryDC

Used to draw graphics to a bitmap stored in memory, not being displayed. You can then select the bitmap, and use the wx.DC.Blit() method to draw the bitmap to a window.

wx.MetafileDC

On Windows operating systems, this device context allows you to create standard windows metafile data.

wx.PaintDC

Identical to wx.ClientDC except that it is only used within the processing of a wx.PaintEvent. Only create instances of this class temporarily.

wx.PostScriptDC

Used to write encapsulated PostScript files

wx.PrinterDC

Used on Windows operating systems to write to a printer.

wx.ScreenDC

Used to draw directly to the screen itself, on top and outside of any windows being displayed. This class should only be created temporarily.

wx.WindowDC

Used to draw on the entire area of a window object, including the border, and any other decorations not included in the client area. Non-Windows operating systems might not support this class.

Listing 6.1 contains the code for the initial pass of the sketch window displayed in figure 6.1. Because this code shows tricks of drawing to device contexts, we'll annotate it in detail.

Listing 6.1 The initial SketchWindow code import wx class SketchWindow(wx.Window):

wx.Window._init_(self, parent, ID)

self.SetBackgroundColour("White") self.color = "Black" self.thickness = 1

self.pen = wx.Pen(self.color, self.thickness, wx.SOLID) self-lines = [] Creating a self-curLine = [] wx.Pen object self.pos = (0, 0) self.InitBuffer()

self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_IDLE, self.Onldle) self.Bind(wx.EVT_PAINT, self.OnPaint)

Linking the events def InitBuffer(self): Creating a buffered size = self.GetClientSize() device context self.buffer = wx.EmptyBitmap(size.width, size.height) dc = wx.BufferedDC(None, self.buffer)

dc.SetBackground(wx.Brush(self.GetBackgroundColour())) dc.Clear()

self DrawLines(dc) Using the device context 6

self.relnitBuffer = False h def GetLinesData(self): return self.lines[:]

def SetLinesData(self, lines): self.lines = lines[:] self.InitBuffer() self.Refresh()

def OnLeftDown(self, event): self.curLine = []

self.pos = event.GetPositionTuple() f Getting the mouse position self.CaptureMouse() Capturing g the mouse def OnLeftUp(self, event): if self.HasCapture():

self.lines.append((self.color, self.thickness, self.curLine))

self.ReleaseMouse() h Releasing the mouse def 0nMotion(self, event): i Determining if a if event.Dragging() and event.LeftIsDown(): <-J drag is ongoing dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) <— self.drawMotion(dc, event) eventSkip() Creating another buffered context j def drawMotion(self, dc, event): dc.SetPen(self.pen) newPos = event.GetPositionTuple() coords = self.pos + newPos self.curLine.append(coords) dc.DrawLine(*coords) self.pos = newPos

Drawing to device context def OnSize(self, event):

self.reInitBuffer = True 1! Handling a resize event def OnIdle(self, event): Idle processing if self.reInitBuffer: self.InitBuffer() self.Refresh(False)

def OnPaint(self, event):

dc = wx.BufferedPaintDC(self, self.buffer) Handling a paint request def DrawLines(self, dc):

for colour, thickness, line in self.lines:

pen = wx.Pen(colour, thickness, wx.SOLID) dc.SetPen(pen) for coords in line:

dc.DrawLine(*coords)

def SetColor(self, color): self.color = color self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)

def SetThickness(self, num): self.thickness = num self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)

class SketchFrame(wx.Frame):

wx.Frame._init_(self, parent, -1, "Sketch Frame", size=(800,600)) self.sketch = SketchWindow(self, -1)

app = wx.PySimpleApp() frame = SketchFrame(None) frame.Show(True) app.MainLoop()

jflft Drawing all lines

O The wx.Pen instance determines the color, thickness, and style of lines drawn to the device context. Styles other than wx.SOLID include wx.DOT, wx.LONGDASH, and wx.SHORTDASH.

© This window needs to respond to several different mouse event types in order to draw the sketch. It responds to left mouse button up and down, mouse motion, window resize, and window repaint. It also specifies processing to take place during idle times. d The buffered device context is created in two steps: (1) Create the empty bitmap that serves as the offscreen buffer and (2) Create a buffered device context using the offscreen buffer. The buffered context is used to prevent the redrawing of the sketched lines from causing screen flicker. Later in this section, we'll discuss the buffered device contexts in more detail.

Q These lines issue drawing commands to the device context; specifically, setting the background drawing brush and clearing the device. The wx.Brush object determines the color and style of the background for fill commands.

f The event method GetPositionTuple() returns a Python tuple containing the exact position of the mouse click being processed.

Q The CaptureMouse() method directs all mouse input to the window, even if you drag the mouse outside the border of the window. This call must be negated by calling ReleaseMouse() later in the program.

Q The ReleaseMouse() call returns the system to the state before the previous Cap-tureMouse() call. The wxPython application uses a stack to keep track of windows that have captured the mouse, and calling ReleaseMouse() is equivalent to popping that stack. This implies that you need the same number of CaptureMouse() and ReleaseMouse() calls.

© This line determines if the motion event is part of a line draw, defined by whether the motion event occurs while the left mouse button is down. Both Dragging() and LeftIsDown() are methods of wx.MouseEvent that return True if the associated condition is true when the motion event occurs.

Q Since wx.BufferedDC is one of the device contexts that is created temporarily, we need to create another one before we draw the lines. In this case we create a new wx.ClientDC as the main device context, and reuse our instance variable bitmap as the buffer.

1) These lines actually use the device context to draw the newly sketched line to the screen. First, we create the coords tuple, which is a combination of the self.pos and the newPos tuples. In this case, the new point comes from the event Get-PositionTuple(), and the old point is stored from the last call to OnMotion(). We save that tuple to the self.curLine list, and then use the function call unpack syntax to call DrawLine(), with the elements of the tuple as the arguments. The DrawLine() method takes as parameters (x1, y1, x2, y2), and draws a line from the point (x1, y1) to the point (x2, y2). The frequency with which the motion event occurs and gives the sketch pad a new data point, is dependent on the underlying system speed.

1! If the window is resized, we make a note of it by storing a True value in the self.reInitBuffer instance attribute. We don't actually do anything until the next idle event.

[email protected] When an idle event comes along, the application takes that opportunity to respond to a resize event, if one (or more) has occurred. The reason we respond in the idle event, rather than the resize event itself, is to allow multiple resize events to occur in quick succession without having to redraw for each one. Handling the request for redraw is surprisingly simple: create a buffered paint device context. The real wx.PaintDC is created (since we are inside a paint request, we need wx.PaintDC and not a wx.ClientDC instance), and then the bitmap is blit-ted to it after the dc instance is deleted. More detailed information about buffering is provided in the following paragraphs.

1$ This is used when the application needs to redraw the lines from the instance data due to a resize (and later due to a load from file). Again, we use the Draw-Func() wrapper. In this case, we walk the list of lines stored in the instance variable, recreate the pen for each line (currently all the same—support for changing pen characteristics will be added shortly), and then draw all the coordinate tuples added for that line.

The sketch example uses two special subclasses of wx.DC to allow the use of a buffer for drawing. A drawing buffer is an undisplayed area where all your primitive drawing commands can be performed one at a time, and then copied to the screen in one step. The advantage of a buffer is that the user does not see individual drawing commands happening, and thus, the screen refreshes with less flicker. For this reason, buffering is commonly used in animation or in cases where the drawing is made up of several smaller parts.

In wxPython, there are two classes used for buffering: wx.BufferDC, usually used to buffer a wx.ClientDC; and wx.BufferPaintDC, used to buffer a wx.PaintDC. Each works essentially the same way. The buffer device context is created with two arguments. The first is a target device context of the appropriate type (for example, in line © of listing 6.1, it's a new wx.ClientDC instance). The second is a wx.Bitmap object. In listing 6.1, we create a bitmap using the wx.EmptyBitmap function. When draw commands are made to the buffered device context, an internal wx.MemoryDC is used to draw to the bitmap. When the buffer object is destroyed, the C + + destructor uses the Blit() method to automatically copy the bitmap to the target. In wxPython, the destruction typically occurs when the object drops out of scope. The implication of this is that buffered device contexts are only useful when created temporarily, so that they can be destroyed and do the blit.

For example, in the OnPaint() method of listing 6.1, the self.buffer bitmap has already been written during the events that built the sketch. The buffered object simply needs to be created, thereby establishing a connection between the existing bitmap and the temporary wx.PaintDC() for the window. The method ends, and the buffered DC immediately drops out of scope, triggering its destructor, and copying the bitmap to the screen.

Functions of the device context

When using device contexts, remember to use the correct context for the kind of drawing you are trying to do (specifically, remember the distinction between wx.PaintDC and wx.ClientDC). Once you have the correct device context, then you can do something with it. Table 6.2 lists some of the more interesting methods of wx.DC.

Table 6.2 Commonly used methods of wx.DC

Function

Description

Blit(xdest, ydest, width, height, source, xsrc, ysrc)

Copies bits directly from a source device context to the device context making the call. The xdest and ydest parameters are the starting point for the copy on the destination context. The next two parameters specify the width and height of the copy area. The source is the source device context, and xsrc and ysrc are the starting point of the copy in the source context. There are further optional parameters to specify a logical overlay function and a mask.

Clear()

Clears the device context by painting the whole thing with the current background brush.

DrawArc(x1, y1, x2, y2, xc, yc)

Draws a circular arc with a start point of (x1, y1) and an end point of (x2, y2). The point (xc, yc) is the center of the circle whose arc is drawn. The resulting arc is filled using the current brush. The function assumes that it will draw a counterclockwise arc from the start point to the end point. There is a related method, DrawEllipticalArc().

DrawBitmap(bitmap, x, y, transparent)

Copies a wx.Bitmap object starting at the point (x, y). If transparent is True, then the bitmap will be drawn transparently.

DrawCircle(x, y, radius) DrawCircle(point, radius)

Draws a circle centered at the given point with the given radius. There is a related method, DrawEllipse.

DrawIcon(icon, x, y)

Draws a wx.Icon object to the context, starting at the point (x, y).

DrawLine(x1, y1, x2, y2)

Draws a line from the point (x1, y1) to the point (x2, y2). There is a related method DrawLines() which takes a Python list of wx.Point objects and connects them.

DrawPolygon(points)

Draws a polygon, given a Python list of wx.Point objects. Differs from DrawLines() in that the end point is connected to the first point, and that the resulting shape is filled using the current brush. There are optional parameters to set an x and y offset and a fill style.

DrawRectangle(x, y, width, height)

Draws a rectangle whose upper left corner is (x, y) and which has the given width and height. The rectangle is filled.

Table 6.2 Commonly used methods of wx.DC (continued)

Function

Description

DrawText(text, x, y)

Draws the given string starting at the point (x, y), using the current font. Related functions include DrawRotatedText() and GetTextExtent(). Text items have separate text foreground and background color properties.

FloodFill(x, y, color, style)

Performs a flood fill starting at (x, y) and using the color of the current brush. The style parameter is optional. The default, wx.flood_surface, assumes the color parameter is the surface to flood—it stops when any other color is found. The other value, wx.flood_border, assumes the color is the border of the shape to flood, and flooding stops when that color is found.

GetBackground() SetBackground(brush)

The background brush is a wx.Brush object, and is used when the Clear() method is called.

GetBrush() SetBrush(brush)

The Brush is a wx.Brush object and is used to fill any shapes that are drawn on the device context.

GetFont() SetFont(font)

The font is a wx.Font object and is used for all text draw operations.

GetPen() SetPen(pen)

The pen is a wx.Pen object and is used for all drawing operations that draw a line.

GetPixel(x, y)

Returns a wx.colour object for the pixel at (x, y).

GetSize() GetSizeTuple()

Returns the pixel size of the device context as either a wx.size object or a Python tuple.

This is not an exhaustive list. In the interest of simplicity, several of the more obscure drawing methods were left out, as were text processing and pixel mapping functions. Those methods will be described in chapter 12.

Was this article helpful?

+2 -7

Responses

  • brady
    How to create 2 lines and circle in wx.dc?
    8 years ago

Post a comment