wxpython入门第八步(画图)

wxPython graphics

GDI(Graphics Device Interface,图形设备接口)是一个与图形工作的接口,用于与显示器、打印机或文件等图形设备交互。它用于与显示器、打印机或文件等图形设备进行交互。GDI允许程序员在屏幕或打印机上显示数据,而不必关注特定设备的细节。GDI将程序员与硬件隔离开来。

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

The GDI

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

派生 wx.DC 类

  • wxBufferedDC
  • wxBufferedPaintDC
  • wxPostScriptDC
  • wxMemoryDC
  • wxPrinterDC
  • wxScreenDC
  • wxClientDC
  • wxPaintDC
  • wxWindowDC

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

画线

我们的第一个例子将在窗口的客户端区域画一条简单的线。

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

这种方法是从第一点到第二点画一条线;不包括第二点。

#draw_line.py

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.CallLater(2000, self.DrawLine)

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

def DrawLine(self):

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

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

理解以下行为是非常重要的。如果我们调整窗口的大小,这条线就会消失。为什么会出现这种情况?如果调整窗口的大小,每个窗口都会被重新绘制。如果它被最大化,它也会被重新绘制。如果我们用另一个窗口覆盖窗口,然后再揭开,窗口也会被重新绘制。窗口被绘制到默认状态,我们的线就会丢失。每次调整窗口的大小时,我们都必须画线。解决方法是wx.PaintEvent。每次窗口被重新绘制时,这个事件都会被触发。我们将在一个与paint事件挂钩的方法中画线。

下面的例子展示了如何完成。

#draw_line2.py

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

image-20201101102113582

计算机图形

有两种不同的计算机图形。矢量和光栅图形。栅格图形以像素的集合来表示图像。矢量图形是使用几何基元,如点、线、曲线或多边形来表示图像。这些基元是使用数学公式创建的。

两种类型的计算机图形都有优点和缺点。与光栅相比,矢量图形的优点是:

  • 尺寸较小
  • 可无限放大
  • 移动、缩放、填充或旋转不会降低图像的质量。

以下是部分图形的列表。

  • points
  • lines
  • polylines
  • polygons
  • circles
  • ellipses
  • splines

属性

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

基本对象

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

颜色

颜色是代表红、绿、蓝(RGB)强度值组合的对象。有效的RGB值的范围是0到255。有三种方法可以设置颜色。我们可以创建一个wx.Colour对象,使用一个预定义的颜色名称或使用十六进制值字符串。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

下面的例子使用了一些颜色值。

#colours.py

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

image-20201101102536628

我们画出九个长方形,并用不同的颜色填充。

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

我们用十六进制符号指定笔刷的颜色。画笔就是形状的背景填充。然后我们用DrawRectangle()方法绘制矩形。

wx.Pen

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

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

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

  • wx.SOLID
  • wx.DOT
  • wx.LONG_DASH
  • wx.SHORT_DASH
  • wx.DOT_DASH
  • wx.TRANSPARENT
#pens.py

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

image-20201101103120278

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

Join和Cap

一个钢笔对象有两个额外的参数:join和cap。连接定义了线条之间的连接方式。连接样式有以下选项。

wx.JOIN_MITER

wx.JOIN_BEVEL

wx.JOIN_ROUND

当使用 wx.JOIN_MITER 时,线条的外缘会被延伸。它们以一定的角度相遇,这个区域被填充。在 wx.JOIN_BEVEL 中,两条线之间的三角形缺口被填充。在wx.JOIN_ROUND中,填充了两条线之间的圆弧。默认值是wx.JOIN_ROUND。

cap定义了钢笔如何绘制线端。选项有

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

wx.CAP_ROUND画的是圆形的两端。wx.CAP_PROJECTINGwx.CAP_BUTT画的是方形端点。它们之间的区别是wx.CAP_PROJECTING会超出端点一半的线条尺寸。wx.CAP_ROUND也会超出端点。

#joins_caps.py

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)

image-20201101103337422

为了看到各种join和cap样式,我们需要将笔的宽度设置为大于1。

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

请注意两条包围的竖线。它们之间的距离是5px。这正好是当前笔宽的一半。

渐变

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

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

此方法用线性渐变填充 "rect "指定的区域,从 "initialColour "开始,最终渐变到 "destColour"。nDirection参数指定颜色变化的方向,默认值是wx.EAST

#gradients.py

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

image-20201101103621870

在例子中,四个矩形被填充了渐变。

wx.Brush

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

wxPython内置了以下笔刷类型。

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

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

image-20201101103835780

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

Custom Patterns

我们不受限于使用预定义的模式。我们可以轻松地创建自己的自定义模式。

#custom_patterns.py

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

image-20201101104057139

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

刷子是由位图创建的,它用于填充一个矩形的内部。

Points

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

DrawPoint(self, x, y)

此方法在x、y坐标处画一个点。

#points.py

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

image-20201101104319232

单点可能很难看到,所以我们创造1000点。

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

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

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

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

Shapes

shape是比较复杂的几何图形对象。我们在下面的例子中画出各种几何图形。

#shapes.py

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

image-20201101110119404

在我们的例子中,我们画了一个椭圆、一个圆角矩形、一个弧形、一个矩形、一个多边形、S型线、线条、一个圆和一个正方形。圆是一种特殊的椭圆,正方形是一种特殊的矩形。

Regions

一个区域可以是任何形状,如矩形或圆形。通过 "Union"、"Intersect"、"Substract "和 "Xor "操作,我们可以创建复杂的区域。区域用于勾勒、填充和剪切。

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

在我们进入区域之前,我们将先创建一个小例子。

#lines.py

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

image-20201101110419096

在这个例子中,我们从客户端区域的中间画出360条线。两条线之间的距离是1度。

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

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

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线都是从坐标系的起点开始画到圆上的点。

Clipping

"剪切 "是将绘图限制在某一区域。剪裁通常用于创建效果和提高应用程序的性能。我们通过SetClippingRegionAsRegion()方法将绘图限制在某个区域。

在下面的例子中,我们将对之前的程序进行修改和增强。

#star.py

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

image-20201101110734081

我们再次画出所有的360线。但这次只画出了一部分。我们限制绘制的区域是一个五角星。

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

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

dc.DestroyClippingRegion()

我们必须destroy剪切区域。

Region 操作

区域可以组合成更复杂的形状。我们可以使用四种集合操作: union, intersect, substract, xor.

下面的例子展示了所有这四种操作的操作。

#region_operations.py

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

image-20201101111519996

In the example, we present six region set operations.

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

这段代码对两个区域进行交集运算。

映射模式

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

逻辑和设备单位

如果我们在客户端区域上绘制文本或几何基元,我们会使用逻辑单位来定位它们。

如果我们要绘制一些文本,我们提供文本参数和 x、y 位置。x、y 是以逻辑单位为单位。然后设备以设备单位绘制文本。逻辑单位和设备单位可能相同,也可能不同。逻辑单位是人们使用的(毫米),设备单位是 "特定 "设备的本地单位。例如,一个屏幕的本地设备单位是像素。惠普LaserJet 1022的原生设备单位是1200dpi(dots per inch)。

设备的映射模式是一种将逻辑单位转换为设备单位的方法。wxPython有以下映射模式。

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

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

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

Ruler 例子

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

#ruler.py

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

image-20201101112246816

在这个例子中,我们创建一个标尺。这个尺子以像素为单位测量屏幕对象。我们保留了默认的映射模式,即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 px。RW + 2RM = 701 + 20 = 721. 标尺上显示700个数字;0 ... 700是701像素。尺子两边都有边距,210是20像素。合起来就是721个像素。

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

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

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

右键点击窗口区域,即可关闭窗口。

你可能感兴趣的