Python 是一种流行的语言,既能进行脚本编写,又能进行面向对象编程。有几个框架为 Python 提供了 GUI(图形用户界面),它们大多数在某些方面都很出色,无论是简单性、效率还是灵活性。其中最流行的两个是 wxPython 和 PyQt,但它们之间有何区别?更重要的是,您应该为您的项目选择哪个?
外观和感觉
让我们先解决大多数用户首先注意到的问题——应用程序的外观。
wxPython 的独特功能之一是它的核心库(用 C++ 编写)是围绕其宿主系统的原生小部件的包装器。当您在 GUI 中为按钮小部件编写代码时,您不会得到看起来像是属于另一个操作系统的东西,也不会得到仅仅是近似值的东西。相反,您会得到与使用原生工具编码时相同的对象。
https://open-source.net.cn/sites/default/files/wxbutton.png" title="Thunar and WxPython on Linux" typeof="foaf:Image" width="492" height="480">
Linux 上的 Thunar 和 WxPython
这与 PyQt 不同,PyQt 基于著名的 Qt 工具包。PyQt 也是用 C++ 编写的,但它不使用原生小部件,而是根据它检测到的操作系统创建小部件的近似值。它做出了很好的近似,我从来没有遇到过用户——即使在艺术学校,那里的用户往往对外观非常挑剔——抱怨应用程序看起来和感觉不像原生的。
如果您使用的是 KDE,您可以使用额外的 PyKDE 库来弥合原始 PyQt 和 Linux 和 BSD 上 Plasma 桌面的外观之间的差距,但这会增加新的依赖项。
https://open-source.net.cn/sites/default/files/qtbutton.png" title="KDE and Qt on Linux" typeof="foaf:Image" width="520" height="288">
Linux 上的 KDE 和 Qt
跨平台
wxPython 和 PyQt 都支持 Linux、Windows 和 Mac,因此它们非常适合著名的跨平台 Python;但是,不要让“跨平台”这个词迷惑了您——您仍然必须在 Python 代码中进行特定于平台的调整。您的 GUI 工具包无法调整到数据目录的路径格式,因此您仍然必须在 Python 中遵循最佳实践,使用 os.path.join 和一些不同的 exit 方法等等。您选择的 GUI 工具包不会神奇地从一个平台抽象到另一个平台。
PyQt 努力使您免受跨平台差异的影响。在允许 Python 本身需要的常见调整的情况下,PyQt 使您免受大多数跨平台问题的困扰,从而使您的 GUI 代码保持不变,而与操作系统无关。总会有例外情况,但 PyQt 处理得非常好。这是一种您会欣赏和钦佩的奢侈。
在 wxPython 中,您可能需要在 GUI 代码中进行一些特定于平台的更改,具体取决于您要编程的内容。例如,为了防止 Microsoft Windows 上某些元素的闪烁,必须将 USE_BUFFERED_DC 属性设置为 True 以双缓冲图形。即使可以无条件地为所有平台执行此操作,但这也不是默认设置,因此在某些用例中可能存在缺点,但这很好地说明了您必须为 wxPython 做出的让步。
安装
作为开发人员,您可能不介意获取应用程序所需库的安装步骤;但是,如果您计划分发您的应用程序,那么您需要考虑您的用户必须经历的安装过程才能使您的应用程序运行。
在任何平台上安装 Qt 都像安装任何其他应用程序一样简单:。给您的用户一个下载链接,告诉他们安装下载的软件包,他们很快就可以使用您的应用程序了。这在所有支持的平台上都是如此。
然而,所有平台都适用的是,PyQt 依赖于 Qt 本身的 C++ 代码。这意味着用户不仅必须安装 PyQt,还必须安装所有 Qt。这不是一个小软件包,它需要大量的点击,并且可能需要逐步完成安装向导。然而,Qt 和 PyQt 团队尽可能地简化了安装过程,因此,虽然这似乎对用户要求很高,但只要您提供直接链接,任何能够安装 Web 浏览器或游戏的用户都应该能够应对 Qt 安装。如果您非常投入,您甚至可以将安装编写为自己的安装程序的一部分。
在 Linux、BSD 和 Ilumos 系列中,通常发行版的软件包管理器已经为您编写了安装脚本。
wxPython 的安装过程在 Linux 和 Windows 上一样简单,但在 Mac OS 上却存在问题。可下载的软件包严重过时,这是苹果公司不重视向后兼容性的另一个受害者。错误票据 中存在修复程序,但软件包尚未更新,因此普通用户找到并实施补丁的可能性很小。目前的解决方案是打包 wxPython 并将其分发给您的 Mac OS 用户,或者依赖外部软件包管理器(尽管当我上次测试 Mac 版 wxPython 时,即使是那些安装脚本也失败了)。
小部件和功能
PyQt 和 wxPython 都具有您期望从 GUI 工具包获得的所有常用小部件,包括按钮、复选框、下拉菜单等等。两者都支持拖放操作、选项卡式界面、对话框和自定义小部件的创建。
PyQt 具有灵活性的优势。您可以在运行时重新排列、浮动、关闭和恢复 Qt 面板,从而为每个应用程序提供高度可配置的以可用性为中心的界面。
https://open-source.net.cn/sites/default/files/panelmove.png" title="Moving Qt panels" typeof="foaf:Image" width="382" height="194">
移动 Qt 面板
只要您使用正确的小部件,这些功能都是内置的,您无需重新发明花哨的技巧来为您的高级用户提供友好的功能。
WxPython 具有许多出色的功能,但在灵活性和用户控制方面,它无法与 PyQt 相提并论。一方面,这意味着作为开发人员,设计和布局对您来说更容易。当在 Qt 上进行开发时,您很快就会收到用户关于如何跟踪自定义布局的请求,或者如何找到意外关闭的丢失面板等等。出于同样的原因,wxPython 对您的用户来说更简单,因为当面板根本无法关闭时,丢失意外关闭的面板要困难得多。
毕竟,wxPython 毕竟只是 wxWidgets 的前端,因此如果您真的需要某个功能,您或许可以用 C++ 实现它,然后在 wxPython 中使用它。然而,与 PyQt 相比,这是一个艰巨的任务。
GUI 应用程序由许多较小的可视化元素组成,通常称为“小部件”。为了使 GUI 应用程序顺利运行,小部件必须相互通信,例如,旨在显示图像的窗格知道用户选择了哪个缩略图。
大多数 GUI 工具包(包括 wxPython)都使用“回调”来处理内部通信。回调是指向某些代码片段(“函数”)的指针。如果您想在例如单击按钮小部件时执行某些操作,您可以为您想要发生的操作编写一个函数。然后,当单击按钮时,您可以在代码中调用该函数,操作就会发生。
它运行良好,只要您将其与 lambda 结合使用,它就是一个非常灵活的解决方案。有时,根据您希望通信有多么复杂,您最终会得到比您预期的更多的代码,但它确实有效。
另一方面,Qt 以其“信号和槽”机制而闻名。如果您将 wxPython 的内部通信网络想象成旧式的电话交换机,那么将 PyQt 的通信想象成网状网络。
https://open-source.net.cn/sites/default/files/abstract-connections.png" title="Qt diagram" typeof="foaf:Image" width="516" height="501">
Qt 中的信号和槽(Qt 图表 GFDL 许可证)
使用信号和槽,一切都获得了签名。发出信号的小部件不需要知道其消息的目标槽,甚至不需要知道它是否以任何槽为目标。只要您将信号连接到槽,当信号广播时,槽就会使用信号的参数被调用。
可以将槽设置为侦听任意数量的信号,并且可以将信号设置为广播到任意数量的槽。您甚至可以将一个信号连接到另一个信号以创建信号的连锁反应。您永远不必回到您的代码中手动“连接”事物。
信号和槽可以接受任意数量的任意类型的参数。您无需编写代码来过滤掉您在某些条件下想要或不想要的东西。
更好的是,槽不仅仅是侦听器;它们是可以在有或没有信号的情况下执行有用操作的普通函数。正如对象不知道是否有任何东西在侦听其信号一样,槽也不知道它是否在侦听信号。任何代码块都不依赖于连接的存在;如果有连接,它只是在不同的时间被触发。
无论您是否理解信号和槽,一旦您使用它们,然后尝试回到传统的回调,您就会迷上它们。
布局
当您编写 GUI 应用程序时,您必须设计其布局,以便所有小部件都知道在应用程序窗口中的哪个位置显示。与网页一样,您可以选择将应用程序设计为可调整大小,或者您可以将其限制为固定大小。在某些方面,这是 GUI 编程中最 GUI 的部分。
在 Qt 中,一切都非常合乎逻辑。小部件被明智地命名(QPushButton、QDial、QCheckbox、QLabel,甚至 QCalendarWidget),并且易于调用。文档非常出色,只要您经常参考它,并且很容易在其中发现很酷的功能。
存在潜在的混淆点,主要是在基本级别的 GUI 元素中。例如,如果您正在编写应用程序,您是应该从 QMainWindow 还是 QWidget 开始来形成您的父窗口?两者都可以用作应用程序的窗口,因此答案是,就像在计算中经常出现的那样:视情况而定。
QWidget 是一个原始的、空的容器。它被所有其他小部件使用,但这意味着它也可以按原样使用,以形成您在其中放置更多小部件的父窗口。QMainWindow 像所有其他小部件一样,使用 QWidget,但它添加了许多大多数应用程序需要 的便利功能,例如顶部的工具栏、底部的状态栏等。
https://open-source.net.cn/sites/default/files/qmainwindow.png" title="QMainwindow" typeof="foaf:Image" width="542" height="377">
QMainwindow
使用 QMainWindow 在 100 多行 Python 代码中实现的小型文本编辑器
#!/usr/bin/env python
# a minimal text editor to demo PyQt5
# GNU All-Permissive License
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved. This file is offered as-is,
# without any warranty.
import sys
import os
import pickle
from PyQt5 import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class TextEdit(QMainWindow):
def __init__(self):
super(TextEdit, self).__init__()
#font = QFont("Courier", 11)
#self.setFont(font)
self.filename = False
self.Ui()
def Ui(self):
quitApp = QAction(QIcon('/usr/share/icons/breeze-dark/actions/32/application-exit.svg'), 'Quit', self)
saveFile = QAction(QIcon('/usr/share/icons/breeze-dark/actions/32/document-save.svg'), 'Save', self)
newFile = QAction('New', self)
openFile = QAction('Open', self)
copyText = QAction('Copy', self)
pasteText = QAction('Yank', self)
newFile.setShortcut('Ctrl+N')
newFile.triggered.connect(self.newFile)
openFile.setShortcut('Ctrl+O')
openFile.triggered.connect(self.openFile)
saveFile.setShortcut('Ctrl+S')
saveFile.triggered.connect(self.saveFile)
quitApp.setShortcut('Ctrl+Q')
quitApp.triggered.connect(self.close)
copyText.setShortcut('Ctrl+K')
copyText.triggered.connect(self.copyFunc)
pasteText.setShortcut('Ctrl+Y')
pasteText.triggered.connect(self.pasteFunc)
menubar = self.menuBar()
menubar.setNativeMenuBar(True)
menuFile = menubar.addMenu('&File')
menuFile.addAction(newFile)
menuFile.addAction(openFile)
menuFile.addAction(saveFile)
menuFile.addAction(quitApp)
menuEdit = menubar.addMenu('&Edit')
menuEdit.addAction(copyText)
menuEdit.addAction(pasteText)
toolbar = self.addToolBar('Toolbar')
toolbar.addAction(quitApp)
toolbar.addAction(saveFile)
self.text = QTextEdit(self)
self.setCentralWidget(self.text)
self.setMenuWidget(menubar)
self.setMenuBar(menubar)
self.setGeometry(200,200,480,320)
self.setWindowTitle('TextEdit')
self.show()
def copyFunc(self):
self.text.copy()
def pasteFunc(self):
self.text.paste()
def unSaved(self):
destroy = self.text.document().isModified()
print(destroy)
if destroy == False:
return False
else:
detour = QMessageBox.question(self,
"Hold your horses.",
"File has unsaved changes. Save now?",
QMessageBox.Yes|QMessageBox.No|
QMessageBox.Cancel)
if detour == QMessageBox.Cancel:
return True
elif detour == QMessageBox.No:
return False
elif detour == QMessageBox.Yes:
return self.saveFile()
return True
def saveFile(self):
self.filename = QFileDialog.getSaveFileName(self, 'Save File', os.path.expanduser('~'))
f = self.filename[0]
with open(f, "w") as CurrentFile:
CurrentFile.write(self.text.toPlainText() )
CurrentFile.close()
def newFile(self):
if not self.unSaved():
self.text.clear()
def openFile(self):
filename, _ = QFileDialog.getOpenFileName(self, "Open File", '', "All Files (*)")
try:
self.text.setText(open(filename).read())
except:
True
def closeEvent(self, event):
if self.unSaved():
event.ignore()
else:
exit
def main():
app = QApplication(sys.argv)
editor = TextEdit()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
wxPython 中的基础小部件是 wx.Window。wxPython 中的一切,无论是实际的窗口还是仅仅是按钮、复选框或文本标签,都基于 wx.Window 类。如果存在最容易被误命名类的奖项,wx.Window 会被忽略,因为它被命名得太糟糕了,以至于没有人会怀疑它是错误的。我听说习惯 wx.Window 不是窗口需要数年时间,这一定是真的,因为我每次使用它都会犯这个错误。
wx.Frame 类扮演着您和我认为的桌面窗口的传统角色。使用 wx.Frame 创建一个空窗口
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import wx
class Myframe(wx.Frame):
def __init__(self, parent, title):
super(Myframe, self).__init__(parent, title=title,
size=(520, 340))
self.Centre()
self.Show()
if __name__ == '__main__':
app = wx.App()
Myframe(None, title='Just an empty frame')
app.MainLoop()
将其他小部件放置在 wx.Frame 窗口内,然后您就可以构建 GUI 应用程序了。例如,wx.Panel 小部件类似于 HTML 中的 div,具有绝对大小约束,因此您可以使用它在主窗口中创建面板(除非它不是窗口,而是 wx.Frame)。
与 PyQt 相比,WxPython 的便利功能较少。例如,复制和粘贴功能内置在 PyQt 中,而它必须在 wxPython 中手动编码(并且仍然部分受其运行平台的影响)。其中一些功能可以通过具有内置功能的优秀桌面来优雅地处理,但为了与 PyQt 应用程序实现功能对等,wxPython 需要更多手动工作。
https://open-source.net.cn/sites/default/files/wxframe.png" title="wx.Frame" typeof="foaf:Image" width="544" height="397">
wx.Frame
wxPython 中的简单文本编辑器
#!/usr/bin/env python
# a minimal text editor to demo wxPython
# GNU All-Permissive License
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved. This file is offered as-is,
# without any warranty.
import wx
import os
class TextEdit(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self,parent,wx.ID_ANY, title, size=(520, 340))
menuBar = wx.MenuBar()
menuFile = wx.Menu()
menuBar.Append(menuFile,"&File")
menuFile.Append(1,"&Open")
menuFile.Append(2,"&Save")
menuFile.Append(3,"&Quit")
self.SetMenuBar(menuBar)
wx.EVT_MENU(self,1,self.openAction)
wx.EVT_MENU(self,2,self.saveAction)
wx.EVT_MENU(self,3,self.quitAction)
self.p1 = wx.Panel(self)
self.initUI()
def initUI(self):
self.text = wx.TextCtrl(self.p1,style=wx.TE_MULTILINE)
vbox = wx.BoxSizer(wx.VERTICAL )
vbox.Add( self.p1, 1, wx.EXPAND | wx.ALIGN_CENTER )
self.SetSizer(vbox)
self.Bind(wx.EVT_SIZE, self._onSize)
self.Show()
def _onSize(self, e):
e.Skip()
self.text.SetSize(self.GetClientSizeTuple())
def quitAction(self,e):
if self.text.IsModified():
dlg = wx.MessageDialog(self,"Quit? All changes will be lost.","",wx.YES_NO)
if dlg.ShowModal() == wx.ID_YES:
self.Close(True)
else:
self.saveAction(self)
else:
exit()
def openAction(self,e):
dlg = wx.FileDialog(self, "File chooser", os.path.expanduser('~'), "", "*.*", wx.OPEN)
if dlg.ShowModal() == wx.ID_OK:
filename = dlg.GetFilename()
dir = dlg.GetDirectory()
f = open(os.path.join(dir, filename),'r')
self.text.SetValue(f.read())
f.close()
dlg.Destroy()
def saveAction(self,e):
dlg = wx.FileDialog(self, "Save as", os.path.expanduser('~'), "", "*.*", wx.SAVE | wx.OVERWRITE_PROMPT)
if dlg.ShowModal() == wx.ID_OK:
filedata = self.text.GetValue()
filename = dlg.GetFilename()
dir = dlg.GetDirectory()
f = open(os.path.join(dir, filename),'w')
f.write(filedata)
f.close()
dlg.Destroy()
def main():
app = wx.App(False)
view = TextEdit(None, "TextEdit")
app.MainLoop()
if __name__ == '__main__':
main()
您应该使用哪一个?
PyQt 和 wxPython GUI 工具包都有其优点。
WxPython 大多很简单,当它不简单时,对于不害怕将解决方案拼凑在一起的 Python 程序员来说,它是直观的。您不会发现很多需要您接受“wxWidget 方式”的实例。它是一个工具包,其中包含您可以用来拼凑 GUI 的各种组件。如果您面向的用户空间已知已安装 GTK,那么 wxPython 会以最少的依赖项利用它。
作为奖励,它使用原生小部件,因此您的应用程序的外观应该与目标计算机上预装的应用程序没有什么不同。
但是,不要太在意 wxPython 声称的跨平台性。它有时在某些平台上存在安装问题,并且它没有那么多抽象层来屏蔽您平台之间的差异。
PyQt 很大,并且几乎总是需要安装一些依赖项(尤其是在非 Linux 和非 BSD 目标上)。伴随着所有庞大的代码而来的是许多便利性。Qt 尽最大努力屏蔽您平台之间的差异;它为您提供了惊人数量的预构建函数和窗口小部件以及抽象。它得到了很好的支持,许多公司都依赖它作为其基础框架,并且一些最重要的开源项目使用它并为其做出贡献。
如果您刚开始入门,您应该尝试每种工具包一点,看看哪一个吸引您。如果您是一位经验丰富的程序员,请尝试一种您尚未使用的工具包,看看您的想法。两者都是开源的,因此您不必只选择一个。重要的是要知道何时使用哪种解决方案。
祝您编程愉快!
13 条评论