Qt 与 Wx:两个最流行的 Python 框架的比较?

您的项目应该选择哪个 Python GUI?
620 位读者喜欢这篇文章。
An introduction to GNU Screen

Opensource.com

Python 是一种流行的语言,既可以进行脚本编写,也可以进行面向对象的编程。一些框架为 Python 提供了 GUI(图形用户界面),它们中的大多数都擅长某些方面,无论是简单性、效率还是灵活性。其中最流行的两个是 wxPythonPyQt,但它们之间如何比较?更重要的是,您的项目应该选择哪个?

外观

让我们先解决大多数用户首先注意到的问题——应用程序的外观。

WxwxPython 的独特功能之一是其核心库(用 C++ 编写)是围绕其宿主系统的原生小部件的包装器。当您在 GUI 中为按钮小部件编写代码时,您不会得到看起来像是属于另一个操作系统的东西,也不会得到仅仅是近似值的东西。相反,您会得到与使用原生工具编码时相同的对象。

Linux 上的 Thunar 和 WxPythonhttps://open-source.net.cn/sites/default/files/wxbutton.png" title="Linux 上的 Thunar 和 WxPython" typeof="foaf:Image" width="492" height="480">

Linux 上的 Thunar 和 WxPython

Qt 标志这与 PyQt 不同,PyQt 基于著名的 Qt 工具包。PyQt 也是用 C++ 编写的,但它不使用原生小部件,而是根据它检测到的操作系统创建小部件的近似值。它做出了很好的近似,我从未遇到过用户——即使是在艺术学校,那里的用户往往对外观非常吹毛求疵——抱怨应用程序看起来和感觉不像原生的。

如果您使用 KDE,您可以使用额外的 PyKDE 库来弥合原始 PyQt 和 Linux 和 BSD 上的 Plasma 桌面外观之间的差距,但这会增加新的依赖项。

Linux 上的 KDE 和 Qthttps://open-source.net.cn/sites/default/files/qtbutton.png" title="Linux 上的 KDE 和 Qt" typeof="foaf:Image" width="520" height="288">

Linux 上的 KDE 和 Qt

跨平台

wxPython 和 PyQt 都支持 Linux、Windows 和 Mac,因此它们非常适合著名的跨平台 Python;但是,不要让“跨平台”这个词蒙蔽了您——您仍然必须在 Python 代码中进行特定于平台的调整。您的 GUI 工具包无法将路径格式调整为数据目录,因此您仍然必须在 Python 中练习最佳实践,使用 os.path.join 和一些不同的 exit 方法等等。您选择的 GUI 工具包不会神奇地从一个平台抽象到另一个平台。

Qt 标志PyQt 努力使您免受跨平台差异的影响。允许 Python 本身需要的常见调整,PyQt 使您免受大多数跨平台问题的困扰,因此您的 GUI 代码无论操作系统如何都保持不变。总会有例外,但 PyQt 处理得非常出色。这是一种您会欣赏和钦佩的奢侈。

Wx在 wxPython 中,您可能需要对 GUI 代码进行一些特定于平台的更改,具体取决于您正在编程的内容。例如,为了防止 Microsoft Windows 上某些元素的闪烁,必须将 USE_BUFFERED_DC 属性设置为 True 以双缓冲图形。即使可以无条件地为所有平台完成此操作,但这也不是默认设置,因此在某些用例中可能会有缺点,但这是您必须为 wxPython 做出的让步的一个很好的例子。

安装

作为开发人员,您可能不介意获取应用程序所需的库所需的安装步骤;但是,如果您计划分发您的应用程序,那么您需要考虑您的用户必须经历的安装过程才能使您的应用程序运行。

Qt 标志在任何平台上安装 Qt 都像安装任何其他应用程序一样简单:。给您的用户一个下载链接,告诉他们安装下载的软件包,他们很快就可以使用您的应用程序了。这在所有受支持的平台上都是如此。

然而,在所有平台上都适用的是,PyQt 依赖于 Qt 本身的 C++ 代码。这意味着用户不仅必须安装 PyQt,还必须安装所有 Qt。这不是一个小软件包,而且需要大量的点击,并且可能需要逐步完成安装向导。然而,Qt 和 PyQt 团队使安装尽可能容易,因此,尽管这似乎对用户要求很高,但只要您提供直接链接,任何可以安装 Web 浏览器或游戏的用户都应该能够处理 Qt 安装。如果您非常专注,您甚至可以将安装编写为您自己的安装程序的一部分。

