1. 程式人生 > >Python PyQt5

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')
      
  • 自定義訊號

自定義訊號與槽的使用,是指在發射訊號時,不使用視窗控制元件的函式,而是使用自定義的函式,(簡單的說就是使用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