wxPython备忘录:从入门到入坟(2023.11.2更新,未完)

本文章不可转载!

在整篇文章开始之前,我觉得有必要写一下我写这篇文章的目的。其实很简单,就是自己学wxPython中的随笔。当然,我其实有意把它写成一个系统的wxPython的教程。我这篇文章用的语言是python,库用的是wxPython(C++库wxWidegts的Python封装),所以用其它语言的人别来找我。

相信肯定有人把它当成工具来用。放心,你想要的我这里都有。因为这里面的知识都是我在写程序的时候真实遇到的问题。

那么,让我们开始吧。

注意:本文长到难以想象,所以没有恒心和毅力看完的人不要来喷我。

目录

本文章不可转载!

写在前面

开始之前

1.开发框架

1.1 代码

1.2 分析代码

1.2.1 导入模块

1.2.2 创建wx.App()对象

1.2.3 创建wx.Frame()对象

1.2.4 style参数

1.2.5 显示wx.Frame()对象

1.2.6 进入wx.App()对象的主循环

1.3 改进的代码

2.用来看的控件:静态控件

2.1 什么是静态控件?

2.2 静态文本控件——wx.StaticText

2.2.1 普通设置

2.2.2 背景颜色、前景颜色

2.2.3 修改字体、大小、样式——wx.Font()

2.2.4 让字变清晰

2.3 显示图片——wx.StaticBitmap

2.3.1 创建位图的方式

2.3.2 分析代码

2.4 静态分割线——wx.StaticLine

2.5 “箱子”控件——wx.StaticBox

2.5.1 创建静态框

2.5.2 背景颜色、文字(前景)颜色

2.5.3 给wx.StaticBox()添加控件

2.5.4 控件的图层和堆叠


写在前面

 在很久以前(其实就是在2023年7月的时候),我用过tkinter,当时我刚用python写GUI,不得不说,tkinter很好上手,但是它也有很多问题。tkinter虽然有很多库,但是这也是它的缺点——太“散”了。甚至我都快弃用tkinter的时候才知道系统标准控件在tkinter.ttk模块里。

尽管它有标准控件,可是能做的变化非常少。而且tkinter主模块里有很多控件毫无用处。像tk.Button,样子极其难看,毫无实际用途。tkinter中的很多操作都是字符串操作,就导致需要查很多文档,可关键是它文档还奇缺······

其实也不能全怪tkinter,毕竟是20多年前的产物。20年前的编码风格肯定和现在不一样。

所以,在我痛定思痛之后,我还是转用了wxPython。倒不是wxPython不好用,只是我当时在用tkinter写一个程序,如果改用wxPython又得耗很长时间。不过,用了才知道,wxPython真的好用啊!

为什么用wxPython?

比tkinter好用。在tkinter中,创建一个对象真的很麻烦。第一步你要:

button = tk.Button(master, ···)

一般来讲这一步的行长要远远超出其它行。关键是,如果要让对象显示出来,还要用上这一步(这里用的是绝对布局):

button.place(x=0, y=0)

而在wxPython中,只需用上:

button = wx.Button(master, ···pos=(0, 0))

 同样是绝对布局,tkinter的代码量就要比wxPython多上一倍。当然,tkinter缺点很多,wxPython也不少,但相对而言,wxPython功能多,语法简洁,这点比tkinter强上不少。

和PyQt比呢?

PyQt我没用过,据说很好用,不过我看到一篇文章,说PyQt行长很长,语法不够简洁。

开始之前

首先,要安装wxPython模块。安装方法参考附录A。

1.开发框架

        1.1 代码

话不多说,直接上代码。

import wx
app = wx.App()
frame = wx.Frame(None, id=wx.ID_ANY, title='第一个wxPython程序', size=(800, 600))
frame.Show(True)
app.MainLoop()

运行结果:

可以看到,一个非常简单,非常基本的wxPython程序都有以下几个部分:

一、导入模块

二、创建wx.App()对象

三、创建wx.Frame()对象

四、显示wx.Frame()对象

五、进入wx.App()对象的主循环

事实上,一个完整的wxPython程序还要包括创建控件,但是因为这个程序没有控件,所以跳过了这一步。

        1.2 分析代码

                1.2.1 导入模块

import wx

行,我们导入了wxPython模块,注意这里是“import wx”,而不是“import wxPython”。

                1.2.2 创建wx.App()对象

在代码的

app = wx.App()

行,我们创建了wx.App()对象。这一行代表我们运行了一个wxPython应用程序。

                1.2.3 创建wx.Frame()对象

frame = wx.Frame(None, id=wx.ID_ANY, title='第一个wxPython程序', size=(800, 600))

行,我们创建了wx.Frame()对象。这是一个窗体,注意这里是“wx.Frame()”而不是“wx.Window()”。wx.Window()是wxPython中所有控件的父类。

一个wx.Frame()对象一般包含以下几个参数:

参数           说明                          
parent该窗体(控件)的父窗体(控件)。即应该显示在窗体parent之上
id窗体的ID。可以自己设置,若传入-1或wx.ID_ANY,则由wxPython自行安排id。
title

窗口的标题。显示在最上面一栏上。

pos窗口的坐标。传入元组,元组的第一项为x坐标,第二项为y坐标。
size窗口的大小。传入元组,元组的第一项为长,第二项为高。
style窗口的风格设置。可传入参数会在下面列出来。
name窗口的名称。传入字符串,可以自己设置,也可不设置。

注意,传入style参数等同于调用frame.SetWindowStyle()方法。

                1.2.4 style参数