在 Linux、BSD 和 Ilumos 系列上,安装通常已由发行版的软件包管理器为您编写脚本。

WxwxPython 的安装过程在 Linux 和 Windows 上同样简单,但在 Mac OS 上却存在问题。可下载的软件包严重过时,这是苹果对向后兼容性不感兴趣的另一个受害者。错误票证 存在修复程序,但软件包尚未更新,因此普通用户找到并实施补丁的可能性很小。目前的解决方案是打包 wxPython 并将其分发给您的 Mac OS 用户,或者依赖外部软件包管理器(尽管当我上次测试 Mac 版 wxPython 时,即使是那些安装脚本也失败了)。

小部件和功能

PyQt 和 wxPython 都具有您期望从 GUI 工具包获得的所有常用小部件,包括按钮、复选框、下拉菜单等等。两者都支持拖放操作、选项卡式界面、对话框和自定义小部件的创建。

Qt 标志PyQt 具有灵活性的优势。您可以在运行时重新排列、浮动、关闭和恢复 Qt 面板,从而为每个应用程序提供高度可配置的以可用性为中心的界面。

移动 Qt 面板https://open-source.net.cn/sites/default/files/panelmove.png" title="移动 Qt 面板" typeof="foaf:Image" width="382" height="194">

移动 Qt 面板

只要您使用正确的小部件,这些功能都是内置的,您无需重新发明花哨的技巧来为您的高级用户提供友好的功能。

WxWxPython 具有许多很棒的功能,但在灵活性和用户控制方面,它无法与 PyQt 相提并论。一方面,这意味着作为开发人员,设计和布局对您来说更容易。当在 Qt 上开发时,您很快就会收到用户关于如何跟踪自定义布局的请求,或者如何找到意外关闭的丢失面板等等。出于同样的原因,wxPython 对于您的用户来说更简单,因为当面板根本无法关闭时,丢失意外关闭的面板要困难得多。

毕竟,最终 wxPython 只是 wxWidgets 的前端,因此如果您真的需要某个功能,您或许可以用 C++ 实现它,然后在 wxPython 中使用它。然而,与 PyQt 相比,这是一个艰巨的任务。

齿轮和滑轮

GUI 应用程序由许多较小的可视化元素组成,通常称为“小部件”。为了使 GUI 应用程序顺利运行,小部件必须相互通信,以便例如,旨在显示图像的窗格知道用户选择了哪个缩略图。

Wx大多数 GUI 工具包,包括 wxPython,都使用“回调”来处理内部通信。回调是指向某些代码片段(“函数”)的指针。如果您想在例如单击按钮小部件时执行某些操作,您可以为您想要发生的动作编写一个函数。然后,当单击按钮时,您可以在代码中调用该函数,并且该动作发生。

它工作得足够好,只要您将其与 lambda 结合使用,它就是一个非常灵活的解决方案。有时,根据您希望通信有多么复杂,您最终得到的代码会比您预期的要多得多,但它确实有效。

Qt 标志另一方面,Qt 以其“信号和槽”机制而闻名。如果您将 wxPython 的内部通信网络想象成旧式的电话交换机,那么将 PyQt 的通信想象成网状网络。

Qt 图表https://open-source.net.cn/sites/default/files/abstract-connections.png" title="Qt 图表" typeof="foaf:Image" width="516" height="501">

Qt 中的信号和槽 (Qt 图表 GFDL 许可)

使用信号和槽,一切都获得签名。发出信号的小部件不需要知道其消息的目标槽是什么,甚至不需要知道其消息是否以任何槽为目标。只要您将信号连接到槽,当信号广播时,就会使用信号的参数调用该槽。

可以将槽设置为侦听任意数量的信号,并且可以将信号设置为广播到任意数量的槽。您甚至可以将信号连接到另一个信号以创建信号的连锁反应。您永远不必返回代码以手动“连接”事物。

信号和槽可以接受任意数量的任意类型的参数。您不必编写代码来过滤掉您在某些条件下想要或不想要的东西。

更好的是,槽不仅仅是侦听器;它们是可以执行有用功能的普通函数,无论是否有信号。正如对象不知道是否有任何东西在侦听其信号一样,槽也不知道它是否在侦听信号。没有任何代码块依赖于连接的存在;如果有连接,它只是在不同的时间被触发。

