wxPython 教程 (十二): GDI

总目录:wxPython 教程目录 
本节内容:wxPython GDI
本节译自:zetcode
上一篇:wxPython 教程 (十一): wxPython 应用骨架
下一篇:wxPython 教程 (十三): 自定义控件

本节讲述 wxPython GDI 。GDI (图形设备接口) 是一个处理图形的接口,可以用它来和图形设备比如屏幕、打印机或者文件来进行交互。GDI 允许编程者在屏幕或打印机上来展示数据,而无需考虑具体设备的细节。GDI 将编程者和硬件隔离开来。

从编程者的角度来看,GDI 是处理图形的一组类和方法。GDI 包括 2D 向量图形、字体和图片。

tutorial wxpython-jiaocheng

图:GDI 结构

为了开始绘制图形,我们首先需要创建一个 device context (DC) 对象。在 wxPython 中,device context 即 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 区域进行绘制, client 区域是指除了装饰(标题栏和边框)的窗口部分。wx.PaintDC 也是在 client 区域绘制,但它和 wx.ClientDC 存在不同。wx.PaintDC 应该只用在 wx.PaintEvent 来源的情况下,而 wx.ClientDC 则不能。wx.MemoryDC 用来在 bitmap 上绘制图形。wx.PostScriptDC 被用来在任意平台上写入 POSTScript 文件。wx.PrinterDC 被用来操作打印机(仅限 Windows 系统)。

绘制直线

我们的第一个例子是在窗口的 client 区域绘制一条简单的直线。

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

这一方法可以从第一个点到第二个点绘制一条线,直线不包括第二个点的位置。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
translated by achen @ 2017/10/10
===
ZetCode wxPython tutorial 

This program draws a line on the
frame window after a while

author: Jan Bodnar
website: zetcode.com 
last edited: November 2010
"""

import wx

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

        wx.FutureCall(2000, self.DrawLine)

        self.Centre()
        self.Show()

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

if __name__ == '__main__':
    app = wx.App()
    Example(None, 'Line')
    app.MainLoop()

两秒之后,我们在屏幕上绘制了一条线。

wx.FutureCall(2000, self.DrawLine)

在窗口创建后,我们调用 DrawLine() 方法。这样做的原因是,在创建窗口的时候会进行绘制,我们自定义的绘制会丢失,所以应该在窗口绘制之后,我们在来绘制我们自己的图形。这就是我们调用 wx.FutureCall() 方法的原因。

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

我们创建了一个 wx.ClientDC device context,唯一的参数是我们想要绘制的窗口。这里的 self 代表我们的 wx.Frame 部件。我们调用 DrawLine() 方法,它在窗口上绘制直线。

理解下面的行为非常重要。如果我们调整窗口的大小,这条线将会消失。为什么呢?因为每个窗口在大小变化的时候会重新绘制,最大化的时候也会重新绘制。如果我们用另一个窗口覆盖它或者挪开的时候,它也会重新绘制,它将绘制成自己的默认状态,因此我们的直线就丢失了。我们必须在每次窗口重绘的时候来绘制直线。解决方案是 wx.PaintEvent。每次当窗口重绘时,就会触发该事件。我们会在这个实践中进行直线绘制。

可参考下面的例子:

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
translated by achen @ 2017/10/10
===
ZetCode wxPython tutorial 

This program draws a line in 
a paint event

author: Jan Bodnar
website: zetcode.com 
last edited: November 2010
"""

import wx

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

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

        self.Centre()
        self.Show()

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

if __name__ == '__main__':
    app = wx.App()
    Example(None, 'Line')
    app.MainLoop()

我们绘制了同样的直线,但这次是在 paint 事件中。

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

这里我们把 OnPaint 方法绑定到 wx.PaintEvent 事件。这意味着每次窗口重绘的时候,我们都会调用 OnPaint 方法。现在我们调整窗口大小时,直线就不会消失了。

dc = wx.PaintDC(self)

注意下,现在我们使用 wx.PaintDC device context。

