pyqt5 自定义控件_PyQt5学习笔记(十六)Pyinstaller打包与SQLite数据库
終于到了最后一章了QAQ,第一次寫4萬字以上的筆記分享,最近也在忙科創和CV的比賽,所以筆記會顯得比較粗糙。其實吧分享這個筆記很大一部分是為了讓自己記得更牢,網上翻閱自己的筆記也方便,如果有講解注釋不清楚的地方歡迎評論留言(秋梨膏)。當然本蒟蒻也會時不時地在以后的時間更新完善這些已經完成的筆記。
以下內容涉及到SQLite數據庫和requests爬蟲庫的使用,有時間我也會做相關方面的筆記噠(`?ω?′)。以下只是初步使用。
使用Pyinstaller打包PyQt5應用
Pyinstaller部分同學想必用過,這是一個在命令行下使用的、將腳本代碼轉成可執行文件的python第三方庫。如果想要使得我們做出來的GUI脫離于python解釋器而讓操作系統直接執行的話,我們可以使用Pyinstaller庫來封裝我們的GUI的腳本程序。這樣不同python、沒有python依賴庫的用戶也可以方便運行程序。
這里直接講windows下可行的方法,安裝完pyinstaller后,打開命令行,一路cd到需要打包的python文件的目錄下。或者也可以在需要打包的python文件的目錄下按住“Shift”+鼠標右鍵,打開“PowerShell“:
點開后,就會有和windows的命令行幾乎一樣的頁面。
下面我們對draft.py文件打包生成可執行文件,那么只要輸入以下指令:
pyinstaller -Fw "draft.py"打開目錄,你會發現多了兩個文件夾build和dist,打開dist文件夾,里面就是可以直接運行的可執行文件了:
如果要修改圖標,比如要給文件“SevenDigitDrawV2.py“附上”curve.ico“的圖標,則輸入命令:
pyinstaller -i curve.ico -Fw "draft.py"需要說明的是,打包完的可執行文件會很大(筆者的這個小小的畫板exe就有37MB),這是因為它將所有python的依賴庫都包括進去了,即你裝的第三方庫越多,打包出來的文件越大。
操作SQLite數據庫
許多桌面應用都有訪問本地數據庫或者遠程數據庫的需求,下面就講講其中的一種SQLite。SQLite是一種輕量級的跨平臺數據庫,因此很常用。
PyQt5中提供了操控SQLite數據庫的API。
- QSqlDatabase.addDatabase():創建一個通用數據庫,參數填入“QSQLITE”表示創建SQLite數據庫
我們現在python文件的目錄下創建一個文件夾"db"。
import sys from PyQt5.QtSql import * ? def createDB():# 創建一個通用數據庫對象,參數"QSQLITE"代表通用數據庫為SQLite數據庫類型db = QSqlDatabase.addDatabase("QSQLITE")# 指定SQLite數據庫的文件名db.setDatabaseName("./db/database.db")if not db.open():print("無法建立與數據庫的連接")return False# 創建查詢功能query = QSqlQuery()# 執行創建表格的指令query.exec('create table people(id int primary key,name varchar(10),address varchar(50))')# 執行往表格中插入數據的指令query.exec('insert into people values(1, "Kirigaya", "GitHub")')db.close()return True ? if __name__ == '__main__':createDB()通過DB Browser for SQLite打開"./db/database.db",點擊"Browser Data"瀏覽數據:
我們創建的表格和數據條已經在里面了建議使用DB Browser for SQLite來打開數據庫進行操作,網上可以下載到。
通過可視化的方式對SQLite數據庫進行增、刪、改、查操作
此處我們使用之前講過的QTableView控件來展示二維數據。之前你可能會覺得QTableView控件不常用,因為與QTableWidget相比,QTableView需要創建一個model,設置模型代表的表格的尺寸、屬性,再在模型上添加數據,再將model與view關聯,最后將view作為控件添加到窗口上才能顯示出我們model代表的表格的樣式與數據,看起來比較繁瑣,沒有QTableWidget那樣直觀和方便。
但是當我們想將數據庫中的數據在GUI上可視化時,QTableView就變得很常用了,因為
import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtSql import * from PyQt5.QtWidgets import * ? def initializeModel(model):# 設置表格名稱model.setTable("people")# 設置編輯策略model.setEditStrategy(QSqlTableModel.OnFieldChange)# 調用select方法model.select()# 設置表格頭model.setHeaderData(0, Qt.Horizontal, "ID")model.setHeaderData(1, Qt.Horizontal, "姓名")model.setHeaderData(2, Qt.Horizontal, "地址") ? def createView(title, model):view = QTableView()view.setModel(model)view.setWindowTitle(title)return view ? def findrow(i):delrow = i.row()print("選中第{}行".format(delrow)) ? def addrow():# 在model.rowCount()行后插入1行,并返回插入的行所在行數ret = model.insertRows(model.rowCount(), 1)print("添加第{}行".format(ret)) ? if __name__ == '__main__':app = QApplication(sys.argv)db = QSqlDatabase.addDatabase("QSQLITE")# 設置數據庫名字(路徑)db.setDatabaseName("./db/database.db") ?# 創建一個數據庫的表格模型,QSqlTableModel()會自動關聯到db上model = QSqlTableModel()delrow = -1# 初始化這個表格模型(函數在上面)initializeModel(model) ?# 根據model創建QTableView對象view = createView("展示數據", model)view.clicked.connect(findrow) ?window = QDialog()layout = QVBoxLayout()layout.addWidget(view)addBtn = QPushButton("添加一行")addBtn.clicked.connect(addrow) ?delBtn = QPushButton("刪除一行")# 下面view.currentIndex().row()表示view中被選中的單元格對象所在行數delBtn.clicked.connect(lambda : model.removeRow(view.currentIndex().row())) ?layout.addWidget(addBtn)layout.addWidget(delBtn)window.setLayout(layout) ?window.setWindowTitle("Database Demo")window.resize(550, 400)window.show() ?sys.exit(app.exec_())運行效果:
因為數據庫路徑設置的是我們之前創建的數據庫,所以一打開就已經有數據了分頁顯示數據
如果數據量很大,那么看起來會很不方便,一頁上只能顯示幾條數據。事實上,不僅是在GUI中,許多網頁也會有這種效果:
通過兩個按鈕控件“前一頁”和“后一頁”和一個QLineEdit控件可供任意頁面的跳轉,用戶就可以在有限的UI中瀏覽整個數據庫中對的數據(emm,事實上,這不就是一般數據庫的基本功能嗎?)
下面,我們完成這個實例。
我們先將整個窗口的布局和控件設置一下:
import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtSql import * from PyQt5.QtWidgets import * ? class DataGrid(QWidget):def __init__(self):super().__init__()self.setWindowTitle("分頁查詢例子")self.resize(820, 420)self.initUI() ?def initUI(self):# 創建窗口(自定義方法)self.createWindow() ? ?# 添加窗口上的控件和布局def createWindow(self):# 添加相關的布局與控件hbox1 = QHBoxLayout()# 向前翻頁的按鈕self.prevButton = QPushButton("前一頁")# 向后翻頁的按鈕self.nextButton = QPushButton("后一頁")# 直接跳轉到指定頁數的按鈕和輸入想要跳轉至的頁數的輸入框self.switchPageButton = QPushButton("Go")self.switchPageLineEdit = QLineEdit()self.switchPageLineEdit.setFixedWidth(40) ?switchPage = QLabel("轉到第")page = QLabel("頁") ?# 添加我們的控件hbox1.addWidget(self.prevButton)hbox1.addWidget(self.nextButton)hbox1.addWidget(switchPage)hbox1.addWidget(self.switchPageLineEdit)hbox1.addWidget(page)hbox1.addWidget(self.switchPageButton)# 通過添加間隔控件使得上面的控件全部被擠到左邊去hbox1.addWidget(QSplitter()) ?# 下面這個布局置于最底端,當做當前頁面的狀態hbox2 = QHBoxLayout()# 創建顯示總也是的labelself.totalPageLabel = QLabel()self.totalPageLabel.setFixedWidth(70)# 創建顯示當前頁數的labelself.currentPageLabel = QLabel()self.currentPageLabel.setFixedWidth(100)# 創建顯示總記錄數的labelself.totalRecordLabel = QLabel()self.totalRecordLabel.setFixedWidth(70) ?hbox2.addWidget(self.totalPageLabel)hbox2.addWidget(self.currentPageLabel)# 通過添加間隔控件使得上面的控件(totalPageLabel和currentPageLabel)在左邊,下面的控件(totalRecordLabel)在右邊hbox2.addWidget(QSplitter())hbox2.addWidget(self.totalRecordLabel) ?# 設置表格屬性self.tabelView = QTableView()# 表格寬度自適應調整self.tabelView.resizeRowsToContents()self.tabelView.resizeColumnsToContents() ?# 創建主布局,并將hbox1,表格和hbox2裝入mainLayout = QVBoxLayout(self)mainLayout.addLayout(hbox1)mainLayout.addWidget(self.tabelView)mainLayout.addLayout(hbox2)self.setLayout(mainLayout)if __name__ == '__main__':app = QApplication(sys.argv)main = DataGrid()main.show()sys.exit(app.exec_())運行效果:
然后我們手動創建26條數據,并且設置查詢模型和QTableView控件,并將查詢模型和QTableView控件關聯起來,就可以在中間空白區域顯示數據了。
完成了布局和控件之后,我們還需要為控件添加槽函數,一通操作后,總體的代碼如下(有點雜,不好寫,所以我就將注釋全部打在上面了(`?ω?′))
import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtSql import * from PyQt5.QtWidgets import * ? class DataGrid(QWidget):def __init__(self):super().__init__()self.setWindowTitle("分頁查詢例子")self.resize(820, 420)# 手動創建表格與數據(自定義方法)self.createTableAndInit() ?# 當前頁self.currentPage = 0# 總頁數self.totalPage = 0# 總記錄數self.totalRecordCount = 0# 每頁顯示的記錄數self.PageRecordCount = 6 ?self.initUI() ?def initUI(self):# 創建窗口(自定義方法)self.createWindow()# 設置表格(自定義方法)self.setTableView() ? ?# 創建一個SQLite數據庫對象,并打開指定路徑上的數據庫文件def createTableAndInit(self):self.db = QSqlDatabase.addDatabase("QSQLITE")self.db.setDatabaseName("./db/database.db")if not self.db.open():return False ?# 申明數據庫的查詢對象query = QSqlQuery()# 創建表query.exec("create table student(id int primary key, name vchar, sex vchar, age int, major vchar)")# 手動添加記錄(嫌累也可以在數據庫查詢中添加)query.exec("insert into student values(1, 'AA', '男', 20, '計算機')")query.exec("insert into student values(2, 'AB', '女', 20, '計算機')")query.exec("insert into student values(3, 'AC', '男', 20, '計算機')")query.exec("insert into student values(4, 'AD', '女', 20, '計算機')")query.exec("insert into student values(5, 'AE', '男', 20, '計算機')")query.exec("insert into student values(6, 'AF', '女', 20, '計算機')")query.exec("insert into student values(7, 'AG', '男', 20, '計算機')")query.exec("insert into student values(8, 'AH', '女', 20, '計算機')")query.exec("insert into student values(9, 'AI', '男', 20, '計算機')")query.exec("insert into student values(10, 'AJ', '女', 20, '計算機')")query.exec("insert into student values(11, 'AK', '男', 20, '計算機')")query.exec("insert into student values(12, 'AL', '女', 20, '計算機')")query.exec("insert into student values(13, 'AM', '男', 20, '計算機')")query.exec("insert into student values(14, 'AN', '女', 20, '計算機')")query.exec("insert into student values(15, 'AO', '男', 20, '計算機')")query.exec("insert into student values(16, 'AP', '女', 20, '計算機')")query.exec("insert into student values(17, 'AQ', '男', 20, '計算機')")query.exec("insert into student values(18, 'AR', '女', 20, '計算機')")query.exec("insert into student values(19, 'AS', '男', 20, '計算機')")query.exec("insert into student values(20, 'AT', '女', 20, '計算機')")query.exec("insert into student values(21, 'AU', '男', 20, '計算機')")query.exec("insert into student values(22, 'AV', '女', 20, '計算機')")query.exec("insert into student values(23, 'AW', '男', 20, '計算機')")query.exec("insert into student values(24, 'AX', '女', 20, '計算機')")query.exec("insert into student values(25, 'AY', '男', 20, '計算機')")query.exec("insert into student values(26, 'Az', '女', 20, '計算機')") ?return True ?# 添加窗口上的控件和布局def createWindow(self):# 添加相關的布局與控件hbox1 = QHBoxLayout()# 向前翻頁的按鈕self.prevButton = QPushButton("前一頁")# 向后翻頁的按鈕self.nextButton = QPushButton("后一頁")# 直接跳轉到指定頁數的按鈕和輸入想要跳轉至的頁數的輸入框self.switchPageButton = QPushButton("Go")self.switchPageLineEdit = QLineEdit()self.switchPageLineEdit.setFixedWidth(40) ?switchPage = QLabel("轉到第")page = QLabel("頁") ?# 綁定信號和槽self.prevButton.clicked.connect(self.onPrevButton)self.nextButton.clicked.connect(self.onNextButton)self.switchPageButton.clicked.connect(self.onSwitchPageButtonClick) ?# 添加我們的控件hbox1.addWidget(self.prevButton)hbox1.addWidget(self.nextButton)hbox1.addWidget(switchPage)hbox1.addWidget(self.switchPageLineEdit)hbox1.addWidget(page)hbox1.addWidget(self.switchPageButton)# 通過添加間隔控件使得上面的控件全部被擠到左邊去hbox1.addWidget(QSplitter()) ?# 下面這個布局置于最底端,當做當前頁面的狀態hbox2 = QHBoxLayout()# 創建顯示總也是的labelself.totalPageLabel = QLabel()self.totalPageLabel.setFixedWidth(70)# 創建顯示當前頁數的labelself.currentPageLabel = QLabel()self.currentPageLabel.setFixedWidth(100)# 創建顯示總記錄數的labelself.totalRecordLabel = QLabel()self.totalRecordLabel.setFixedWidth(70) ?hbox2.addWidget(self.totalPageLabel)hbox2.addWidget(self.currentPageLabel)# 通過添加間隔控件使得上面的控件(totalPageLabel和currentPageLabel)在左邊,下面的控件(totalRecordLabel)在右邊hbox2.addWidget(QSplitter())hbox2.addWidget(self.totalRecordLabel) ?# 設置表格屬性self.tabelView = QTableView()# 表格寬度自適應調整self.tabelView.resizeRowsToContents()self.tabelView.resizeColumnsToContents() ?# 創建主布局,并將hbox1,表格和hbox2裝入mainLayout = QVBoxLayout(self)mainLayout.addLayout(hbox1)mainLayout.addWidget(self.tabelView)mainLayout.addLayout(hbox2)self.setLayout(mainLayout) ?def setTableView(self):# 聲明查詢模型self.queryModel = QSqlQueryModel(self)# 設置當前頁self.currentPage = 1# 獲取總記錄數self.totalRecordCount = self.getTotalRecordCount()# 獲取總頁數self.totalPage = self.getPageCount()# 刷新狀態(也就是更新hbox2中各控件顯示的值)self.updateStatus()# 設置總頁數文本self.setTotalPageLabel()# 設置總記錄數self.setTotalRecordLabel() ?# 記錄查詢(0代表從第1條開始查),從第一條往后查詢6條self.recordQuery(0)# 將數據庫中的model和tabelView關聯# 這樣數據庫中的數據就可以直接顯示在tableView代表的表格上了self.tabelView.setModel(self.queryModel) ?# 得到記錄數def getTotalRecordCount(self):# 設置查詢模型,獲得一個查詢所有數據的查詢模型self.queryModel.setQuery("select * from student")# 通過模型的rowCount()方法得到數據條數rowCount = self.queryModel.rowCount()return rowCount ?# 得到頁數def getPageCount(self):# 實際得到的頁數應該是:總的記錄數 / 每頁能夠容納下的記錄條數,再向上取整# 下面就是通過判斷來達到向上取整 ?if self.totalRecordCount % self.PageRecordCount == 0:return int(self.totalRecordCount / self.PageRecordCount)else:return int(self.totalRecordCount / self.PageRecordCount + 1) ? ?# 記錄查詢函數(顯示limitIndex到limitIndex+5的6條數據)def recordQuery(self, limitIndex):# 查詢語句,從limitIndex開始往后查詢6條szQuery = ("select * from student limit %d,%d" % (limitIndex, self.PageRecordCount))# 對查詢模型使用set方法來設置模型self.queryModel.setQuery(szQuery) ?# 刷新狀態def updateStatus(self):szCurrentText = ("當前第%d頁" % self.currentPage)# 這是更新顯示當前頁數的labelself.currentPageLabel.setText(szCurrentText) ?# 設置按鈕是否可用# 如果當前在第一頁,那么“向前一頁”的按鈕不可用if self.currentPage == 1:self.prevButton.setEnabled(False)self.nextButton.setEnabled(True)# 如果當前在最后一頁,那么“向后一頁”的按鈕不可用elif self.currentPage == self.totalPage:self.prevButton.setEnabled(True)self.nextButton.setEnabled(False)# 如果當前在既不在第一頁,也不在最后一頁,那么兩個按鈕都可用else:self.prevButton.setEnabled(True)self.nextButton.setEnabled(True) ?# 設置顯示總頁數def setTotalPageLabel(self):szTotalPageText = ("共%d頁" % self.totalPage)self.totalPageLabel.setText(szTotalPageText) ?# 設置顯示總記錄數def setTotalRecordLabel(self):szTotalRecordText = ("共%d條" % self.totalRecordCount)self.totalRecordLabel.setText(szTotalRecordText) ?# 跳轉到前一頁的按鈕按下的槽函數def onPrevButton(self):# 計算上一頁第一條數據的索引# 注意我們的當前頁數是從1開始的,而數據索引是從0開始的limitIndex = (self.currentPage - 2) * self.PageRecordCount# 設置查詢模型(顯示上一頁的6條數據)self.recordQuery(limitIndex)# 記錄當前頁數的變量也需要變self.currentPage -= 1# 刷新狀態self.updateStatus() ?# 跳轉到后一頁的按鈕按下的槽函數def onNextButton(self):# 如法炮制limitIndex = self.currentPage * self.PageRecordCountself.recordQuery(limitIndex)self.currentPage += 1self.updateStatus() ?# 轉到指定頁面的按鈕按下的槽函數def onSwitchPageButtonClick(self):# 得到輸入的字符串szText = self.switchPageLineEdit.text() ?# 得到頁數pageIndex = int(szText)# 判斷是否有指定頁數if pageIndex > self.totalPage or pageIndex < 1:QMessageBox.information(self, "提示", "沒有指定的頁面,請重新輸入")return ?# 得到查詢起始行號limitIndex = (pageIndex - 1) * self.PageRecordCount ?# 設置查詢模型self.recordQuery(limitIndex)# 設置當前頁self.currentPage = pageIndex# 刷新狀態self.update() ? ? if __name__ == '__main__':app = QApplication(sys.argv)main = DataGrid()main.show()sys.exit(app.exec_())運行效果:
實戰項目:天氣信息查詢
最后我們完成一個查詢北京、天津、上海三地的天氣查詢的小GUI。
基本思路是先通過PyQt5搭建基本的布局和控件,然后為了獲取三地的天氣信息,我們需要使用Python中的Requests庫,這是一個超級輕量的python爬蟲庫,可以靜態爬取指定url的數據流(這不是重點啦),反正將爬取的數據setText()在我們的控件上就行了。至于怎么使用requests庫爬取,讀者可以搜索MOOC上北京理工大學的requests課程。
最后我們搭建主循環,在其中設置窗口的QSS樣式。
from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * import requests import sys ? ? class QueryWeather(QWidget):# 構造函數后面三個參數決定默認的圖像生成的寬度,高度和清晰度(每英寸像素點數)def __init__(self, parent=None):super(QueryWeather, self).__init__(parent)self.setWindowTitle("查詢城市天氣")# 設置固定的窗口大小self.setFixedSize(800, 600)# 設置透明度self.setWindowOpacity(0.98)# 城市編碼self.cityName2Code = {"北京":"101010100", "天津":"101030100", "上海":"101020100"} ?self.initUI() ?# 搭建窗口,你也可以根據自己的喜好搭建,這里只是參考def initUI(self):mainVbox = QVBoxLayout()vbox = QVBoxLayout()hbox1 = QHBoxLayout()hbox2 = QHBoxLayout()self.cityLabel = QLabel("城市")self.cityLabel.setAlignment(Qt.AlignCenter)self.cityLabel.setFixedSize(100, 50)self.cityComboBox = QComboBox()self.cityComboBox.setFixedSize(200, 35)self.cityComboBox.addItems(["北京","天津","上海"])self.weatherInfoText = QTextBrowser()self.weatherInfoText.setFixedSize(750, 300)self.weatherInfoText.setAlignment(Qt.AlignCenter)hbox1.addWidget(self.cityLabel)hbox1.addWidget(self.cityComboBox)hbox1.addWidget(QSplitter())hbox2.addWidget(self.weatherInfoText)vbox.addLayout(hbox1)vbox.addLayout(hbox2) ?buttonHBox = QHBoxLayout()self.queryButton = QPushButton("查詢")self.queryButton.setFixedSize(150, 40)self.queryButton.setProperty("name", "queryButton")self.clearButton = QPushButton("清空")self.clearButton.setFixedSize(150, 40)self.clearButton.setProperty("name", "clearButton")buttonHBox.addStretch(1)buttonHBox.addWidget(self.queryButton)buttonHBox.addStretch(1)buttonHBox.addWidget(self.clearButton)buttonHBox.addStretch(1) ?mainVbox.addLayout(vbox)mainVbox.addStretch(1)mainVbox.addLayout(buttonHBox) ?self.setLayout(mainVbox)self.queryButton.clicked.connect(self.queryWeather)self.clearButton.clicked.connect(self.clearWeather) ? ?def queryWeather(self):cityName = self.cityComboBox.currentText()# 獲取城市對應我們要爬取的頁面中的編碼cityCode = self.cityName2Code[cityName] ?# 爬取頁面內容,返回response對象(json數據)rep = requests.get("http://www.weather.com.cn/data/sk/" + cityCode + ".html")# 設置response對象的編碼rep.encoding = "utf-8"print(rep.json()) ?# 獲取json數據中的鍵值對,就是我們需要的信息msg1 = "城市: {}".format(rep.json()["weatherinfo"]["city"] + "n")msg2 = "風向: {}".format(rep.json()["weatherinfo"]["WD"] + "n")msg3 = "溫度: {}".format(rep.json()["weatherinfo"]["temp"] + "n")msg4 = "風力: {}".format(rep.json()["weatherinfo"]["WS"] + "n")msg5 = "濕度: {}".format(rep.json()["weatherinfo"]["SD"] + "n")result = msg1 + msg2 + msg3 + msg4 + msg5self.weatherInfoText.setText(result) ?def clearWeather(self):# 因為setText方法本來就是覆蓋寫,那我們將全文信息設置成一個空格就可以達到清空頁面信息的效果self.weatherInfoText.setText(" ") ? if __name__ == '__main__':app = QApplication(sys.argv)main = QueryWeather()# QSS樣式表字符串style = '''QLabel{font-size:30px;}QComboBox{font-size:30px;}QPushButton[name="queryButton"]{background-color:#938FD9;color:#2f4f4f;font-size:25px;}QPushButton[name="clearButton"]{background-color:#C0C0C0;font-size:25px;}QTextBrowser{font-size:40px;color:#00CED1;}'''main.setStyleSheet(style)main.show()sys.exit(app.exec_())運行效果:
總結
以上是生活随笔為你收集整理的pyqt5 自定义控件_PyQt5学习笔记(十六)Pyinstaller打包与SQLite数据库的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: rabbitmq 持久化_RabbitM
- 下一篇: linux内核怎么修改屏幕旋转方向_运维