PyQt 高级编程:模型视图、在线帮助与国际化
1. 树状结构中的表格数据表示
理解树模型比理解表格模型(或列表模型,列表模型可看作只有一列的表格)更具挑战性。不过,在很多情况下,可以借助或修改相关代码来降低难度。
2. PyQt 视图与自定义视图
PyQt 的内置视图小部件和图形视图小部件为数据集可视化提供了很大的空间。但当需求与这些类提供的功能不匹配时,我们可以创建自定义视图,按照自己的方式展示数据。
由于自定义视图可能要显示非常大的数据集的一部分,通常最好优化绘制事件处理程序,只检索和显示实际可见的数据项。如果需要滚动条,可以要求视图类的用户使用
QScrollArea
,或者创建一个包含几个
QScrollBar
的复合小部件,或者创建一个继承自
QAbstractScrollArea
的小部件。第一种方法只需在用户代码中添加几行,并且使视图的实现更加容易。
3. 通用委托与列委托
使用通用委托和特定数据类型的列委托可以轻松为视图创建临时的“自定义”委托。列委托易于创建,并且可以减少代码重复,因为对于每种要处理的数据类型,我们只需要一个列委托。通用委托方法适用于每列数据都包含单一数据类型值的数据集,如数据库表。
4. 创建树模型的挑战与方法
创建树模型可能比较困难,因为我们必须从父节点和子节点的角度去思考,子节点可能也是父节点,以此递归到任意深度。这不像树和列模型那样从行和列的角度思考那么容易。虽然这里展示的表格树模型是一个具体示例,但一些提供树功能的方法,如
index()
、
parent()
和
nodeFromIndex()
,可以直接使用或稍加修改后使用,其他方法,如
addRecord()
也应该是可适应的。
5. 示例练习:柱状图应用程序
这个练习整合了模型/视图的许多特性。需要创建一个应用程序,显示两个小部件:
QListView
和自定义的
BarGraphView
。数据应存储在自定义的
BarGraphModel
中。用户应该能够通过
QListView
编辑数据,使用自定义的
BarGraphDelegate
来控制列表视图中数据项的显示和编辑。
5.1 模型要求
-
模型应是
QAbstractListModel的子类,它应包含一个数据值(整数)列表和一个颜色字典(以“行”为键;例如,键为 6 的颜色对应第七个数据值,依此类推)。 -
模型应重新实现
rowCount()、insertRows()(在适当位置调用beginInsertRows()和endInsertRows())、flags()(使模型可编辑)和setData()(允许设置值(Qt.DisplayRole)和值的颜色(Qt.UserRole),并发出信号指示数据已更改)以及data()(应返回值、颜色,对于Qt.DecorationRole,返回一个填充了该颜色的 20×20 像素图。如果没有为特定行设置颜色,则使用默认的红色)。
5.2 委托要求
委托非常简单,与本章前面提到的
IntegerColumnDelegate
非常相似。关键区别在于必须重新实现
paint()
方法,但只需将对齐方式设置为
Qt.AlignRight
;绘制操作仍可由基类很好地完成。
5.3 自定义视图要求
自定义视图需要重新实现
setModel()
(在其中连接到基类的
update()
方法,以便在模型数据更改时进行重绘)、
minimumSizeHint()
、
sizeHint()
(可以简单地调用
minimumSizeHint()
)和
paintEvent()
。绘制事件可以用十几行代码完成,确保使用
QPainter.setWindow()
使图形始终填充可用空间。所有这些方法在未设置模型时也应正常工作,例如,没有模型时,绘制事件不应绘制任何内容。
以下是
MainForm
的代码示例:
class MainForm(QDialog):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
self.model = BarGraphModel()
self.barGraphView = BarGraphView()
self.barGraphView.setModel(self.model)
self.listView = QListView()
self.listView.setModel(self.model)
self.listView.setItemDelegate(BarGraphDelegate(0, 1000, self))
self.listView.setMaximumWidth(100)
self.listView.setEditTriggers(QListView.DoubleClicked|
QListView.EditKeyPressed)
layout = QHBoxLayout()
layout.addWidget(self.listView)
layout.addWidget(self.barGraphView, 1)
self.setLayout(layout)
self.setWindowTitle("Bar Grapher")
在模型解决方案中,添加了一些额外的代码来创建 20 个随机项以创建初始柱状图。整个程序可以在不到 200 行代码内完成。
6. 在线帮助系统
一些简单的应用程序,用户可能只需通过阅读菜单选项和按钮文本来使用。而其他一些应用程序可能需要更多信息,在这种情况下,工具提示和状态提示是一种易于编程的解决方案。但有些应用程序非常复杂,用户可能需要更全面的帮助来了解可用的功能以及如何使用这些应用程序。
提供在线帮助系统有三种常见方法:
|方法|描述|优点|缺点|
|----|----|----|----|
|HTML 文件与浏览器|以 HTML 文件形式提供帮助,并启动 Web 浏览器打开相关页面|实现简单,借助浏览器功能|依赖外部浏览器,用户体验可能不一致|
|Qt Assistant|使用随 Qt 提供的 Qt Assistant 应用程序|提供自动索引|需要创建特殊格式的 XML 文件,且要随应用程序分发 Qt Assistant|
|自定义帮助表单|使用 HTML 文件和图像作为资源提供帮助表单|可定制性强,资源管理方便|需要自己实现表单的导航和显示逻辑|
我们选择使用自定义帮助表单和 HTML 文件及图像作为资源的方法。以下是 Image Changer 应用程序的
MainWindow.helpHelp()
方法的代码:
def helpHelp(self):
form = helpform.HelpForm("index.html", self)
form.show()
使用帮助表单很简单,只需提供一个 HTML 文件和
self
(表单将以其为中心显示)。注意使用
show()
而不是
exec_()
,这通常意味着显示的表单将设置关闭时删除属性。
以下是
HelpForm
类的部分代码:
class HelpForm(QDialog):
def __init__(self, page, parent=None):
super(HelpForm, self).__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setAttribute(Qt.WA_GroupLeader)
self.textBrowser.setSearchPaths([":/"])
self.textBrowser.setSource(QUrl(page))
self.connect(backAction, SIGNAL("triggered()"),
self.textBrowser, SLOT("backward()"))
self.connect(homeAction, SIGNAL("triggered()"),
self.textBrowser, SLOT("home()"))
self.connect(self.textBrowser, SIGNAL("sourceChanged(QUrl)"),
self.updatePageTitle)
def updatePageTitle(self):
self.pageLabel.setText(self.textBrowser.documentTitle())
QTextBrowser
类是
QTextEdit
的子类,可用于显示大部分 HTML 标签,包括图像、列表和表格。我们将其搜索路径设置为资源文件的根目录,并将初始页面设置为传入的页面。
绘制事件处理流程如下:
graph TD;
A[开始] --> B[检查是否有模型];
B -- 有模型 --> C[获取可见数据项];
C --> D[使用 QPainter 绘制];
D --> E[设置窗口大小使图形填充空间];
E --> F[结束];
B -- 无模型 --> F[结束];
编写提供在线帮助系统的代码很直接,但设计一个易于导航且易于理解的系统可能颇具挑战。
PyQt 高级编程:模型视图、在线帮助与国际化
7. 国际化
当为使用不同语言的用户设计应用程序时,需要考虑多个问题。最明显的问题是所有用户可见的字符串都必须翻译成目标语言,这不仅包括菜单选项和对话框按钮的字符串,还包括工具提示、状态提示和任何其他在线帮助。此外,还必须进行其他本地化操作,例如确保数字使用适当的小数点和千位分隔符,时间和日期格式正确,纸张尺寸和度量系统合适。
7.1 字符显示与文件处理
由于使用了 Unicode,几乎任何人类语言使用的字符都可以显示。可以使用 Unicode 转义字符和目标字符的十六进制代码点,或使用
unichr()
函数将任何 Unicode 字符包含在 Unicode 或
QString
字符串中。对于包含 Unicode 的文本文件的读写,可以使用 Python 的
codecs.open()
函数,或 PyQt 的
QTextStream
。
7.2 本地化工具使用
在本地化的某些方面,可以使用
QString
、
QDate
和
QDateTime
。例如,假设
n
是一个数字,
QString("%L1").arg(n)
将生成一个包含适合当前区域设置的千位和小数点分隔符的
QString
。
QDate
和
QDateTime
都有
toString()
方法,可以接受自定义格式或预定义格式,如
Qt.SystemLocaleDate
(旧代码中的
Qt.LocalDate
)或
Qt.ISODate
(通用格式)。
QLocale
类提供了许多用于返回本地化
QString
的方法,以及一些从本地化
QString
中提取数字的方法,还提供了返回特定区域设置字符的方法,如负号、百分比符号等。
7.3 翻译工具与字符串标记
PyQt 提供了一个由三个工具组成的工具链:
pylupdate4
、
lrelease
和 Qt Linguist。为了使这些工具发挥作用,每个用户可见的字符串都必须进行特殊标记。可以使用
QObject.tr()
方法(所有
QWidget
子类,包括所有对话框和主窗口都继承了该方法)来标记字符串。例如,将
QString("&Save")
替换为
self.tr("&Save")
。如果需要使用 ASCII 范围之外的字符,则使用
trUtf8()
。对于不在类内部但需要翻译的字符串,必须使用
QApplication.translate()
方法,并自己提供上下文字符串。
以下是一些标记字符串的示例:
fileNewAction = self.createAction(self.tr("&New..."),
self.fileNew, QKeySequence.New, "filenew",
self.tr("Create an image file"))
self.fileMenu = self.menuBar().addMenu(self.tr("&File"))
self.statusBar().showMessage(self.tr("Ready"), 5000)
reply = QMessageBox.question(self,
self.tr("Image Changer - Unsaved Changes"),
self.tr("Save unsaved changes?"),
QMessageBox.Yes|QMessageBox.No|
QMessageBox.Cancel)
不建议使用 Python 字符串的
%s
格式化,而应始终使用
QString
和
QString.arg()
,这样更便于翻译人员处理。对于由
pyuic4
从
.ui
文件生成的
.py
文件,不需要手动标记字符串,因为
pyuic4
会自动对所有字符串使用
QApplication.translate()
。
7.4 加载翻译文件
使用
tr()
标记所有用户可见字符串后,需要修改应用程序的启动代码,以便在运行时加载适合当前区域设置的翻译文件。以下是示例代码:
app = QApplication(sys.argv)
locale = QLocale.system().name()
qtTranslator = QTranslator()
if qtTranslator.load("qt_" + locale, ":/"):
app.installTranslator(qtTranslator)
appTranslator = QTranslator()
if appTranslator.load("imagechanger_" + locale, ":/"):
app.installTranslator(appTranslator)
app.setOrganizationName("Qtrac Ltd.")
app.setOrganizationDomain("qtrac.eu")
app.setApplicationName(app.translate("main", "Image Changer"))
app.setWindowIcon(QIcon(":/icon.png"))
form = MainWindow()
form.show()
app.exec_()
QLocale.system().name()
调用将返回一个字符串,如 “en_US”(美国英语)或 “fr_CA”(加拿大法语)等。
QTranslator.load()
方法接受一个文件主干和一个路径。如果有冲突,即同一字符串有不同的翻译,最后安装的翻译器将生效。
7.5 资源文件配置
可以将翻译文件包含在资源文件中,以下是一个资源文件
resource.qrc
的示例:
<qresource>
<file>qt_fr.qm</file>
<file>imagechanger_fr.qm</file>
</qresource>
<qresource>
<file alias="editmenu.html">help/editmenu.html</file>
<file alias="filemenu.html">help/filemenu.html</file>
<file alias="index.html">help/index.html</file>
</qresource>
<qresource lang="fr">
<file alias="editmenu.html">help/editmenu_fr.html</file>
<file alias="filemenu.html">help/filemenu_fr.html</file>
<file alias="index.html">help/index_fr.html</file>
</qresource>
如果当前区域设置是 “en_US”,主帮助文件将是
:/index.html
;但如果区域设置是 “fr_CA” 或 “fr” 或任何其他 “fr_*”,当在代码中访问
:/index.html
文件时,实际获取的文件将是
:/index_fr.html
。
7.6 创建和更新翻译文件
创建和更新翻译文件的步骤如下:
1.
创建应用程序
:使用
QObject.tr()
或
QApplication.translate()
标记所有用户可见字符串。
2.
修改启动代码
:使应用程序在启动时读取适合当前区域设置的
.qm
文件(如果可用)。
3.
创建
.pro
文件
:列出应用程序的
.ui
文件、
.py
和
.pyw
源文件,以及要使用的
.ts
(翻译源)文件。示例
.pro
文件如下:
FORMS += newimagedlg.ui
SOURCES += helpform.py
SOURCES += imagechanger.pyw
SOURCES += newimagedlg.py
SOURCES += resizedlg.py
TRANSLATIONS += imagechanger_fr.ts
-
运行
pylupdate4:创建或更新.ts文件。例如:
C:\>cd c:\pyqt\chap17
C:\pyqt\chap17>pylupdate4 -verbose imagechanger.pro
pylupdate4
会在
.ts
文件不存在时创建它,并将使用
tr()
和
translate()
标记的字符串的所有上下文和字符串放入其中。如果
.ts
文件已经存在,它会添加任何必要的新上下文和字符串,同时保留在此期间添加的任何翻译。
5.
翻译
.ts
文件
:让翻译人员使用 Qt Linguist 翻译
.ts
文件中的字符串。
6.
运行
lrelease
:将
.ts
文件转换为
.qm
文件。例如:
C:\pyqt\chap17>lrelease -verbose imagechanger.pro
也可以不使用
.pro
文件,直接依赖
mkpyqt.py
或 Make PyQt 构建工具,只需在命令行上运行一次
pylupdate4
,例如:
C:\>cd c:\pyqt\chap17
C:\pyqt\chap17>pylupdate4 *.py *.pyw -ts imagechanger_fr.ts
整个国际化流程如下:
graph TD;
A[标记字符串] --> B[修改启动代码];
B --> C[创建 .pro 文件];
C --> D[运行 pylupdate4];
D --> E[翻译 .ts 文件];
E --> F[运行 lrelease];
F --> G[应用程序使用 .qm 文件];
通过以上步骤,可以为 PyQt 应用程序提供强大的模型视图功能、完善的在线帮助系统和全面的国际化支持,提升应用程序的可用性和用户体验。
超级会员免费看
16

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



