wxPython in Action(三十七)

6.1 在屏幕上绘画

你的绘画程序的首先的工作是勾画线条并显示出来。像其它的GUI工具一样, wxPython提供了一套独立于设备的工具用于绘画。下面,我们将讨论如何在屏幕上绘画。

6.1.1 如何在屏幕上绘画

要 在屏幕上绘画,我们要用到一个名为device context(设备上下文)的wxPython对象。设备上下文代表抽象的设备,它对于所有的设备有一 套公用的绘画方法,所以对于不同的设备,你的代码是相同的,而不用考虑你所在的具体设备。设备上下文使用抽象的wxPython的类wx.DC和其子类来 代表。由于wx.DC是抽象的,所以对于你的应用程序,你需要使用它的子类。

使用设备上下文

表6.1 显示了wx.DC的子类及其用法。设备上下文用来在wxPython窗口部件上绘画,它应该是局部的,临时性的,不应该以实例变量、全局变量或其它形式在 方法调用之间保留。在某些平台上,设备上下文是有限的资源,长期持有wx.DC可能导致你的程序不稳定。由于wxPython内部使用设备上下文的方式, 对于在窗口部件中绘画,就存在几个有着细微差别的wx.DC的子类。第十二章将更详细地说明这些差别。

表6.1

wx.BufferedDC:用于缓存一套绘画命令,直到命令完整并准备在屏幕上绘画。这防止了显示中不必要的闪烁。

wx.BufferedPaintDC:和wx.BufferedDC一样,但是只能用在一个wx.PaintEvent的处理中。仅临时创建该类的实例。

wx.ClientDC:用于在一个窗口对象上绘画。当你想在窗口部件的主区域上(不包括边框或别的装饰)绘画时使用它。主区域有时也称为客户区。wx.ClientDC类也应临时创建。该类仅适用于wx.PaintEvent的处理之外。

wx.MemoryDC:用于绘制图形到内存中的一个位图中,此时不被显示。然后你可以选择该位图,并使用wx.DC.Blit()方法来把这个位图绘画到一个窗口中。

wx.MetafileDC:在Windows操作系统上,wx.MetafileDC使你能够去创建标准窗口图元文件数据。

wx.PaintDC:等同于wx.ClientDC,除了它仅用于一个wx.PaintEvent的处理中。仅临时创建该类的实例。

wx.PostScriptDC:用于写压缩的PostScript文件。

wx.PrinterDC:用于Windows操作系统上,写到打印机。

wx.ScreenDC:用于直接在屏幕上绘画,在任何被显示的窗口的顶部或外部。该类只应该被临时创建。

wx.WindowDC:用于在一个窗口对象的整个区域上绘画,包括边框以及那些没有被包括在客户区域中的装饰。非Windows系统可能不支持该类。

下例6.1包含了显示图6.1的代码。因为该代码展示了基于设备上下文绘画的技巧,所以我们将对其详细注释。

例6.1 初始的SketchWindow代码

import wx


class SketchWindow(wx.Window):
    def __init__(self, parent, ID):
        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)#1 创建一个wx.Pen对象
        self.lines = []
        self.curLine = []
        self.pos = (0, 0)
        self.InitBuffer()

    #2 连接事件
        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.OnIdle)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def InitBuffer(self):
        size = self.GetClientSize()

    #3 创建一个缓存的设备上下文
        self.buffer = wx.EmptyBitmap(size.width, size.height)
        dc = wx.BufferedDC(None, self.buffer)

    #4 使用设备上下文
        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
        dc.Clear()
        self.DrawLines(dc)

        self.reInitBuffer = False

    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()#5 得到鼠标的位置
        self.CaptureMouse()#6 捕获鼠标

    def OnLeftUp(self, event):
        if self.HasCapture():
            self.lines.append((self.color,
                               self.thickness,
                               self.curLine))
            self.curLine = []
            self.ReleaseMouse()#7 释放鼠标

    def OnMotion(self, event):
        if event.Dragging() and event.LeftIsDown():#8 确定是否在拖动
            dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)#9 创建另一个缓存的上下文
            self.drawMotion(dc, event)
        event.Skip()
    #10 绘画到设备上下文
    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

    def OnSize(self, event):
        self.reInitBuffer = True #11 处理一个resize事件

    def OnIdle(self, event):#12 空闲时的处理
        if self.reInitBuffer:
            self.InitBuffer()
            self.Refresh(False)

    def OnPaint(self, event):
        dc = wx.BufferedPaintDC(self, self.buffer)#13 处理一个paint(描绘)请求

    #14 绘制所有的线条
    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):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, "Sketch Frame",
                size=(800,600))
        self.sketch = SketchWindow(self, -1)

if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = SketchFrame(None)
    frame.Show(True)
    app.MainLoop()

说明

#1:wx.Pen实例决定绘画到设备上下文的线条的颜色、粗细和样式。样式除了wx.SOLID还有wx.DOT, wx.LONGDASH, 和wx.SHORTDASH。

#2:窗口需要去响应几个不同的鼠标类型事件以便绘制图形。响应的事件有鼠标左键按下和释放、鼠标移动、窗口大小变化和窗口重绘。这里也指定了空闲时的处理。

