ZetCode

wxPython 图形

最后修改于 2023 年 1 月 10 日

GDI图形设备接口)是处理图形的接口。它用于与诸如显示器、打印机或文件之类的图形设备进行交互。GDI 允许程序员在屏幕或打印机上显示数据,而无需关注特定设备的细节。GDI 将程序员与硬件隔离开来。

从程序员的角度来看,GDI 是一组用于处理图形的类和方法。GDI 由二维矢量图形、字体和图像组成。

The GDI
图:GDI 结构

要开始绘制图形,我们必须创建一个设备上下文DC)对象。在 wxPython 中,设备上下文称为 wx.DC。文档将 wx.DC 定义为可以在其上绘制图形和文本的设备上下文。它以通用的方式表示许多设备。同一段代码可以写入不同类型的设备。无论是屏幕还是打印机。wx.DC 不打算直接使用。相反,程序员应该选择派生类之一。每个派生类都打算在特定条件下使用。

派生的 wx.DC 类

wx.ScreenDC 用于在屏幕上的任何位置进行绘制。如果要绘制整个窗口(仅限 Windows),则使用 wx.WindowDC。这包括窗口装饰。wx.ClientDC 用于在窗口的客户区进行绘制。客户区是窗口的没有装饰(标题和边框)的区域。wx.PaintDC 也用于在客户区进行绘制。但 wx.PaintDCwx.ClientDC 之间有一个区别。wx.PaintDC 只能从 wx.PaintEvent 中使用。不应从 wx.PaintEvent 中使用 wx.ClientDCwx.MemoryDC 用于在位图上绘制图形。wx.PostScriptDC 用于在任何平台上写入 PostScript 文件。wx.PrinterDC 用于访问打印机(仅限 Windows)。

绘制简单直线

我们的第一个示例将在窗口的客户区绘制一条简单的直线。

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

此方法从第一个点绘制一条直线到第二个点;不包括第二个点。

draw_line.py
#!/usr/bin/env python

"""
ZetCode wxPython tutorial

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

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

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

        self.InitUI()

    def InitUI(self):

        wx.CallLater(2000, self.DrawLine)

        self.SetTitle("Line")
        self.Centre()

    def DrawLine(self):

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

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

我们在两秒钟后在框架窗口上绘制一条线。

wx.FutureCall(2000, self.DrawLine)

我们在窗口创建后调用 DrawLine() 方法。我们这样做是因为,当窗口创建时,它会被绘制。因此,我们所有的绘图都会丢失。我们可以在窗口创建后开始绘制。这就是为什么我们调用 wx.FutureCall() 方法的原因。

def DrawLine(self):

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

我们创建了一个 wx.ClientDC 设备上下文。唯一的参数是我们想要在其上绘制的窗口。在我们的例子中,它是 self,它是我们 wx.Frame 控件的引用。我们调用设备上下文的 DrawLine() 方法。此调用实际上是在我们的窗口上绘制一条线。

理解以下行为非常重要。如果调整窗口大小,直线将消失。为什么会发生这种情况?每次窗口调整大小时都会重绘。最大化时也会重绘。如果用另一个窗口覆盖窗口然后再次显示,窗口也会重绘。窗口会绘制到其默认状态,并且我们的直线会丢失。我们必须在每次调整窗口大小时重绘直线。解决方案是 wx.PaintEvent。每次重绘窗口时都会触发此事件。我们将在挂钩到 paint 事件的方法中绘制直线。

以下示例显示了如何实现。

draw_line2.py
#!/usr/bin/env python

"""
ZetCode wxPython tutorial

This program draws a line in
a paint event.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

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

        self.InitUI()

    def InitUI(self):

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

        self.SetTitle("Line")
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)
        dc.DrawLine(50, 60, 190, 60)


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

我们绘制了相同的直线。这次是响应 paint 事件。

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

在这里,我们将 OnPaint 方法绑定到 wx.PaintEvent 事件。这意味着每次我们的窗口被重绘时,我们都会调用 OnPaint() 方法。现在,如果我们调整窗口大小(覆盖它、最大化它),线条将不会消失。

