Python GUI开发:Tkinter、PythonWin与wxPython详解
1. 引言
在Python中开发图形用户界面(GUI)有多种选择。本文将详细介绍三种在Windows平台上运行的GUI工具包:Tkinter、PythonWin和wxPython。这些工具包都为创建用户界面提供了丰富的功能,但每一个都值得用一本书来深入探讨,本文旨在让你对它们有一个基本的了解,以便在不同的场景中做出合适的选择。
2. Tkinter
2.1 概述
Tkinter是Python与Tk GUI工具包的接口,由Scriptics维护。由于其跨平台能力,它已成为Python事实上的标准GUI工具包,可在Windows、Macintosh和大多数Unix及Linux系统上运行。
2.2 术语
- Tk :一个用C例程库实现的GUI工具包,用于管理和操作窗口,处理GUI事件和用户交互。
- Tkinter :Python的Tk接口模块,提供了一系列Python类和方法,用于从Python内部访问Tk工具包。
- Tcl :Tk和Tkinter用于与Tk工具包通信的(大部分隐藏的)语言。
- Widget :用户界面元素,如文本框、组合框或顶级窗口,在Windows上通常称为控件或窗口。
2.3 优缺点
-
优点
:
- 简洁 :Python程序使用Tkinter可以非常简洁,部分原因是Python的强大功能,也得益于Tk为创建和布局小部件提供了合理的默认值。
- 跨平台 :Tk在Windows、Mac和大多数Unix系统上提供小部件,且对特定平台的依赖很小。
- 成熟 :自1990年首次发布以来,核心部分已经非常成熟和稳定。
- 可扩展性 :Tk有许多扩展,并且在网络上经常有新的扩展发布。通过Tkinter可以方便地访问这些扩展。
-
缺点
:
- 速度 :Tkinter的速度可能会受到一些关注。大多数对Tkinter的调用会被格式化为Tcl命令(字符串),并由Tcl解释执行,理论上会导致速度减慢,但在实际应用中这种影响很少被察觉。
- Tcl依赖 :Python纯粹主义者可能会对需要安装另一种脚本语言(Tcl)来执行GUI任务感到不满。虽然有人尝试直接使用Tk的C语言API来消除对Tcl的依赖,但尚未成功。
- 缺乏现代小部件 :Tk提供的基本小部件集较少,缺乏一些现代的高级小部件,如分割窗口、标签窗口、进度条和树状结构小部件。不过,可以通过组合基本小部件来创建新的小部件。
- 原生外观 :Windows上的Tkinter应用程序可能看起来不像Windows应用程序,但当前版本的Tkinter提供的界面已经可以被大多数人接受,未来版本有望与Windows应用程序几乎无差别。
2.4 运行GUI应用程序
Tkinter应用程序是普通的Python脚本,但在Windows上运行图形应用程序时需要注意一些问题。标准的Python.exe是一个控制台应用程序,使用它运行Tkinter程序会关联一个Windows控制台,可能会带来一些副作用。为了解决这个问题,Python提供了一个特殊的GUI版本Pythonw.exe。不过,.py文件默认与Python.exe关联,为了使用Pythonw.exe,可以给GUI Python应用程序一个.pyw扩展名。另外,由于Pythonw.exe没有控制台,Python打印的回溯信息通常无法看到,因此可以在开发时使用Python.exe,在最终运行时使用Pythonw.exe。
2.5 “Hello World”示例
from sys import exit
from Tkinter import *
root = Tk()
Button(root, text='Hello World!', command=exit).pack()
root.mainloop()
这个示例展示了Tkinter的基本使用方法。除了导入语句,主要有三行代码:创建一个顶级窗口,创建一个按钮并将其放置在窗口中,最后进入主事件处理循环。
2.6 核心小部件
Tkinter实现了一组相对较小的核心小部件,其他小部件或完整的应用程序可以基于这些核心小部件构建。以下是一些核心小部件及其描述:
| Widget Name | Description |
| — | — |
| Toplevel | 顶级小部件,没有主小部件,不支持几何管理方法。所有其他小部件直接或间接与顶级小部件关联。 |
| Frame | 用作其他子小部件的容器。 |
| Label | 显示文本或图像。 |
| Message | 显示具有自动换行和对齐功能的文本。 |
| Text | 显示具有高级格式、编辑和高度交互功能的文本。 |
| Canvas | 从显示列表中显示图形项,具有高度交互功能。 |
| Button | 标准的简单输入小部件,也称为控件小部件。 |
| Checkbox | 复选框。 |
| Entry | 输入框。 |
| Scale | 滑块。 |
| Radiobutton | 单选按钮。 |
| List box | 列表框。 |
| Scrollbar | 滚动条。 |
| Menu | 用于实现和响应菜单的小部件。 |
| Menubutton | 菜单按钮。 |
2.7 文本和画布小部件
2.7.1 文本小部件
文本小部件不仅可以显示和编辑文本,还支持嵌入图像和子窗口。其真正的强大之处在于对索引、标签和标记的支持:
-
索引
:提供了丰富的模型来描述文本控件中的位置,可以用行和列位置(相对或绝对)、像素位置、特殊系统索引名称等方式指定。
-
标签
:将一个名称与一个或多个文本区域关联起来,区域可以重叠,标签可以动态创建和销毁。与标签关联的文本可以有多种显示特性,如字体、颜色等。结合Tkinter的事件模型,可以轻松构建高度交互的应用程序。
-
标记
:表示文本中的一个位置(更准确地说,是两个字符之间的位置)。标记会随着周围文本的插入和删除而自然移动,适合实现书签或断点等概念。
2.7.2 画布小部件
画布小部件可以显示图形项,如线条、弧线、位图、图像、椭圆、多边形、矩形、文本字符串或任意Tkinter小部件。它也实现了一个强大的标记系统,允许将画布上的任何项与一个名称关联起来。
2.8 对话框和其他非核心小部件
许多有用的小部件实际上是由核心小部件构建而成的。最常见的例子是对话框小部件,Tkinter的最新版本提供了一些类似于Windows通用对话框的高级对话框小部件。以下是一些常见的对话框模块及其功能:
| Module Name | Description |
| — | — |
| tkMessageBox | 简单的消息框相关对话框,如“是/否”、“中止/重试/忽略”等。 |
| tkSimpleDialog | 包含用于构建自定义对话框的基类,还包括一些简单的输入对话框,如询问字符串、整数或浮点值。 |
| tkFileDialog | 功能与Windows通用文件对话框非常接近的对话框。 |
| tkColorChooser | 用于选择颜色的对话框。 |
2.9 小部件属性和方法
Tkinter为所有小部件提供了灵活而强大的属性集。几乎所有属性都可以在小部件创建时或创建并显示后进行设置。在指定小部件属性时,Tkinter大量使用Python关键字参数。例如:
label = Label(parent, background='white',
text='Hello World!',
relief=RAISED,
borderwidth=3)
创建一个标签后,可以随时使用
configure
方法重新配置其属性:
label.configure(background='red')
label.configure(background='white')
以下是一些常见的小部件属性:
| Property | Description |
| — | — |
| height, width | 小部件的高度和宽度(像素)。 |
| background, foreground | 小部件的颜色(字符串)。可以通过名称(如“red”或“light blue”)或十六进制表示法(如“#ffffff”表示白色)指定颜色。 |
| relief | 对象的3D外观(RAISED、SUNKEN、RIDGE或GROOVE)或2D外观(FLAT或SOLID)。 |
| borderwidth | 边框的宽度(像素)。 |
| text wrap, justify | 小部件的窗口文本(即标题)和多个小部件的附加格式选项。 |
| font | 显示文本的字体。可以有多种格式,最常见的是包含字体名称、字号和样式的元组(如
("Times", 10, "bold")
)。 |
| command, variable | 控件小部件用于与应用程序通信的技术。
command
选项允许指定一个Python函数,当指定的操作发生时调用该函数;
variable
选项可以指定一个
StringVar
、
IntVar
、
DoubleVar
或
Booleanvar
类的实例,小部件的变化会立即反映在该对象上,反之亦然。 |
Tkinter还提供了许多方法,其中
bind
方法是Tkinter事件模型的核心。它允许将一个GUI事件绑定到一个Python函数,接受两个参数:要绑定的事件(以字符串形式指定)和事件触发时要调用的Python对象。Tkinter提供了丰富的事件集,涵盖了键盘和鼠标操作、窗口焦点或状态变化等20多种基本事件类型。
2.10 几何管理
Tkinter提供了一种在Windows GUI工具包中通常找不到的强大概念——几何管理。它用于在父小部件中布局子小部件,而传统的Windows环境通常要求指定每个控件的绝对位置。Tkinter小部件提供了三种几何管理方法:
pack()
、
grid()
和
place()
。
-
place()
:最简单的机制,类似于大多数Windows用户习惯的方式,需要显式指定每个小部件的位置,可以是绝对坐标或相对坐标。
-
grid()
:自动将小部件排列成网格模式。
-
pack()
:最强大和最常用的方法。当小部件被打包时,它们会根据父小部件的大小和已放置的其他小部件自动定位。
这些几何管理功能允许定义不依赖于特定屏幕分辨率的用户界面,并且可以在窗口大小变化时自动调整控件的大小和布局。
2.11 Tkinter示例代码
本文包含了一个用Tkinter编写的Doubletalk浏览器示例(
tkBrowser.py
),这是一个功能齐全的交易查看和编辑应用程序。还提供了
TkDemo.py
示例,用于演示所有Tkinter核心小部件的基本操作。
2.12 Tkinter总结
Tkinter非常适合小型、快速的GUI应用程序,并且由于它可以在比其他Python GUI工具包更多的平台上运行,因此在需要考虑可移植性时是一个不错的选择。要了解更多关于Tkinter的信息,可以参考标准Python文档、PythonWare和Fredrik Lundh提供的资源、Scriptics的Tcl和Tk文档以及Python megawidgets(PMW)包等。
3. PythonWin
3.1 概述
PythonWin是一个将Microsoft Foundation Classes(MFC)的大部分功能暴露给Python的框架。MFC是一个C++框架,为Windows GUI API提供了基于对象的模型,并提供了一些对应用程序有用的服务。
3.2 MFC简介
Microsoft Foundation Classes是一个用于在C++中开发完整应用程序的框架,主要提供两个功能:
-
面向对象的包装
:将许多Windows API函数的“句柄”参数封装在对象中,提供了
CWnd
和
CDC
等类,这些类包含了相关的方法。
-
框架设施
:减少了创建一个完整的、独立的Windows应用程序所需的繁琐工作。
3.3 PythonWin对象模型
PythonWin由两部分组成:提供原始MFC功能的Python模块和使用这些模块的Python代码。要理解PythonWin的工作原理,需要了解MFC的关键概念。
win32ui
模块提供了对原始MFC类的访问,对于许多MFC对象,都有对应的
win32ui
对象。为了能够覆盖MFC对象层次结构中的默认方法,
win32ui
模块提供了一种机制,将Python类实例对象“附加”到
win32ui
类型上。当MFC需要调用覆盖的方法时,会调用附加的Python对象上的方法。
3.4 开发PythonWin示例应用程序
以MFC的Scribble示例为基础,开发一个用Python编写的版本。
3.4.1 定义简单框架
定义三个对象:
ScribbleTemplate
、
ScribbleDocument
和
ScribbleView
。
# scribble1.py
import win32ui
import win32con
import pywin.mfc.docview
class ScribbleTemplate(pywin.mfc.docview.DocTemplate):
pass
class ScribbleDocument(pywin.mfc.docview.Document):
def OnNewDocument(self):
"""Called whenever the document needs initializing.
For most MDI applications, this is only called as the document
is created.
"""
self.strokes = []
return 1
class ScribbleView(pywin.mfc.docview.ScrollView):
def OnInitialUpdate(self):
self.SetScrollSizes(win32con.MM_TEXT, (0, 0))
# Now we do the work to create the document template, and
# register it with the framework.
# For debugging purposes, we first attempt to remove the old template.
# This is not necessary once our app becomes stable!
try:
win32ui.GetApp().RemoveDocTemplate(template)
except NameError:
# haven't run this before - that's ok
pass
# Now create the template object itself…
template = ScribbleTemplate(None, ScribbleDocument, None, ScribbleView)
# Set the doc strings for the template.
docs='\nPyScribble\nPython Scribble Document\nScribble documents (*.psd)\n.psd'
template.SetDocStrings(docs)
# Then register it with MFC.
win32ui.GetApp().AddDocTemplate(template)
这个示例代码注册了
ScribbleTemplate
,使得MFC能够创建新的文档。要在PythonWin中注册模板,可以按照以下步骤操作:
1. 启动PythonWin。
2. 使用“文件”菜单打开示例代码。
3. 从“文件”菜单中选择“导入”,在PythonWin环境中执行该模块。
3.4.2 增强文档模板
由于MFC和PythonWin支持多个文档模板,当MFC被要求打开一个文档文件时,它会依次询问每个注册的
DocumentTemplate
是否可以处理该文档类型。为了确保
ScribbleTemplate
能够正确处理Scribble文档,需要重写
MatchDocType()
方法:
class ScribbleTemplate(pywin.mfc.docview.DocTemplate):
def MatchDocType(self, fileName, fileType):
doc = self.FindOpenDocument(fileName)
if doc: return doc
ext = string.lower(os.path.splitext(fileName)[1])
if ext =='.psd':
return win32ui.CDocTemplate_Confidence_yesAttemptNative
return win32ui.CDocTemplate_Confidence_noAttempt
3.4.3 增强文档
ScribbleDocument
对象负责处理文档数据,需要添加一些公共方法来处理笔画:
class ScribbleDocument(pywin.mfc.docview.Document):
def AddStroke(self, start, end, fromView):
self.strokes.append((start, end))
self.SetModifiedFlag()
self.UpdateAllViews( fromView, None )
def GetStrokes(self):
return self.strokes
def OnOpenDocument(self, filename):
file = open(filename, "rb")
self.strokes = pickle.load(file)
file.close()
win32ui.AddToRecentFileList(filename)
return 1
def OnSaveDocument(self, filename):
file = open(filename, "wb")
pickle.dump(self.strokes, file)
file.close()
self.SetModifiedFlag(0)
win32ui.AddToRecentFileList(filename)
return 1
3.4.4 定义视图
视图对象是示例中最复杂的对象,负责与用户的所有交互。它需要收集用户绘制的笔画,并在窗口需要重绘时绘制整个笔画列表。
class ScribbleView(pywin.mfc.docview.ScrollView):
def OnInitialUpdate(self):
self.SetScrollSizes(win32con.MM_TEXT, (0, 0))
self.HookMessage(self.OnLButtonDown,win32con.WM_LBUTTONDOWN)
self.HookMessage(self.OnLButtonUp,win32con.WM_LBUTTONUP)
self.HookMessage(self.OnMouseMove,win32con.WM_MOUSEMOVE)
self.bDrawing = 0
def OnLButtonDown(self, params):
assert not self.bDrawing, "Button down message while still drawing"
startPos = params[5]
# Convert the startpos to Client coordinates.
self.startPos = self.ScreenToClient(startPos)
self.lastPos = self.startPos
# Capture all future mouse movement.
self.SetCapture()
self.bDrawing = 1
def OnLButtonUp(self, params):
assert self.bDrawing, "Button up message, but not drawing!"
endPos = params[5]
endPos = self.ScreenToClient(endPos)
self.ReleaseCapture()
self.bDrawing = 0
# And add the stroke to the document.
self.GetDocument().AddStroke( self.startPos, endPos, self )
def OnMouseMove(self, params):
# If Im not drawing at the moment, I don't care
if not self.bDrawing:
return
pos = params[5]
dc = self.GetDC()
# Setup for an inverting draw operation.
dc.SetROP2(win32con.R2_NOT)
# "undraw" the old line
dc.MoveTo(self.startPos)
dc.LineTo(self.lastPos)
# Now draw the new position
self.lastPos = self.ScreenToClient(pos)
dc.MoveTo(self.startPos)
dc.LineTo(self.lastPos)
def OnDraw(self, dc):
# All we need to is get the strokes, and paint them.
doc = self.GetDocument()
for startPos, endPos in doc.GetStrokes():
dc.MoveTo(startPos)
dc.LineTo(endPos)
3.4.5 创建应用程序对象
创建一个简单的应用程序对象,继承自
pywin.framework.app.CApp
:
# scribbleApp.py
from pywin.framework.app import CApp
class ScribbleApplication(CApp):
def InitInstance(self):
# All we need do is call the base class,
# then import our document template.
CApp.InitInstance(self)
import scribble2
# And create our application object.
ScribbleApplication()
要运行这个应用程序,可以使用以下命令行:
C:\Scripts> start pythonwin /app scribbleApp.py
也可以使用资源编辑器(如Microsoft Visual C++)来避免使用命令行。
3.5 PythonWin和资源
MFC的框架架构很大程度上依赖于资源ID,这些整数用于在DLL或可执行文件中标识Windows资源。例如,定义
DocumentTemplate
时需要指定一个资源ID,MFC在创建文档时会使用该资源ID来加载菜单、图标和加速器等。这种架构有一些优点,如方便将应用程序的各个部分连接在一起、易于本地化等,但也存在一些缺点,如Python缺乏定义资源的技术,手动管理资源可能会很繁琐。PythonWin可以使用任意DLL中的资源,通过
win32ui.LoadLibrary()
加载DLL后,PythonWin可以定位和使用其中的资源。
3.6 PythonWin总结
对于大多数Windows上的Python用户来说,PythonWin可能只是一个有趣的IDE环境。但对于一些Windows开发者来说,它可以用于开发Windows应用程序。在比较本文介绍的三种GUI工具包时,PythonWin可能不太适合简单的小型GUI应用程序,但在特定情况下(如已有MFC投资或需要使用PythonWin提供的特定用户界面功能)可能是一个不错的选择。不过,PythonWin的文档不够完善,需要参考MFC相关的书籍和Microsoft的资料。
4. wxPython
4.1 概述
wxPython是一个Python扩展模块,封装了wxWindows C++类库,为Python提供了一个跨平台的GUI框架,在Windows平台上已经相当成熟。
4.2 wxWindows
wxWindows是一个免费的C++框架,旨在使跨平台编程变得简单。它支持Windows 3.1/95/98/NT、Unix(使用GTK/Motif/Lesstif),并且正在开发Mac版本。wxWindows提供了一组库,允许C++应用程序在不同类型的计算机上编译和运行,只需对源代码进行最小的更改。它不仅为GUI功能提供了通用的API,还提供了访问一些常用操作系统设施的功能,如复制或删除文件。在支持的平台上,使用本地版本的控件、通用对话框和其他窗口类型,对于其他平台,则使用wxWindows本身创建合适的替代方案。
4.3 wxWindows + Python = wxPython
wxPython将wxWindows库与Python语言绑定在一起,允许Python程序员创建wxWindows类的实例并调用这些类的方法。wxPython尽可能地镜像了wxWindows的类层次结构,大部分wxPython文档实际上是对C++文档的注释,描述了wxPython与C++版本的不同之处。
4.4 获取wxPython
可以从http://alldunn.com/wxPython/ 下载最新版本的wxPython,该网站提供了Win32系统的自安装程序,包括预构建的扩展模块、HTML帮助格式的文档和一组演示程序。如果想自己从源代码构建wxPython,还需要从http://www.wxwindows.org/ 下载wxWindows源代码。
4.5 简单示例
from wxPython.wx import *
class MyApp(wxApp):
def OnInit(self):
frame = wxFrame(NULL, -1, "Hello from wxPython")
frame.Show(true)
self.SetTopWindow(frame)
return true
app = MyApp(0)
app.MainLoop()
这个示例展示了一个基本的wxPython应用程序的结构。首先导入整个wxPython库,然后创建一个继承自
wxApp
的类,并提供
OnInit
方法。在
OnInit
方法中创建一个框架窗口并显示它,最后调用
SetTopWindow
方法将该框架设置为应用程序的主框架。最后创建应用程序类的实例并调用
MainLoop
方法,该方法是应用程序的核心,负责处理和分发事件,直到最后一个窗口关闭。
4.6 wxPython中的事件处理
要使菜单等元素能够响应事件,可以使用wxPython提供的辅助函数将方法或独立函数绑定到事件上。wxPython还提供了
wxEvent
类和一系列派生类来包含事件的详细信息。例如,为了使菜单的“退出”选项能够正常工作,可以对之前的示例进行修改:
from wxPython.wx import *
ID_ABOUT = 101
ID_EXIT = 102
class MyFrame(wxFrame):
def __init__(self, parent, ID, title):
wxFrame.__init__(self, parent, ID, title,
wxDefaultPosition, wxSize(200, 150))
self.CreateStatusBar()
self.SetStatusText("This is the statusbar")
menu = wxMenu()
menu.Append(ID_ABOUT, "&About",
"More information about this program")
menu.AppendSeparator()
menu.Append(ID_EXIT, "E&xit", "Terminate the program")
menuBar = wxMenuBar()
menuBar.Append(menu, "&File");
self.SetMenuBar(menuBar)
EVT_MENU(self, ID_ABOUT, self.OnAbout)
EVT_MENU(self, ID_EXIT, self.TimeToQuit)
def OnAbout(self, event):
dlg = wxMessageDialog(
self, "This sample program shows off\n"
"frames, menus, statusbars, and this\n"
"message dialog.",
"About Me", wxOK | wxICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
def TimeToQuit(self, event):
self.Close(true)
class MyApp(wxApp):
def OnInit(self):
frame = MyFrame(NULL, -1, "Hello from wxPython")
frame.Show(true)
self.SetTopWindow(frame)
return true
app = MyApp(0)
app.MainLoop()
这里使用
EVT_MENU
函数将菜单选项的事件绑定到相应的方法上。以下是一些常见的wxPython事件函数及其描述:
| Event Function | Event Description |
| — | — |
| EVT_SIZE | 当窗口大小发生变化时发送,无论是用户交互还是程序控制。 |
| EVT_MOVE | 当窗口被移动时发送,无论是用户交互还是程序控制。 |
| EVT_CLOSE | 当框架被请求关闭时发送。除非关闭是强制的,否则可以通过调用
event.Veto(true)
取消关闭。 |
| EVT_PAINT | 当窗口的一部分需要重绘时发送。 |
| EVT_CHAR | 当窗口具有焦点时,每个非修饰键(如Shift键)的按键事件都会发送。 |
| EVT_IDLE | 当系统没有处理其他事件时定期发送。 |
| EVT_LEFT_DOWN | 左鼠标按钮被按下。 |
| EVT_LEFT_UP | 左鼠标按钮被释放。 |
| EVT_LEFT_DCLICK | 左鼠标按钮被双击。 |
| EVT_MOTION | 鼠标移动。 |
| EVT_SCROLL | 滚动条被操作。实际上是一组事件,如果需要可以单独捕获。 |
| EVT_BUTTON | 按钮被点击。 |
| EVT_MENU | 菜单项被选中。 |
4.7 构建Doubletalk浏览器
接下来将构建一个基于Doubletalk类库的小型应用程序,用于浏览和编辑交易记录。
4.7.1 MDI框架
class DoubleTalkBrowserApp(wxApp):
def OnInit(self):
frame = MainFrame(NULL)
frame.Show(true)
self.SetTopWindow(frame)
return true
app = DoubleTalkBrowserApp(0)
app.MainLoop()
class MainFrame(wxMDIParentFrame):
title = "Doubletalk Browser - wxPython Edition"
def __init__(self, parent):
wxMDIParentFrame.__init__(self, parent, -1, self.title)
self.bookset = None
self.views = []
if wxPlatform == '__WXMSW__':
self.icon = wxIcon('chart7.ico', wxBITMAP_TYPE_ICO)
self.SetIcon(self.icon)
# create a statusbar that shows the time and date on the right
sb = self.CreateStatusBar(2)
sb.SetStatusWidths([-1, 150])
self.timer = wxPyTimer(self.Notify)
self.timer.Start(1000)
self.Notify()
menu = self.MakeMenu(false)
self.SetMenuBar(menu)
menu.EnableTop(1, false)
EVT_MENU(self, ID_OPEN, self.OnMenuOpen)
EVT_MENU(self, ID_CLOSE, self.OnMenuClose)
EVT_MENU(self, ID_SAVE, self.OnMenuSave)
EVT_MENU(self, ID_SAVEAS,self.OnMenuSaveAs)
EVT_MENU(self, ID_EXIT, self.OnMenuExit)
EVT_MENU(self, ID_ABOUT, self.OnMenuAbout)
EVT_MENU(self, ID_ADD, self.OnAddTrans)
EVT_MENU(self, ID_JRNL, self.OnViewJournal)
EVT_MENU(self, ID_DTAIL, self.OnViewDetail)
EVT_CLOSE(self, self.OnCloseWindow)
使用
wxMDIParentFrame
作为主框架的基类,可以自动实现多文档界面(MDI)的功能,而无需担心背后的具体实现。
4.7.2 图标
通过指定
.ico
文件的完整路径来创建图标,并使用
SetIcon
方法将图标关联到框架上。
4.7.3 定时器
使用
wxPyTimer
对象来更新状态栏中的日期和时间。
CreateStatusBar
方法可以创建指定数量的状态栏部分,
SetStatusWidths
方法可以指定每个部分的宽度。
# Time-out handler
def Notify(self):
t = time.localtime(time.time())
st = time.strftime(" %d-%b-%Y %I:%M:%S", t)
self.SetStatusText(st, 1)
4.7.4 主菜单
将菜单的创建逻辑封装在
MakeMenu
方法中,根据需要添加或删除菜单项。通过
EnableTop
方法可以禁用整个子菜单,
Enable
方法可以启用或禁用单个菜单项。
def MakeMenu(self, withEdit):
fmenu = wxMenu()
fmenu.Append(ID_OPEN, "&Open BookSet", "Open a BookSet file")
fmenu.Append(ID_CLOSE, "&Close BookSet",
"Close the current BookSet")
fmenu.Append(ID_SAVE, "&Save", "Save the current BookSet")
fmenu.Append(ID_SAVEAS, "Save &As", "Save the current BookSet")
fmenu.AppendSeparator()
fmenu.Append(ID_EXIT, "E&xit", "Terminate the program")
dtmenu = wxMenu()
dtmenu.Append(ID_ADD, "&Add Transaction",
"Add a new transaction")
if withEdit:
dtmenu.Append(ID_EDIT, "&Edit Transaction",
"Edit selected transaction in current view")
dtmenu.Append(ID_JRNL, "&Journal view",
"Open or raise the journal view")
dtmenu.Append(ID_DTAIL, "&Detail view",
"Open or raise the detail view")
hmenu = wxMenu()
hmenu.Append(ID_ABOUT, "&About",
"More information about this program")
main = wxMenuBar()
main.Append(fmenu, "&File")
main.Append(dtmenu,"&Bookset")
main.Append(hmenu, "&Help")
return main
4.7.5 wxFileDialog
使用
wxFileDialog
来打开BookSet文件,这是一个与Windows通用文件打开对话框类似的类。
def OnMenuOpen(self, event):
# This should be checking if another is already open,
# but is left as an exercise for the reader…
dlg = wxFileDialog(self)
dlg.SetStyle(wxOPEN)
dlg.SetWildcard("*.dtj")
if dlg.ShowModal() == wxID_OK:
self.path = dlg.GetPath()
self.SetTitle(self.title + ' - ' + self.path)
self.bookset = BookSet()
self.bookset.load(self.path)
self.GetMenuBar().EnableTop(1, true)
win = JournalView(self, self.bookset, ID_EDIT)
self.views.append((win, ID_JRNL))
dlg.Destroy()
4.7.6 wxListCtrl
JournalView
类使用
wxListCtrl
来显示交易记录的摘要信息。在初始化时,插入列并设置事件处理程序。
class JournalView(wxMDIChildFrame):
def __init__(self, parent, bookset, editID):
wxMDIChildFrame.__init__(self, parent, -1, "")
self.bookset = bookset
self.parent = parent
tID = wxNewId()
self.lc = wxListCtrl(self, tID, wxDefaultPosition,
wxDefaultSize, wxLC_REPORT)
## Forces a resize event to get around a minor bug…
self.SetSize(self.GetSize())
self.lc.InsertColumn(0, "Date")
self.lc.InsertColumn(1, "Comment")
self.lc.InsertColumn(2, "Amount")
self.currentItem = 0
EVT_LIST_ITEM_SELECTED(self, tID, self.OnItemSelected)
EVT_LEFT_DCLICK(self.lc, self.OnDoubleClick)
menu = parent.MakeMenu(true)
self.SetMenuBar(menu)
EVT_MENU(self, editID, self.OnEdit)
EVT_CLOSE(self, self.OnCloseWindow)
self.UpdateView()
def OnItemSelected(self, event):
self.currentItem = event.m_itemIndex
def OnDoubleClick(self, event):
self.OnEdit()
def UpdateView(self):
self.lc.DeleteAllItems()
for x in range(len(self.bookset)):
trans = self.bookset[x]
self.lc.InsertStringItem(x, trans.getDateString())
self.lc.SetStringItem(x, 1, trans.comment)
self.lc.SetStringItem(x, 2, str(trans.magnitude()))
self.lc.SetColumnWidth(0, wxLIST_AUTOSIZE)
self.lc.SetColumnWidth(1, wxLIST_AUTOSIZE)
self.lc.SetColumnWidth(2, wxLIST_AUTOSIZE)
self.SetTitle("Journal view - %d transactions" %
len(self.bookset))
def OnEdit(self, *event):
if self.currentItem:
trans = self.bookset[self.currentItem]
dlg = EditTransDlg(self, trans,
self.bookset.getAccountList())
if dlg.ShowModal() == wxID_OK:
trans = dlg.GetTrans()
self.bookset.edit(self.currentItem, trans)
self.parent.UpdateViews()
dlg.Destroy()
4.7.7 wxDialog和相关控件
构建一个对话框来编辑交易记录,使用了
wxStaticText
、
wxTextCtrl
、
wxComboBox
等控件,并使用
EVT_BUTTON
等事件处理程序来处理用户操作。
class EditTransDlg(wxDialog):
def __init__(self, parent, trans, accountList):
wxDialog.__init__(self, parent, -1, "")
self.item = -1
if trans:
self.trans = copy.deepcopy(trans)
self.SetTitle("Edit Transaction")
else:
self.trans = Transaction()
self.trans.setDateString(dates.ddmmmyyyy(self.trans.date))
self.SetTitle("Add Transaction")
# Create some controls
wxStaticText(self, -1, "Date:", wxDLG_PNT(self, 5,5))
self.date = wxTextCtrl(self, ID_DATE, "",
wxDLG_PNT(self, 35,5), wxDLG_SZE(self, 50, -1))
wxStaticText(self, -1, "Comment:", wxDLG_PNT(self, 5,21))
self.comment = wxTextCtrl(self, ID_COMMENT, "",
wxDLG_PNT(self, 35, 21), wxDLG_SZE(self, 195,-1))
self.lc = wxListCtrl(self, ID_LIST,
wxDLG_PNT(self, 5,34), wxDLG_SZE(self, 225,60),
wxLC_REPORT)
self.lc.InsertColumn(0, "Account")
self.lc.InsertColumn(1, "Amount")
self.lc.SetColumnWidth(0, wxDLG_SZE(self, 180,-1).width)
self.lc.SetColumnWidth(1, wxDLG_SZE(self, 40,-1).width)
wxStaticText(self, -1, "Balance:", wxDLG_PNT(self, 165,100))
self.balance = wxTextCtrl(self, ID_BAL, "",
wxDLG_PNT(self, 190,100),
wxDLG_SZE(self, 40, -1))
self.balance.Enable(false)
wxStaticLine(self, -1, wxDLG_PNT(self, 5,115),
wxDLG_SZE(self, 225,-1))
wxStaticText(self, -1, "Account:", wxDLG_PNT(self, 5,122))
self.account = wxComboBox(self, ID_ACCT, "",
wxDLG_PNT(self, 30,122), wxDLG_SZE(self, 130,-1),
accountList, wxCB_DROPDOWN | wxCB_SORT)
wxStaticText(self, -1, "Amount:", wxDLG_PNT(self, 165,122))
self.amount = wxTextCtrl(self, ID_AMT, "",
wxDLG_PNT(self, 190,122),
wxDLG_SZE(self, 40, -1))
btnSz = wxDLG_SZE(self, 40,12)
wxButton(self, ID_ADD, "&Add Line", wxDLG_PNT(self, 52,140), btnSz)
wxButton(self, ID_UPDT, "&Update Line", wxDLG_PNT(self, 97,140),
btnSz)
wxButton(self, ID_DEL, "&Delete Line", wxDLG_PNT(self, 142,140),
btnSz)
self.ok = wxButton(self, wxID_OK, "OK", wxDLG_PNT(self, 145,5),
btnSz)
self.ok.SetDefault()
wxButton(self, wxID_CANCEL, "Cancel", wxDLG_PNT(self, 190,5), btnSz)
# Resize the window to fit the controls
self.Fit()
# Set some event handlers
EVT_BUTTON(self, ID_ADD, self.OnAddBtn)
EVT_BUTTON(self, ID_UPDT, self.OnUpdtBtn)
EVT_BUTTON(self, ID_DEL, self.OnDelBtn)
EVT_LIST_ITEM_SELECTED(self, ID_LIST, self.OnListSelect)
EVT_LIST_ITEM_DESELECTED(self, ID_LIST, self.OnListDeselect)
EVT_TEXT(self, ID_DATE, self.Validate)
# Initialize the controls with current values
self.date.SetValue(self.trans.getDateString())
self.comment.SetValue(self.trans.comment)
for x in range(len(self.trans.lines)):
account, amount, dict = self.trans.lines[x]
self.lc.InsertStringItem(x, account)
self.lc.SetStringItem(x, 1, str(amount))
self.Validate()
def Validate(self, *ignore):
bal = self.trans.balance()
self.balance.SetValue(str(bal))
date = self.date.GetValue()
try:
dateOK = (date == dates.testasc(date))
except:
dateOK = 0
if bal == 0 and dateOK:
self.ok.Enable(true)
else:
self.ok.Enable(false)
def OnAddBtn(self, event):
account = self.account.GetValue()
amount = string.atof(self.amount.GetValue())
self.trans.addLine(account, amount)
# update the list control
idx = len(self.trans.lines)
self.lc.InsertStringItem(idx-1, account)
self.lc.SetStringItem(idx-1, str(amount))
self.Validate()
self.account.SetValue("")
self.amount.SetValue("")
4.8 wxPython总结
wxPython的功能非常强大,本文只是触及了它的表面。它提供了比本文展示的更多的窗口和控件类型,其高级功能适用于构建高度灵活和动态的GUI应用程序,并且可以在多个平台上运行。结合Python的灵活性,wxPython是快速创建世界级应用程序的强大工具。要了解更多关于wxPython的信息,可以访问其官方网站http://alldunn.com/wxPython/ ,以及wxWindows的官方网站http://www.wxwindows.org/ 。
综上所述,Tkinter适合小型、可移植的应用程序;PythonWin在特定的Windows开发场景中有其优势;而wxPython则提供了一个强大的跨平台解决方案。在选择GUI工具包时,需要根据项目的具体需求和特点来做出决策。
5. 三种GUI工具包的对比
5.1 功能特性对比
| 工具包 | 跨平台能力 | 小部件丰富度 | 事件处理 | 布局管理 |
|---|---|---|---|---|
| Tkinter | 强,支持Windows、Mac、Unix和Linux | 基本小部件集,缺乏现代高级小部件 |
通过
bind
方法绑定事件,事件类型丰富
|
提供
pack()
、
grid()
、
place()
三种几何管理方法
|
| PythonWin | 主要针对Windows平台 | 依赖MFC,有一定的小部件支持 | 结合MFC的事件处理机制 | 基于MFC的布局方式 |
| wxPython | 跨平台,支持多种操作系统 | 丰富的窗口和控件类型 |
提供多种
EVT_*
辅助函数绑定事件
| 提供布局约束、布局算法、Sizers等多种布局方式 |
5.2 开发难度对比
- Tkinter :相对简单,语法简洁,适合初学者快速上手。其文档资源丰富,有大量的示例代码可供参考。
- PythonWin :需要对MFC有一定的了解,开发难度较大。MFC的概念和机制较为复杂,对于没有C++和Windows开发经验的开发者来说,学习曲线较陡。
- wxPython :有一定的学习成本,但由于其类层次结构与C++版本的wxWindows相似,对于有C++开发经验的开发者来说,容易上手。其文档和示例代码也比较丰富。
5.3 性能对比
- Tkinter :理论上由于需要通过Tcl解释执行,可能会有一定的性能损失,但在实际应用中,大多数情况下性能表现良好。
- PythonWin :基于MFC,性能表现与MFC应用程序类似,但由于涉及Python和C++的交互,可能会有一些性能开销。
- wxPython :封装了wxWindows C++类库,性能相对较好,能够满足大多数应用程序的需求。
5.4 适用场景对比
- Tkinter :适合小型、快速开发的GUI应用程序,尤其是对跨平台性要求较高的场景。例如,简单的脚本工具、教学示例等。
- PythonWin :适合已有MFC投资或需要使用MFC特定功能的Windows开发场景。例如,与Windows系统深度集成的应用程序、需要使用Windows特定API的应用程序等。
- wxPython :适合开发跨平台的大型GUI应用程序,尤其是需要丰富的窗口和控件类型、高级布局管理和事件处理的场景。例如,桌面应用程序、图形编辑器、IDE等。
6. 选择合适的GUI工具包
6.1 考虑因素
- 项目需求 :明确项目的功能需求、平台需求、性能需求等。例如,如果项目需要在多个平台上运行,那么Tkinter或wxPython可能是更好的选择;如果项目需要与Windows系统深度集成,那么PythonWin可能更合适。
- 开发经验 :考虑开发团队的技术栈和开发经验。如果团队成员熟悉Python但缺乏Windows开发经验,那么Tkinter或wxPython可能更容易上手;如果团队成员有C++和MFC开发经验,那么PythonWin可能是一个不错的选择。
- 学习成本 :评估学习新工具包所需的时间和精力。如果项目时间紧迫,那么选择一个简单易用的工具包可以提高开发效率。
- 社区支持 :选择一个有活跃社区支持的工具包,可以更容易地获取帮助和资源。例如,Tkinter和wxPython都有庞大的社区,提供了丰富的文档、示例代码和论坛。
6.2 决策流程
graph TD;
A[明确项目需求] --> B{是否需要跨平台};
B -- 是 --> C{对小部件丰富度要求高吗};
B -- 否 --> D{是否已有MFC投资};
C -- 是 --> E[选择wxPython];
C -- 否 --> F[选择Tkinter];
D -- 是 --> G[选择PythonWin];
D -- 否 --> H{对开发难度要求低吗};
H -- 是 --> F;
H -- 否 --> E;
7. 总结与展望
7.1 总结
本文详细介绍了Python中三种常用的GUI工具包:Tkinter、PythonWin和wxPython。Tkinter是Python的标准GUI工具包,具有跨平台能力强、语法简洁等优点,适合小型、快速开发的应用程序;PythonWin基于MFC,为Windows开发提供了强大的功能,但开发难度较大,适合已有MFC投资的场景;wxPython封装了wxWindows C++类库,提供了丰富的窗口和控件类型、高级布局管理和事件处理功能,适合开发跨平台的大型GUI应用程序。
7.2 展望
随着Python的不断发展,GUI工具包也在不断完善和创新。未来,我们可以期待这些工具包在性能、功能和易用性方面取得更大的进步。例如,Tkinter可能会进一步优化性能,减少对Tcl的依赖;PythonWin可能会提供更完善的文档和开发工具,降低开发难度;wxPython可能会增加更多的高级功能和控件类型,提高开发效率。同时,新的GUI工具包也可能会不断涌现,为Python开发者提供更多的选择。
在选择GUI工具包时,开发者应根据项目的具体需求和自身的开发经验,综合考虑各种因素,做出合适的决策。希望本文能够为Python开发者在GUI开发方面提供有价值的参考。
超级会员免费看
1452

被折叠的 条评论
为什么被折叠?