tutorial wxpython-jiaocheng

图:绘制直线

电脑绘图

电脑绘图有两种,一种是向量一种是光栅 。光栅图形使用一组像素来代表图像,向量图形则使用一些几何图元,比如点、线、曲线或多边形来代表图片。这些图元使用数学等式来创建。

两种电脑绘图都有优点和缺点。向量绘图的优点在于:

  • 更小的尺寸
  • 无限缩放的能力
  • 移动、缩放、填充或者旋转不会降低图片的质量

不同类型的图元包括:

  • 线
  • 折线
  • 多边形
  • 椭圆
  • 曲线

Device context 属性

属性 对象 默认值 Get 方法 Set 方法
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)

基本概念

下面我们将介绍一些基本的对象:Colours, Brushes, Pens, Joins, Caps, Gradients.

colours

Colour 是一个类,表示 Red、Green 和 Blue (RGB) 值的组合。有效的 RGB 值在 0 到 255 之间。有 3 种方法设置颜色:可以创建 wx.Colour 对象、使用一个预定义的颜色名称或者使用 16 进制的值。wx.Colour(0,0,255), ‘BLUE’, ‘#0000FF’ ,这三个代表的是同一种颜色。

推荐一个与颜色等人的一个非常完美的工具:colorjack.com ,或者我们可以使用类似 Gimp 的工具。

我们也有一些预定义的颜色名可以来在程序中使用:

标准颜色数据库

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
# -*- coding: utf-8 -*-

"""
translated by achen @ 2017/10
===
ZetCode wxPython tutorial 

This program draws nine colours
on the window

author: Jan Bodnar
website: zetcode.com 
last edited: November 2010
"""

import wx

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

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

        self.Centre()
        self.Show()
        

    def OnPaint(self, e):
        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)
        

if __name__ == '__main__':
    app = wx.App()
    Example(None, 'Colours')
    app.MainLoop()

我们画了 9 个矩形并填充了不同的颜色。

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

我们用十六进制定义了笔刷的颜色,代表所画形状的背景填充,然后我们绘制了矩形。
tutorial wxpython-jiaocheng

图:各种颜色

 

wx.Pen

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

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

wx.Pen 构造函数有 3 个参数,颜色、宽度和样式,下面是一个可选的 pen 样式:

  • 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()

如果我们不定义特定的笔刷,将使用默认笔刷 wx.WHITE_BURSH。矩形边框由 pen 来绘制,最后一个矩形因为 style 设置的透明,所以看不到边框。
tutorial wxpython-jiaocheng

图:Pens

Join 和 cap

一个 pen 对象有两个额外的参数:Join 和 Cap。Join 定义了线和线之间如何连接,包括以下的样式:

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

当我们使用 wx.JOIN_MITER 时,线的外边缘将会延伸至一定角度并填充。wx.JOIN_BEVEL 将填充线和线之间的三角缺口。wx.JOIN_ROUND 将填充两条线之间的圆弧。默认的样式即 wx.JOIN_ROUND。

Cap定义了线的末端如何被 pen 绘制,选项包括:

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

wx.CAP_ROUND 会绘制圆形的末端, wx.CAP_PROJECTING 和 wx.CAP_BUTT 都绘制方形的末端,区别在于 wx.CAP_PROJECTING 将超出终点半个线宽。wx.CAP_ROUND 也是一样。

#!/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()
pen = wx.Pen('#4c4c4c', 10, wx.SOLID)

为了看到多种 Join 和 Cap 样式,我们需要将 pan 的宽度设置大于 1.

dc.DrawLine(150, 130, 150, 250)
dc.DrawLine(155, 130, 155, 250)

注意到两个闭合的竖直线,之间的距离是 5px,正好是当前 pen 宽度的一半。
tutorial wxpython-jiaocheng

图:Jion 和 Cap

Gradients 渐变

在计算机图形中,渐变是指从亮到暗或从一个颜色到另一个颜色的平滑转变。在 2D 绘制程序和画画程序中,渐变可以创建多彩的背景、特殊效果以及模拟光和阴影。(answers.com)

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