dc = wx.PaintDC(self)

请注意,这次我们使用了 wx.PaintDC 设备上下文。

Drawing a line
图:绘制直线

计算机图形学

有两种不同的计算机图形:矢量光栅图形。光栅图形将图像表示为像素集合。矢量图形是使用几何图元(如点、线、曲线或多边形)来表示图像。这些图元是使用数学方程创建的。

两种类型的计算机图形都有优点和缺点。矢量图形相对于光栅图形的优点是

图元类型

以下是图形图元的非详尽列表。

设备上下文属性

设备上下文包含几个属性,例如画笔、笔或字体。wx.Brush 是用于填充区域的绘图工具。它用于绘制形状的背景。它具有颜色和样式。wx.Pen 用于绘制形状的轮廓。它具有颜色、宽度和样式。wx.Font 是一个决定文本外观的对象。

基本元素

在接下来的几行中,我们将介绍几个基本对象:颜色、画笔、画笔、连接、笔帽和渐变。

颜色

颜色是表示红色、绿色和蓝色(RGB)强度值组合的对象。有效的 RGB 值范围是 0 到 255。有三种设置颜色\}的方法。我们可以创建一个 wx.Colour 对象,使用预定义的颜色名称,或使用十六进制值字符串。wx.Colour(0,0,255)'BLUE''#0000FF'。这三种表示法产生相同的颜色。

处理颜色的绝佳工具可以在 colorjack.com 网站上找到。或者,我们可以使用 Gimp 等工具。

我们还有一个预定义颜色名称列表,可以在我们的程序中使用。

标准颜色数据库
水绿色黑色蓝色蓝紫色棕色
灰蓝色珊瑚色矢车菊蓝青色深灰色
深绿色深橄榄绿深紫罗兰色深蓝紫色深灰蓝色
深青色暗灰色耐火砖色森林绿金色
金黄色灰色绿色黄绿色印度红
卡其色浅蓝色浅灰色浅钢蓝色亮绿色
品红色栗色中水绿色中蓝色中森林绿
中金黄色中紫罗兰色中海绿色中蓝紫色中春绿色
中青色中紫红色午夜蓝海军蓝橙色
橙红色紫罗兰色淡绿色粉红色梅红色
紫色红色鲑鱼色海绿色赭石色
天蓝色蓝紫色春绿色钢蓝色棕褐色
蓟色青色紫罗兰色紫红色小麦色
白色黄色黄绿色

以下示例使用了一些颜色值。

colours.py
#!/usr/bin/env python

"""
ZetCode wxPython tutorial

This program draws nine coloured rectangles
on the window.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

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

        self.InitUI()

    def InitUI(self):

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

        self.SetTitle("Colours")
        self.Centre()


    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)


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

我们绘制九个矩形并用不同颜色填充它们。

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

我们以十六进制表示法指定画笔颜色。画笔是形状的背景填充。然后,我们使用 DrawRectangle() 方法绘制矩形。

Colours
图:颜色

wx.Pen

Pen 是一个基本的图形对象。它用于绘制线、曲线和矩形、椭圆、多边形或其他形状的轮廓。

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

wx.Pen 构造函数有三个参数:颜色、宽度和样式。以下是可能的笔样式列表

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

"""
ZetCode wxPython tutorial

This program draws six rectangles with different pens.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

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

        self.InitUI()

    def InitUI(self):

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

        self.SetTitle("Pens")
        self.Centre()

    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)


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

如果我们不指定自定义画笔,则会使用默认画笔。默认画笔是 wx.WHITE_BRUSH。矩形的边框由画笔绘制。最后一个没有边框。它是透明的,即不可见。

Pens
图:画笔

连接和笔帽

画笔对象还有两个附加参数:连接笔帽。连接定义了如何绘制线之间的连接。连接样式具有以下选项

使用 wx.JOIN_MITER 时,线的外部边缘会延长。它们以一个角度相交,并且该区域被填充。在 wx.JOIN_BEVEL 中,两条线之间的三角形缺口被填充。在 wx.JOIN_ROUND 中,两条线之间的圆弧被填充。默认值为 wx.JOIN_ROUND

