一个典型的GUI应用程序可以抽象为:主界面(菜单栏、工具栏、状态栏、内容区域),二级界面(模态、非模态),信息提示(Tooltip),程序图标等组成。本篇根据作者使用PyQt5编写的一个工具,介绍如何使用PyQt5构建一个典型的GUI应用。

1. 主界面

QMainWindow类提供一个有菜单条、锚接窗口(例如工具条)和一个状态条的主应用程序窗口。主窗口通常用在提供一个大的中央窗口部件(例如文本编辑或者绘制画布)以及周围菜单、工具条和一个状态条。QMainWindow常常被继承,因为这使得封装中央部件、菜单和工具条以及窗口状态变得更容易。

菜单栏

创建菜单的代码如下:

self.addMenu = self.menuBar().addMenu("&添加")
self.addMenu.addAction(self.addAvatarAct)
self.addMenu.addAction(self.addAvatarSetAct)
self.addMenu.addAction(self.addAvatarDecorationAct)
self.modifyMenu = self.menuBar().addMenu("&修改")
self.modifyMenu.addAction(self.modifyAvatarAct)
self.modifyMenu.addAction(self.modifyAvatarSetAct)
self.settingMenu = self.menuBar().addMenu("&设置")
self.settingMenu.addAction(self.settingAct)

其中每个菜单项,关联一个QAction,定义了图标、菜单名、回调函数、快捷键等等,这里没有设置快捷键。

self.addAvatarAct = QAction(QIcon("res/ico/addAvatar.ico"), "&Add Avatar", self, triggered=self.addAvatar)
self.addAvatarSetAct = QAction(QIcon("res/ico/addAvatarSet.ico"), "&Add AvatarSet", self, triggered=self.addAvatarSet)
self.addAvatarDecorationAct = QAction(QIcon("res/ico/addAvatarDecoration.ico"), "&Add AvatarDecoration", self, triggered=self.addAvatarDecoration)
self.modifyAvatarAct = QAction(QIcon("res/ico/modifyAvatar.ico"), "&Modify Avatar or Decoration", self, triggered=self.modifyAvatar)
self.modifyAvatarSetAct = QAction(QIcon("res/ico/modifyAvatarSet.ico"), "&Modify AvatarSet", self, triggered=self.modifyAvatarSet)
self.settingAct = QAction(QIcon("res/ico/settingPath.ico"), "&路径", self, triggered=self.settingPath)
self.homeAct = QAction(QIcon("res/ico/home.ico"), "&首页", self, triggered=self.homePage)

说明:QAction类提供了一个可以同时出现在菜单和工具条上的抽象用户界面操作。

在图形用户界面应用程序中很多命令可以通过菜单选项、工具条按钮和键盘快捷键调用。因为同一个操作将会被执行,而与它的调用方法无关,并且因为菜单和工具条必须保持同步,所以提供一个操作这样的命令很有用。一个操作可以被添加到菜单和工具条中并且将会自动使它们同步。例如,如果用户按下“加粗”工具条按钮,“加粗”菜单项将会自动被选中。

QAction可以包含图标、菜单文本、快捷键、状态条文本、这是什么文本和工具提示。它们可以分别通过setIconSet()、setText()、setMenuText()、setToolTip()、setStatusTip()、setWhatsThis()和setAccel()来设置。

工具栏

创建工具栏的代码如下:

self.toolbar = self.addToolBar('Home')
self.toolbar.addAction(self.homeAct)
self.toolbar = self.addToolBar('AddAvatar')
self.toolbar.addAction(self.addAvatarAct)
self.toolbar = self.addToolBar('AddAvatarDecoration')
self.toolbar.addAction(self.addAvatarDecorationAct)
self.toolbar = self.addToolBar('AddAvatarSet')
self.toolbar.addAction(self.addAvatarSetAct)
self.toolbar = self.addToolBar('ModifyAvatar')
self.toolbar.addAction(self.modifyAvatarAct)
self.toolbar = self.addToolBar('ModifyAvatarSet')
self.toolbar.addAction(self.modifyAvatarSetAct)

工具栏项也需要关联一个QAction,可以和菜单项共用一个QAction,即一个QAction可以被关联到多个地方。

状态栏

设置状态栏,只需要:

self.statusBar().showMessage("数据加载完成")

第一次调用self.statusBar()获取工具栏时,会初始化工具栏实例,后面再次调用不会在创建新的实例。

程序图标

程序图标分为2个:程序窗口图标;执行文件的图标。

l  setWindowIcon(QIcon(“res/ico/icon.ico”))设置程序窗口的图标

l  执行文件的图标,通过打包工具设置

2. UI布局

PyQt的布局系统提供了一个规定子窗口部件布局的简单的和强有力的方式。当你一旦规定了合理的布局,你就会获得如下利益:

l  布置子窗口部件。

l  最高层窗口部件可感知的默认大小。

l  最高层窗口部件可感知的最小大小。

l  调整大小的处理。

l  当内容改变的时候自动更新:

n  字体大小、文本或者子窗口部件的其它内容。

n  隐藏或者显示子窗口部件。

n  移去一些子窗口部件。

PyQt支持的布局方式有很多,如下表所示:

布局相关类为最小值并且伸缩因子为stretch直到布局末尾

网络布局

网格布局(QGridLayout)顾名思义,将空间划分成多行多列的网络,然后通过addWidget、addItem将widget填充到指定的单元格(cell)。这个比较像网页中使用table布局的思路。下面的代码即创建上图中的网格布局:

grid = QGridLayout()
grid.addWidget(setidLabel, 0, 0)
grid.addWidget(self.setidLineEdit, 0, 1)
grid.addWidget(QLabel("(第1位-2,第2~3位-表示适用角色,第4~5位-挂点位置,第6~8位-序号)"), 0, 2)
grid.addWidget(subidLabel, 1, 0)
grid.addWidget(self.subidLineEdit, 1, 1)
grid.addWidget(QLabel("(套装包含的物品,多个物品适用逗号分隔;必须在套装之前添加)"), 1, 2)
grid.addWidget(fashionLabel, 2, 0)
grid.addWidget(self.fashionLineEdit, 2, 1)
grid.addWidget(nameLabel, 3, 0)
grid.addWidget(self.nameLineEdit, 3, 1)
grid.addWidget(descLabel, 4, 0)
grid.addWidget(self.descLineEdit, 4, 1)
grid.addWidget(marketTagLabel, 5, 0)
grid.addWidget(self.tagCombox, 5, 1)
grid.addWidget(recommendLabel, 6, 0)
grid.addWidget(self.recommendCombox, 6, 1)
grid.addWidget(roleLabel, 8, 0)
grid.addWidget(self.roleChkBoxGroup, 8, 1)
grid.addWidget(beginLabel, 9, 0)
grid.addWidget(self.beginTime, 9, 1)
grid.addWidget(endLabel, 10, 0)
grid.addWidget(self.endTime, 10, 1)

gridWidget = QWidget()
gridWidget.setLayout(grid)

上述往网格中添加的widget都是占一个单元格的情况,其实还支持占用几个单元格。如下代码,往网格中的第二行、第一列添加一个widget,占用1行、2列:

grid.addWidget(self.createDetail(), 1, 0, 1, 2)

网格布局默认是均分每列,为了更好的控制布局,QGridLayout为每列提供了最小宽度(setColumnMinimumWidth())、伸缩因子(setColumnStretch()),为每行提供了最小高度(setRowMinimumHeight())、伸缩因子(setRowStretch())。最小宽/高度很好理解,伸缩因子如下面代码,设置了第二列和三列的比例是1:2。

layout.setColumnStretch(1, 10)
layout.setColumnStretch(2, 20)

3. 二级弹窗

QDialog类是对话框窗口的基类。对话框窗口是主要用于短期任务以及和用户进行简要通讯的顶级窗口。QDialog可以是模态对话框也可以是非模态对话框。QDialog支持扩展性并且可以提供返回值。它们可以有默认按钮。

内置对话框

内置常用的对话框有:QColorDialogQErrorMessageQFileDialogQFontDialogQInputDialogQMessageBoxQProgressDialogQTabDialogQWizard

内置的对话框提供了一些常用的功能,使用起来也必将遍历。编写该工具使用到了,选择文件、目录的对话框QFileDialog。

自定义对话框

如果内置的对话框不能满足需求,可以自定义对话框(继承自QDialog)。如下定义了一个设置路径的对话框:

class SettingDialog(QDialog):
    def __init__(self, parent=None):
        super(SettingDialog, self).__init__(parent)
        self.path = Global.path
        self.initUI()
        self.setWindowIcon(QIcon("res/ico/settingPath.ico"))
        self.setWindowTitle("设置")
        self.resize(240, 100)

    def initUI(self):
        grid = QGridLayout()
        grid.addWidget(QLabel("路径:"), 0, 0)
        self.pathLineEdit = QLineEdit()
        self.pathLineEdit.setFixedWidth(200)
        self.pathLineEdit.setText(Global.path)
        grid.addWidget(self.pathLineEdit, 0, 1)
        button = QPushButton("更改")
        button.clicked.connect(self.changePath)
        grid.addWidget(button, 0, 2)
        grid.addWidget(QLabel("<font color='#ff0000'>包含Keywords.xml、Avatar,AvatarSet,Market.xls的路径</font>"), 1, 0, 1, 3)
        buttonBox = QDialogButtonBox()
        buttonBox.setOrientation(Qt.Horizontal)  # 设置为水平方向
        buttonBox.setStandardButtons(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
        buttonBox.accepted.connect(self.accept)  # 确定
        buttonBox.rejected.connect(self.reject)  # 取消
        grid.addWidget(buttonBox, 2, 1)
        self.setLayout(grid)

    def changePath(self):
        open = QFileDialog()
        self.path = open.getExistingDirectory()
        self.pathLineEdit.setText(self.path)
        print(self.path)

使用对话框,只需要:

dialog = SettingDialog()
if dialog.exec_():
       # -----

4. 常用组件

下面介绍编写工具过程中使用到的组件的一些注意事项。

QTableWidget

列自适应

如果有很多列,QTableWidget出出现水平滚动条,但是有不希望有滚动条可以通过设置列自适应方式:

tw.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

保证所以列都能显示,不会出现水平滚动条,这样有的单元格显示会被截断显示,如图中的"青年套装下装"-->"青年套装...",这时可以设置单元的tooltip提供完整显示的途径。

禁止编辑

编写工具时,有要求QTableWidget展示出来的数据不能编辑,是通过以下方式实现:

tw.setEditTriggers(QAbstractItemView.NoEditTriggers)

QAbstractItemView还定义了其它的模式,如下表所示:

个问题:

l  点了下qtablewidget 的标题,它排序正常,修改数据,在查询,数据显示有问题

重新获取数据之前先关闭可排序性,获取到数据之后再开启排序性

l  排序规则问题,默认使用字母排序

使用以下方式设置单元格,会使用字母排序

item = QTableWidgetItem()
item.setData(Qt.DisplayRole, "xxx")

或者

item = QTableWidgetItem()
item.setText("xxx")

如果需要按照数值排序需要使用以下方式设置单元格

item = QTableWidgetItem()
item.setData(Qt.DisplayRole, int(1212))

自定义单元格控件

可以对QTableWidget自定义(添加)widget,如下为QTableWidget设置单元格为一个下拉选择的QCombox

combox = QComboBox()
for _, v in ParseKeyword.currencyType.items():
         combox.addItem(v["cname"], v["value"])
combox.setCurrentText("点券")
tw.setCellWidget(row, 1, combox)

效果如下图所示:

QDateTimeEdit

显示格式

默认的时间显示格式(如2015/1/16 17:42),可能不满足需求,可以通过setDisplayFormat()设置显示格式来定制。格式选项如下所示:

这些是可能用到的日期表达式:

d - 没有前置0的数字的天(1-31)

dd - 前置0的数字的天(01-31)

ddd - 缩写的日名称(Mon-Sun)。使用QDate.shortDayName()。

dddd - 长的日名称(Monday-Sunday)。使用QDate.longDayName()。

M - 没有前置0的数字的月(1-12)

MM - 前置0的数字的月(01-12)

MMM - 缩写的月名称(Jan-Dec)。使用QDate.shortMonthName()。

MMMM - 长的月名称(January-December)。使用QDate.longMonthName()。

yy - 两位数字的年(00-99)

yyyy - 四位数字的年(0000-9999)

这些是可能用到的时间表达式:

h - 没有前置0的数字的小时(0-23或者如果显示AM/PM时,1-12

hh - 前置0的数字的小时(00-23或者如果显示AM/PM时,01-12

m - 没有前置0的数字的分钟(0-59

mm - 前置0的数字的分钟(00-59

s - 没有前置0的数字的秒(0-59

ss - 前置0的数字的秒(00-59

z - 没有前置0的数字的毫秒(0-999

zzz - 前置0的数字的毫秒(000-999

AP - 切换为AM/PM显示。AP将被“AM”或“PM”替换。

ap - 切换为am/pm显示。ap将被“am”或“pm”替换。

如工具中使用的格式为:

setDisplayFormat("yyyy-MM-dd hh:mm:ss")

显示效果如下图所示:

弹出日期选择窗口

希望点击QDateTimeEdit可以弹出日期选择窗口,可以简单的通过setCalendarPopup(True)实现,非常的简单。

5. 打包

python常用的打包工具有py2exe、pyinstaller、cx_freeze,而且现在都开始支持python3,py2exe可以打包成单exe文件,一般简单的东西都是用它来打包供其他人使用。但是使用py2exe打包PyQt5时,碰到了不少错误,后面干脆使用cx_freeze打包一次成功(不足之处,就是不能打包成单个exe)。下面简单介绍编写setup.py几个关键的点,详细的参考官方文档(http://cx-freeze.readthedocs.org/en/latest/index.html)。

l  默认只会打包代码文件,如果程序有非代码文件,如配置、资源文件需要打包,需要显示指定。如"include_files": ["setting.ini", "res"],打包时会将setting.ini文件、res资源目录拷贝到exe目录下。

l  cx_freeze会自动检测依赖文件,但是有时候会抽风,可以通过"packages": ["os", "xlrd3", "xlwt3", "lxml"]显示包含。同时对不要的包,可以"excludes": ["tkinter"]指定不要编译到最终的软件包中。

l  指定文件名需要带exe后缀,cx_freeze是不会自动添加exe后缀的。

l  如果需要一次编译多个exe,可以在executables数组中列出多个,例如:

executables = [
Executable("main.py", base=base, targetName="Joker3DAvatarMgr.exe", compress=True, icon="res/ico/icon.ico"),

Executable("test.py", base=base, targetName="test.exe", compress=True, icon="res/ico/test.ico")
]

完整的setup.py文件如下所示:

import sys
from cx_Freeze import setup, Executable

# GUI applications require a different base on Windows (the default is for a
# console application).
base = None
if sys.platform == "win32":
base = "Win32GUI"

# Dependencies are automatically detected, but it might need fine tuning.
build_exe_options = {
"packages": ["os", "xlrd3", "xlwt3", "lxml"],
"excludes": ["tkinter"],
"include_files": ["setting.ini", "res"]
}

#
executables = [
Executable("main.py", base=base, targetName="Joker3DAvatarMgr.exe", compress=True, icon="res/ico/icon.ico")
]

setup( name = "setup",
version = "0.1",
description = "Joker3D prop manager tool!",
author = "tylerzhu",
author_email = "saylor.zhu@gmail.com",
options = {"build_exe": build_exe_options},
executables = executables,
)

编写好setup.py之后,可以通过python setup.py build打包。

网上有不少人反馈打包之后,放到没有按照PyQt的PC上执行,会报以下错误:“This application failed to start because it could not find or load the Qt platform plugin windows”

这个问题,我以前也碰到过,但是这次我用的Python3.4 + cx_freeze 4.3.4 + PyQt5-5.4-gpl-Py3.4-Qt5.4.0-x32.exe并没有出现这个问题。如果出现了这个问题也不要紧,通过以下方法可以解决:将PyQt5安装目录(Lib\site-packages\PyQt5)下的libGLESv2.dll拷到打包的exe目录下即可。

PyQt5应用与实践的更多相关文章

  1. 【Python开发】PyQt5应用与实践

    一个典型的GUI应用程序可以抽象为:主界面(菜单栏.工具栏.状态栏.内容区域),二级界面(模态.非模态),信息提示(Tooltip),程序图标等组成.本篇根据作者使用PyQt5编写的一个工具,介绍如何 ...

  2. [ PyQt入门教程 ] PyQt5环境搭建和配置

    PyQt入门系列教程主要目的是希望通过该系列课程学习,可以使用PyQt5工具快速实现简单的界面开发,包括界面设计.布局管理以及业务逻辑实现(信号与槽).简单说就是可以使用PyQt5工具快速画一个控件摆 ...

  3. c++ 发布动态.so

    原文地址 代码改变世界 Posts - 105, Articles - 0, Comments - 1561 Cnblogs Dashboard Logout Home Contact Gallery ...

  4. PyQt5开发实践(一、准备篇)

    前言 近一年来我开发了不少PyQt小项目,因为之前没用过使用C++语言的Qt,所以可以算是从零基础开始边学边做的,这个过程中再一次体会到国内技术社区的匮乏-- 国内关于PyQt的资料说少不少,说多也不 ...

  5. 安装PyQt5之后mayavi和VTK不能使用

    mayavi在显示数据的过程中需要调用PyQt4的GUI方法产生应用框架.但是新发布的PyQt5和PyQt4在很多方面都是不兼容的,这也就导致了用mayavi编写的程序运行失败.在实践之后,我的解决方 ...

  6. QT学习之windows下安装配置PyQt5

    windows下安装配置PyQt5 目录 为什么要学习QT 命令行安装PyQt5以及PyQt5-tools 配置QtDesigner.PyUIC及PyRcc 为什么要学习QT python下与界面开发 ...

  7. PyQt5系列教程(二)利用QtDesigner设计UI界面

    软硬件环境 OS X EI Capitan Python 3.5.1 PyQt 5.5.1 PyCharm 5.0.1 前言 在PyQt5系列教程的第一篇http://blog.csdn.net/dj ...

  8. linux下Python2.7编译安装PyQt5

    ---作者吴疆,未经允许,严禁转载,违权必究--- ---欢迎指正,需要源码和文件可站内私信联系--- -----------点击此处链接至博客园原文----------- 功能说明:在ubuntu系 ...

  9. [ PyQt入门教程 ] PyQt5信号与槽

    信号和槽是PyQt编程对象之间进行通信的机制.每个继承自QWideget的控件都支持信号与槽机制.信号发射时(发送请求),连接的槽函数就会自动执行(针对请求进行处理).本文主要讲述信号和槽最基本.最经 ...

随机推荐

  1. Number()数据类型转换

    Number() 如果是 Boolean 值, 和.如果是数字值,只是简单的传入和返回.如果是 .如果是 undefined,返回NaN. 如果是字符串,遵循下列规则:    如果字符串中只包含数字( ...

  2. python入门简介

    Python前世今生 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC ...

  3. JS-随机函数

    // alert( Math.round(3.4) );// 0~1 : Math.round(Math.random());// 0~10// alert( Math.round(Math.rand ...

  4. 从程序员到CTO的Java技术路线图 作者:zz563143188

    在技术方面无论我们怎么学习,总感觉需要提升自已不知道自己处于什么水平了.但如果有清晰的指示图供参考还是非常不错的,这样我们清楚的知道我们大概处于那个阶段和水平. Java程序员 高级特性 反射.泛型. ...

  5. 在 Mac OS X 上创建的 .NET 命令行程序访问数据库 (使用Entity Framework 7 )

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  6. python基础整理笔记(六)

    一. 关于hashlib模块的一些注意点 hashlib模块用于加密相关的操作,代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512, MD ...

  7. UVALive 2191 Potentiometers (树状数组)

    题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_ ...

  8. MongoDB学习笔记-01 简介、安装

    MongoDB简介 MongoDB是一种强大.灵活.可拓展的存储方式.是一个面向文档(相当于"行"的概念)的数据库. 可拓展:通过添加服务器而增加存储量. Windows下安装 版 ...

  9. Linux系统MySQL开启远程连接

    1.远程连接上Linux系统,确保Linux系统已经安装上了MySQL数据库.登陆数据库.mysql -uroot -p(密码). 2.创建用户用来远程连接 GRANT ALL PRIVILEGES ...

  10. Intent组件的传参应用

    Intent是要执行的操作的抽象描述 可以在startActivity.startService等方法中使用 最为常见的用法是在Activity之间传递数据 跳转并传值: Intent intent= ...