#3: 用两步创建了缓存的设备上下文:(1)创建空的位图,它作为画面外(offscreen)的缓存(2)使用画面外的缓存创建一个缓存的设备上下文。这个缓 存的上下文用于防止我勾画线的重绘所引起的屏幕闪烁。在这节的较后面的部分,我们将更详细地讨论这个缓存的设备上下文。

#4: 这几行发出绘制命令到设备上下文;具体就是,设置背景色并清空设备上下文(dc.Clear())。必须调用dc.Clear(),其作用是产生一个 wx.EVT_PAINT事件,这样,设置的背景就显示出来了,否则屏幕颜色不会改变。wx.Brush对象决定了背景的颜色和样式。

#5:事件方法GetPositionTuple()返回一个包含鼠标敲击的精确位置的Python元组。

#6:CaptureMouse()方法控制了鼠标并在窗口的内部捕获鼠标,即使是你拖动鼠标到窗口边框的外面,它仍然只响应窗口内的鼠标动作。在程序的后面必须调用ReleaseMouse()来取消其对鼠标的控制。否则该窗口将无法通过鼠标关闭等,试将#7注释掉。

#7:ReleaseMouse ()方法将系统返回到调用CaptureMouse()之前的状态。wxPython应用程序使用一个椎栈来对捕获了鼠标的窗口的跟踪,调用 ReleaseMouse()相当于从椎栈中弹出。这意味着你需要调用相同数据的CaptureMouse()和ReleaseMouse()。

#8:这行确定移动事件是否是线条绘制的一部分,由移动事件发生时鼠标左键是否处于按下状态来确定。Dragging()和LeftIsDown()都是wx.MouseEvent的方法,如果移动事件发生时所关联的条件成立,方法返回true。

#9:由于wx.BufferedDC是一个临时创建的设备上下文,所以在我们绘制线条之前需要另外创建一个。这里,我们创建一个新的wx.ClientDC作为主要的设备上下文,并再次使用我们的实例变量位图作为缓存。

#10: 这几行实际是使用设备上下文去绘画新近的勾画线到屏幕上。首先,我们创建了coords元组,它合并了self.pos和newPos元组。这里,新的位 置来自于事件GetPositionTuple(),老的位置是最后对OnMotion()调用所得到的。我们把该元组保存到self.curLine列 表中,然后调用DrawLine()。*coords返回元组coords中的元素x1,y1,x2,y2。DrawLine()方法要求的参数形如 x1,y1,x2,y2,并从点(x1,y1)到(x2,y2)绘制一条线。勾画的速度依赖于底层系统的速度。

#11:如果窗口大小改变了,我们存储一个True值到self.reInitBuffer实例属性中。我们实际上不做任何事直到下一个空闲事件。

#12:当一个空闲产生时,如果已发生了一个或多个尺寸改变事件,这个应用程序抓住时机去响应一个尺寸改变事件。我们存储一个True值到self.reInitBuffer实例属性中,并在一个空闲产生时响应的动机是避免对于接二连三的尺寸改变事件都进行屏幕刷新。

#13: 对于所有的显示要求,都将产生wx.EVT_PAINT事件(描绘事件),并调用我们这里的方法OnPaint进行屏幕刷新(重绘),你可以看到这是出乎 意料的简单:创建一个缓存的画图设备上下文。实际上wx.PaintDC被创建(因为我们处在一个Paint请求里,所以我们需要wx.PaintDC而 非一个wx.ClientDC实例),然后在dc实例被删除后(函数返回时被销毁),位图被一块块地传送(blit)给屏幕并最终显示。关于缓存的更详细 的信息将在随后的段落中提供。

#14:当由于尺寸改变(和由于从文件载入)而导致应用程序需要根据实际数据重绘线条时,被使用。这里,我们遍历存储在实例变量self.lines中行的列表,为每行重新创建画笔,然后根据坐标绘制每一条线。

这 个例子使用了两个特殊的wx.DC的子类,以使用绘画缓存。一个绘画缓存是一个不显现的区域,其中存储了所有的绘画命令(这些命令能够一次被执行),并且 一步到位地复制到屏幕上。缓存的好处是用户看不到单个绘画命令的发生,因此屏幕不会闪烁。正因如此,缓存被普遍地用于动画或绘制是由一些小的部分组成的场 合。

在wxPython中,有两个用于缓存的类:wx.BufferDC(通常用于缓存一个wx.ClientDC)、 wx.BufferPaintDC(用于缓存一个wx.PaintDC)。它们工作方式基本上一样。缓存设备上下文的创建要使用两个参数。第一个是适当类 型的目标设备上下文(例如,在例6.1中的#9,它是一个新的wx.ClientDC实例)。第二个是一个wx.Bitmap对象。在例6.1中,我们使 用函数wx.EmptyBitmap创建一个位图。当绘画命令到缓存的设备上下文时,一个内在的wx.MemoryDC被用于位图绘制。当缓存对象被销毁 时,C++销毁器使用Blit()方法去自动复制位图到目标。在wxPython中,销毁通常发生在对象退出作用域时。这意味缓存的设备上下文仅在临时创 建时有用,所以它们能够被销毁并能用于块传送(blit)。

例如例6.1的OnPaint()方法中,self.buffer位图在建造 勾画(sketch)期间已经被写了。只需要创建缓存对象,从而建立关于窗口的已有的位图与临时wx.PaintDC()之间的连接。方法结束后,缓存 DC立即退出作用域,触发它的销毁器,同时将位图复制到屏幕。
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值