无论您是否理解信号和槽,一旦您使用它们,然后尝试返回到传统的回调,您就会被迷住。

布局

当您编程 GUI 应用程序时,您必须设计其布局,以便所有小部件都知道在应用程序窗口中的何处显示。像网页一样,您可以选择将应用程序设计为可调整大小,也可以将其限制为固定大小。在某些方面,这是 GUI 编程中最 GUI 的部分。

Qt 标志在 Qt 中,一切都非常合乎逻辑。小部件的命名很合理(QPushButtonQDialQCheckboxQLabel 甚至 QCalendarWidget),并且易于调用。文档非常出色,只要您经常参考它,并且很容易在其中发现很酷的功能。

可能存在混淆点,主要是在基本级别的 GUI 元素中。例如,如果您正在编写应用程序,您是应该从 QMainWindow 还是 QWidget 开始来形成您的父窗口?两者都可以用作应用程序的窗口,因此答案是,就像在计算中经常出现的那样:这取决于。

QWidget 是一个原始的空容器。它被所有其他小部件使用,但这意味着它也可以按原样使用,以形成您在其中放置更多小部件的父窗口。QMainWindow 像所有其他小部件一样使用 QWidget,但它添加了大多数应用程序需要的许多便利功能,例如顶部的工具栏、底部的状态栏等。

QMainwindowhttps://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()

WxwxPython 中的基础小部件是 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 需要更多的人工工作。

wx.Framehttps://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 工具包都有其优势。

WxWxPython 大多很简单,当它不简单时,对于不害怕将解决方案拼凑在一起的 Python 程序员来说,它是直观的。您不会发现很多需要您被灌输的“wxWidget 方式”的例子。它是一个工具包,其中包含您可以用来拼凑 GUI 的各种组件。如果您以您知道已经安装了 GTK 的用户空间为目标,那么 wxPython 可以以最少的依赖项利用它。

作为奖励,它使用原生小部件,因此您的应用程序的外观应该与预装在目标计算机上的应用程序没有什么不同。

不过,不要太认真对待 wxPython 的跨平台声明。它有时在某些平台上存在安装问题,并且没有那么多抽象层来保护您免受平台之间差异的影响。

Qt 标志PyQt 很大,并且几乎总是需要安装一些依赖项(尤其是在非 Linux 和非 BSD 目标上)。随着所有这些庞大的代码而来的是很多便利。Qt 尽最大努力保护您免受平台差异的影响;它为您提供了惊人数量的预构建函数、小部件和抽象。它得到了很好的支持,许多公司都依赖它作为其基础框架,并且一些最重要的开源项目使用它并为其做出贡献。

如果您刚刚开始,您应该尝试每种工具包,看看哪一种对您有吸引力。如果您是一位经验丰富的程序员,请尝试一种您尚未使用的工具包,看看您的想法。两者都是开源的,因此您不必只选择一个。重要的是要知道何时使用哪种解决方案。

祝您编程愉快。

标签
Seth Kenlon
Seth Kenlon 是一位 UNIX 极客、自由文化倡导者、独立多媒体艺术家和 D&D 爱好者。他曾在电影和计算机行业工作,而且经常同时工作。

13 条评论

感谢您的文章!多年来,我一直告诉自己应该学习一个 GUI 框架,而 PyQt(因为我是 KDE 用户)和 wxPython(因为“wx”是“天气”的常用缩写)是让我印象深刻的两个框架。我仍然在掷硬币,但这篇文章是对这两个框架的优点和缺点的精彩介绍。

一个绝对重要的问题被遗漏了:Python 3 支持。wxPython 目前没有,直到其“Project Phoenix”重写接近生产状态。虽然这项重写至少从 2010 年就开始了,并且我相信它正在取得进展,但节奏似乎太慢了,无法成为未来的可靠替代方案。

Qt 在 Python 上也有自己的问题,但由于 PySide 现在已被 Qt 基金会采用为官方端口,因此情况应该会有所好转。仍在等待 Python >3.4 支持和 PySide 的 Qt 5 端口(参见 PySide 2)。PyQt 暂时填补了这个空白,但可能存在许可问题(GPLv3 或付费商业)。