笔帽定义了画笔如何绘制线的末端。选项是

wx.CAP_ROUND 绘制圆角端。wx.CAP_PROJECTINGwx.CAP_BUTT 绘制方形端。它们之间的区别在于 wx.CAP_PROJECTING 会超出端点半个线条长度。wx.CAP_ROUND 也会超出端点。

joins_caps.py
#!/usr/bin/env python

"""
ZetCode wxPython tutorial

This program draws uses different joins
and caps in drawing.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

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

        self.InitUI()

    def InitUI(self):

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

        self.SetTitle("Joins and caps")
        self.Centre()

    def OnPaint(self, e):

        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)


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()
pen = wx.Pen('#4c4c4c', 10, wx.SOLID)

为了看到各种连接和笔帽样式,我们需要将画笔宽度设置为大于 1。

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

注意两条环绕的垂直线。它们之间的距离是 5 像素。这正好是当前画笔宽度的一半。

Joins and Caps
图:连接和笔帽

渐变

在计算机图形学中,渐变是指从浅到深或从一种颜色到另一种颜色的平滑混合。在二维绘图程序和绘画程序中,渐变用于创建丰富多彩的背景和特殊效果,以及模拟光影。

GradientFillLinear(self, rect, initialColour, destColour, nDirection=RIGHT)

此方法用线性渐变填充由 rect 指定的区域,从 initialColour 开始,最终淡化到 destColournDirection 参数指定颜色变化的方向;默认值为 wx.EAST

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

"""
ZetCode wxPython tutorial

This program draws four rectangles filled
with gradients.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

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

        self.InitUI()

    def InitUI(self):

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

        self.SetTitle("Gradients")
        self.Centre()

    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)


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

在此示例中,四个矩形填充了渐变。

Gradients
图:渐变

wx.Brush

Brush 是一个基本的图形对象。它用于绘制图形形状(如矩形、椭圆或多边形)的背景。

wxPython 具有以下内置画笔类型

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

"""
ZetCode wxPython tutorial

This program draws eight rectangles filled
with different brushes.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

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

        self.InitUI()

    def InitUI(self):

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

        self.SetTitle("Brushes")
        self.Centre()

    def OnPaint(self, e):

        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)


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

示例中使用了八种不同的内置画笔类型。

Brushes
图:画刷

自定义图案

我们不限于使用预定义的图案。我们可以轻松创建自己的自定义图案。

custom_patterns.py
#!/usr/bin/env python

"""
ZetCode wxPython tutorial

This program draws three rectangles with custom
brush patterns.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

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

        self.InitUI()

    def InitUI(self):

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

        self.SetTitle("Custom patterns")
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)

        dc.SetPen(wx.Pen('#C7C3C3'))

        brush1 = wx.Brush(wx.Bitmap('pattern1.png'))
        dc.SetBrush(brush1)
        dc.DrawRectangle(10, 15, 90, 60)

        brush2 = wx.Brush(wx.Bitmap('pattern2.png'))
        dc.SetBrush(brush2)
        dc.DrawRectangle(130, 15, 90, 60)

        brush3 = wx.Brush(wx.Bitmap('pattern3.png'))
        dc.SetBrush(brush3)
        dc.DrawRectangle(250, 15, 90, 60)


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

我们创建了一些小的位图。这些位图是在 Gimp 中创建的。

brush1 = wx.Brush(wx.Bitmap('pattern1.png'))
dc.SetBrush(brush1)
dc.DrawRectangle(10, 15, 90, 60)

从位图创建画笔并将其设置为设备上下文。它用于填充矩形的内部。

Custom Patterns
图:自定义图案

最简单的几何对象是点。它是窗口上的一个普通点。

DrawPoint(self, x, y)

此方法在 x, y 坐标处绘制一个点。

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