这个方法填充一个矩形 rect,使用线性梯度的渐变,从颜色 initialColour 到颜色 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()

在例子中,我们对 4 个居中进行了渐变填充。
tutorial wxpython-jiaocheng

图:渐变

wx.Brush

Brush 也是一个基本的绘图对象,可以绘制图形的背景色,比如矩形、椭圆或多边形。

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

wx.Brush 构造函数接受两个参数,颜色 colour 和 样式 style,下面是可用的样式:

  • 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()

在这个例子中,我们使用了不同的内置 brush 样式类型。
tutorial wxpython-jiaocheng

图:Brush

自定义模式

除了使用预定义的模式,我们也可以很容易的自定义模式。

wx.Brush BrushFromBitmap(wx.Bitmap stippleBitmap)

这一方法可以从 bitmap 创建一个自定义的 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()

我们创建了一些小的 bitmaps,这些 bitmaps 在 Gimp 中创建,都是矩形的,通常从 40 到 150px。

tutorial wxpython-jiaocheng

图:自定义模式

基本图元

最简单的几何对象就是点。

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 个点。

dc.SetPen(wx.Pen('RED'))

这里,我们设置了 pen 的颜色为红色。

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

点随机分布在窗口的 client 区域,它们是动态分布的。如果我们调整窗口的大小,这些点将重新绘制。randint(a, b) 方法返回一个区间 [a,b] 内的随机整数。
tutorial wxpython-jiaocheng

图:绘制点

形状

形状是更复杂的几何对象,下面的例子中,我们绘制了多个几何形状。

#!/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()

在我们的例子中,我们绘制了一个椭圆、圆角矩形、扇形、矩形、多边形、曲线、直线、和正方形。圆形是特殊的椭圆,正方形是特殊的矩形。

tutorial wxpython-jiaocheng

图:形状

区域

Device context 可以被划分为多个部分,叫做 Region。一个 region 可以是任何形状,可以使简单的矩形或者圆圈。使用 Union、Intersect、Substract 和 Xor 操作,我们可以从简单的 region 创建复杂的 region。region 被用来描框、填充或裁剪。

我们可以通过 3 种方式来创建 region。最简单的是创建一个矩形 region。复杂一点的 region 可以从 bitmap 中的一系列点中来创建。

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 可以用上面的方法创建。

在我们深入 region 之前,我们先创建一些简单的例子。

#!/usr/bin/python
# -*- coding: utf-8 -*-

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

class Example(wx.Frame):
  
    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 

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

        self.SetSize((350, 250))
        self.SetTitle('Lines')
        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

def main():
    
    ex = wx.App()
    Example(None)
    ex.MainLoop()    

if __name__ == '__main__':
    main()   

在这个例子中,我们从 client 区域中央绘制了 260 根直线,相邻两条线之间的角度是 1 度。我们创建了一个有意思的图片。

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

我们需要 math 模块的三个数学方法和一个数学常量。

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

SetDeviceOrigin() 方法创建了一个坐标系统的原点。我们把它放在 client 区域的中央,这样可以使我们的绘制不那么复杂。

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

这里我们得到了 Hypotenuse,它是从屏幕中间出发我们可以绘制的最长的直线。这样,多数的线不会完整绘制,重叠的部分将不可见。参考:Hypotenuse

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

这些参变数函数用来找到曲线上的 [x,y] 点。所有的点都是从原点出发,到达圆形上的点。

tutorial wxpython-jiaocheng

图:线条

Clipping 裁剪

裁剪可以限制绘制的区域。两种情况下可以使用裁剪,一种是创建特效,另一种是提高应用的性能。我们使用 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 = wx.RegionFromPoints(points)
dc.SetClippingRegionAsRegion(region)

我们使用 wx.RegionFromPoins() 方法从一系列点创建一个 region。SetClippingRegionAsRegion() 方法将绘制限制到特定区域,即五角星。

dc.DestroyClippingRegion()