总而言之,Python 的原生 GUI 支持目前是其生态系统的薄弱环节之一。在实践中,我认为最好的方法通常是创建一个 Web 界面,以便有机会实现简单的平台互操作性,尽管它并不适合所有任务。我已经靠编写 Python 谋生多年,我认为它是一种非常棒的语言,拥有同样出色的社区,我对 PySide 2 寄予厚望。

说得好。我使用 Python3 和 PyQt 没有问题,但这可能是一个复杂性问题。由于缺乏 Qt5 支持,我有点放弃了 PySide。

回复 作者 Daniel Andersson (未验证)

感谢这篇精彩而详细的文章。

但是,恕我直言,您遗漏了一个非常重要的点:这两个框架的许可差异。

我可能是错的,但据我所知,对于商业软件开发,您始终需要相当昂贵的 QT 许可证,而您也可以在商业产品中使用 wxPython。

我是一位开源和 FOSS 软件的坚定支持者,但从长远来看,wxPython 可以为您提供一种简单而廉价的双重许可软件产品的方式。与此相反,您将不得不为 QT 许可证付费,原则上,即使在您决定提供商业版本的软件之前,您也必须为此付费。

我不是律师,但有时在互联网上扮演律师。据我所知,没有什么可以阻止您使用 GPL/LGPL 许可的 Qt 库编写应用程序。商业许可证主要提供支持和一些附加库。https://www.qt.io/licensing-comparison/

回复 作者 Marco A. Harrendorf (未验证)

是的,您可以使用 GPL,但那样您就必须使用 GPL。许多人不想完全开源他们的代码,而 wx 许可证提供了更大的灵活性。

回复 作者 bcotton

请注意,LGPL 不是 GPL。除非您必须在分发之前修改 Qt 本身的内部结构,否则您没有义务公开您的代码,并且只有对 LGPL 部分(即 Qt)的修改——而不是整个应用程序。

例如,我知道《星际争霸 2》包含 LGPL 许可的库(LZMA、ImageLib——在鸣谢名单中列出),即使它绝对是一个闭源商业应用程序。

关于 LGPL v3 的“Tivoization 条款”可能有一些说法,但这不应适用于 Python 通常使用的环境(如果您受到此条款的影响,您可能已经知道了)。

回复 作者 Chris Barker (未验证)

虽然这可能不适用于此处讨论的情况(因为它与通过 Python 间接使用 Qt 有关),但应注意,(据我所知)当采用专有方式时,即使在使用“不太严格”的 LGPL 而不是完整的 GPL 时,也可能发生相同的许可违规(换句话说:软件 *用户* 权利侵犯):一旦通过静态链接(而不是动态链接!)将 LGPL 代码链接到专有组件中,就会发生许可违规,就像使用普通的 GPL 许可组件一样。
因此,或许最好仔细设计您的经济模型,以最大程度地减少因您的开发工作与许可证不兼容而产生的任何“问题” ;-)

回复 作者 Daniel Andersson (未验证)

我对这方面的理解是,Qt 库本身是在 LGPL 许可下许可的,因此您可以在不重新许可您自己的项目的情况下使用它们。但是,PyQt Python 模块仅在 GPL 许可下可用。这造成了一种奇怪的情况,您可以使用 Qt,但如果您想从 Python 中使用它,则需要根据 GPL 发布或购买商业许可证。这就是 PySide 的用武之地,它为 Python 提供了 Qt 绑定,但在 LGPL 许可下。唯一的问题是缺乏对 Qt 5 的支持,我认为 Qt 5 的支持已经开始,但我不确定是否有稳定版本。

回复 作者 Daniel Andersson (未验证)

在您的 PyQt5 示例中,CurrentFile.close() 是多余的。“with”上下文管理器会自动关闭资源。

谢谢,Jason。可能是以不同方式打开的遗留问题。我不打算在文章中更改它,因为您的评论在这里有效地解决了这个问题。我们将其称为新 PyQt 用户的学习机会 :)

回复 作者 Jason Gray (未验证)

感谢您的这篇文章!目前我正在开发一个应用程序,我正在 wx 或 qt 之间犹豫不决。这篇文章帮助我决定了我的选择。

我很高兴它有所帮助。我刚刚更新了我用 Python 2.7 和 PyQt4 编写的应用程序到最新的 Python 3 + PyQt5,我不得不说,移植过程就像我希望的那样简单。

我还没有在 WxPython 应用程序中尝试过,虽然这也在计划之中。 希望它也能同样轻松顺利。

回复 ,作者是 Bryan James

知识共享许可协议本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.