不是CS專業(yè),從來沒做過界面,入職就被分配了寫一個(gè)上位機(jī)的工作。幸虧讀master的時(shí)候被操練得盲目自信不怕困難,說干就干。邊學(xué)邊寫用Matlab寫了個(gè)很爛的上位機(jī),一旦啟動阻塞電腦其他所有動作…… 但是也算能用了,空閑了幾天,這時(shí)候有了新需求,那么就趁機(jī)學(xué)一下Qt吧。開始的時(shí)候,決定用PyQt 5 + Python 3,因?yàn)楦杏X比C++開發(fā)速度快。新需求完成后,就決定繼續(xù)把舊的功能移植過來了,最后……又做了一個(gè)很爛的上位機(jī)?!昂軤€”主要是性能問題,后面再細(xì)說。
請多指教。
筆者也不知道Python 2用來開發(fā)會怎樣,選Python 3一是因?yàn)榭吹絇ython 2將停止維護(hù)還是什么的,二是用Vim的時(shí)候不少插件要Python 3支持。筆者是在Windows 7上開發(fā),安裝Python 3之后,就可以用pip安裝需要的包了,比如說最主要的PyQt 5。中途從Python 3.5換到3.6,因?yàn)楦铝薞im,結(jié)果是并無影響,至少是在這個(gè)項(xiàng)目涉及到的范圍內(nèi)沒有影響。
不需要安裝Qt 5。一開始以為使用PyQt 5要先安裝Qt 5,后來在另外一臺電腦上搭環(huán)境的時(shí)候嘗試了一下,發(fā)現(xiàn)并不需要。開始用的時(shí)候是PyQt/Qt 5.9,寫這篇文章的時(shí)候是5.10了。PyQt 5相對于4,很多類都被移動了,目前來說網(wǎng)上的PyQt 5的教程比4的少很多,雖然可以參考著來開發(fā),但是import的時(shí)候就要小心了,要去PyQt 5的網(wǎng)站或Qt官網(wǎng)查看一下。
既然提到了,就稍微說一下,并不是要引戰(zhàn)。筆者是學(xué)ROS的時(shí)候突然想起有這么個(gè)編輯器,懷著一顆好奇心就入坑了,花了一個(gè)星期入門——“入門”的意思是知道:wq等等。筆者腦子不好使,.h和.c(pp)分開看不行,界面和邏輯分開看也不行,也嘗試過學(xué)習(xí)Emacs之類,無奈再也沒有這么多時(shí)間了,所以一直用Vim。另外通過配置vimrc也可以直接在Vim中通過快捷鍵運(yùn)行Python代碼。
Vim用了快3年,配置上基本滿足當(dāng)前需求了,不能說是高端配置。先挖個(gè)坑,后面補(bǔ)一篇指南,比如語法檢查插件ALE我就搞了很久。
還是先上點(diǎn)入門資料吧。
(1) First Programs in PyQt 5 - 建議先看這個(gè)。
(2) 《Matplotlib for Python Developers》的Chapter No. 6 - 第6章講的是怎么把matplotlib的坐標(biāo)嵌入到PyQt中去,但例子用的都是PyQt 4,需要讀者自行移植。其他matplotlib的內(nèi)容,筆者都是通過其官方文檔及Stack Overflow學(xué)習(xí)的。
也許主文件為其他命名也可以,但是筆者的主文件就是一鍋粥,以main概括就是了;里面是各個(gè)面板的實(shí)例化,以及最重要的——整個(gè)程序的實(shí)例化。這里要用到的是:
from PyQt5.QtWidgets import QApplication
PyQt 4的界面有很多種風(fēng)格可選,但是PyQt 5中刪減了??赏ㄟ^setStyle函數(shù)來設(shè)置:
- if __name__ == '__main__':
- app = QApplication(sys.argv)
- app.setStyle('fusion')
在看過的教程里,似乎主窗口都要用到QMainWindow:
- from PyQt5.QtWidgets import QMainWindow
- class MainWindow(QMainWindow):
- def __init__(self):
- super().__init__()
- # Your code afterwards
用的時(shí)候并沒有深究為什么一定要用QMainWindow,后來其他部件全都是用QWidget。寫到這里,筆者去查了一下,有興趣的可以看一下——鏈接一,鏈接二,鏈接三。(“待審核”了,刪了,不好意思。)
QWidget用法和以上代碼差不多,就改個(gè)類名。主要用來做了一些從主窗口分離出去的小面板,例如嵌入了matplotlib的面板。
筆者寫的軟件只定義了窗口初始大小和位置,以及圖標(biāo):
- from PyQt5.QtGui import QIcon
- from PyQt5.QtWidgets import QDesktopWidget
- from PyQt5.QtWidgets import QWidget
- class Panel(QWidget):
- def __init__(self):
- super().__init__()
- # Your code afterwards.
- def init_panel(self):
- screen = QDesktopWidget().availableGeometry()
- width = screen.width() * 0.5 # Change the scaler (0.5 here) at your will.
- height = screen.height() * 0.5 # Same as above.
- x = screen.width() * 0.01 # Same as above.
- y = screen.height() * 0.03 # Same as above.
- self.setGeometry(x, y, width, height)
- self.setWindowTitle('Panel')
- self.setWindowIcon(QIcon('icon.png'))
先把用到的組件列一下:
- from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
- from matplotlib.figure import Figure
- from PyQt5.QtWidgets import QSizePolicy
- from PyQt5.QtWidgets import QCheckBox, \
- QComboBox, \
- QDial, \
- QGroupBox, \
- QLabel, \
- QLineEdit, \
- QProgressBar, \
- QPushBotton
- from PyQt5.QtWidgets import QStyleFactory
(1) 1至3行是用來嵌入matplotlib的,QSizePolicy的用法,筆者也是對著教程依樣畫葫蘆:
- class Canvas(FigureCanvasQTAgg):
- def __init__(self, parent):
- self.figure = Figure()
- FigureCanvasQTAgg.__init__(self, self.figure)
- self.axes = self.figure.add_subplot(1, 1, 1)
- self.setParent(parent)
- FigureCanvasQTAgg.setSizePolicy(self,
- QSizePolicy.Expanding,
- QSizePolicy.Expanding)
- FigureCanvasQTAgg.updateGeometry(self)
(2) 14行的QStyleFactory是與8行的QDial配合用的,筆者用QDial簡單封裝了一個(gè)羅盤,但是QDial的指針樣式在PyQt 5里默認(rèn)是一個(gè)圓點(diǎn),需要用QStyleFactory強(qiáng)制顯示為針狀(只列出部分代碼,其他屬性請自行設(shè)置):
- class Compass(QDial):
- def __init__(self, parent):
- super().__init__()
- self.setStyle(QStyleFactory.create('windows'))
(3) 第6行QCheckBox:很直接,用于勾選某些選項(xiàng)
(4) 第7行QComboBox:用于創(chuàng)建下拉菜單
- from PyQt5.QtWidgets import QComboBox
- from PyQt5.QtWidgets import QWidget
- class Panel(QWidget):
- def __init__(self):
- super().__init__()
- def create_widget(self):
- self.menu = QComboBox(self)
- def init_widget(self):
- self.menu.addItem('1', 1)
- self.menu.addItem('2', 2)
- self.menu.addItem('3', 3)
addItem的第一個(gè)參數(shù)為菜單顯示的字符串,第二個(gè)參數(shù)為item里對應(yīng)的數(shù)據(jù),可通過itemData來獲取,例如,在上述例子的前提下,以下語句為True:
- self.menu.itemData(0) == 1
- self.menu.itemData(1) == 2
- self.menu.itemData(2) == 3
(5) 第9行QGroupBox:就是在窗口里的一個(gè)小區(qū)域,視覺上將一些關(guān)系緊密組件放在一起,可以為這個(gè)group設(shè)置一個(gè)名字。QGroupBox在面板的QGridLayout上用addWidget從(0,0)開始擺放后,組內(nèi)要另外定義QGridLayout,從(0,0)開始擺放組件。其他細(xì)節(jié)請自行參考官方文檔。
(6) 第10、11行配合使用:QLabel即創(chuàng)建標(biāo)簽,通常用來標(biāo)記QLineEdit里顯示的數(shù)據(jù)是什么東西。QLineEdit中,用setText來設(shè)置要顯示的數(shù)據(jù),同時(shí)可用setReadOnly防止數(shù)據(jù)被誤改;如果是作為輸入框,用getText然后轉(zhuǎn)換數(shù)據(jù)類型。其他細(xì)節(jié)請自行參考官方文檔。
(7) 第12行QProgressBar:用來當(dāng)作電池顯示條。隨便鼓搗自己想要的功能,其中還用到一個(gè)網(wǎng)站(uiGradient)提供的漸變色配色方案。
(8) 第13行QPushButton:創(chuàng)建按鈕,可用setCheckable(True)來使得按鈕可以保持按下(Checked)的狀態(tài)。
筆者只用了QGridLayout,因?yàn)楦杏X這個(gè)布局便于安排組件。雖然冥冥中感覺QVBoxLayout和QHBoxLayout能夠讓界面在resize的過程中使組件的位置保持得更好(如使用addStrecth等,若有誤懇請讀者賜教),但由于并不是產(chǎn)品級的軟件,所以什么方便快捷就用什么了。QGridLayout中的setRowStrecth和setColumnStretch只是用來對齊不同QBoxGroup中的組件,但效果也并不完美。
原理上,setRowStrecth和setColumnStretch可以用來調(diào)整不同組件所占空間的大小,但是筆者直接用addWidget來調(diào)整了:
- from PyQt5.QtWidgets import QGridLayout
- from PyQt5.QtWidgets import QLineEdit
- from PyQt5.QtWidgets import QWidget
- class Panel(QWidget):
- def __init__(self):
- super().__init__()
- self.layout = QGridLayout(self)
- # Your code afterwards
- def create_widgets(self):
- self.fat_box = QLineEdit(self)
- self.thin_box = QLineEdit(self)
- def add_widgets(self):
- self.layout.addWidget(self.fat_box, 0, 0, 3, 3)
- self.layout.addWidget(self.thin_box, 3, 0, 1, 1)
聯(lián)系客服