Ross Wan's World!

Python, Ajax, PHP and Linux.

wxPython:GDI

Posted by Ross Wan 于 2008/05/04

       GDI(Graphics Device Interface)是一套图形接口,主要负责管理一些图形设备,如显示器、打印机等等。使用 GDI ,程序员可以直接在显示器或者打印机上显示数据,而不必关心具体设备的细节,真正做到与设备无关。

       从程序员的角度来看,GDI 是一套管理图形的类和方法的集合,它由 2D Vector Graphics(二维矢量图形)、Fonts(字体)和 Images(图像)组成。

       使用 GDI 绘画图形之前,必须先创建一个 device context(DC),在 wxPython 对应的是 wx.DC。但 wx.DC 类并不能直接使用,必须根据特定的环境来选择 wx.DC 的一个派生类!

wx.DC 的预定义派生类:

  • wx.BufferedDC
  • wx.BufferedPaintDC
  • wx.PostScriptDC
  • wx.MemoryDC
  • wx.PrinterDC
  • wx.ScreenDC
  • wx.ClientDC
  • wx.PaintDC
  • wx.WindowDC

       wx.ScreenDC 用于在屏幕上绘图;wx.WindowDC 用于窗口绘图(仅适用于 windows 操作系统);wx.ClientDC 用于窗口用户区域(client area)绘图;wx.PaintDC 跟 wx.ClientDC 一样,唯一的不同点是,wx.PaintDC 只能用于 wx.PaintEvent 事件中,而 wx.ClientDC 不能;wx.MemoryDC 用于位图绘画;wx.PostScriptDC 用于编写适用于任何平台的 PostScript 文件;wx.PrinterDC 用于访问打印机(仅适用于 windows 操作系统)。

绘制一条简单的直线(Drawing a simple line)

       我们的第一个例子就是在窗口用户区域绘画一条直线。

 DrawLine(int x1, int y1, int x2, int y2)

       绘制一条从指定起点到终点的直线,但不包括终点。

#!/usr/bin/python
# line1.py

import wx

class Line(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))

        wx.FutureCall(2000, self.DrawLine)

        self.Centre()
        self.Show(True)

    def DrawLine(self):
        dc = wx.ClientDC(self)
        dc.DrawLine(50, 60, 190, 60)

app = wx.App()
Line(None, -1, ‘Line’)
app.MainLoop()

 wx.FutureCall(2000, self.DrawLine)

       必须在窗口创建之后才调用 DrawLine() 来绘画直线,所以使用 wx.FutureCall() 推迟2秒调用 DrawLine()。

 def DrawLine(self):
     dc = wx.ClientDC(self)
     dc.DrawLine(50, 60, 190, 60)

       创建一个 wx.ClientDC,其参数是将要绘制图形的窗口的引用。然后调用其 DrawLine() 来绘画直线。

       我们必须注意,当窗口的大小改变时,绘画的直线会自动消失!因为每当窗口改变大小,或者最小化,又或者被其它窗口覆盖着,窗口都会被重画,所以我们必须每 次在窗口发生改变时,对直线进行重画。使用 wx.PaintEvent 事件是一个理想的解决方案,它在窗口重画时会触发。

#!/usr/bin/python
# line2.py

import wx