我们需要销毁裁剪区域。
tutorial wxpython-jiaocheng

图:星形

Region 操作

可以组合多个 region 来创建更复杂的形状。我们可以使用 4 个操作符: Union、Interset、Substract 和 Xor,即并集、交集、差集和抑或。

下面的例子展示了 4 种操作。

#!/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()

tutorial wxpython-jiaocheng

图:Region 的集合操作

Mapping mode

说英语,用公制

英语是全球交流的语言,公制也是全球公用的测量系统。根据这篇文章,仅有3个例外,美国、利比亚和缅甸。比如,美国人使用华氏度来度量温度、加仑来度量汽油、磅来度量重量。

尽量我们在欧洲使用公制系统,但仍然有例外。美国主导着 IT 界,所以我们需要使用他们的标准,因此我们也会说我们有 17 英寸的显示器。图形可以被放置在文件中、展示在屏幕上或其他设备上(照相机、摄像机、手机)或者通过打印机打印。纸张大小可以通过毫米、points 或者英寸来设置,屏幕分辨率通过像素设置,文本质量通过每英寸的 dots 数目来确定。我们也有 dots、bits 和 samples,这是我们有 logical 单位和 device 单位的原因。

Logical 和 device 单位

如果我们绘制文本或者几何图形,我们使用 logical 单位来放置他们。
tutorial wxpython-jiaocheng

图:绘制文本

如果我们要绘制一些文本,我们提供了文本参数和 x,y 坐标,x 和 y 都是 logical 单位。设备通过 device 单位来绘制文本。Logical 单位和 device 单位可能相同,也可能不同。Logical 单位是人来使用的(毫米),device 单位是特定设备来使用的。比如,对于屏幕来说它的 device 单位就是像素。对于 HEWLETT PACKARD LaserJet 1022 来说 device 单位是 dpi(每英寸的 dots)。

目前我们讨论了多种度量单元,设备的 mapping mode 是一种从 logical 单位转换到 device 单位的方式。wxPython 有以下的 mapping mode:

Mapping 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

默认的 mapping mode 是 wx.MM_TEXT。在该模式下, logical 单元和 device 单元是一样的。当人们把元素放置在屏幕上或设计页面时,他们都是以像素来定位。如果我们想以毫米为单位来绘制结构,我们可以使用这两个标准的 mapping mode。直接在屏幕上使用毫米可能太粗,这就是为什么我们使用 wx.MM_LOMETERIC 这个 mapping mode 的原因。

tutorial wxpython-jiaocheng

图:Map mode

为了设置不同的 mapping mode,我们使用 SetMapMode() 方法。

尺子样例

尺子可以用像素为单位来度量屏幕中对象的大小。

#!/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()

在这个例子中,我们创建了一个尺子,它以像素为单位来度量屏幕中的对象。我们使用了默认的 mapping mode,即 wx.MM_TEXT。之前提到,这个模式下 logical 和 device 单位一直,都是像素。

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

我们创建了一个无边框的窗口,尺子是 721px 宽, 等于 RW + 2*RM = 701 + 20。它展示了 700 个数字,0 到 700 是 701 像素,两边都有边距即 2*10 = 20,所以宽度是 721 像素。

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

这里我们使用了自定义的 pattern,我们使用了花岗岩图片做背景。

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

这些线确保我们可以把文本正确的对齐。GetTextExtent() 方法可以返回文本的宽度和高度。

我们的窗口没有边框,所以我们必须通过额外的代码来应对移动事件, OnLeftDown() 和 OnMouseMove() 使得我们可以移动尺子。
tutorial wxpython-jiaocheng

图:尺子

实践样例

你也许会问,我什么我们需要这些 直线、pen 还有渐变?它们好在哪里?下面的脚本将给我们一些实际的例子,我们将利用我们之前所学到的东西。

图表

绘制图表是应用绘制函数的绝佳例子。图表不是 GUI 部件,gui 工具包多数不提供图表功能,一个特例是 wxWidgets 工具包(wxPython 也一样),但提供的图标非常简单,不能在实际应用中使用。开发者可以使用第三方图表库或者创建自己的图表库。