style参数(摘自知乎文章):

  • wx.ICONIZE: 窗口初始时将被最小化显示。这个样式只在Windows系统中起作用;
  • wx.CAPTION: 一个有标题栏的窗口。如果要自定义最大化框、最小化框、系统菜单和上下文帮助,可以选择这个样式
  • wx.MINIMIZE: 同wx.ICONIZE一样的效果
  • wx.MINIMIZE_BOX: 在标题栏的标准位置放置一个最小化按钮(隐藏或禁用最大化和关闭按钮)
  • wx.MAXIMIZE: 窗口初始时将被最大化显示(全屏),这个样式只在Windows系统中起作用
  • wx.MAXIMIZE_BOX: 在标题栏的标准位置放置一个最大化按钮(隐藏或禁用最小化和关闭按钮)
  • wx.CLOSE_BOX: 在标题栏的标准位置放置一个关闭按钮(隐藏或禁用最大化和最小化按钮)
  • wx.STAY_ON_TOP: 窗口将始终在系统中其它框架的上面(如果你有多个框架使用了这个样式,那么它们将相互重叠,但对于系统中其它的框架,它们仍在上面)
  • wx.SYSTEM_MENU: 在标题栏上放置一个系统菜单。这个系统菜单的内容与你所使用的装饰样式有关。例如,如果你使用wx.MINIMIZE_BOX,那么系统菜单项就有“最小化”选项
  • wx.RESIZE_BORDER: 给窗口一个标准的可以手动调整尺寸的边框
  • wx.FRAME_TOOL_WINDOW: 窗口的标题栏比标准的小些,通常用于包含多种工具按钮的辅助窗口。在Windows操作系统下,工具窗口将不显示在任务栏中
  • wx.FRAME_NO_TASKBAR: 一个完全标准的窗口,除了一件事:在Windows系统和别的支持这个特性的系统下,它不显示在任务栏中。当最小化时,该框架图标化到桌面而非任务栏
  • wx.FRAME_FLOAT_ON_PARENT: 窗口将漂浮在其父窗口(仅其父窗口)的上面(很明显,要使用这个样式,框架需要有一个父窗口),其它的框架可以遮盖这个框架
  • wx.FRAME_SHAPED: 非矩形的窗口。窗口的确切形状使用frame.SetShape()方法来设置。

以下是我的:

创建窗口时的style参数等同于调用frame.SetWindowStyle()方法,也就是说,

frame = wx.Frame(···style=wx.RESIZE_BORDER···)

等同于

frame = wx.Frame(······)
frame.SetWindowStyle(wx.RESIZE_BORDER)

。注意,如有多个风格,风格和风格之间用“|”分隔。即:

frame.SetWindowStyle(风格1|风格2|风格3···风格n)

如在wx.DEFAULT_FRAME_STYLE(即:标准窗口)中禁用风格,需要:

wx.SetWindowStyle(wx.DEFAULT_FRAME_STYLE^(要禁用的风格1|风格2|风格3···风格n))

也就是在wx.DEFAULT_FRAME_STYLE后加上“^”,再用“()”包裹起来要禁用的风格。

注:刚才说的frame.SetWindowStyle()的特性在style参数中也同样适用。

                1.2.5 显示wx.Frame()对象
frame.Show(True)

行,让窗口显示。如果不传递参数,那么行为默认与frame.Show(True)一样。如果传递参数为False,那么窗口将不会显示,即关闭窗口,与frame.Close()一样。如果不写这一行,窗口也不会显示(即窗口默认不显示)。

                1.2.6 进入wx.App()对象的主循环

最后的一行代码

app.MainLoop()

让程序运行,一直侦听事件,一直刷新、绘制屏幕。

        1.3 改进的代码

其实上面的代码还可以改进,让代码变得更好扩展(虽然还是一个窗口):

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='改进的窗口', size=(800, 600))


class Application(wx.App):
    
    def OnInit(self):
        """实例化对象时自动调用"""
        frame = Frame()
        frame.Show(True)
        return True  # 这行一定要写,返回True或类真值代表运行成功,否则wxPython检测到返回False会报错


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

运行结果和上面一样,这里不再赘述。

需要注意的几个点:

  • super().__init__()和调用wx.Frame()一样
  • OnInit()会自动调用,不需要手动调用
  • return True一定要写,如不写就会返回None,而None是类假值,所以wxPython会检测为False,就会出现报错消息。同样,如果显式说明返回类假值,那么也会报错。

报错消息类似于(这里给super().__init__()传递一个不存在的参数frame): 

Traceback (most recent call last):
  File "e:\编码专区\程序\Python程序\命令行\测试.py", line 72, in OnInit
    frame = Frame()
            ^^^^^^^
  File "e:\编码专区\程序\Python程序\命令行\测试.py", line 65, in __init__
    super().__init__(None, id=wx.ID_ANY, title='改进的窗口', size=(800, 600), frame=None)
TypeError: Frame(): arguments did not match any overloaded call:
  overload 1: too many arguments
  overload 2: 'frame' is not a valid keyword argument
OnInit returned false, exiting...

TypeError之后的overload是比较有用的报错消息,最后一行代表app.OnInit()返回了False,导致报错(其实是出错了才让OnInit()返回False的)。

2.用来看的控件:静态控件

其实如果对wxPython有一定了解的话,你会发现所有的静态控件都带有一个“Static”前缀。Static就是“静态”的意思。静态控件一般有静态文本控件——wx.StaticText,静态图片控件——wx.StaticBitmap,静态分割线——wx.StaticLine,静态框控件——wx.StaticBox,以及进度显示条控件——wx.Gauge等。

在上面的控件中,你会发现有一个进度显示条控件,你可能觉得它不是静态控件(因为进度条在动),包括它的名字也不带Static前缀。那么这个时候就要提出一个问题了——

        2.1 什么是静态控件?

这里我引用的是微软的介绍,它简明扼要地回答了这个问题:

应用程序通常使用静态控件来标记其他控件或分隔一组控件。 尽管静态控件是子窗口,但无法选择它们。 因此,它们无法接收键盘焦点,并且不能具有键盘接口。 具有SS_NOTIFY样式的静态控件接收鼠标输入,当用户单击或双击控件时通知父窗口。 静态控件属于 STATIC 窗口类。

尽管静态控件可用于重叠窗口、弹出窗口和子窗口,但它们设计用于对话框,系统在其中标准化其行为。 通过在对话框外使用静态控件,开发人员会增加应用程序可能以非标准方式运行的风险。 通常,开发人员要么在对话框中使用静态控件,要么使用SS_OWNERDRAW样式来创建自定义静态控件。

 最关键的其实是第一段的前三句话。简单来说,静态控件是不可交互的。不可交互的意思是:

  • 无法获取鼠标焦点。
  • 无法获取键盘焦点
  • 无法输入任何文本。

简而言之,“不可交互”就是只能看的花瓶。它什么都不能动,你做出任何操作都和它无关。这种控件可以管理其它控件(如wx.StaticLine和wx.StaticBox),也可以动,如wx.Gauge。但最关键的是:

不可交互!不可交互!不可交互!

PS:其实绑定事件之后倒是也可以交互,在这一点上,wxPython的静态控件和动态控件倒是没什么区别。当然,那是第5章的事了。

不要看PS!把写PS的人抓起来!

呃······好像是我?

咳咳···我收回我刚才的话······咳咳,嗯。

接下来,我们按照顺序介绍各个控件,第一个——

        2.2 静态文本控件——wx.StaticText

                2.2.1 普通设置

静态文本控件的参数和窗体的参数一样。只不过那里的title参数是这里的label参数。

参数           说明                          
parent该窗体(控件)的父窗体(控件)。即应该显示在窗体parent之上
id控件的ID。可以自己设置,若传入-1或wx.ID_ANY,则由wxPython自行安排id。
label

控件的文本。传入字符串。

pos控件的坐标。传入元组,元组的第一项为x坐标,第二项为y坐标。
size控件的大小。传入元组,元组的第一项为长,第二项为高。
style控件的风格设置。可传入参数会在下面列出来。
name控件的名称。传入字符串,可以自己设置,也可不设置。

这里把style的参数列出来(引用这篇博客和CSDN的创作助手):

  • wx.ALIGN_CENTER:静态文本居中。
  • wx.ALIGN_LEFT:静态文本居左,这是默认样式。
  • wx.ALIGN_RIGHT:静态文本居右。
  • wx.ST_NO_AUTORESIZE:若使用此样式,则在使用SetLabel()改变文本之后,静态文本控件不会自我调整尺寸。
  • wx.ST_ELLIPSIZE_END:如果文本过长,则使用省略号来表示文本的结尾。
  • wx.ST_NO_ERASE_BACKGROUND:在绘制文本时不擦除背景。

 注意:

text.SetId()                   = id参数

text.SetLabel()             = label参数

text.SetWindowStyle() = style参数

text.SetSize()               = size参数

text.SetPosition()         = pos参数

不明白?看了这个程序,你就明白了。

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='静态文本控件示例', size=(800, 600))
        nothing = wx.StaticText(self)  # 这似乎是一个bug,没有其它控件时pos参数没效果
        text = wx.StaticText(self, id=wx.ID_ANY, label='一个普通的静态文本框', pos=(100, 100))


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

运行结果:

看起来很丑,对吗?别急,我们一步一步来。先来分析代码。

首先在第8行,我们定义了一个没有任何属性的空文本。当然,用其它的控件也可以(比如wx.Window)。创建它的目的是为了解决一个bug:没有其它控件文本的pos属性没有用。注意,其实这个控件是存在于窗口上的,只不过你看不见而已(所以算存在吗)。

在第9行,我们创建了一个文本,它的内容是“一个普通的静态文本框”。这里的内容是传递的label参数。它的左上点坐标是处于窗口的(100, 100)点。第一个参数self,即它的父窗口(控件),就是这个窗口本身。

你可以看到,在wxPython中显示控件不需要任何的代码。事实上,对象创建完就显示了。但是这个说法有一个特例,那就是没写parent。看看没写parent(用None填充)的结果是什么。

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='静态文本控件示例', size=(800, 600))
        nothing = wx.StaticText(None)
        text = wx.StaticText(None, id=wx.ID_ANY, label='一个普通的静态文本框',pos=(100, 100))


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

和之前的代码差不多,只是把self变成了None。但是最后报错了:

Traceback (most recent call last):
  File "e:\编码专区\程序\Python程序\命令行\cs.py", line 15, in OnInit
    frame = Frame()
            ^^^^^^^
  File "e:\编码专区\程序\Python程序\命令行\cs.py", line 8, in __init__
    nothing = wx.StaticText(None)
              ^^^^^^^^^^^^^^^^^^^
wx._core.wxAssertionError: C++ assertion ""parent"" failed at ..\..\src\common\ctrlcmn.cpp(79) in wxControlBase::CreateControl(): all controls must have parents
OnInit returned false, exiting...

说明parent不能不写。接下来,我们来改改这堆垃圾屎山代码。

                2.2.2 背景颜色、前景颜色

在改进之前,我们要明确改的东西。首先,我们来改颜色。

改背景颜色需要这一行代码:

text.SetBackgroundColour((255, 255, 255))

这个方法的名字就叫“设置背景颜色”,要传入一个元组,代表颜色的RGB值。

加上这行代码的窗口如下:

顺便说一下,没有其它控件,窗口就会被填满,所以颜色就是:

 看到了吧,这就是wxPython的bug。

修改窗口的背景色也一样:

frame.SetBackgroundColour((255, 255, 255))

加上这行代码,窗口变成(把frame改为self):

继续修改。接下来我们改前景颜色:

text.SetForegroundColour((255, 0, 0))

同样的,元组是RGB值。text.SetForegroundColour()修改的是字的颜色。而text.SetBackgroundColour()修改的是除了字以外的颜色。所以,可以看出来,所有的系统标准控件都是矩形的。

运行效果:

那么,窗口具有SetForegroundColour()属性吗?我们可以来试一下。

self.SetForegroundColour((255, 0, 0))

可以运行,但是没有用。

这一节修改过后的代码:

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='静态文本控件示例', size=(800, 600))
        self.SetBackgroundColour((255, 255, 255))
        nothing = wx.StaticText(self)
        text = wx.StaticText(self, id=wx.ID_ANY, label='一个普通的静态文本框',pos=(100, 100))
        text.SetForegroundColour((255, 0, 0))
        text.SetBackgroundColour((255, 255, 255))


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()
                2.2.3 修改字体、大小、样式——wx.Font()

众所周知,在wxPython中,默认的字体是微软雅黑。而只有一个微软雅黑和一个看不清的字,是远远不够的。而且,在我这个平台上,默认的字体大小是非常模糊的(真正的模糊)。所以,要进行字体、大小、样式等等的修改,我们就要用到一个类和一个方法。现在就让我们来介绍介绍这两个大名鼎鼎的东西——

  • 类是wx.Font()类。它可以修改字体、大小、样式等等。
  • 方法是text.SetFont()方法。它传入wx.Font()对象,将进行的设置应用在该wx.StaticText()对象上。

 先让我们来看看wx.Font()类。

参数说明
pointSize字体的大小。传入整数,单位为磅。
family指定一个字体而无需知道字体名。具体的字体要看运行的平台。
style字体的样式。具体的样式会在下面列出来。
weight字体的粗细。粗细程度也会列出来。
underline字体是否有下划线。True为有下划线,False为没有下划线。
faceName字体的字体(听起来有点傻)。具体的字体名会列出来。
encoding字体的编码。注意,编码是wxPython内部的编码。一般可以使用默认编码。

注意family和faceName的区别:family是在不知道字体名的情况下指定字体,而且,字体和平台有一定关系,也就是说,平台不同字体也就不同。而faceName是通过字体名指定字体,只要安装了这个字体就会显示,在各个平台上都一样。

family可传入参数(引用卡图卢斯的文章):

  • wx.FONTFAMILY_DECORATIVE:一个正式的,老的英文样式字体。
  • wx.FONTFAMILY_DEFAULT:系统默认字体。
  • wx.FONTFAMILY_MODERN:一个单间隔(固定字符间距)字体。
  • wx.FONTFAMILY_ROMAN:serif字体,通常类似于Times New Roman。
  • wx.FONTFAMILY_SCRIPT:手写体或草写体。
  • wx.FONTFAMILY_SWISS:sans-serif字体,通常类似于Helvetica或Arial。 

style可传入参数:

  • wx.FONTSTYLE_NORMAL:默认字体。
  • wx.FONTSTYLE_SLANT:(意大利)斜体。
  • wx.FONTSTYLE_ITALIC:(罗马)斜体。
  • wx.FONTSTYLE_MAX:似乎被遗忘了,没有它的介绍。好像和wx.FONTSTYLE_NORMAL一样。

 weight可传入参数(引用思念停滞不前的文章):

  • wx.FONTWEIGHT_THIN :最细字体
  • wx.FONTWEIGHT_EXTRALIGHT :极细字体
  • wx.FONTWEIGHT_LIGHT :细体
  • wx.FONTWEIGHT_NORMAL :正常字重
  • wx.FONTWEIGHT_MEDIUM :中等字重(比 FONTWEIGHT_NORMAL 稍粗)
  • wx.FONTWEIGHT_SEMIBOLD :稍粗字体
  • wx.FONTWEIGHT_BOLD :粗体
  • wx.FONTWEIGHT_EXTRABOLD :较粗字体
  • wx.FONTWEIGHT_HEAVY :极粗字体
  • wx.FONTWEIGHT_EXTRAHEAVY :最粗字体

顺便说一下,虽然wxPython这里分出了这么多级别,但是我只能用两种:正常和粗体。剩下的和这两种没有任何区别。

faceName可传入参数(字符串):

宋体SimSun
黑体SimHei
微软雅黑Microsoft Yahei
微软正黑体Microsoft JhengHei
楷体KaiTi
新宋体NSimSun
仿宋FangSong

encoding参数:

  • wx.FONTENCODING_SYSTEM :系统默认编码
  • wx.FONTENCODING_DEFAULT :应用默认编码
  • wx.FONTENCODING_ISO8859_1
  • wx.FONTENCODING_ISO8859_2
  • wx.FONTENCODING_ISO8859_3
  • wx.FONTENCODING_ISO8859_4
  • wx.FONTENCODING_ISO8859_5
  • wx.FONTENCODING_ISO8859_6
  • wx.FONTENCODING_ISO8859_7
  • wx.FONTENCODING_ISO8859_8
  • wx.FONTENCODING_ISO8859_9
  • wx.FONTENCODING_ISO8859_10

要填编码的话,直接选wx.FONTENCODING_DEFAULT就可以。


上面是wx.Font()的内容。东西太多,消化不了?别急,先来看看text.SetFont()方法。

text.SetFont(wx.Font(···))

传入的参数是wx.Font()对象。


现在我们来写写代码。

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='静态文本控件示例', size=(800, 600))
        nothing = wx.StaticText(self)
        text = wx.StaticText(self, id=wx.ID_ANY, label='一个普通的静态文本框',pos=(100, 100))
        font = wx.Font(18, family=wx.FONTFAMILY_DEFAULT, style=wx.FONTSTYLE_SLANT, weight=wx.FONTWEIGHT_MEDIUM, underline=False, faceName='KaiTi', encoding=wx.FONTENCODING_DEFAULT)
        text.SetFont(font)


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

可以看到,我把背景色和前景色的代码删去了,这样更有利于观察字体的改变。代码是这样,接下来我要修改的是font中的样式。

运行效果:

 这么一个文本框肯定不能说“普通”了,所以修改一下:

我们来分析一下代码。

第7行我们创建了一个窗口,第8行创建了一个占位的静态文本控件。

在第9行,我们创建了一个静态文本控件。修改过后,第9行的文本内容是“一个不是很普通的静态文本框”。

程序的关键是第10行。在第10行,我们创建了一个wx.Font()对象,并将其赋值给了变量font。在第11行,我们通过text.SetFont()方法给text设置了第10行的字体。

首先,我们设置了字体的大小,是18磅。通过修改pointSize,我们可以修改字体的大小。

接下来的参数是family。这个参数一般不需要用,所以我们使用默认,即wx.FONTFAMILY_DEFAULT(系统默认字体)。

style我们用的是斜体,即wx.FONTSTYLE_SLANT。如果要普通倾斜,即不倾斜,可以直接写style=0。(当然也可以用wx.FONTSTYLE_NORMAL)

然后是weight。这里我们选择普通。说实话,楷体的粗体不太好看。

underline参数传递的是False,如果是True,整个文本控件的所有文字下面都会有一条下划线,就像这样。

faceName,这个参数非常重要。它可以修改字体。注意,如果字体不存在,它会直接选用默认字体(在我的平台上,默认是宋体)。

最后是字体的编码。编码不用修改,选择默认即可。所以,这里我选择的是wx.FONTENCODING_DEFAULT(即默认编码)。

后面的东西就没什么了。都是之前的开发框架。


你可能会觉得,wx.FONTSTYLE_SLANT和wx.FONTSTYLE_ITALIC差不多。其实不是。看这个效果,下面的文本使用wx.FONTSTYLE_ITALIC,上面的文本使用wx.FONTSTYLE_SLANT:

似乎wx.FONTSTYLE_ITALIC的倾斜程度比wx.FONTSTYLE_SLANT更斜一点。


经过这次修改过后的代码(加上上一次修改):

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='静态文本控件示例', size=(800, 600))
        self.SetBackgroundColour((255, 255, 255))
        nothing = wx.StaticText(self)
        font = wx.Font(18, family=wx.FONTFAMILY_DEFAULT, style=wx.FONTSTYLE_SLANT, weight=wx.FONTWEIGHT_MEDIUM, underline=False, faceName='KaiTi', encoding=wx.FONTENCODING_DEFAULT)
        text = wx.StaticText(self, id=wx.ID_ANY, label='一个不是很普通的静态文本框',pos=(100, 100))
        text.SetFont(font)
        text.SetForegroundColour((255, 0, 0))
        text.SetBackgroundColour((255, 255, 255))


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

运行效果:

我们下一节见。

                2.2.4 让字变清晰

这里我主要引用这篇文章。

编译我们的程序,运行后:

在程序上右键,选择属性,选择兼容性,单击更改高DPI设置

勾选高DPI缩放替代中的替代高DPL缩放行为

一路单击确定,运行程序:

显示清晰了(虽然窗口变小了,这个应该是自己调)。

        2.3 显示图片——wx.StaticBitmap

图片的底层是位图。事实上,不管什么类型的图片,在底层都是以位图形式存在的。所以,这也就意味着wxPython只能加载位图。而位图的加载方式就是——wx.StaticBitmap()。

                2.3.1 创建位图的方式

这块我不想列表了,因为表格和前面的差不多。所以呢,我们直接来看代码:

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='静态图片控件示例', size=(800, 600))
        parent = __file__.replace('静态图片控件示例.py', '')
        image = wx.Image(parent+'bitmap.jpg', wx.BITMAP_TYPE_ANY).ConvertToBitmap()
        bitmap = wx.StaticBitmap(self, id=wx.ID_ANY, bitmap=image)
        bitmap.Center()


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

bitmap.jpg是这样的:

 是win11的一个桌面壁纸。我的壁纸就是这个。运行效果:

可以自适应窗口。效果显示:

接下来我们来分析代码。

                2.3.2 分析代码

直接来看第8行,我们定义了一个叫parent的变量。__file__是程序文件的路径,包括文件名。用replace()方法把字符串(文件名)“静态图片控件示例.py”删除掉后,剩下的就是父目录和一个反斜杠。这个变量将在指定位图路径时使用。

继续看第9行,我们定义了一个wx.Image()对象,该对象就是wxPython中的图片对象。不同于wx.StaticBitmap(),wx.StaticBitmap()是控件,而wx.Image()只是图片。在wx.Image()中我们传入了两个参数:第一个,图片路径,传入路径后wxPython会自动加载图片。第二个,图片类型,我并不觉得有什么用,所以选wx.BITMAP_TYPE_ANY(其实更正规的话应该写wx.BITMAP_TYPE_JPEG,因为本身这个图片就是JPG/JPEG格式的)。最后的.ConvertToBitmap()方法,将wx.Image()对象加载为位图,所以可以想象的到的是,image应该是一个位图。

然后是第10行。我们创建了控件wx.StaticBitmap,这里它的父窗口是这个对象,所以是self。我们传入的bitmap参数就是刚才已经被加载为位图的image变量。你会发现这里我没有写pos,这就是下一行的缘故了。

第11行,我们写了window.Center()。之所以写它,是因为我们始终要让这个位图保持在屏幕的中心,即(400, 300)点(wxPython的坐标系是以左上角为(0, 0)的)。window.Center()不是绝对布局,它会自适应窗口,不管如何拉伸都能保持在窗口的正中心。window.Center中如果传入参数dir为dwx.HORIZONTAL,那么控件会保证永远在这一行的最中心;如果传入wx.VERTICAL,那么控件永远在这一列的最中心。至于是哪一行哪一列,就得看传入的pos参数了。

注意:有些时候你会在代码里看到window.Centre(),它等同于window.Center()。事实上,在wxPython中,所有的center都和centre是一个意思。(例如上文出现的wx.ALIGN_CENTER,它和wx.ALIGN_CENTRE相同。)

        2.4 静态分割线——wx.StaticLine

因为StaticBitmap的内容比较简单,实际能用上的就那么点,所以我就简单写了。这静态分割线呢,更简单,简单到我都没用过它,算是一个没什么存在感的控件吧。

我也不往下分节了,直接看代码:

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='静态线控件示例', size=(800, 600))
        nothing = wx.Window(self)  # 老bug了,我发现如果不写这一行线都不显示
        nothing.SetBackgroundColour(self.GetBackgroundColour())  # 获取窗口的颜色RGB元组,将空控件的颜色设置为得到颜色
        line = wx.StaticLine(self, id=wx.ID_ANY, pos=(50, 50), size=(700, -1), style=wx.LI_HORIZONTAL)


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

效果:

就一条线而已,真的不知道该讲啥了。

style那块还可以写成wx.LI_VERTICAL,就是垂直线。

        2.5 “箱子”控件——wx.StaticBox

这个控件可就需要说说了。首先我们要知道,静态框控件是什么。它长这样:

引用本站文章 ,可以看到日志输出这四个字右面有一条浅浅的线。如果你没看出来的话,没关系,我把线给你描出来:

这个控件实际上是一个StaticText带一个框。关键是,它其实是用来框住其它控件的。也就是说,其它控件的parent参数可以填它。 

那么接下来,就让我们来了解了解这个控件——wx.StaticBox。

                2.5.1 创建静态框

其实我觉得,静态框这个控件就是一个wx.StaticText加几条wx.StaticLine。你自己完全可以用现有控件画出来。我打算接下来专门搞一章来写自定义控件。到那个时候,我会写一篇,来做这个静态框的拼接绘制。

创建静态框很简单,和我们之前看到的wx.StaticText控件一模一样

来看代码:

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='静态框控件示例', size=(800, 600))
        nothing = wx.Window(self)
        nothing.SetBackgroundColour(self.GetBackgroundColour())
        box = wx.StaticBox(self, id=wx.ID_ANY, label='静态框', pos=(50, 50), size=(700, 200))


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

运行效果:

程序很简单,我这里不多说了。

你会发现wxPython框架窗体的默认颜色在Windows平台上要深一些。那么非常简单,我们通过

self.SetBackgroundColour((240, 240, 240))

来设置窗口的背景颜色。

演示一下效果:

这就舒服多了吧。

                2.5.2 背景颜色、文字(前景)颜色

我们来设置wx.Static对象的颜色。先来设置背景颜色。

box.SetBackgroundColour((0, 255, 0))

是绿色的背景颜色。其实我并不喜欢设置静态框的背景颜色,因为它把框都盖住了。wxPython无法改变框的颜色。

那么,

box.SetForegroundColour((0, 255, 0))

对应的是什么呢?我们可以来看一下。(我这里把背景颜色删掉了)

修改了文字颜色。 

                2.5.3 给wx.StaticBox()添加控件

我们添加这一行:

text = wx.StaticText(box, id=wx.ID_ANY, label='静态文本控件')

运行效果:

 非常难看,文本控件把框的文本挡住了。所以我们现在请毫无负担的抛弃它(狗头)。


写到这里,你可能会发现(至少我发现了)wxPython的两个实现细节:

  • 第一,所有的控件在没有声明背景颜色的情况下会自适应窗口背景颜色。从2.5.2节我修改窗口背景色可以看出来。
  • 第二,给静态框设置前景色不仅仅是设置了静态框的文本,还有所有的静态文本控件,即wx.StaticText。我没有显式写出设置文本控件的代码,但是颜色确确实实改了。

以上这两个是wxPython的bug(也可说成特性,本质上,都没有什么区别,至少对于我来说(因为对于我来说都是意外的)),也是我刚刚发现的(没有查文档资料)。


我们回到文章的主题,在框里面其实完全可以用布局管理器,当然,我还没写到那。所以,我们可以用pos参数:

text = wx.StaticText(box, id=wx.ID_ANY, label='静态文本控件', pos=(0, 0))

运行效果:

 这个图可以说明:

  • 一、在wx.StaticBox中的所有控件的坐标都是相对于静态框的;
  • 二、(0, 0)坐标是在框线的左上点的。

所以我们现在修改坐标:

text = wx.StaticText(box, id=wx.ID_ANY, label='静态文本控件', pos=(9, 20))

使用这行代码几乎能让框的文本和文本控件平齐。

                2.5.4 控件的图层和堆叠

不知道你曾经有没有疑惑过:wxPython的图层是怎么排序的,该怎么解决这个问题?我之前写过一个程序,后面有一个背景图片,前面有几个按钮控件(自己绘制的)。但是当我要显示按钮的时候出现了问题:按钮根本没有出现。我以为是参数什么的出现了问题,可是调了半天,就是出不来。

后来我突然想到一个想法,我把创建图片的那行代码注释掉了,果然,按钮出现。然后我就开始调创建先后顺序,没用。所以我就开始找“wxPython 图层”,“wxPython 堆叠”,“wxPython 先后顺序”,“wxPython 控件先后顺序”,“wxPython 图层先后顺序”等等,结果我愣是找了一个小时,啥都没找着。后来我有了一个大胆的想法——

把这个按钮的父控件设为背景图片。

事情就这么成了。

没错,你没有看错,我也没有写错,真的就这么简单。

所以我现在要做的,就是把这个坑填上,别再踩这个坑。

不过在写这段的时候,我突然发现之前的情况没法模仿出来了。没办法,那我把代码贴出来:

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='演示', size=(810, 650), 
                         style=wx.DEFAULT_FRAME_STYLE^(wx.RESIZE_BORDER|wx.MAXIMIZE_BOX))
        self.InitUi()

    def InitUi(self):
        self.SetBgImage(r'路径')
        self.SetButton((60, 581))
        self.SetButton((750, 581))

    def SetBgImage(self, path):
        image = wx.Image(path, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
        self.bitmap = wx.StaticBitmap(self, id=wx.ID_ANY, bitmap=image, pos=(0, 0))
        self.bitmap.Center()

    def SetButton(self, pos):
        button = wx.Button(self, id=wx.ID_ANY, label='按钮', pos=pos)


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

我把和这个演示无关的所有东西都删掉了,有些东西你可能看不懂,不过没关系,来看效果:

什么都没有,对吧?注释掉创建图片的方法呢?

按钮出现。

所以该怎么改呢?前面说了,要把按钮的父窗口(控件)设为图片本身。 看到了吧。

再说说我做的无效努力:改创建先后顺序。

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='演示', size=(810, 650), 
                         style=wx.DEFAULT_FRAME_STYLE^(wx.RESIZE_BORDER|wx.MAXIMIZE_BOX))
        self.InitUi()

    def InitUi(self):
        self.SetButton((60, 581))
        self.SetButton((750, 581))
        self.SetBgImage(r'路径')

    def SetBgImage(self, path):
        image = wx.Image(path, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
        self.bitmap = wx.StaticBitmap(self, id=wx.ID_ANY, bitmap=image, pos=(0, 0))
        self.bitmap.Center()

    def SetButton(self, pos):
        button = wx.Button(self, id=wx.ID_ANY, label='按钮', pos=pos)


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

 效果:

没有。

但是我发现了一个奇怪的现象:当我在截完这张图之后,按钮又出现了。不过之前一般运行的时候都没有出现。

我根本解释不了这是怎么一回事。先后顺序在这里没用,可是在其它的程序里有用;截图之前、一般运行显示不出来,截图之后就显示出来了。

难道,这也是wxPython的bug吗?欢迎大佬现身说法。


我之所以要在这里说图层和重叠,是因为它和wx.StaticBox()的处理方式很相似。所以就在这里一并讲了。

                2.2.5 wx.Panel():面板的重要性

我之所以要讲它,是因为我在刚刚写的程序里面用到了它,而且它不可或缺;至于在这里讲它,只是因为它和wx.StaticBox()很像,甚至可以说它是隐式的wx.StaticBox()。

这玩意实在没啥好讲的,和wx.Window()差不多,能改颜色,具有和一般控件一样的参数,能当父控件,仅此而已。

(然后这一节就结束了)

顺便说一下,面板很重要,在我后面讲自定义控件的时候你就明白了。


下面的文字没有任何的章节:

本来在这里我是想写wx.Gauge的,但是由于涉及到的东西太多,所以我就打算在后面写了。

那么,我们现在开始讲动态控件吧。

3.动态控件之按钮

动态控件,我打算用两章写完,一章写按钮,一章写其它(听着是不是有点像一种黑客是俄罗斯黑客,另一种是其他黑客的味道)。是因为按钮实在是太多太杂,光标准库就有三个:普通按钮、位图按钮和开关按钮。扩展的还有四个,所以我就专门来搞这么一章来写按钮。

那么,让我们从最简单的普通文本按钮开始吧。

        3.1 从wx.Button()开始

按说动态控件应该和静态控件不一样,可是找了半天,也看不出不一样的地方。要说没事件,大家都一样;绑定鼠标事件,静态控件照样能绑。后来我发现,动态和静态的唯一区别应该就是键盘事件了,静态绑不了动态能绑,可是这个和按钮没什么关系······

所以,结论就是,按钮没用。

别急,我先卖个关子,等讲到第5章事件的时候,我再揭晓答案。

(这里透露一下,按钮能绑定键盘事件,原因我以后再说)

                3.1.1 基础设置

直接说吧,wx.Button的表格和wx.StaticText一模一样。所以这里也不过多赘述。直接看代码。

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='按钮示例', size=(800, 600))
        nothing = wx.StaticText(self)
        button = wx.Button(self, id=wx.ID_ANY, label='一个普通到不能再普通的没有存在感的按钮', pos=(100, 100))


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

效果是:

 事实上,这个控件真的有很多细节。如果我的鼠标放上去了:

水印有点挡,不过注意看,按钮的背景变蓝了。下面是按下时的样子:

 可以看到,颜色变得更深了。

你可能觉得,这些东西只是动画,其实不是。

它背后其实是一个非常重要的东西,而这就是动态控件和静态控件不同的地方,它导致静态控件不能绑定某些事件,它就是——

第5章再详细说。

你可能会发现,这个按钮按下时毫无用处,所以,我们隆重请出这个非常重要的内容——

区分静态、动态控件的关键内容,GUI程序不可或缺的一部分,没有它GUI就是扯,GUI的左膀右臂,让按钮站起来的按钮恩人元素,它就是——

事件。

现在,让我们来介绍一下这个内容。

                3.1.2 绑定事件

话不多说,咱们直接代码伺候(这里的代码改编于上面的代码):

import wx, random


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='按钮示例', size=(800, 600))
        nothing = wx.StaticText(self)
        self.button = wx.Button(self, id=wx.ID_ANY, label='一个普通到不能再普通的没有存在感的按钮', pos=(100, 100))
        self.button.Bind(wx.EVT_BUTTON, self.OnClick)

    def OnClick(self, event):
        text = ('按钮被按下了', '终于有点存在感了', '别按了', '再按我就把你吃掉')
        self.button.SetLabel(text[random.randint(0, len(text)-1)])


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

演示效果也很有意思,按下按钮之后文本是随机的(拜random模块所赐):

效果就大概是这么个效果,简单看一下代码。

第9行我们把原先的button改为了self.button,为了在OnClick方法中使用。

比较关键的,是第10行,不过由于卖关子,我简单说一下。self.button是你创建的对象,window.Bind()方法绑定的事件都基于前面的window。总之,把你创建的按钮对象写在前面就肯定不会错。第1个参数就照我这么写就行了,意思是按下按钮,注意,这个元素只能用于wx.Button()对象。第2个就是按下了按钮之后要执行的函数,一般来讲,以On开头,我这里是按下按钮,所以写OnClick。

第12行,我们创建了OnClick方法,由于是第5章的内容,所以我还是简单说。参数event必须得写,不写就报错,名字随便,但不需要用。

第13行我们创建了一个元组,是要显示在按钮上的文本。

第14行看起来有点绕,我来说一下我要干什么:在text元组随机选一个字符串放在按钮上。所以,这里的对象是self.button,所以,这里的方法是SetLabel,所以,我用了random.randint()函数。text[]填的是一个索引,但因为是随机的,所以调用random.randint()函数。索引的上下限分别是0和iterable的长度减1。写成代码就是random.randint(0, len(text)-1)。(都4层括号了)

代码就大概是这么个代码,相信你已经理解了,来聊一聊背景、前景颜色(也没啥可聊的)。

                3.1.3 颜色设置

这节我也不废话了,还是那几个方法,也没啥可说的。

不过呢,在做SetBackgroundColour的行为时,我发现了很有趣的一点:

用文字说就是,按下时和光标没进入按钮时是你设置的颜色,光标进入且没有按下时是它自己的颜色。在win11,这个颜色是:

 这节也没什么可说的了,就这样。

                3.1.4 风格设置

style设置就没什么可说了,直接引用官方文档

  • wx.BU_LEFT:左对齐标签。仅限 Windows 和 GTK+。

  • wx.BU_TOP:将标签与按钮顶部对齐。仅限 Windows 和 GTK+。

  • wx.BU_RIGHT:右对齐位图标签。仅限 Windows 和 GTK+。

  • wx.BU_BOTTOM:将标签与按钮底部对齐。仅限 Windows 和 GTK+。

  • wx.BU_EXACTFIT:默认情况下,所有按钮都至少由标准按钮大小组成,即使它们的内容足够小以适合较小的大小。这样做是为了保持一致性,因为大多数平台在本机对话框中使用相同大小的按钮,但可以通过指定此标志来覆盖。如果给定,按钮将使其足够大,以容纳其内容。请注意,在 MSW 下,如果按钮具有非空标签,则即使使用此样式,该按钮仍将至少具有标准高度。

  • wx.BU_NOTEXT:禁用按钮中文本标签的显示,即使它有一个,或者它的 ID 是具有关联标签的标准库存 ID 之一:如果不使用此样式,只应该显示位图但使用标准 ID 的按钮也会显示标签。

  • wx.BORDER_NONE:创建不带边框的按钮。这目前在 MSW、GTK2 和 OSX/Cocoa 中实现。^^

  • wx.BU_LEFT:左对齐标签。仅限 Windows 和 GTK+。

  • wx.BU_TOP:将标签与按钮顶部对齐。仅限 Windows 和 GTK+。

  • wx.BU_RIGHT:右对齐位图标签。仅限 Windows 和 GTK+。

  • wx.BU_BOTTOM:将标签与按钮底部对齐。仅限 Windows 和 GTK+。

  • wx.BU_EXACTFIT:默认情况下,所有按钮都至少由标准按钮大小组成,即使它们的内容足够小以适合较小的大小。这样做是为了保持一致性,因为大多数平台在本机对话框中使用相同大小的按钮,但可以通过指定此标志来覆盖。如果给定,按钮将使其足够大,以容纳其内容。请注意,在 MSW 下,如果按钮具有非空标签,则即使使用此样式,该按钮仍将至少具有标准高度。

  • wx.BU_NOTEXT:禁用按钮中文本标签的显示,即使它有一个,或者它的 ID 是具有关联标签的标准库存 ID 之一:如果不使用此样式,只应该显示位图但使用标准 ID 的按钮也会显示标签。

  • wx.BORDER_NONE:创建不带边框的按钮。这目前在 MSW、GTK2 和 OSX/Cocoa 中实现。^^

 就这么几个,顺道一提,wx.NO_BORDER和wx.BORDER_NONE是一样的。

在文档中,还说了有几个状态,就是normal和disable。接下来,我会着重介绍这几个状态。

                3.1.5 加入位图

其实wx.Button()是完全可以有位图的。这里我用一个我做其它程序的位图。代码如下:

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='按钮示例', size=(800, 600))
        nothing = wx.StaticText(self)
        image = wx.Image(r'路径', wx.BITMAP_TYPE_ANY).ConvertToBitmap()
        self.button = wx.Button(self, id=wx.ID_ANY, label='按这个按钮继续', pos=(100, 100), size=(350, 100))
        self.button.SetBitmap(image)
        self.button.Bind(wx.EVT_BUTTON, self.OnClick)
        self.button.SetWindowStyle(wx.NO_BORDER)

    def OnClick(self, event):
        self.button.SetLabel('哈哈,我骗你的,你居然也信')


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

效果就是:

左边是位图,右边是按钮文本。

设置位图的就是SetBitmap(),还没有bitmap参数。比较简单,我就不多说了。

                3.1.6 状态介绍

众所周知,wx.Button有很多种状态,有意思的是,它们都只支持位图(引用这一篇):

  • normal: 默认状态。
  • disabled: 禁用按钮时显示的位图。
  • pressed: 按下按钮时显示的位图(例如,当用户一直按下鼠标按钮时)。
  • focus: 当按钮有键盘焦点时显示位图(但没有按下,因为在这种情况下按钮处于按下状态)。
  • current: 当鼠标在按钮上时显示的位图(但它没有被按下,尽管它可能有焦点)。注意,如果没有指定当前位图,但当前平台UI为按钮使用悬停图像(如Windows或GTK+),那么焦点位图也用于悬停状态。这样只需设置焦点位图就可以在所有平台上获得合理的良好行为。

 我不一一用位图来展示变化了,简单介绍一下方法:

  • normal:button.SetBitmap()
  • disabled:button.SetBitmapDisabled()
  • pressed:button.SetBitmapPressed()
  • focus:button.SetBitmapFocus()
  • current:button.SetBitmapCurrent()

这就算结束了。我来说一下状态是什么样子的:

首先是normal。来看一下:

 很简单,没有焦点,鼠标不在上面,没有按下,没有键盘,说白了统统的没有,比较正常的状态。

然后是禁用状态(使用button.Disable(),没有最后的d):

 这个状态,不仅仅是没有焦点,鼠标不在上面,没有按下,没有键盘,而是根本就接收不了。整个按钮通体显灰,就是根本就用不了的意思。

继续看pressed:

整个按钮相当的蓝,就是鼠标按下时的意思。

focus状态我没有理解是什么意思,鉴于讲到了键盘交互,比较复杂,所以我先鸽着,第5章再讲(为什么总是第五章?)。

来看看current:

按钮通体淡蓝,要比pressed浅一些。

                3.1.7 继承关系

我前几天做了一个wxPython继承图,在继承图的最下面,我写了按钮控件的关系:

 主要看wx.Button,wx.ToggleButton暂且不管。可以看到wx.Button继承于wx.AnyButton,所有的按钮都继承于这个类(不过我们一般不调用)。再往上是wx.Control,在英文中的意思是“中心”,其实是所有控件的父类。再往上就是我之前提到过两嘴的wx.Window,它看起来是我这个继承图中所有东西的父类,不过我没列出来的有一些不是它的子类。顺道一提,我刚开始做这个继承图的时候就是以它为中心做的(不过让我没想到的是它上面还有这么多的类)。上面是wx.WindowBase,不知道是干什么用的。再上面是wx.EvtHandler,一看名字就知道,和event(事件)有点关系。再上面是wx.Trackable和wx.Object。这两个之间没有继承关系。先说wx.Object,它是wxPython中所有类的父类,代表wxPython中的对象。在这里,只有wx.Object()→wx.EvtHandler()→wx.WindowBase()→wx.Window()这条线,不过还有一些我没列出来的几个类直接继承于它(例如wx.Event)。wx.Trackable我不知道是什么,不过我认为是提供报错消息的(因为wxPython的报错消息很特别)。再上面是sip模块的东西,我也不知道是什么,导入也导入不了。顺便说一下,有一些控件还有其它我没有列出来的、和这幅图中的所有类没有任何关系的父类,这些我在这里声明一下,有wx.ItemContainer()和wx.ItemContainerImmutable()。

我在这里提出的所有问题,大家都可以来讨论,谢谢大家。

        3.2 位图按钮——wx.BitmapButton()

说句实在话,这个控件实在没啥可说的。因为我之前在wx.Button的章节里讲过如何创建位图按钮,所以呢,我就发个代码,拍个效果,就可以了。

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='按钮示例', size=(800, 600))
        nothing = wx.StaticText(self)
        image = wx.Image(r'路径', wx.BITMAP_TYPE_ANY).ConvertToBitmap()
        self.button = wx.BitmapButton(self, id=wx.ID_ANY, bitmap=image, pos=(100, 100))


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

效果:

没了,就这些。

        3.3 开关:wx.ToggleButton()

                3.3.1 什么意思?

在开始之前, 说一下什么是开关按钮。所谓开关按钮,就和开关一样,按一下,保持状态,再按一下,同样保持。

                3.3.2 开关按钮示例

我先放个代码,接下来我们通过代码来讲解我要写的知识点。

代码相当有趣,我们来看看:

import wx


class Frame(wx.Frame):

    def __init__(self):
        super().__init__(None, id=wx.ID_ANY, title='开关按钮示例', size=(800, 600))
        nothing = wx.StaticText(self)
        self.toggle_button = wx.ToggleButton(self, id=0, label='开关按钮', pos=(100, 100))
        self.Bind(wx.EVT_TOGGLEBUTTON, self.OnClick)

    def OnClick(self, event):
        if event.GetId() == 0:
            text = '按钮开' if event.IsChecked() else '按钮关'
            self.toggle_button.SetLabel(text)


class Application(wx.App):
    
    def OnInit(self):
        frame = Frame()
        frame.Show(True)
        return True


if __name__ == '__main__':
    app = Application()
    app.MainLoop()

效果:

 有趣在哪?你先观察,我11月4日来说。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值