Python PyQt5
下載PyQt5
- pip install PyQt5
下載designer
- pip install PyQt5-tools
使用Qtdesigner
- 將生產的ui檔案轉py檔案,使用命令pyuic5 xx.ui -o xx.py
- 此時只自動生成了ui介面的程式碼,需要自己寫主程式呼叫你的介面類,並且需要自己編寫介面的類並註冊UI的類
- from PyQt5 import uic
- 使用uic的loadUi()方法讀取designer設計的ui檔案,就可以不必ui檔案轉py檔案這一部。
- 主程式入口:呼叫你的Qt視窗,然後show()出來,然後退出
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyMainWindow()
window.show()
sys.exit(app.exec_())
- 設計的主窗體類:
class MyMainWindow(QMainWindow):
def __init__(self):
super().__init__()
uipath = os.path.join(os.path.dirname(__file__), 'window.ui' ) # 獲取designer的ui檔案的路徑拼接
uic.loadUi(uipath, self) # 讀取designer設計的ui檔案
佈局
- 絕對定位
obj.move()
- 佈局類
- 表格佈局QGridLayout
QtSql
- 引入庫
from PyQt5.QtSql import QSqlDatabase, QSqlQuery
- 連線資料庫
- 建立資料庫連線並開啟
- 這裡是連線SqlServer的例子
server = '100.88.16.2' dbname = '市本級' username = 'xx' password = 'xx' db = QSqlDatabase.addDatabase('QODBC') db.setDatabaseName( f'Driver={{Sql Server}};Server={server};Database={dbname};Uid={username};Pwd={password}' ) # f-String中如果有{dsfd}的正常字串就再用一個{}來包裹 db.open()
- 執行sql語句
query = QSqlQuery() query.exec("Select name From sys.databases") query = QSqlQueryModel() query.setQuery("xxxxx") # 上述兩種方式都可以執行sql語句,不過最好分開執行,一次執行多條sql語句會出錯
- 獲取查詢結果
- 使用QSqlQueryModel物件的屬性與方法來獲取結果集
model.columnCount() # 獲取總列數 model.rowCount() # 獲取總行數 model.index(x,y) # 獲取一個結果集的位置索引物件,只是一個索引物件,還不是資料 model.data(indexObj) # 根據索引物件來取得資料,這裡的索引物件就是上面的結果
- model物件不會一次獲取所有資料
while model.canFetchMore(): # model不會一次讀取所有,需要獲取到所有記錄 model.fetchMore() # 獲取多一點的記錄
- 關聯tableView控制元件
- 需要先建立model物件,再將tableView與model相關聯起來
from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel # 先建立跟tableView關聯的模型類,該類執行sql語句 qqm = QSqlQueryModel() qqm.setQuery('select * from alltables') # 在窗體主類的tableView物件中設定該model,使用setModel方法 self.tableView_3.setModel(qqm) # tableView設定了setModel後控制元件自動渲染結果
QtSql資料來源切換remove問題
- 在切換資料來源時需要執行removeDatabase方法,該方法如果要正常執行,需要關閉與該資料庫相關的所有物件
def DisConnectDb(self): self.tableView.setModel(None) if hasattr(self, 'model'): if self.model: self.model = None if hasattr(self, 'db'): if self.db: name = self.db.connectionName() QSqlDatabase.database(name).close() self.db = None QSqlDatabase.removeDatabase(name)
- 總之需要把相關聯的所有引用給關閉
PyQt5事件與訊號
1. 在事件模型中有三個參與者:
- 事件源:事件源是狀態發生變化的物件
- 事件(物件):事件(物件)封裝了事件源中狀態的變動
- 事件接收者:事件源物件將事件處理的工作交給事件接收者
- PyQt5有一個獨特的signal&slot(訊號槽)機制來處理事件。訊號槽用於物件間的通訊。signal在某一特定事件發生時被觸發,slot可以是任何callable物件。當signal觸發時會呼叫與之相連的slot。
2. Signals & slots
// 窗體中初始化
// valueChanged訊號,lcd.display是槽
sld.valueChanged.connect(lcd.display)
- 重寫事件處理器
- KeyPressEvent(self,e) 按鍵事件
# KeyPressEvent事件處理視窗中的按鍵事件,事件處理器 def keyPressEvent(self, e): if e.key() == Qt.Key_Escape: self.close() if e.key() == Qt.Key_A: print('你按了a或A') if e.key() == Qt.Key_F1: print('你按了F1')
- KeyPressEvent(self,e) 按鍵事件
- 自定義訊號
自定義訊號與槽的使用,是指在發射訊號時,不使用視窗控制元件的函式,而是使用自定義的函式,(簡單的說就是使用pyqtsSignal類例項發射訊號),之所以要使用自定義的訊號與槽,是因為通過內建函式發射訊號有自身的缺陷,首先,內建函式只包含一些常用地訊號,有些訊號發射找不到對應的內建函式,其次在特定的情況下,才能發射此訊號,如按鈕的點選事件,最後,內建函式傳遞的引數是特定的,不可以自定義,使用自定義的訊號與槽函式則沒有這些缺陷
在pyqt5中,自定義訊號與槽的適用很靈活,比如因為業務需要,在程式中的某些地方需要發射一個訊號,傳遞多種資料,然後在槽函式接受這些資料,這樣就可以很靈活的實現一些業務邏輯
-
自定義訊號的一般流程如下
- 定義訊號
- 定義槽函式
- 連線訊號與槽
- 發射訊號
//匯入相關類
from PyQt5.QtCore import pyqtSignal, QObject
//新建一個類來自定義訊號
class Communicate(QObject):
closeApp = pyqtSignal() //自定義一個closeApp的訊號
//向窗體類中的訊號繫結槽
self.c.closeApp.connect(self.close) # 為自定義訊號繫結槽//重寫滑鼠點選事件的事件處理器
def mousePressEvent(self, event):
print(‘觸發滑鼠點選事件’)
self.c.closeApp.emit() //觸發closeApp訊號的事件 -
自定義的訊號需要定義在
QObject
類下 -
emit()觸發訊號,訊號再觸發繫結槽的方法,也就是emit->connet的槽方法
-
如果訊號無引數,但是槽函式需要接受某些引數:
- 第一種:lamdba表示式
- 第二種:使用functools中的partial函式
//單擊訊號關聯槽函式,利用Lanbda表示式傳遞一個引數 button1.clicked.connect(lambda :self.onButtonClick(1)) button2.clicked.connect(lambda :self.onButtonClick(2))
PyQt5的狀態列
- 可用於顯示程式的當前狀態
self.statusBar().showMessage(f'正在連線{server}的{dbname}........')
QTimer控制元件
如果在應用程式中週期性地進行某項操作,比如週期性的檢測主機的cpu值,則需要用到QTimer定時器,QTimer類提供了重複和單次的定時器,要使用定時器,需要先建立一個QTimer例項,將其Timeout訊號連線到槽函式,並呼叫start(),然後,定時器,會以恆定的間隔發出timeout訊號
self.timer = QTimer() # 定義定時器
self.timer.setInterval(500) # 設定時間間隔毫秒
self.timer.start() # 開始定時器
self.timer.timeout.connect(self.status) # 為定時器超時連線一個槽
def status(self):
pass
PyQt5的Dialog會話框控制元件
- 普通的dialog
- 這個QInputDialog是一個彈出的會話框,getText函式獲取輸入的值與點選的按鈕值,text為輸入值,ok為點選的按鈕值;getText的引數,第一個為對話方塊標題,第二個為提示訊息
def showDialog(self): # 下面這一句是重點 text, ok = QInputDialog.getText(self, 'Input Dialog', 'Enter your name:') if ok: self.le.setText(text)
- 選擇檔案的dialog
fname = QFileDialog.getOpenFileName(self, 'Open file')
返回的是一個元組,該元組的資訊為選擇的檔案路徑和檔案的篩選方式,所以我們通過返回值的第一個元素來取到檔案路徑
def showDialog(self): # 下面這一句是重點 fname = QFileDialog.getOpenFileName(self, 'Open file') if fname[0]: f = open(fname[0], 'r') with f: data = f.read() self.textEdit.setText(data)
- 選擇資料夾dialog
QFileDialog.getExistingDirectory(self, '選擇資料夾', './') # 第三個引數為開啟時所處的路徑,返回選擇的資料夾路徑
def showFileDialog(self): dic = QFileDialog.getExistingDirectory( self, '選擇資料夾', './') # 第三個引數為開啟時所處的路徑,返回選擇的資料夾路徑 return dic
分離UI主執行緒與工作執行緒
- PyQt提供了一個QThread類來處理執行緒問題
- 建立一個執行緒類重寫其run函式,來實現以往單執行緒的功能
- 主程式中設定槽,槽呼叫的函式中例項化你的執行緒類並就緒start該執行緒
# threads.py
from PyQt5.QtCore import QThread
import time
class SleepQThread(QThread):
def __init__(self, *args, **kwargs):
super().__init__()
def run(self):
time.sleep(10)
print('sleep thread is going.....')
# main.py
from PyQt5.QtWidgets import QApplication, QWidget
from sleep import Ui_Form
from threads import SleepQThread
import time
import sys
class MyWidget(QWidget, Ui_Form):
def __init__(self, *args, **kwargs):
super().__init__()
self.setupUi(self)
self.pushButton.clicked.connect(self.sleep)
def sleep(self):
self.sleepthread = SleepQThread() # 需要賦給類中的變數
self.sleepthread.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
- 高階用法,涉及主執行緒與子執行緒之間的資料傳輸
# thread.py
from PyQt5.QtCore import QThread, pyqtSignal
import time
class SleepQThread(QThread):
# 自定義訊號
finalSignal = pyqtSignal(list) # list為該訊號的返回值,返回一個list物件
def __init__(self, *args, **kwargs):
super().__init__()
self.args = args # 通過初始化該執行緒類來傳入資料到子執行緒中
def run(self):
time.sleep(10)
print('sleep thread is going.....')
self.finalSignal.emit([self.args]) # 訊號觸發,並傳list物件參給connect的槽函式的第一個引數 ,此處便是子執行緒傳資料給主執行緒
# main.py
from PyQt5.QtWidgets import QApplication, QWidget
from sleep import Ui_Form
from threads import SleepQThread
import time
import sys
class MyWidget(QWidget, Ui_Form):
def __init__(self, *args, **kwargs):
super().__init__()
self.setupUi(self)
self.pushButton.clicked.connect(self.sleep)
def sleep(self):
self.pushButton.setDisabled(True)
self.sleepthread = SleepQThread(int(1), int(2))
self.sleepthread.finalSignal.connect(self.sleepEnd) # 執行緒中的自定義訊號繫結槽
self.sleepthread.start()
def sleepEnd(self, fromfinalsignal): # fromfinalsigal引數是來源於訊號的emit的引數
print('End')
print(fromfinalsignal)
self.pushButton.setDisabled(False)
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
Pyinstaller打包的時候
- ui檔案需要轉化為類並裝載的寫法,使用Pyinstaller打包才ok不然會出錯
- Pyinstaller打包PyQt5程式時,出現Hidden import的錯誤,此時通過修改命令,忽略某個不需要的包的匯入即可;
Pyinstaller -F -w xxx.py --hidden-import PyQt5.sip
- 如果打包的程式需要讀取配置檔案,就將配置檔案複製到dist的程式下,通過建立快捷方式來使用程式
- 打包的時候程式中如果使用了圖片,在開發時是可以看見的,打包後就看不見,其實就是ui生成的py檔案中尋找圖片的路徑改變了,個人做法是將需要用到的圖片放到打包後的dist資料夾下,並修改ui生成的py檔案中相關圖片路徑
(./xxx.ico)
。 - 如果要修改軟體的圖示,在生成的.spec檔案的最後一個小括號內加
icon='xxx.ico'
,然後執行pyinstaller xxx.spec
,這裡圖片的檔名不能是中文,且是ico字尾的圖片。
自適應佈局
- 需要利用佈局來實現,整個窗體的外圍設定一種佈局,該佈局可以自適應視窗,往裡同樣需要如此。
- 先拖幾個控制元件上去,在designer中右鍵佈局,使佈局自動適應。
comboBox使用
// comboBox中的選擇變更時觸發的訊號
self.comboBox_1.currentIndexChanged.connect(self.getKm)
// 為一個comboBox新增內容
self.comboBox_3.addItems(['先入先出','後入先出']) # 為ComboBox新增選項,且該list內的元素必須為str型別
// 獲取當前comboBox選擇項的文字內容
com_name = self.comboBox_1.currentText()
// 清空一個comboBox的值
self.comboBox_2.clear()
QTableWidget使用
- 設定每一行資料的雙擊事件
self.tableWidget.doubleClicked.connect(self.yourchoice)
- 設定總的列數,來控制表格表頭的數量
self.tableWidget.setColumnCount(len(result))
- 設定表頭
self.tableWidget.setHorizontalHeaderLabels([i[1] for i in result])
- 設定總的行數,來控制表格總的記錄數
self.tableWidget.setRowCount(len(result))
- 往一個單元格中填充內容
item = QTableWidgetItem(str(j[1])) // 單元格中的內容需要時QTableWidgetItem型別的物件,且該物件的內容只能是str型別的 self.tableWidget.setItem(i[0], j[0], item) // 前兩個引數是單元格對應的位置
- 根據表名與sql來實現內容的顯示:
def displayData(self, sql): self.cursor.execute(sql) # 根據執行的sql語句獲取表頭資訊 labels = [x[0] for x in self.cursor.description ] # cursor.description可以獲取每個欄位的描述 self.tableWidget.setColumnCount(len(labels)) self.tableWidget.setHorizontalHeaderLabels(labels) # 獲取sql語句執行結果 result = self.cursor.fetchall() self.tableWidget.setRowCount(len(result)) self.statusBar().showMessage(f'當前頁記錄數:{len(result)}條') for i in enumerate(result): for j in enumerate(i[1]): item = QTableWidgetItem(str(j[1])) self.tableWidget.setItem(i[0], j[0], item)
- 獲取選中的記錄
one = [i.text() for i in self.tableWidget.selectedItems()]
- 清空該空間中的所有內容
self.tableWidget.clear()
- 獲取tablewidget中的內容
self.tableWidget.item(row_index, column_index).text()
QTreeWidget使用
- 類似於QTableWidget控制元件的使用
self.treeWidget.clear() # 清空treewidget
self.treeWidget.setColumnCount(1) # 設定列數
self.treeWidget.setHeaderLabels(['方法']) # 設定表頭
m1 = QTreeWidgetItem(self.treeWidget) # 建立該控制元件的子節點
m2 = QTreeWidgetItem(self.treeWidget)
m3 = QTreeWidgetItem(self.treeWidget)
m1.setText(0, filename[0]) # 為m1這個節點0列設定內容
m2.setText(0, filename[1])
m3.setText(0, filename[2])
item = self.treeWidget.currentItem() # 獲取當前選擇的tree節點
self.treeWidget.doubleClicked.connect(self.clickTheTree) # 雙擊事件
self.treeWidget.clicked.connect(self.clickTheTree) # 單擊事件
設定字型顏色
from PyQt5.QtGui import QPalette
from PyQt5.QtCore import Qt
pe = QPalette()
pe.setColor(QPalette.WindowText, Qt.red) # 設定文字顏色
self.label_11.setPalette(pe) # 應用於label