在下面的例子中,我们创建一个简单的直线图表。我们不深入细节,样例中也忽略了很多信息,仅限于提供主要思路。

#!/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] 开始的,它位于 client 区域的左上角。x 的值从左向右增长, y 的值从上至下增长,且这些值只能是负的。这些在所有 GUI 工具包中都一样。

对于图标我们需要使用笛卡尔坐标系,在其中有正值也有负值。起点一般在中间,但并非必须。

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

SetDeviceOrigin() 方法移动原点至 client 区域的一个新的目标,这叫做线性移动。然后我们使用
SetAxisOrientation() 来改变坐标轴方向。

SetAxisOrientation(bool xLeftRight, bool yBottomUp)

上面的方法简单明了,我们可以给这两个参数赋值为真或假。

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

我们将图标的构建划分为 4 个部分。第一个绘制坐标轴,第二个绘制网格,第三个绘制标题,最后一个绘制数据。

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

由于脚本比较简单,其中有一些神奇数字。事实上,我们必须计算得到他们。通过一些尝试或者计算,我们可以将文字居中或者达到其他的目的。这可能在不同操作系统上由不同的表现,但你需要多多尝试和计算。
tutorial wxpython-jiaocheng

图:图表

Note

我们使用下面的脚本展示了 GDI 的有趣的几个特性。我们将看到,如何创建一个特定形状的窗口。有一些小程序可以创建可见的笔记,用来提醒人们一些备忘事项。

#!/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()

创建一个有形状的窗口并不难。大多数的应用都是矩形的,并且都很相似,它们有菜单、工具栏、标题等等,这可能有些无聊。一些开发者会开发一些更精致的应用。使用图片可以使得我们的应用更加具有吸引力。思路如下。我们创建一个没有边框的 frame,然后在 paint 事件时在 frame 中绘制一个自定义的图片。

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

为了创建一个有形状的应用,我们必须设置需要的 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')

我们在这个例子中使用的字体是 Comic Sans MS。这是一个专有字体,Linux 使用者必须安装 msttcorefonts 包才可以使用。如果我们没有安装改字体,将会选择另外的字体。

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

我们创建了两个 bitmaps,第一个是一个圆角的矩阵,被橘色填充,该图片是我们使用 Inkscape vector illustrator 创建的。第二个是一个 x,用来关闭应用,它是我们使用 Gimp 图片编辑器创建的。

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

我们将在 frame 中创建一个 bitmap,为了覆盖住整个 frame,我们得到 bitmap 的大小,然后我们设置 frame 的大小与其一致。

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

上面是平台依赖的代码。 Linux 开发者应该在 wx.WindowCreateEvent 事件后立刻调用 SetNoteShape() 方法。

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

这些线条并非必须,因为在应用新建的时候会触发 paint 事件。但我们相信这会让样例更加清晰。

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

这里我们设置 frame 的形状与 bitmap 一致。image 外面的像素将会变得透明。

如果我们移除了 frame 的边界,我们将无法移动窗口。OnLeftDown() 和 OnMouseMove() 方法将允许用户实现对窗口的移动。

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() 方法中,我们绘制了两个 bitmap 和三行文本。

最终,我们讲一讲如何关闭 note 脚本。

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()

我们从两个 bitmap 中创建了两个 region,并取交集,这样我们可以得到两个 bitmap 共有的像素。最后,我们移动 region 到放置关闭按钮的地方。我们使用了 Offset() 方法,默认情况下是从[0,0]开始的。

在 OnLeftDown() 方法内部,我们检查是否点击到区域的内部,如果是,我们关闭脚本。

tutorial wxpython-jiaocheng

图:Note

本节,我们主要讲解 GDI。


上一节:wxPython 教程 (十一): wxPython 应用骨架                     下一节:wxPython 教程 (十三): 自定义控件

发表评论

电子邮件地址不会被公开。 必填项已用*标注