"""
ZetCode wxPython tutorial

This program draws one thousand points
randomly on the window.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx
import random

class Example(wx.Frame):

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

        self.InitUI()

    def InitUI(self):

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

        self.SetTitle("Points")
        self.Centre()

    def OnPaint(self, e):

        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)


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

一个点可能难以看到,所以我们创建了 1000 个点。

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

这里我们将画笔颜色设置为红色。

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

这些点随机分布在窗口的客户区域周围。它们也是动态分布的。如果调整窗口大小,点将随机绘制在新客户区域上。randint(a, b) 方法返回范围 [a, b] 中的随机整数,例如包含这两个端点。

Points
图:绘制点

形状

形状是更复杂的几何对象。我们在以下示例中绘制各种几何形状。

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

"""
ZetCode wxPython tutorial

This program draws various shapes on
the window.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

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

        self.InitUI()

    def InitUI(self):

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

        self.SetTitle("Shapes")
        self.Centre()


    def OnPaint(self, e):

        dc = wx.PaintDC(self)
        dc.SetBrush(wx.Brush('#777'))
        dc.SetPen(wx.Pen("#777"))

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

        dc.DrawRectangle(20, 120, 80, 50)
        dc.DrawPolygon(((130, 140), (180, 170), (180, 140), (220, 110), (140, 100)))
        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)


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

在我们的示例中,我们绘制了一个椭圆、一个圆角矩形、一个弧形、一个矩形、一个多边形、样条、直线、一个圆和一个正方形。圆是椭圆的一种特殊类型,正方形是矩形的一种特殊类型。

Shapes
图:图形

区域

设备上下文可以分为几个部分,称为区域。区域可以是任何形状,例如矩形或圆形。使用 UnionIntersectSubstractXor 操作,我们可以创建复杂的区域。区域用于轮廓、填充和裁剪。

我们可以通过三种方式创建区域。最简单的方法是创建矩形区域。更复杂的区域可以从点列表或位图中创建。

在进行区域操作之前,我们先创建一个小示例。我们将主题分为几个部分,以便更容易理解。您可能会发现复习学校数学是个好主意。 此处 我们可以找到一篇好文章。

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

"""
ZetCode wxPython tutorial

This program draws various shapes on
the window.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

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

    def InitUI(self):

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

        self.SetTitle('Lines')
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)
        size_x, size_y = self.GetClientSize()
        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.DrawLine((0, 0), (x, y))
            angle = angle + 2*pi/360


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

在此示例中,我们从客户区域的中心绘制 360 条线。两条线之间的距离是 1 度。我们创建了一个有趣的图形。

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

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

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

SetDeviceOrigin() 方法创建了坐标系的新起点。我们将其放在客户区域的中心。通过重新定位坐标系,我们使绘图不那么复杂。

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

这里我们得到斜边。它是最长的线,可以从客户区域的中心绘制。它是从起点绘制到窗口角落的线的长度。这样,大多数线都不是完全绘制的。重叠的部分是不可见的。参见 斜边

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

这些是参数方程。它们用于在曲线上查找 [x, y] 点。所有 360 条线都从坐标系的原点绘制到圆上的点。

Lines
图:线条

剪裁

Clipping 是将绘图限制在特定区域。裁剪通常用于创建效果和提高应用程序性能。我们使用 SetClippingRegionAsRegion() 方法将绘图限制在特定区域。

在以下示例中,我们将修改和增强我们之前的程序。

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

"""
ZetCode wxPython tutorial

This program demonstrates a clipping operation
when drawing a star object.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

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

    def InitUI(self):

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

        self.SetTitle("Star")
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)

        dc.SetPen(wx.Pen('#424242'))
        size_x, size_y = self.GetClientSize()
        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.Region(points)
        dc.SetDeviceClippingRegion(region)

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

        while (angle < 2*pi):

            x = radius*cos(angle)
            y = radius*sin(angle)
            dc.DrawLine((0, 0), (x, y))
            angle = angle + 2*pi/360

        dc.DestroyClippingRegion()


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

我们再次绘制所有 360 条线。但这次,只有一部分客户区域被绘制。我们将其限制的区域是一个星形对象。

region = wx.Region(points)
dc.SetDeviceClippingRegion(region)

我们从点列表中创建一个区域。SetDeviceClippingRegion() 方法将绘图限制在指定的区域。在我们的例子中,它是一个星形对象。

