PyQt笔记
PyQt5.15.4(python 32bit)连接mysql 64位数据库
-
下载 mysql-connector-c-6.1.6-win32.msi :版本太新的话,可能不含所需dll文件。
解压后的文件
F.lib.libmysql.dll
重命名为libmysql.dll
,放入python/Lib/site-packages/PyQt5/Qt5/bin文件夹中 -
下载 qsqlmysql.dll_Qt_SQL_driver_5.15.2_MSVC2019_32-bit.zip ,其他版本/位数的dll详见: https://github.com/thecodemonkey86/qt_mysql_driver/releases )
解压后sqldrivers中的文件
qsqlmysql.dll
与qsqlmysqld.dll
两份文件,放入python/Lib/site-packages/PyQt5/Qt5/plugins/sqldrivers文件夹中 -
Complete!
-
原因:每个PyQt版本都对应一个Qt版本,由于Qt在5.12.x更新后官方不提供mysql的dll,需要自行编译。
-
Pycharm Debug显示数据库driver无mysql,连接失败
设置-构建,部署,执行-Python调试器-P yQt兼容:自动改为Pyqt5
五分钟刷新一次表: https://stackoverflow.com/questions/60876325/python-pyqt5-table-with-sql-postgrees
QTableWidget
cellchanged/itemchanged 区别
ItemDelegate设置数字/空字符
要匹配数字或空字符串’’,即用户没有输入任何输入,请执行此操作
1
(^[0-9]+$|^$)
匹配数字、空字符串或空白字符
1
(^[0-9]+$|^$|^\s$)
designer.exe位置 -PyQt5与PySide2
1
2
3两个designer.exe的文件大小一致,使用起来应该没有区别
C:\Python37\Lib\site-packages\PySide2
C:\Python37\Lib\site-packages\qt5_applications\Qt\binPyInstaller打包PyQt应用程序
1
2
3
4-F, –onefile 打包成一个exe文件。
-D, –onedir 创建一个目录,包含exe文件,但会依赖很多文件(默认选项)
-c, –console, –nowindowed 使用控制台,无界面(默认) - 会弹出一个cmd界面
-w, –windowed, –noconsole 使用窗口,无控制台不同分辨率的显示器编译代码
- 不同分辨率的显示器编译PyQt程序,有的控件大小会变(在整个窗口是一种布局的情况下)
- Designer下icon->选择资源->选择图片,图片位置与.ui文件在同层目录,
- pyinstaller打包之后,放在dist文件夹下的一级目录,可正常显示
-
代码中运行
self.setWindowIcon(QIcon("Path"))
-
Table View中使用自定义的数据模型来显示表格的内容,通过
setModel
来绑定数据源1
2
3df = pd.read_csv('file_path')
model = DataFrameModel(df)
self.tv_badn.setModel(model) -
关于自定义的DataFrame数据模型(不会写,copy的),自定义的函数关注
data
与headerData
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77# 没有序号的DataFrame - TableModel
class DataFrameModel(QAbstractTableModel):
def __init__(self, data):
QAbstractTableModel.__init__(self)
self._data = data
# 行数
def rowCount(self, parent=None):
return self._data.shape[0]
# 列数
def columnCount(self, parent=None):
return self._data.shape[1]
# 数据项的字段
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return str(self._data.iloc[index.row(), index.column()])
return None
# 表头/行序号数据?
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[col]
return None
# 行序号从0开始的DataFrame - TableModel
class DataFrameModel(QAbstractTableModel):
def __init__(self, data):
QAbstractTableModel.__init__(self)
self._data = data
def rowCount(self, parent=None):
return self._data.shape[0]
def columnCount(self, parent=None):
return self._data.shape[1]
def data(self, index, role=Qt.DisplayRole):
string_role = str(self._data.iloc[index.row(), index.column()]).strip()
if index.isValid():
if role == Qt.ForegroundRole:
if pd.isna(self._data.iloc[index.row(), index.column()]):
return QColor("red")
elif string_role == 'FAIL':
return QColor("red")
elif str(self._data.iloc[index.row(), index.column()]).strip() == 'PASS':
return QColor("Green")
if role == Qt.DisplayRole:
return str(self._data.iloc[index.row(), index.column()])
return None
def headerData(self, section, orientation, role):
if role != Qt.DisplayRole:
return QVariant()
if orientation == Qt.Horizontal:
try:
return self._data.columns.tolist()[section]
except(IndexError, ):
return QVariant()
elif orientation == Qt.Vertical:
try:
return self._data.index.tolist()[section]
except(IndexError, ):
return QVariant()
return None
# 序号从1开始的headerData函数
def headerData(self, section: int, orientation, role: int) -> str:
# header data (first row and first column)
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self._data.columns[section])
if orientation == Qt.Vertical:
if isinstance(self._data.index[section], str):
return self._data.index[section]
else:
return str(self._data.index[section] + 1) -
Table Widget是Table View的子类,只能使用标准的数据模型(
QStandardItemModel
?),单元格数据通过Table Widget Item对象来实现。 -
与Table View最大的不同除了数据模型固定以外,可以直接在表格视图中进行数据的修改
-
Table Widget的水平或垂直表头可以直接在designer中设定,也可以使用函数设定,在设定前,需要先初始化行号与列号
1
2
3
4tableWidget.setRowCount(3)
tableWidget.setColumnCount(3)
tableWidget.setHorizontalHeaderLabels(['姓名','性别','体重(kg)'])
tableWidget.setVerticalHeaderLabels(row_list) -
Designer中的
editTriggers
中可设定表格的编辑方式,可勾选NoEditTriggers
使其为禁止编辑的只读状态。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28# 表头为自适应的伸缩模式,可以根据窗口大小来改变网格大小
tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
# 将表格变为禁止编辑
tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
# 设置表格为整行选择
tableWidget.setSelectionBehavior( QAbstractItemView.SelectRows)
#表格表头的显示与隐藏
tableWidget.verticalHeader().setVisible(False)
tableWidget.horizontalHeader().setVisible(True)
# 将行和列的大小设为与内容相匹配(取决于内容的长短/大小)
tableWidget.resizeColumnsToContents()
tableWidget.resizeRowsToContents()
# Table Widget的单元格中还可以放ComboBox(下拉选框)与按钮等控件
comBox = QComboBox()
comBox.addItem("男")
comBox.addItem("女")
comBox.setStyleSheet("QComboBox{margin:3px};")
tableWidget.setCellWidget(0,1,comBox)
searchBtn = QPushButton("修改")
searchBtn.setDown(True)
searchBtn.setStyleSheet("QPushButton{margin:3px};")
tableWidget.setCellWidget(0, 2, searchBtn)Line Edit
自动补全方式
1
2
3
4
5
6
7
8
9
10from PyQt5.QtWidgets import QCompleter
from PyQt5.QtCore import Qt
def init_completor(itme_list):
# itme_list 补全选项列表
completer = QCompleter(itme_list)
completer.setFilterMode(Qt.MatchContains) # 模糊匹配
completer.setCompletionMode(QCompleter.PopupCompletion) # 下拉框选项补全
completer.setCaseSensitivity(QCompleter.CaseInsensitive) # 大小写不敏感
return completer -
匹配方式:completer.setFilterMode(
MatchMode
)- Qt.MatchStartsWith 开头匹配
- Qt.MatchContains 内容匹配(模糊匹配)
- Qt.MatchEndsWith 结尾匹配
-
补全模式:completer.setCompletionMode(
CompletionMode
)- QCompleter.PopupCompletion 弹出下拉框选项补全
- QCompleter.InlineCompletion 行内显示补全 (建议开头匹配)
- QCompleter.UnfilteredPopupCompletion 全显示选项补全
-
大小写是否敏感:completer.setCaseSensitivity(
SensitivityMode
)- QCompleter.CaseInsensitive 大小写不敏感
- QCompleter.CaseSensitive 大小写敏感
- 两个下拉框实现级联:父级CurrentIndexChanged信号,关联子级Items刷新
- addItems([‘str1’, ‘str2’, ‘str3’])
- QComboBox在designer中可以设置为可输入(Edit)的样式,输入的内容如果和选项模糊匹配会补齐。
- 来源: CheckableComboBox
QCalendar
QDate
setDateRange() 设置日期范围选择 setMinimumDate() 设置最小日期 setMaximumDate() 设置最大日期 setSelectedDate 设置一个QDate对象,作为日期控件所选定的日期 setFirstDayOfWeek() 重新设置星期的第一天,默认是星期日,其参数枚举值[Qt.Monday, Qt.Tuesday, Qt.Wednesday, Qt.Thursday, Qt.Friday, Qt.Saturday, Qt.Sunday] selectedDate() 返回当前选定的日期 minimumDate() 获取日历控件的最小日期 maximumDate() 获取日历控件的最大日期 setGridvisible() 设置日历控件视图是否有网格线 1
2date = self.cal.selectedDate()
date.to_string('YYYY-mm-dd')QComboBox 下拉框
复选框:自定义控件,ComboCheckBox
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120"""
Check Combo Box
---------------
A QComboBox subclass designed for multiple item selection.
The combo box popup allows the user to check/uncheck multiple items at
once.
"""
import sys
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtGui import QPalette, QFontMetrics, QStandardItem
from PyQt5.QtWidgets import QComboBox, QStyledItemDelegate, QApplication, qApp, QMainWindow, QWidget
class CheckableComboBox(QComboBox):
# Subclass Delegate to increase item height
class Delegate(QStyledItemDelegate):
def sizeHint(self, option, index):
size = super().sizeHint(option, index)
size.setHeight(20)
return size
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make the combo editable to set a custom text, but readonly
self.setEditable(True)
self.lineEdit().setReadOnly(True)
# Make the lineedit the same color as QPushButton
palette = qApp.palette()
palette.setBrush(QPalette.Base, palette.button())
self.lineEdit().setPalette(palette)
# Use custom delegate
self.setItemDelegate(CheckableComboBox.Delegate())
# Update the text when an item is toggled
self.model().dataChanged.connect(self.updateText)
# Hide and show popup when clicking the line edit
self.lineEdit().installEventFilter(self)
self.closeOnLineEditClick = False
# Prevent popup from closing when clicking on an item
self.view().viewport().installEventFilter(self)
def resizeEvent(self, event):
# Recompute text to elide as needed
self.updateText()
super().resizeEvent(event)
def eventFilter(self, object, event):
if object == self.lineEdit():
if event.type() == QEvent.MouseButtonRelease:
if self.closeOnLineEditClick:
self.hidePopup()
else:
self.showPopup()
return True
return False
if object == self.view().viewport():
if event.type() == QEvent.MouseButtonRelease:
index = self.view().indexAt(event.pos())
item = self.model().item(index.row())
if item.checkState() == Qt.Checked:
item.setCheckState(Qt.Unchecked)
else:
item.setCheckState(Qt.Checked)
return True
return False
def showPopup(self):
super().showPopup()
# When the popup is displayed, a click on the lineedit should close it
self.closeOnLineEditClick = True
def hidePopup(self):
super().hidePopup()
# Used to prevent immediate reopening when clicking on the lineEdit
self.startTimer(100)
# Refresh the display text when closing
self.updateText()
def timerEvent(self, event):
# After timeout, kill timer, and reenable click on line edit
self.killTimer(event.timerId())
self.closeOnLineEditClick = False
def updateText(self):
texts = []
for i in range(self.model().rowCount()):
if self.model().item(i).checkState() == Qt.Checked:
texts.append(self.model().item(i).text())
text = ", ".join(texts)
# Compute elided text (with "...")
metrics = QFontMetrics(self.lineEdit().font())
elidedText = metrics.elidedText(text, Qt.ElideRight, self.lineEdit().width())
self.lineEdit().setText(elidedText)
def addItem(self, text, data=None):
item = QStandardItem()
item.setText(text)
if data is None:
item.setData(text)
if __name__ == "__main__":
# Example
app = QApplication(sys.argv)
dialog = QMainWindow()
mainWidget = QWidget()
dialog.setCentralWidget(mainWidget)
cb = CheckableComboBox(mainWidget)
items = ['123','456']
cb.addItems(items)
dialog.show()
sys.exit(app.exec_())QSpinBox 数字写入
QMessageBox设置弹窗信息:Open File,格式化超链接的绝对路径
setMinimum() / setMaximum() 设置计数器的max min边界值 singleStep() 设置计数器的步长值 setRange() 设置计数器的max min边界值+步长值 setValue() / Value() 设置/获取计数器的当前值 1
2
3
4
5
6
7
8'file:///F:/CSVEdit/test_data/CSV%E5%90%88%E5%B9%B6/001-1%E6%B1%87%E6%80%BB.csv'
os.path.normpath(self.tgt_path) 'F:\\CSVEdit\\test_data\\CSV合并\\001-1汇总.csv'
self.tgt_path 'F:/CSVEdit/test_data/CSV合并/001-1汇总.csv'
os.path.normpath(self.tgt_path)
file_link = bytearray(QUrl.fromLocalFile(os.path.normpath(self.tgt_path)).toEncoded()).decode()
QMessageBox.information(self, "Message", "合并完成!<a href='%s'>Open</a>" % file_link, QMessageBox.Ok, QMessageBox.Ok)Designer动态加载 自定义控件
1
2
3
4
5
6if __name__ == '__main__':
QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling # 加上这行代码即可规避分辨率导致的问题
app = QApplication(sys.argv)
win = myWin()
win.show()
sys.exit(app.exec())QDialog / QMainWindow 设置窗口和Win系统下的任务栏图标
Table View/Widget 设置列宽模式
宽/高分配模式
1
2
3
4
5
6
7
8
9
10# 列宽自动分配
self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
# 行高自动分配
self.tableWidget.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
# 表格适应内容指定列
self.tableWidget.resizeColumnToContents(column)
# 手动调整
self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
# 固定值:用户无法调整该部分的大小,只能使用resizeSection()以编程方式调整大小,默认为defaultSectionSize。
self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Fixed)、样式设置示例
1
2
3
4
5
6
7
8
9
10
11
12
13# 随内容分配列宽
self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.tableWidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
# 随内容分配行高
self.tableWidget.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.tableWidget.verticalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
# 水平方向标签拓展剩下的窗口部分,填满表格
self.tableWidget.horizontalHeader().setStretchLastSection(True)
# 列宽整个表格为自动分配的,但可单独设置第一列为手动调整宽度
self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.tableWidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.Interactive)
# 自定义列宽
self.tableWidget.setColumnWidth(0, 200)Table View[可绑定数据库模型]
TableView自定义数据模型 - dataframe
TableView设置委托:设置单元格不可编辑/输入类型卡控
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class EmptyDelegate(QItemDelegate):
def __init__(self, parent):
super(EmptyDelegate, self).__init__(parent)
def createEditor(self, parent: QWidget, option: 'QStyleOptionViewItem', index: QModelIndex):
# 设置委托不返回编辑器 即不可编辑
return None
class IntEditDelegate(QItemDelegate):
def __init__(self, parent):
super().__init__(parent)
def createEditor(self, parent, option, index):
# 设置委托返回QLineEdit编辑器 且设置数据类型为整数
editor = QLineEdit(parent)
editor.setValidator(QIntValidator())
return editor
# 委托的使用:设置第5、6列为整数输入,其余不可编辑。(TableView的列序号从0开始)
self.tableView.setItemDelegate(EmptyDelegate(self))
self.tableView.setItemDelegateForColumn(4, IntEditDelegate(self))
self.tableView.setItemDelegateForColumn(5, IntEditDelegate(self))Sqlite3数据库连接、查询与显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class EmptyDelegate(QItemDelegate):
def __init__(self, parent):
super(EmptyDelegate, self).__init__(parent)
def createEditor(self, parent: QWidget, option: 'QStyleOptionViewItem', index: QModelIndex):
# 设置委托不返回编辑器 即不可编辑
return None
class IntEditDelegate(QItemDelegate):
def __init__(self, parent):
super().__init__(parent)
def createEditor(self, parent, option, index):
# 设置委托返回QLineEdit编辑器 且设置数据类型为整数
editor = QLineEdit(parent)
editor.setValidator(QIntValidator())
return editor
# 委托的使用:设置第5、6列为整数输入,其余不可编辑。(TableView的列序号从0开始)
self.tableView.setItemDelegate(EmptyDelegate(self))
self.tableView.setItemDelegateForColumn(4, IntEditDelegate(self))
self.tableView.setItemDelegateForColumn(5, IntEditDelegate(self))Table Widget[可编辑/识别item字段]