一個典型的GUI應用程序能夠抽象爲:主界面(菜單欄、工具欄、狀態欄、內容區域),二級界面(模態、非模態),信息提示(Tooltip),程序圖標等組成。本篇根據做者使用PyQt5編寫的一個工具,介紹如何使用PyQt5構建一個典型的GUI應用。
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 執行文件的圖標,經過打包工具設置
PyQt的佈局系統提供了一個規定子窗口部件佈局的簡單的和強有力的方式。當你一旦規定了合理的佈局,你就會得到以下利益:
l 佈置子窗口部件。
l 最高層窗口部件可感知的默認大小。
l 最高層窗口部件可感知的最小大小。
l 調整大小的處理。
l 當內容改變的時候自動更新:
n 字體大小、文本或者子窗口部件的其它內容。
n 隱藏或者顯示子窗口部件。
n 移去一些子窗口部件。
PyQt支持的佈局方式有不少,以下表所示:
佈局相關類
|
做用
|
QBoxLayout
|
Lines up child widgets horizontally or vertically
|
QButtonGroup
|
Container to organize groups of button widgets
|
QFormLayout
|
Manages forms of input widgets and their associated labels
|
QGraphicsAnchor
|
Represents an anchor between two items in a QGraphicsAnchorLayout
|
QGraphicsAnchorLayout
|
Layout where one can anchor widgets together in Graphics View
|
QGridLayout
|
Lays out widgets in a grid
|
QGroupBox
|
Group box frame with a title
|
QHBoxLayout
|
Lines up widgets horizontally
|
QLayout
|
The base class of geometry managers
|
QLayoutItem
|
Abstract item that a QLayout manipulates
|
QSizePolicy
|
Layout attribute describing horizontal and vertical resizing policy
|
QSpacerItem
|
Blank space in a layout
|
QStackedLayout
|
Stack of widgets where only one widget is visible at a time
|
QStackedWidget
|
Stack of widgets where only one widget is visible at a time
|
QVBoxLayout
|
Lines up widgets vertically
|
QWidgetItem
|
Layout item that represents a widget
|
其中使用比較多的是如下佈局方式(或者說是我使用比較多,不表明你們):
水平佈局(QHBoxLayout)顧名思義,將空間水平切成多段,而後經過addWidget、addItem將widget填充指定的位置。以下代碼即實現了上圖中,適合角色選擇的水平佈局:
hbox = QHBoxLayout()
self.roleChkBoxGroup.setLayout(hbox)
for _, v in sorted(ParseKeywords.profession.items()):
checkBox = QRadioButton(v["cname"] + " " + str(v["value"]))
hbox.addWidget(checkBox)
刪除一個控件,使用removeWidget,或者調用QWidget.hide()同樣能夠從佈局中刪除,直到QWidget.show()被調用。下面的垂直佈局、網格佈局,甚至其餘佈局都是注意的。
垂直佈局(QVBoxLayout)顧名思義,將空間垂直切成多段,而後經過addWidget、addItem將widget填充指定的位置。以下代碼即實現了上圖中,細節信息的垂直佈局(垂直佈局中,還嵌套了水平佈局):
vbox = QVBoxLayout()
groupBox.setLayout(vbox)
count = QWidget()
hbox = QHBoxLayout()
countLabel = QLabel("細節數目:")
hbox.addWidget(countLabel)
self.countSpineBox = QSpinBox()
self.countSpineBox.setRange(0, 10)
self.countSpineBox.valueChanged.connect(self.countSpineValueChanged)
hbox.addWidget(self.countSpineBox)
hbox.addStretch()
count.setLayout(hbox)
vbox.addWidget(count) #垂直佈局,添加widget1
self.detailTable = QTableWidget()
self.detailTable.setColumnCount(9)
self.detailTable.setHorizontalHeaderLabels(
['有效期', '貨幣類型', '價格', '普通折扣價', '藍鑽價', '藍鑽折扣價', '超級藍鑽折扣價', '贈送禮包ID', '快捷購買'])
vbox.addWidget(self.detailTable) #垂直佈局,添加widget2
垂直佈局中,還嵌套了水平佈局。
說明:QHBoxLayout、QVBoxLayout都是繼承自QBoxLayout,爲了更好的控制佈局,都繼承瞭如下方法:
l QBoxLayout.addSpacing (size)
添加一個不能伸縮的空間(一個QSpacerItem),其寬度設置爲size到佈局末尾。框佈局提供了默認的邊距margin和spacing,這是額外添加的空間。
l QBoxLayout.addStretch(stretch)
添加一個可伸縮的空間(一個QSpacerItem),設0爲最小值而且伸縮因子爲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)
QDialog類是對話框窗口的基類。對話框窗口是主要用於短時間任務以及和用戶進行簡要通信的頂級窗口。QDialog能夠是模態對話框也能夠是非模態對話框。QDialog支持擴展性而且能夠提供返回值。它們能夠有默認按鈕。
內置經常使用的對話框有:QColorDialog、QErrorMessage、QFileDialog、QFontDialog、QInputDialog、QMessageBox、QProgressDialog、QTabDialog、QWizard。
內置的對話框提供了一些經常使用的功能,使用起來也必將遍歷。編寫該工具使用到了,選擇文件、目錄的對話框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_():
# -----
下面介紹編寫工具過程當中使用到的組件的一些注意事項。
若是有不少列,QTableWidget出出現水平滾動條,可是有不但願有滾動條能夠經過設置列自適應方式:
tw.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
保證因此列都能顯示,不會出現水平滾動條,這樣有的單元格顯示會被截斷顯示,如圖中的"青年套裝下裝"-->"青年套裝...",這時能夠設置單元的tooltip提供完整顯示的途徑。
編寫工具時,有要求QTableWidget展現出來的數據不能編輯,是經過如下方式實現:
tw.setEditTriggers(QAbstractItemView.NoEditTriggers)
QAbstractItemView還定義了其它的模式,以下表所示:
Constant
|
Value
|
Description
|
QAbstractItemView.NoEditTriggers
|
0
|
No editing possible.
|
QAbstractItemView.CurrentChanged
|
1
|
Editing start whenever current item changes.
|
QAbstractItemView.DoubleClicked
|
2
|
Editing starts when an item is double clicked.
|
QAbstractItemView.SelectedClicked
|
4
|
Editing starts when clicking on an already selected item.
|
QAbstractItemView.EditKeyPressed
|
8
|
Editing starts when the platform edit key has been pressed over an item.
|
QAbstractItemView.AnyKeyPressed
|
16
|
Editing starts when any key is pressed over an item.
|
QAbstractItemView.AllEditTriggers
|
31
|
Editing starts for all above actions.
|
設置QTableWidget按行選擇:
tw.setSelectionBehavior(QAbstractItemView::SelectRows); //整行選中的方式
QAbstractItemView還定義了其它的模式,以下表所示:
Constant
|
Value
|
Description
|
QAbstractItemView.SelectItems
|
0
|
Selecting single items.
|
QAbstractItemView.SelectRows
|
1
|
Selecting only rows.
|
QAbstractItemView.SelectColumns
|
2
|
Selecting only columns.
|
若是但願單擊QTableWidget表頭進行數據排序,能夠簡單經過如下接口實現:
tw.setSortingEnabled(True)
可是,排序須要注意的2個問題:
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)
效果以下圖所示:
默認的時間顯示格式(如2015/1/16 17:42),可能不知足需求,能夠經過setDisplayFormat()設置顯示格式來定製。格式選項以下所示:
這些是可能用到的日期表達式:
l d - 沒有前置0的數字的天(1-31)
l dd - 前置0的數字的天(01-31)
l ddd - 縮寫的日名稱(Mon-Sun)。使用QDate.shortDayName()。
l dddd - 長的日名稱(Monday-Sunday)。使用QDate.longDayName()。
l M - 沒有前置0的數字的月(1-12)
l MM - 前置0的數字的月(01-12)
l MMM - 縮寫的月名稱(Jan-Dec)。使用QDate.shortMonthName()。
l MMMM - 長的月名稱(January-December)。使用QDate.longMonthName()。
l yy - 兩位數字的年(00-99)
l yyyy - 四位數字的年(0000-9999)
這些是可能用到的時間表達式:
l h - 沒有前置0的數字的小時(0-23或者若是顯示AM/PM時,1-12)
l hh - 前置0的數字的小時(00-23或者若是顯示AM/PM時,01-12)
l m - 沒有前置0的數字的分鐘(0-59)
l mm - 前置0的數字的分鐘(00-59)
l s - 沒有前置0的數字的秒(0-59)
l ss - 前置0的數字的秒(00-59)
l z - 沒有前置0的數字的毫秒(0-999)
l zzz - 前置0的數字的毫秒(000-999)
l AP - 切換爲AM/PM顯示。AP將被「AM」或「PM」替換。
l ap - 切換爲am/pm顯示。ap將被「am」或「pm」替換。
如工具中使用的格式爲:
setDisplayFormat("yyyy-MM-dd hh:mm:ss")
顯示效果以下圖所示:
但願點擊QDateTimeEdit能夠彈出日期選擇窗口,能夠簡單的經過setCalendarPopup(True)實現,很是的簡單。
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目錄下便可。