dc.DestroyClippingRegion()

我们必须销毁剪辑区域。

Star
图:星形

区域操作

区域可以组合以创建更复杂的形状。我们可以使用四种集合运算:并集、交集、差集、异或。

以下示例展示了所有四种运算。

region_operations.py
#!/usr/bin/env python

"""
ZetCode wxPython tutorial

This program performs set operations on regions.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

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

        self.InitUI()

    def InitUI(self):

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

         self.SetTitle("Regions")
         self.Centre()

    def OnPaint(self, e):

         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.Intersect(region2)

         rect = region1.GetBox()
         dc.SetDeviceClippingRegion(region1)
         dc.SetBrush(wx.Brush('#ff0000'))
         dc.DrawRectangle(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.Union(region2)
         dc.SetDeviceClippingRegion(region1)

         rect = region1.GetBox()
         dc.SetBrush(wx.Brush('#fa8e00'))
         dc.DrawRectangle(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.Xor(region2)

         rect = region1.GetBox()
         dc.SetDeviceClippingRegion(region1)
         dc.SetBrush(wx.Brush('#619e1b'))
         dc.DrawRectangle(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.Subtract(region2)

         rect = region1.GetBox()
         dc.SetDeviceClippingRegion(region1)
         dc.SetBrush(wx.Brush('#715b33'))
         dc.DrawRectangle(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.Subtract(region1)

         rect = region2.GetBox()
         dc.SetDeviceClippingRegion(region2)
         dc.SetBrush(wx.Brush('#0d0060'))
         dc.DrawRectangle(rect)
         dc.DestroyClippingRegion()


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

在此示例中,我们展示了六种区域集合运算。

 region1 = wx.Region(100, 20, 50, 50)
 region2 = wx.Region(110, 40, 50, 50)
 region1.Intersect(region2)

此代码对两个区域执行交集运算。

Set operations on regions
图:区域上的集合运算

映射模式

映射模式定义了用于将页空间单位转换为设备空间单位的度量单位,并且还定义了设备 x 和 y 轴的方向。

说英语,用公制测量

英语成为沟通的全球语言。度量系统也成为测量的全球系统。根据这篇维基百科 文章,只有三个例外。美国、利比里亚和缅甸。例如,美国人使用华氏度测量温度,使用加仑给汽车加油,或使用磅称重。

即使我们在欧洲使用公制系统,仍然存在例外。美国在 IT 领域占主导地位,我们进口他们的标准。因此,我们也说我们有一个 17 英寸的显示器。图形可以放入文件、显示在监视器或其他设备(相机、摄像机、手机)的屏幕上,或由打印机打印。纸张尺寸可以以毫米、点或英寸为单位设置,屏幕分辨率以像素为单位,文本质量由每英寸点数决定。我们还有点、比特或样本。这是我们拥有逻辑设备单元的原因之一。

逻辑单位和设备单位

如果我们要在客户区绘制文本或几何图元,我们使用逻辑单位来定位它们。

如果我们想绘制一些文本,我们会提供文本参数和 x, y 位置。x, y 是以逻辑单位表示的。然后设备以设备单位绘制文本。逻辑单位和设备单位可能相同,也可能不同。逻辑单位由人使用(毫米),设备单位是特定设备的本地单位。例如,屏幕的本地设备单位是像素。HEWLETT PACKARD LaserJet 1022 的本地设备单位是 1200 dpi(每英寸点数)。

到目前为止,我们已经讨论了各种度量单位。设备的映射模式是将逻辑单位转换为设备单位的一种方式。wxPython 具有以下映射模式

映射模式 逻辑单位
wx.MM_TEXT1 像素
wx.MM_METRIC1 毫米
wx.MM_LOMETRIC1/10 毫米
wx.MM_POINTS1 点,1/72 英寸
wx.MM_TWIPS1/20 点或 1/1440 英寸

默认映射模式是 wx.MM_TEXT。在此模式下,逻辑单位与设备单位相同。当人们在屏幕上定位对象或设计网页时,他们通常以像素为单位思考。网页设计师创建三栏页面,这些栏以像素为单位设置。页面的最低公分母通常是 800 像素等。这种思维方式是自然的,因为我们知道我们的显示器有例如 1024x768 像素。我们不打算进行转换,而是习惯于以像素为单位思考。如果我们想以毫米为单位绘制结构,我们可以使用两个公制映射模式。直接以毫米为单位绘制对于屏幕来说太粗糙了,这就是为什么我们有 wx.MM_LOMETRIC 映射模式。

要设置不同的映射模式,我们使用 SetMapMode() 方法。

标尺示例

标尺以像素为单位测量屏幕对象。

ruler.py
#!/usr/bin/env python

"""
ZetCode wxPython tutorial