class Line(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        dc.DrawLine(50, 60, 190, 60)

app = wx.App()
Line(None, -1, ‘Line’)
app.MainLoop()

       注意,我们必须使用 wx.PaintDC 来代替 wx.ClientDC!

二维矢量图形(2D Vector Graphics)

       目前有两种不同的计算机图形:矢量图形(Vector Graphics)和光栅图形(Raster Graphics)。光栅图形以像素集合来呈现一幅图像;而矢量图形则使用数学公式计算创建的几何元素,如点、直线、曲线、多边形来呈现一幅图像。

两种计算机图形都有其各自的优点和缺点。相对光栅图形,矢量图形有如下的优点:

  • 文件更小
  • 图像能够无限量放大
  • 无论移动、缩放、填充还是旋转,都不会令到图像失真。

矢量图形几何元素的类型:

  • points(点)
  • lines(直线)
  • polylines(折线)
  • polygons(多边形)
  • circles(圆形)
  • ellipses(椭圆形)
  • Splines(样条线)

Device context 属性:

Attribute Object Default value Get Method Set Method
Brush wx.Brush wx.WHITE_BRUSH wx.Brush GetBrush() SetBrush(wx.Brush brush)
Pen wx.Pen wx.BLACK_PEN wx.Pen GetPen() SetPen(wx.Pen pen)
Mapping Mode wx.MM_TEXT int GetMapMode() SetMapMode(int mode)
BackgroundMode wx.TRANSPARENT int GetBackgroundMode() SetBackgroundMode(int mode)
Text background colour wx.Colour wx.WHITE wx.Colour GetTextBackground() SetTextBackground(wx.Colour colour)
Text foreground colour wx.Colour wx.BLACK wx.Colour GetTextForeground() SetTextForeground(wx.Colour colour)

基本对象(Basic Elements)

       下面,我们将介绍几个基本图形对象:Colours、Brushes、Pens、Joins、Caps、Gradients。

颜色(Colours

       颜色是由红(Red)、绿(Green)、蓝(Blue)(RGB)组合而成的数值,其范围是 0~255。有3种标记可以表示颜色:例如 wx.Colour(0,0,255)、’BLUE’、’#0000FF’,它们都表示同一种颜色。

下面是预定义的颜色名列表:

AQUAMARINE BLACK BLUE BLUE VIOLET BROWN
CADET BLUE CORAL CORNFLOWER BLUE CYAN DARK GREY
DARK GREEN DARK OLIVE GREEN DARK ORCHID DARK SLATE BLUE DARK SLATE GREY
DARK TURQUOISE DIM GREY FIREBRICK FOREST GREEN GOLD
GOLDENROD GREY GREEN GREEN YELLOW INDIAN RED
KHAKI LIGHT BLUE LIGHT GREY LIGHT STEEL BLUE LIME GREEN
MAGENTA MAROON MEDIUM AQUAMARINE MEDIUM BLUE MEDIUM FOREST GREEN
MEDIUM GOLDENROD MEDIUM ORCHID MEDIUM SEA GREEN MEDIUM SLATE BLUE MEDIUM SPRING GREEN
MEDIUM TURQUOISE MEDIUM VIOLET RED MIDNIGHT BLUE NAVY ORANGE
ORANGE RED ORCHID PALE GREEN PINK PLUM
PURPLE RED SALMON SEA GREEN SIENNA
SKY BLUE SLATE BLUE SPRING GREEN STEEL BLUE TAN
THISTLE TURQUOISE VIOLET VIOLET RED WHEAT
WHITE YELLOW YELLOW GREEN
#!/usr/bin/python
# colours.py

import wx

class Colours(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 280))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        dc.SetPen(wx.Pen(‘#d4d4d4’))

        dc.SetBrush(wx.Brush(‘#c56c00’))
        dc.DrawRectangle(10, 15, 90, 60)

        dc.SetBrush(wx.Brush(‘#1ac500’))
        dc.DrawRectangle(130, 15, 90, 60)

        dc.SetBrush(wx.Brush(‘#539e47’))
        dc.DrawRectangle(250, 15, 90, 60)

        dc.SetBrush(wx.Brush(‘#004fc5’))
        dc.DrawRectangle(10, 105, 90, 60)

        dc.SetBrush(wx.Brush(‘#c50024’))
        dc.DrawRectangle(130, 105, 90, 60)

        dc.SetBrush(wx.Brush(‘#9e4757’))
        dc.DrawRectangle(250, 105, 90, 60)

        dc.SetBrush(wx.Brush(‘#5f3b00’))
        dc.DrawRectangle(10, 195, 90, 60)

        dc.SetBrush(wx.Brush(‘#4c4c4c’))
        dc.DrawRectangle(130, 195, 90, 60)

        dc.SetBrush(wx.Brush(‘#785f36’))
        dc.DrawRectangle(250, 195, 90, 60)

app = wx.App()
Colours(None, -1, ‘Colours’)
app.MainLoop()

wx.Pen

       Pen 是一个基本的图形对象,它可以用来绘画直线、曲线、矩形、椭圆、多边形和其它图形。

 wx.Pen(wx.Colour colour, widht=1, style=wx.SOLID)

       wx.Pen 其构造函数有三个参数:Colour、width、style。下面是可选的 style:

  • wx.SOLID
  • wx.DOT
  • wx.LONG_DASH
  • wx.SHORT_DASH
  • wx.DOT_DASH
  • wx.TRANSPARENT

#!/usr/bin/python
# pens.py

import wx

class Pens(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 190))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)

        dc.SetPen(wx.Pen(‘#4c4c4c’, 1, wx.SOLID))
        dc.DrawRectangle(10, 15, 90, 60)

        dc.SetPen(wx.Pen(‘#4c4c4c’, 1, wx.DOT))
        dc.DrawRectangle(130, 15, 90, 60)

        dc.SetPen(wx.Pen(‘#4c4c4c’, 1, wx.LONG_DASH))
        dc.DrawRectangle(250, 15, 90, 60)

        dc.SetPen(wx.Pen(‘#4c4c4c’, 1, wx.SHORT_DASH))
        dc.DrawRectangle(10, 105, 90, 60)

        dc.SetPen(wx.Pen(‘#4c4c4c’, 1, wx.DOT_DASH))
        dc.DrawRectangle(130, 105, 90, 60)

        dc.SetPen(wx.Pen(‘#4c4c4c’, 1, wx.TRANSPARENT))
        dc.DrawRectangle(250, 105, 90, 60)

app = wx.App()
Pens(None, -1, ‘Pens’)
app.MainLoop()

       如果我们没有指定 brush(用于填充),默认使用 wx.WHITE_BRUSH(以白色来填充矩形)。

Joins and Caps

       Pen 对象还有两个附加的参数:Join 和 Cap。Join 定义两条线条是如何接合的。下面是可选的 Join style(默认为 wx.JOIN_ROUND):

  • wx.JOIN_MITER
  • wx.JOIN_BEVEL
  • wx.JOIN_ROUND

       Cap 定义线条的末端如何绘画,可选的 style:

  • wx.CAP_ROUND
  • wx.CAP_PROJECTING
  • wx.CAP_BUTT

#!/usr/bin/python
# joinscaps.py

import wx

class JoinsCaps(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(330, 300))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)

        pen = wx.Pen(‘#4c4c4c’, 10, wx.SOLID)

        pen.SetJoin(wx.JOIN_MITER)
        dc.SetPen(pen)
        dc.DrawRectangle(15, 15, 80, 50)

        pen.SetJoin(wx.JOIN_BEVEL)
        dc.SetPen(pen)
        dc.DrawRectangle(125, 15, 80, 50)

        pen.SetJoin(wx.JOIN_ROUND)
        dc.SetPen(pen)
        dc.DrawRectangle(235, 15, 80, 50)

        pen.SetCap(wx.CAP_BUTT)
        dc.SetPen(pen)
        dc.DrawLine(30, 150,  150, 150)

        pen.SetCap(wx.CAP_PROJECTING)
        dc.SetPen(pen)
        dc.DrawLine(30, 190,  150, 190)

        pen.SetCap(wx.CAP_ROUND)
        dc.SetPen(pen)
        dc.DrawLine(30, 230,  150, 230)

        pen2 = wx.Pen(‘#4c4c4c’, 1, wx.SOLID)
        dc.SetPen(pen2)
        dc.DrawLine(30, 130, 30, 250)
        dc.DrawLine(150, 130, 150, 250)
        dc.DrawLine(155, 130, 155, 250)

app = wx.App()
JoinsCaps(None, -1, ‘Joins and Caps’)
app.MainLoop()

渐变色(Gradients

       梯度渐变可以使一种颜色平滑地通过光影变化到另一种颜色。在2D 绘图程序中,它经常被用来创造五彩缤纷的背景颜色,以及模拟光影效果。

  GradientFillLinear(wx.Rect rect, wx.Colour initialColour, wx.Colour destColour, int nDirection=wx.EAST)

       这个方法是使用线形渐变来填充一个区域 rect,initalColour 指定起始颜色,destColour 指定终结颜色,nDirection 指定颜色变化的方向,默认是 wx.EAST。

#!/usr/bin/python
# gradients.py

import wx

class Gradients(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(220, 260))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)

        dc.GradientFillLinear((20, 20, 180, 40), ‘#ffec00’, ‘#000000’, wx.NORTH)
        dc.GradientFillLinear((20, 80, 180, 40), ‘#ffec00’, ‘#000000’, wx.SOUTH)
        dc.GradientFillLinear((20, 140, 180, 40), ‘#ffec00’, ‘#000000’, wx.EAST)
        dc.GradientFillLinear((20, 200, 180, 40), ‘#ffec00’, ‘#000000’, wx.WEST)

app = wx.App()
Gradients(None, -1, ‘Gradients’)
app.MainLoop()

wx.Brush

       Brush 是用于填充图形的背景,如矩形、椭圆和多边形等。

 wx.Brush(wx.Colour colour, style=wx.SOLID)

下面是可选的 styles:

  • wx.SOLID
  • wx.STIPPLE
  • wx.BDIAGONAL_HATCH
  • wx.CROSSDIAG_HATCH
  • wx.FDIAGONAL_HATCH
  • wx.CROSS_HATCH
  • wx.HORIZONTAL_HATCH
  • wx.VERTICAL_HATCH
  • wx.TRANSPARENT

#!/usr/bin/python
# brushes.py

import wx

class Brush(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 280))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)

        dc.SetBrush(wx.Brush(‘#4c4c4c’, wx.CROSS_HATCH))
        dc.DrawRectangle(10, 15, 90, 60)

        dc.SetBrush(wx.Brush(‘#4c4c4c’, wx.SOLID))
        dc.DrawRectangle(130, 15, 90, 60)

        dc.SetBrush(wx.Brush(‘#4c4c4c’, wx.BDIAGONAL_HATCH))
        dc.DrawRectangle(250, 15, 90, 60)

        dc.SetBrush(wx.Brush(‘#4c4c4c’, wx.CROSSDIAG_HATCH))
        dc.DrawRectangle(10, 105, 90, 60)

        dc.SetBrush(wx.Brush(‘#4c4c4c’, wx.FDIAGONAL_HATCH))
        dc.DrawRectangle(130, 105, 90, 60)

        dc.SetBrush(wx.Brush(‘#4c4c4c’, wx.HORIZONTAL_HATCH))
        dc.DrawRectangle(250, 105, 90, 60)

        dc.SetBrush(wx.Brush(‘#4c4c4c’, wx.VERTICAL_HATCH))
        dc.DrawRectangle(10, 195, 90, 60)

        dc.SetBrush(wx.Brush(‘#4c4c4c’, wx.TRANSPARENT))
        dc.DrawRectangle(130, 195, 90, 60)

app = wx.App()
Brush(None, -1, ‘Brushes’)
app.MainLoop()

自定义填充模式(Custom Patterns)

       当 Brush 的预定义填充模式不能满足时,我们可以创建自定义模式。

 wx.Brush BrushFromBitmap(wx.Bitmap stippleBitmap)

       创建一个用位图填充的 Brush。

#!/usr/bin/python
# custompatterns.py

import wx

class CustomPatterns(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 280))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)

        dc.SetPen(wx.Pen(‘#C7C3C3’))

        brush1 = wx.BrushFromBitmap(wx.Bitmap(‘pattern1.png’))
        dc.SetBrush(brush1)
        dc.DrawRectangle(10, 15, 90, 60)

        brush2 = wx.BrushFromBitmap(wx.Bitmap(‘pattern2.png’))
        dc.SetBrush(brush2)
        dc.DrawRectangle(130, 15, 90, 60)

        brush3 = wx.BrushFromBitmap(wx.Bitmap(‘pattern3.png’))
        dc.SetBrush(brush3)
        dc.DrawRectangle(250, 15, 90, 60)

        brush4 = wx.BrushFromBitmap(wx.Bitmap(‘pattern4.png’))
        dc.SetBrush(brush4)
        dc.DrawRectangle(10, 105, 90, 60)

        brush5 = wx.BrushFromBitmap(wx.Bitmap(‘pattern5.png’))
        dc.SetBrush(brush5)
        dc.DrawRectangle(130, 105, 90, 60)

        brush6 = wx.BrushFromBitmap(wx.Bitmap(‘pattern6.png’))
        dc.SetBrush(brush6)
        dc.DrawRectangle(250, 105, 90, 60)

        brush7 = wx.BrushFromBitmap(wx.Bitmap(‘pattern7.png’))
        dc.SetBrush(brush7)
        dc.DrawRectangle(10, 195, 90, 60)

        brush8 = wx.BrushFromBitmap(wx.Bitmap(‘pattern8.png’))
        dc.SetBrush(brush8)
        dc.DrawRectangle(130, 195, 90, 60)

        brushr9 = wx.BrushFromBitmap(wx.Bitmap(‘pattern9.png’))
        dc.SetBrush(brushr9)
        dc.DrawRectangle(250, 195, 90, 60)

app = wx.App()
CustomPatterns(None, -1, ‘Custom Patterns’)
app.MainLoop()

基本几何对象(Basic Primitives)

点(Point)

       这是最基本的几何对象。

 DrawPoint(int x, int y)

       这个方法会在 x,y 坐标上绘画一个点。

#!/usr/bin/python
# points.py

import wx
import random

class Points(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)

        dc.SetPen(wx.Pen(‘RED’))

        for i in range(1000):
            w, h = self.GetSize()
            x = random.randint(1, w-1)
            y = random.randint(1, h-1)
            dc.DrawPoint(x, y)

app = wx.App()
Points(None, -1, ‘Points’)
app.MainLoop()

       上面的例子中,我们绘画了1000个点。

 w, h = self.GetSize()
 x = random.randint(1, w-1)

       这些点是随机分布在窗口用户区域里的,而且是动态生成的,当我们改变窗口的时候,这些点将会被重画在其它随机的位置。randint(a,b) 方法返回一个[a,b]之间的随机值。


十字准线(Cross Hair

       Cross Hair 是一条窗口等高的垂直和一条与窗口等宽的水平线,它们的中心点(交点)位于给定的坐标点。

 CrossHair(int x, int y)

       可以用这个方法绘画一个以 x,y 为中心点的十字准线。

#!/usr/bin/python
# crosshair.py

import wx

class CrossHair(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        dc.CrossHair(50, 50)

app = wx.App()
CrossHair(None, -1, ‘CrossHair’)
app.MainLoop()

       下面这个例子中,将创建一个在游戏里经常见到的瞄准线。

#!/usr/bin/python
# crosshair2.py

import wx

class CrossHair(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))

        self.Bind(wx.EVT_MOTION, self.OnMotion)
        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)

        self.SetBackgroundColour(‘WHITE’)
        self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
        self.Centre()
        self.Show(True)

    def DrawCrossHair(self, a, b):
        dc = wx.ClientDC(self)
        dc.Clear()
        dc.SetPen(wx.Pen(wx.Colour(100, 100, 100), 1, wx.DOT))
        dc.CrossHair(a, b)

    def OnMotion(self, event):
        x, y = event.GetPosition()
        self.DrawCrossHair(x, y)

    def OnLeaveWindow(self, event):
        dc = wx.ClientDC(self)
        dc.SetBackground(wx.Brush(‘WHITE’))
        dc.Clear()

app = wx.App()
CrossHair(None, -1, ‘CrossHair2’)
app.MainLoop()

 self.Bind(wx.EVT_MOTION, self.OnMotion)
 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)

      当我们在窗口上移动鼠标的时候,会触发 wx.EVT_MOTION 事件,当鼠标移出窗口时,会触发 wx.EVT_LEAVE 事件。

 def OnMotion(self, event):
     x, y = event.GetPosition()
     self.DrawCrossHair(x, y)

       取得当前鼠标的位置坐标,当以其为中心,调用 DrawCrossHair() 方法绘画十字准线。

   def DrawCrossHair(self, a, b):
     dc = wx.ClientDC(self)
     dc.Clear()
     dc.SetPen(wx.Pen(wx.Colour(100, 100, 100), 1, wx.DOT))
     dc.CrossHair(a, b)

       注意,我们使用的是 wx.ClientDC,因为这不是窗口的 Paint 事件。在绘画一个新的十字准线前,先要消除原有的,调用 clear() 方法就可以清除 device context area 的图形(它会使用默认的 wx.WHITE_BRUSH 重新填充区域,除非我们指定其它的 Brush)。

√(Check mark

 DrawCheckMark(int x, int y, int width, int height)

       DrawCheckMark() 方法可以窗口的 x,y 位置上绘画一个 √ 符号,√ 的宽度和高度由参数 width 和 height 决定。

#!/usr/bin/python
# checkmark.py

import wx

class CheckMark(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        dc.DrawCheckMark(100, 50, 30, 40)

app = wx.App()
CheckMark(None, -1, ‘Check Mark’)
app.MainLoop()

      
绘画图形(Shapes)

       在下面的例子中,我们将绘画各种不类型的图形。

#!/usr/bin/python
# shapes.py

import wx

class Shapes(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 300))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)

        dc.DrawEllipse(20, 20, 90, 60)
        dc.DrawRoundedRectangle(130, 20, 90, 60, 10)
        dc.DrawArc(240, 40, 340, 40, 290, 20)

        dc.DrawPolygon(((130, 140), (180, 170), (180, 140), (220, 110), (140, 100)))
        dc.DrawRectangle(20, 120, 80, 50)
        dc.DrawSpline(((240, 170), (280, 170), (285, 110), (325, 110)))

        dc.DrawLines(((20, 260), (100, 260), (20, 210), (100, 210)))
        dc.DrawCircle(170, 230, 35)
        dc.DrawRectangle(250, 200, 60, 60)

app = wx.App()
Shapes(None, -1, ‘Shapes’)
app.MainLoop()

绘制 Regions

       Device context 可以被划分成多个部分,称之为 regions。Regions 可以是任何形状的,如简单的矩形或者圆形。利用 Union、Intersect 和 Xor 等操作,我们可以创作出复杂的 regions。Regions 通常用作描绘轮廓(outlining)、填充(filling)和剪裁(clipping)。

       有3种方式可以创建一个 region,而最简单的就是创建一个矩形 region,更为复杂的 regions 可以通过点集和位图来创建。

wx.Region(int x=0, int y=0, int width=0, int height=0)

       创建一个矩形 region。

 wx.RegionFromPoints(list points, int fillStyle=wx.WINDING_RULE)

       创建一个多边形 region。参数 fillStyle 可以是 wx.WINDING_RULE 或者 wx.ODDEVEN_RULE。

 wx.RegionFromBitmap(wx.Bitmap bmp)

       最复杂的 region 可以通过位图来创建。

#!/usr/bin/python
# lines.py

import wx
from math import hypot, sin, cos, pi

class Lines(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(450, 400))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        size_x, size_y = self.GetClientSizeTuple()
        dc.SetDeviceOrigin(size_x/2, size_y/2)

        radius = hypot(size_x/2, size_y/2)
        angle = 0

        while (angle < 2*pi):
            x = radius*cos(angle)
            y = radius*sin(angle)
            dc.DrawLinePoint((0, 0), (x, y))
            angle = angle + 2*pi/360

app = wx.App()
Lines(None, -1, ‘Lines’)
app.MainLoop()

       在这个例子中,我们创建了360条以用户区域(client area)中心为起点的直线。相邻的两条直线之间的夹角是 1 degree。

 import wx
 from math import hypot, sin, cos, pi

       要使用到一些数学函数和数学常量。

 dc.SetDeviceOrigin(size_x/2, size_y/2)

       将用户区域的中心设置为坐标系统的原点。改变坐标系统的位置,可以减少我们绘画的难度。

 radius = hypot(size_x/2, size_y/2)

       计算出斜边的长度,这是从用户区域中心出发的最长的一条直线。

 x = radius*cos(angle)
 y = radius*sin(angle)

       计算每条直线的终点坐标。

剪裁(Clipping)

       当我们要限制绘画到某一 region 时,可以使用 SetClippingRegionAsRegion() 方法。

       下面这个例子是对上面例子的代码进行修改。

#!/usr/bin/python
# star.py

import wx
from math import hypot, sin, cos, pi

class Star(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 300))

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)

        dc.SetPen(wx.Pen(‘#424242’))
        size_x, size_y = self.GetClientSizeTuple()
        dc.SetDeviceOrigin(size_x/2, size_y/2)

        points = (((0, 85), (75, 75), (100, 10), (125, 75), (200, 85),
            (150, 125), (160, 190), (100, 150), (40, 190), (50, 125)))

        region = wx.RegionFromPoints(points)
        dc.SetClippingRegionAsRegion(region)

        radius = hypot(size_x/2, size_y/2)
        angle = 0

        while (angle < 2*pi):
            x = radius*cos(angle)
            y = radius*sin(angle)
            dc.DrawLinePoint((0, 0), (x, y))
            angle = angle + 2*pi/360

        dc.DestroyClippingRegion()

app = wx.App()
Star(None, -1, ‘Star’)
app.MainLoop()

       我们再次绘画了360条直线,但只限制绘画在用户区域的某个 region 上 —— 例子中是一个星形的 region 对象。

 region = wx.RegionFromPoints(points)
 dc.SetClippingRegionAsRegion(region)

       使用 wx.RegionFromPoins() 创建一个由一系列的点联结而成的 region,然后调用 SetClippingRegionAsRegion() 设置它为当前窗口用户区域的 region,限制在 region 上绘画图形。

 dc.DestroyClippingRegion()

       必须手动清除剪裁 region。

Operations over Regions

       我们可以通过 Union、Intersect、Substrace 和 Xor 操作,使多个 regions 可以组合创建出一个复杂的 region。

       下面的例子将展示对多个 regions 的操作。

#!/usr/bin/python
# operations.py

import wx

class Operations(wx.Frame):
     def __init__(self, parent, id, title):
         wx.Frame.__init__(self, parent, id, title, size=(270, 220))

         self.Bind(wx.EVT_PAINT, self.OnPaint)

         self.Centre()
         self.Show(True)

     def OnPaint(self, event):
         dc = wx.PaintDC(self)
         dc.SetPen(wx.Pen(‘#d4d4d4’))

         dc.DrawRectangle(20, 20, 50, 50)
         dc.DrawRectangle(30, 40, 50, 50)

         dc.SetBrush(wx.Brush(‘#ffffff’))
         dc.DrawRectangle(100, 20, 50, 50)
         dc.DrawRectangle(110, 40, 50, 50)
         region1 = wx.Region(100, 20, 50, 50)
         region2 = wx.Region(110, 40, 50, 50)
         region1.IntersectRegion(region2)
         rect = region1.GetBox()
         dc.SetClippingRegionAsRegion(region1)
         dc.SetBrush(wx.Brush(‘#ff0000’))
         dc.DrawRectangleRect(rect)
         dc.DestroyClippingRegion()

         dc.SetBrush(wx.Brush(‘#ffffff’))
         dc.DrawRectangle(180, 20, 50, 50)
         dc.DrawRectangle(190, 40, 50, 50)
         region1 = wx.Region(180, 20, 50, 50)
         region2 = wx.Region(190, 40, 50, 50)
         region1.UnionRegion(region2)
         dc.SetClippingRegionAsRegion(region1)
         rect = region1.GetBox()
         dc.SetBrush(wx.Brush(‘#fa8e00’))
         dc.DrawRectangleRect(rect)
         dc.DestroyClippingRegion()

         dc.SetBrush(wx.Brush(‘#ffffff’))
         dc.DrawRectangle(20, 120, 50, 50)
         dc.DrawRectangle(30, 140, 50, 50)
         region1 = wx.Region(20, 120, 50, 50)
         region2 = wx.Region(30, 140, 50, 50)
         region1.XorRegion(region2)
         rect = region1.GetBox()
         dc.SetClippingRegionAsRegion(region1)
         dc.SetBrush(wx.Brush(‘#619e1b’))
         dc.DrawRectangleRect(rect)
         dc.DestroyClippingRegion()

         dc.SetBrush(wx.Brush(‘#ffffff’))
         dc.DrawRectangle(100, 120, 50, 50)
         dc.DrawRectangle(110, 140, 50, 50)
         region1 = wx.Region(100, 120, 50, 50)
         region2 = wx.Region(110, 140, 50, 50)
         region1.SubtractRegion(region2)
         rect = region1.GetBox()
         dc.SetClippingRegionAsRegion(region1)
         dc.SetBrush(wx.Brush(‘#715b33’))
         dc.DrawRectangleRect(rect)
         dc.DestroyClippingRegion()

         dc.SetBrush(wx.Brush(‘#ffffff’))
         dc.DrawRectangle(180, 120, 50, 50)
         dc.DrawRectangle(190, 140, 50, 50)
         region1 = wx.Region(180, 120, 50, 50)
         region2 = wx.Region(190, 140, 50, 50)
         region2.SubtractRegion(region1)
         rect = region2.GetBox()
         dc.SetClippingRegionAsRegion(region2)
         dc.SetBrush(wx.Brush(‘#0d0060’))
         dc.DrawRectangleRect(rect)
         dc.DestroyClippingRegion()

app = wx.App()
Operations(None, -1, ‘Operations’)
app.MainLoop()

逻辑单位和设备单位的转换

       我们在用户区域绘画文本或者几何图形所有到位置坐标,是使用逻辑单位的(Logical unit)。

       DrawText() 的位置参数是 x,y 是使用逻辑单位计算的,而设备在绘制文本时,是使用设备单位的。逻辑和设备单位有时是一致的,但有时可能不尽相同。人们经常使用的毫米 (millimeters)是逻辑单位,但设备单位要视乎具体的设备而定,例如显示器屏幕使用像素(pixel)为单位。

       当然,逻辑单位与设备单位之间是可以相互转换的,wxPython 支持下列的转换方式(Mapping Mode):

Mode Logical Unit
wx.MM_TEXT     1 pixel
wx.MM_METRIC     1 millimeter
wx.MM_LOMETRIC     1/10 of a millimeter
wx.MM_POINTS     1 point, 1/72 of an inch
wx.MM_TWIPS     1/20 of a point or 1/1440 of an inch

       默认是 wx.MM_TEXT,在这个方式下,逻辑单位与设备单位相同,都是像素(相对于显示器来说)。

       调用 SetMapMode() 方法,可以设置不用的 maping modes。

       下面的例子,将绘制一把尺子,以像素为单位来量度显示器的尺寸。

#!/usr/bin/python
# ruler1.py

import wx

RW = 701 # ruler widht
RM = 10  # ruler margin
RH = 60  # ruler height

class Ruler1(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(RW + 2*RM, 60),
            style=wx.FRAME_NO_TASKBAR | wx.NO_BORDER | wx.STAY_ON_TOP)
        self.font = wx.Font(7, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
            wx.FONTWEIGHT_BOLD, False, ‘Courier 10 Pitch’)

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
        self.Bind(wx.EVT_MOTION, self.OnMouseMove)

        self.Centre()
        self.Show(True)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)

        brush = wx.BrushFromBitmap(wx.Bitmap(‘granite.png’))
        dc.SetBrush(brush)
        dc.DrawRectangle(0, 0, RW+2*RM, RH)
        dc.SetFont(self.font)

        dc.SetPen(wx.Pen(‘#F8FF25’))
        dc.SetTextForeground(‘#F8FF25’)

        for i in range(RW):
            if not (i % 100):
                dc.DrawLine(i+RM, 0, i+RM, 10)
                w, h = dc.GetTextExtent(str(i))
                dc.DrawText(str(i), i+RM-w/2, 11)
            elif not (i % 20):
                dc.DrawLine(i+RM, 0, i+RM, 8)
            elif not (i % 2): dc.DrawLine(i+RM, 0, i+RM, 4)

    def OnLeftDown(self, event):
        pos = event.GetPosition()
        x, y = self.ClientToScreen(event.GetPosition())
        ox, oy = self.GetPosition()
        dx = x – ox
        dy = y – oy
        self.delta = ((dx, dy))

    def OnMouseMove(self, event):
        if event.Dragging() and event.LeftIsDown():
            x, y = self.ClientToScreen(event.GetPosition())
            fp = (x – self.delta[0], y – self.delta[1])
            self.Move(fp)

    def OnRightDown(self, event):
        self.Close()

app = wx.App()
Ruler1(None, -1, ”)
app.MainLoop()

 wx.Frame.__init__(self, parent, id, title, size=(RW + 2*RM, 60), style=wx.FRAME_NO_TASKBAR |
    wx.NO_BORDER | wx.STAY_ON_TOP)

       创建一个没有边框的窗口,尺子的大小是 RW + 2*RM = 701 + 20 = 721。尺子将显示700个刻度,0~700,也即是701pixels,再加上尺子左右两边各留空10pixels,加起来刚好 701 + 10 * 2 = 721。

 brush = wx.BrushFromBitmap(wx.Bitmap(‘granite.png’))
 dc.SetBrush(brush)
 dc.DrawRectangle(0, 0, RW+2*RM, RH)

       使用自定义的 Brush 来填充窗口背景。

 w, h = dc.GetTextExtent(str(i))
 dc.DrawText(str(i), i+RM-w/2, 11)

       GetTextExtent() 返回文本的宽度和高度。

       因为窗口没有边框,所以要移动尺子的话,必须通过附加的 OnLeftDown() 和 OnMouseMove() 方法来实现。

实用例子(Practical Examples)

图表(Charts)

       Charts (图表)并不属于 wxPython 的 widgets,但我们可以利用 GDI 来绘制。在下面的例子中,我们将创建一个简单的图表。

#!/usr/bin/python
# linechart.py

import wx

data = ((10, 9), (20, 22), (30, 21), (40, 30), (50, 41),
(60, 53), (70, 45), (80, 20), (90, 19), (100, 22),
(110, 42), (120, 62), (130, 43), (140, 71), (150, 89),
(160, 65), (170, 126), (180, 187), (190, 128), (200, 125),
(210, 150), (220, 129), (230, 133), (240, 134), (250, 165),
(260, 132), (270, 130), (280, 159), (290, 163), (300, 94))

years = (‘2003’, ‘2004’, ‘2005’)

class LineChart(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour(‘WHITE’)

        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        dc.SetDeviceOrigin(40, 240)
        dc.SetAxisOrientation(True, True)
        dc.SetPen(wx.Pen(‘WHITE’))
        dc.DrawRectangle(1, 1, 300, 200)
        self.DrawAxis(dc)
        self.DrawGrid(dc)
        self.DrawTitle(dc)
        self.DrawData(dc)

    def DrawAxis(self, dc):
        dc.SetPen(wx.Pen(‘#0AB1FF’))
        font =  dc.GetFont()
        font.SetPointSize(8)
        dc.SetFont(font)
        dc.DrawLine(1, 1, 300, 1)
        dc.DrawLine(1, 1, 1, 201)

        for i in range(20, 220, 20):
            dc.DrawText(str(i), -30, i+5)
            dc.DrawLine(2, i, -5, i)

        for i in range(100, 300, 100):
            dc.DrawLine(i, 2, i, -5)

        for i in range(3):
            dc.DrawText(years[i], i*100-13, -10)

    def DrawGrid(self, dc):
        dc.SetPen(wx.Pen(‘#d5d5d5’))

        for i in range(20, 220, 20):
            dc.DrawLine(2, i, 300, i)

        for i in range(100, 300, 100):
            dc.DrawLine(i, 2, i, 200)

    def DrawTitle(self, dc):
        font =  dc.GetFont()
        font.SetWeight(wx.FONTWEIGHT_BOLD)
        dc.SetFont(font)
        dc.DrawText(‘Historical Prices’, 90, 235)

    def DrawData(self, dc):
        dc.SetPen(wx.Pen(‘#0ab1ff’))
        for i in range(10, 310, 10):
            dc.DrawSpline(data)

class LineChartExample(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(390, 300))

        panel = wx.Panel(self, -1)
        panel.SetBackgroundColour(‘WHITE’)

        hbox = wx.BoxSizer(wx.HORIZONTAL)
        linechart = LineChart(panel)
        hbox.Add(linechart, 1, wx.EXPAND | wx.ALL, 15)
        panel.SetSizer(hbox)

        self.Centre()
        self.Show(True)

app = wx.App()
LineChartExample(None, -1, ‘A line chart’)
app.MainLoop()

 dc.SetDeviceOrigin(40, 240)
 dc.SetAxisOrientation(True, True)

       wxPython 默认坐标系统的原点 [0,0] 位于窗口用户区域的左上角,x,y 坐标的值只能是正数。为了绘制图表,我们改用笛卡尔坐标系统(cartesian corrdinate system),其 x,y 坐标可即可以为正数也可以为负数。

 SetAxisOrientation(bool xLeftRight, bool yBottomUp)

       这是 SetAxisOrientation() 方法的原型。

 self.DrawAxis(dc)
 self.DrawGrid(dc)
 self.DrawTitle(dc)
 self.DrawData(dc)

       图表的构造分别同4个方法来完成。第一个方法负责会绘制轴线,第一个方法负责绘制风格,第三个方法负责绘制标题,第四个方法负责绘制数据记录。


      

便笺(Note)

       在这个例子中,我们将创建一个自定义形状的窗口。

#!/usr/bin/python
# note.py

import wx

class Note(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title,
                        style=wx.FRAME_SHAPED |
                        wx.SIMPLE_BORDER |
                        wx.FRAME_NO_TASKBAR)

        self.font = wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
        wx.FONTWEIGHT_BOLD, False, ‘Comic Sans MS’)
        self.bitmap = wx.Bitmap(‘note.png’, wx.BITMAP_TYPE_PNG)
        self.cross = wx.Bitmap(‘cross.png’, wx.BITMAP_TYPE_PNG)

        w = self.bitmap.GetWidth()
        h = self.bitmap.GetHeight()
        self.SetClientSize((w, h))

        if wx.Platform == ‘__WXGTK__’:
            self.Bind(wx.EVT_WINDOW_CREATE, self.SetNoteShape)
        else: self.SetNoteShape()

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.Bind(wx.EVT_MOTION, self.OnMouseMove)

        self.bitmapRegion = wx.RegionFromBitmap(self.bitmap)
        self.crossRegion = wx.RegionFromBitmap(self.cross)

        self.bitmapRegion.IntersectRegion(self.crossRegion)
        self.bitmapRegion.Offset(170, 10)

        dc = wx.ClientDC(self)
        dc.DrawBitmap(self.bitmap, 0, 0, True)
        self.PositionTopRight()
        self.Show(True)

    def PositionTopRight(self):
        disx, disy = wx.GetDisplaySize()
        x, y = self.GetSize()
        self.Move((disx-x, 0))

    def SetNoteShape(self, *event):
        region = wx.RegionFromBitmap(self.bitmap)
        self.SetShape(region)

    def OnLeftDown(self, event):
        pos = event.GetPosition()
        if self.bitmapRegion.ContainsPoint(pos):
            self.Close()
        x, y = self.ClientToScreen(event.GetPosition())
        ox, oy = self.GetPosition()
        dx = x – ox
        dy = y – oy
        self.delta = ((dx, dy))

    def OnMouseMove(self, event):
        if event.Dragging() and event.LeftIsDown():
            x, y = self.ClientToScreen(event.GetPosition())
            fp = (x – self.delta[0], y – self.delta[1])
            self.Move(fp)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        dc.SetFont(self.font)
        dc.SetTextForeground(‘WHITE’)

        dc.DrawBitmap(self.bitmap, 0, 0, True)
        dc.DrawBitmap(self.cross, 170, 10, True)
        dc.DrawText(‘- Go shopping’, 20, 20)
        dc.DrawText(‘- Make a phone call’, 20, 50)
        dc.DrawText(‘- Write an email’, 20, 80)

app = wx.App()
Note(None, -1, ”)
app.MainLoop()

       我们创建了一个没无边框的窗口,并在上面绘画了一些自定义的图像。

 wx.Frame.__init__(self, parent, id, title,
          style=wx.FRAME_SHAPED |
          wx.SIMPLE_BORDER |
             wx.FRAME_NO_TASKBAR)

       wx.FRAME_SHAPED 允许用户自定义窗口形状,wx.SIMPLE_BORDER 移除多余的边框,wx.FRAME_NO_TASKBAR 使程序启动后不会出现在系统任务栏上。

 self.bitmap = wx.Bitmap(‘note.png’, wx.BITMAP_TYPE_PNG)
 self.cross = wx.Bitmap(‘cross.png’, wx.BITMAP_TYPE_PNG)

       自的两幅图片,第一幅是一个背景色为橙色的圆角矩形;第二幅是一个小十字图片,用来替代关闭按钮。

 w = self.bitmap.GetWidth()
 h = self.bitmap.GetHeight()
 self.SetClientSize((w, h))

       因为要将图片绘制到窗口上,所以先取得图片的大小,并以此设置窗口的大小,使得图片可以完成覆盖住窗口。

 if wx.Platform == ‘__WXGTK__’:
     self.Bind(wx.EVT_WINDOW_CREATE, self.SetNoteShape)
 else: self.SetNoteShape()

       这是一段与平台有关的代码。Linux 用户将会在 wx.WindowCreateEvent 事件触发时调用 SetNoteShape()方法。

 def SetNoteShape(self, *event):
     region = wx.RegionFromBitmap(self.bitmap)
     self.SetShape(region)

       用位图设置窗口的形状。

 dc.DrawBitmap(self.bitmap, 0, 0, True)
 dc.DrawBitmap(self.cross, 170, 10, True)
 dc.DrawText(‘- Go shopping’, 20, 20)
 dc.DrawText(‘- Make a phone call’, 20, 50)
 dc.DrawText(‘- Write an email’, 20, 80)

       在 OnPaint() 方法内,分别绘制2个位图和3个文本。

       最后,就关于是如何关闭窗口:

 self.bitmapRegion = wx.RegionFromBitmap(self.bitmap)
 self.crossRegion = wx.RegionFromBitmap(self.cross)

 self.bitmapRegion.IntersectRegion(self.crossRegion)
 self.bitmapRegion.Offset(170, 10)
 …
 pos = event.GetPosition()
 if self.bitmapRegion.ContainsPoint(pos):
     self.Close()

Advertisements

2条回应 to “wxPython:GDI”

  1. Ross Wan said

    翻译自:The wxPython tutorial

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

 
%d 博主赞过: