本文章不可转载!
在整篇文章开始之前,我觉得有必要写一下我写这篇文章的目的。其实很简单,就是自己学wxPython中的随笔。当然,我其实有意把它写成一个系统的wxPython的教程。我这篇文章用的语言是python,库用的是wxPython(C++库wxWidegts的Python封装),所以用其它语言的人别来找我。
相信肯定有人把它当成工具来用。放心,你想要的我这里都有。因为这里面的知识都是我在写程序的时候真实遇到的问题。
那么,让我们开始吧。
注意:本文长到难以想象,所以没有恒心和毅力看完的人不要来喷我。
目录
写在前面
在很久以前(其实就是在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日来说。