This program creates a ruler.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx


RW = 701 # ruler width
RM = 10  # ruler margin
RH = 80  # ruler height


class Example(wx.Frame):

    def __init__(self, parent):
        wx.Frame.__init__(self, parent, size=(RW + 2*RM, RH),
            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.InitUI()

    def InitUI(self):

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

        self.Centre()
        self.Show(True)

    def OnPaint(self, e):

        dc = wx.PaintDC(self)

        brush = wx.Brush(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, e):

        x, y = self.ClientToScreen(e.GetPosition())
        ox, oy = self.GetPosition()

        dx = x - ox
        dy = y - oy

        self.delta = ((dx, dy))

    def OnMouseMove(self, e):

        if e.Dragging() and e.LeftIsDown():

            self.SetCursor(wx.Cursor(wx.CURSOR_HAND))

            x, y = self.ClientToScreen(e.GetPosition())
            fp = (x - self.delta[0], y - self.delta[1])
            self.Move(fp)

    def OnLeftUp(self, e):

        self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))

    def OnRightDown(self, e):

        self.Close()


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

在此示例中,我们创建了一个标尺。此标尺以像素为单位测量屏幕对象。我们保留了默认映射模式 wx.MM_TEXT。正如我们已经提到的,此模式的逻辑单位和设备单位相同。在我们的例子中,这些是像素。

def __init__(self, parent):
    wx.Frame.__init__(self, parent, size=(RW + 2*RM, RH),
        style=wx.FRAME_NO_TASKBAR | wx.NO_BORDER | wx.STAY_ON_TOP)

我们创建了一个无边框窗口。标尺宽度为 721 像素:RW + 2*RM = 701 + 20 = 721。标尺显示 701 个数字;0 ... 700 是 701 像素。标尺两侧都有边距,2*10 是 20 像素。总共是 721 像素。

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

这里我们在窗口上绘制了一个自定义图案。我们使用了 Gimp 中可用的预定义图案。它被称为 granite。

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

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

我们的窗口没有边框。所以我们必须手动处理移动。OnLeftDown()OnMouseMove() 方法使我们能够移动标尺。

def OnLeftDown(self, e):

    x, y = self.ClientToScreen(e.GetPosition())
    ox, oy = self.GetPosition()

    dx = x - ox
    dy = y - oy

    self.delta = ((dx, dy))

OnLeftDown() 方法中,我们确定了窗口和鼠标光标坐标;delta 值是鼠标指针到窗口左上角的距离。我们需要 delta 值来移动窗口。

def OnMouseMove(self, e):

    if e.Dragging() and e.LeftIsDown():

        self.SetCursor(wx.Cursor(wx.CURSOR_HAND))

        x, y = self.ClientToScreen(e.GetPosition())
        fp = (x - self.delta[0], y - self.delta[1])
        self.Move(fp)

当我们在拖动窗口的同时按下鼠标左键时,代码就会执行。在代码块中,我们使用 SetCursor() 更改鼠标光标,并使用 Move() 方法移动窗口。Delta 值用于获取距离。

def OnLeftUp(self, e):

    self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))

当我们释放鼠标左键时,我们将光标改回箭头。

def OnRightDown(self, e):

    self.Close()

通过右键单击窗口区域来关闭窗口。

Ruler example
图:标尺示例

在本章中,我们使用了 wxPython 的图形。