wxPython和pycairo练习记录1

wxPython和pycairo练习记录1

学习,分为学和习,学完了不练习,让大脑觉得不重要,会忘记。所以,我决定练习并记录 wxPython 和 pycairo 的代码编写,如果你也想试试请自行配置环境。

wxPython官网:https://www.wxpython.org/
pycairo官网:https://cairographics.org/pycairo/

本机环境:
WIN10
Python(3.8.7)
wxPython(4.1.1 msw (phoenix) wxWidgets 3.1.5) pycairo(1.21.0)

因为没有相关完整项目经验,所以不能把全局都设计好,先从基本的迭代。把可能经常需要用到的,几乎不会变的公共部分先写出来,主要参考zetcode的wxPython教程https://zetcode.com/wxpython/ 。

1.1 导入模块

把练习代码放到一个模块,测试不同的练习只改动这里的模块名。

import wx
import wx.lib.wxcairo
import cairo
import example # 用来编写练习代码的模块

1.2 设置全局变量

感觉很多概念都是用来避免命名冲突的,局部变量、全局变量、闭包、内置变量、类属性、对象属性,当然作用还有便于权限控制,降低耦合。

说到属性,很多教程解释类属性和对象属性的区别,就是讲在哪声明,讲在哪存储,讲怎么调用,看了也还是不理解。后来终于找到一个能看懂的说法,类属性就是跟类绑定的属性,对象属性就是跟对象绑定的属性。

非重点的内容可以先当作常识,不然很快就会沿着各种分支偏离目标,例如为什么要避免使用全局变量?

class cv:
    """
    常量,用类属性避免使用全局变量
    """
    # 刷新定时器 id
    TIMER_ID = 1
    # 刷新间隔时间 毫秒, FPS (frames per second) 就是 1000 // SPEED
    SPEED = 10
    # 面板尺寸
    BOARD_WIDTH = 800
    BOARD_HEIGHT = 600

1.3 主面板编写

数据的更新和图像的重新绘制分开处理,如响应坦克更新位置操作,只更新位置数据,在重新绘制的时候,读取新的位置数据并绘制坦克。

class Board(wx.Panel):
    def __init__(self, parent):
        super(Board, self).__init__(parent=parent, style=wx.WANTS_CHARS) # wx.WANTS_CHARS 可接收键盘事件
        self.SetDoubleBuffered(True) # 双缓冲,防止闪烁
        self.InitVariables()
        self.BindEvent()

1.4 完整代码

用一个定时器刷新数据和图像,初始公共部分仅绘制黑色背景。

# -*- coding: utf-8 -*-
import wx
import wx.lib.wxcairo
import cairo
import example # 用来编写练习代码的模块


class cv:
    """
    常量,用类属性避免使用全局变量
    """
    # 刷新定时器 id
    TIMER_ID = 1
    # 刷新间隔时间 毫秒, FPS (frames per second) 就是 1000 // SPEED
    SPEED = 10
    # 面板尺寸
    BOARD_WIDTH = 800
    BOARD_HEIGHT = 600


class Board(wx.Panel):
    def __init__(self, parent):
        super(Board, self).__init__(parent=parent, style=wx.WANTS_CHARS) # wx.WANTS_CHARS 可接收键盘事件
        self.SetDoubleBuffered(True) # 双缓冲,防止闪烁
        self.InitVariables()
        self.BindEvent()

    def InitVariables(self):
        # 初始化变量
        self.InitSceneObjects()
        # 设置定时器
        self.timer = wx.Timer(owner=self, id=cv.TIMER_ID)
        self.timer.Start(cv.SPEED)

    def InitSceneObjects(self):
        # 初始化场景中对象变量,如坦克实例、计分栏初始分数等
        self.sceneObjects = []

    def BindEvent(self):
        # 绑定事件
        self.Bind(wx.EVT_TIMER, self.OnTimer, id=cv.TIMER_ID)
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)

    def OnTimer(self, e):
        # 处理更新事件,定时更新变量,刷新重绘
        for so in self.sceneObjects:
            so.Update()

        self.CheckStrategies()
        self.Refresh() # 重绘,执行OnPaint

    def CheckStrategies(self):
        # 碰撞检测等
        pass

    def OnPaint(self, e):
        # 处理重绘事件
        dc = wx.PaintDC(window=self) # device context,设备上下文,相当于画布或虚拟的屏幕,
        ctx = wx.lib.wxcairo.ContextFromDC(dc) # 获取 cairo.Context 对象,同上

        self.DrawBackground(ctx)
        self.DrawSceneObjects(ctx)

    def DrawBackground(self, ctx):
        # 填充黑色背景
        ctx.set_source_rgb(0, 0, 0)
        ctx.paint()

    def DrawSceneObjects(self, ctx):
        # 绘制要显示的对象,如坦克、计分栏等
        pass

    def OnKeyDown(self, e):
        # 处理键盘按下事件
        pass

    def OnKeyUp(self, e):
        # 处理键盘弹起事件
        pass


class MyFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(MyFrame, self).__init__(*args, **kwargs)
        Board(parent=self)
        self.Center() # 窗口在屏幕居中


def main():
    app = wx.App()
    frame = MyFrame(parent=None, title="cairotest", size=(cv.BOARD_WIDTH, cv.BOARD_HEIGHT))
    frame.Show()
    app.MainLoop()


if __name__ == "__main__":
    main()

1.5 拆分公共部分

上面的文件,作为单独的文件运行没有问题,但是我们还要进一步分离出变化和不变化的部分。
期望的结果是,只需要在example中写练习用的代码,其他基础部分都不变。而example中需要继承cv和Board,那么需要把cv和Board单独分离到一个文件作为模板,另外还需要一个文件写主要执行流程。

# -*- coding: utf-8 -*-
# main.py

import wx
from example import cv, Board


class MyFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(MyFrame, self).__init__(*args, **kwargs)
        Board(parent=self)
        self.Center() # 窗口在屏幕居中


def main():
    app = wx.App()
    frame = MyFrame(parent=None, title="cairotest", size=(cv.BOARD_WIDTH, cv.BOARD_HEIGHT))
    frame.Show()
    app.MainLoop()


if __name__ == "__main__":
    main()

# -*- coding: utf-8 -*-
# board.py

import wx
import wx.lib.wxcairo


class cv:
    """
    常量,用类属性避免使用全局变量
    """
    # 刷新定时器 id
    TIMER_ID = 1
    # 刷新间隔时间 毫秒, FPS (frames per second) 就是 1000 // SPEED
    SPEED = 10
    # 面板尺寸
    BOARD_WIDTH = 800
    BOARD_HEIGHT = 600


class Board(wx.Panel):
    def __init__(self, parent):
        super(Board, self).__init__(parent=parent, style=wx.WANTS_CHARS) # wx.WANTS_CHARS 可接收键盘事件
        self.SetDoubleBuffered(True) # 双缓冲,防止闪烁
        self.InitVariables()
        self.BindEvent()

    def InitVariables(self):
        # 初始化变量
        self.InitSceneObjects()
        # 设置定时器
        self.timer = wx.Timer(owner=self, id=cv.TIMER_ID)
        self.timer.Start(cv.SPEED)

    def InitSceneObjects(self):
        # 初始化场景中对象变量,如坦克实例、计分栏初始分数等
        self.sceneObjects = []

    def BindEvent(self):
        # 绑定事件
        self.Bind(wx.EVT_TIMER, self.OnTimer, id=cv.TIMER_ID)
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)

    def OnTimer(self, e):
        # 处理更新事件,定时更新变量,刷新重绘
        for so in self.sceneObjects:
            so.Update()

        self.CheckStrategies()
        self.Refresh() # 重绘,执行OnPaint

    def CheckStrategies(self):
        # 碰撞检测等
        pass

    def OnPaint(self, e):
        # 处理重绘事件
        dc = wx.PaintDC(window=self) # device context,设备上下文,相当于画布或虚拟的屏幕,
        ctx = wx.lib.wxcairo.ContextFromDC(dc) # 获取 cairo.Context 对象,同上

        self.DrawBackground(ctx)
        self.DrawSceneObjects(ctx)

    def DrawBackground(self, ctx):
        # 填充黑色背景
        ctx.set_source_rgb(0, 0, 0)
        ctx.paint()

    def DrawSceneObjects(self, ctx):
        # 绘制要显示的对象,如坦克、计分栏等
        pass

    def OnKeyDown(self, e):
        # 处理键盘按下事件
        pass

    def OnKeyUp(self, e):
        # 处理键盘弹起事件
        pass

# -*- coding: utf-8 -*-
# example.py
# 这个例子我修改了窗口尺寸和添加了smilebasic文字显示

import wx
import wx.lib.wxcairo
import cairo
import board


class cv(board.cv):
    """
    常量,用类属性避免使用全局变量
    """
    # 面板尺寸
    BOARD_WIDTH = 600
    BOARD_HEIGHT = 400


class Board(board.Board):
    def DrawBackground(self, ctx):
        super().DrawBackground(ctx)

        text = "SmileBasic"
        ctx.set_font_size(40)
        _, _, w, h, _, _ = ctx.text_extents(text)
        x = (cv.BOARD_WIDTH - w) // 2
        y = (cv.BOARD_HEIGHT - h) // 2

        # 文字是以首个字左下角坐标定位,而矩形是以左上角定位,y轴相差文字的高度,不需要考虑线条宽度。
        # 另外PaintDC是不含标题栏的。
        ctx.rectangle(x - 10, y - 10 - h, w + 20, h + 20)
        ctx.set_source_rgb(1, 0, 0)
        ctx.stroke()

        ctx.move_to(x, y)
        ctx.set_source_rgb(1, 1, 1)
        ctx.show_text(text)

1.6 效果

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/SmileBasic/article/details/126323666