在PyQt中设置的如下的窗口:
其中的图标是通过新建Resource File加入的
images里面的图片可以在这里面取:
链接:https://pan.baidu.com/s/1gOgBpW7s-ZWn_5aRoaYLkQ
提取码:jyjy
我们把这个文件取名为res.qrc
资源文件的使用可以看这里:http://t.csdn.cn/ba4X4
因为结构有些复杂,对于窗口的ui文件可以从这里自取
链接:https://pan.baidu.com/s/1NKFRtbxS_xE9eQq_bd_hnA
提取码:jyjy
1.建立如下数据库:
2.新建appMain文件
import sys from PyQt6.QtWidgets import QApplication from myMainWindow import QmyMainWindow app=QApplication(sys.argv)#构造GUI应用程序 mainform=QmyMainWindow() #创建主窗体 mainform.show() #显示主窗体 sys.exit(app.exec())
3.新建myMainWindow文件
其中def on_actOpenDB_triggered是通过添加槽函数得来的
点击后选择trigger信号
其他按钮操作类似,这里不再一一演示:
import sys from PyQt6.QtWidgets import (QApplication,QMainWindow, QMessageBox, QAbstractItemView, QDataWidgetMapper) from PyQt6.QtSql import QSqlDatabase, QSqlTableModel from PyQt6.QtCore import pyqtSlot,Qt, QItemSelectionModel, QModelIndex from Ui_MainWindow import Ui_MainWindow from myDelegates import QmyComboBoxDelegate class QmyMainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) #调用父类构造函数,创建窗体 self.ui=Ui_MainWindow() #创建UI对象 self.ui.setupUi(self) #构造UI界面 self.setCentralWidget(self.ui.splitter) #将splitter放在窗体中间 #限制选择时只能选择一行 self.ui.tableView.setSelectionBehavior(QAbstractItemView.SelectionBehavior. SelectItems) self.ui.tableView.setSelectionMode(QAbstractItemView.SelectionMode. SingleSelection) self.ui.tableView.setAlternatingRowColors(True) self.ui.tableView.verticalHeader().setDefaultSectionSize(22) self.ui.tableView.horizontalHeader().setDefaultSectionSize(100) @pyqtSlot() def on_actOpenDB_triggered(self): self.DB=QSqlDatabase.addDatabase("QODBC") self.DB.setDatabaseName("Driver={sql Server};Server=localhost;Database=pyqt;Uid=pyqt;Pwd=xxx") if self.DB.open():#打开数据库 self.__openTable()#打开数据表 else: QMessageBox.warning(self, "错误", "打开数据失败") #添加下拉框的数据 def __getFieldNames(self):##获取所有字段名称 emptyRec=self.tabModel.record()#获取空记录,只有字段名 self.fldNum={}#字段名与序号的字典 for i in range(emptyRec.count()): fieldName=emptyRec.fieldName(i) self.ui.comboFields.addItem(fieldName) self.fldNum.setdefault(fieldName) self.fldNum[fieldName]=i def __openTable(self): self.tabModel=QSqlTableModel(self, self.DB)#数据模型 self.tabModel.setTable("employee")#设置数据表 self.tabModel.setEditStrategy(QSqlTableModel.EditStrategy.OnManualSubmit) self.tabModel.setSort(self.tabModel.fieldIndex("EmoNo"), Qt.SortOrder.AscendingOrder) if(self.tabModel.select()==False):#查询数据失败 QMessageBox.critical(self, "错误信息", "打开数据库错误,错误信息\n"+self.tabModel.lastError().text()) return self.__getFieldNames()#获取字段名与序号 #将表列名变为自定义的列名,可以显示表头 self.tabModel.setHeaderData(0, Qt.Orientation.Horizontal, "工号") self.tabModel.setHeaderData(1, Qt.Orientation.Horizontal, "姓名") self.tabModel.setHeaderData(2, Qt.Orientation.Horizontal, "性别") self.tabModel.setHeaderData(3, Qt.Orientation.Horizontal, "出生日期") self.tabModel.setHeaderData(4, Qt.Orientation.Horizontal, "省份") self.tabModel.setHeaderData(5, Qt.Orientation.Horizontal, "部门") self.tabModel.setHeaderData(0, Qt.Orientation.Horizontal, "工资") self.tabModel.setHeaderData(0, Qt.Orientation.Horizontal, "备注") self.ui.tableView.setModel(self.tabModel)#设置数据模型 self.ui.tableView.resizeColumnsToContents()#重新调整列宽 #QDataWidgetMapper用于建立界面组件与数据模型的字段之间的数据映射。这样,我们选 #择一个tableView的行,才能在右边看到相关数据的信息。 #选择模型的作用是当用户在tableView上操作时,获取当前选择的行、列信息,并在选择的 #单元格变化时发射currentChanged信号,在当前行变化时发射currentRowChanqed()信号。 self.mapper =QDataWidgetMapper()#创建界面组件与数据模型的字段之间的数据映射 self.mapper.setModel(self.tabModel)#设置数据模型 self.mapper.setSubmitPolicy(QDataWidgetMapper.SubmitPolicy.AutoSubmit) self.mapper.addMapping(self.ui.dbspinEmpNo, 0) self.mapper.addMapping(self.ui.dbEditName, 1) self.mapper.addMapping(self.ui.dbComboDep, 2) self.mapper.addMapping(self.ui.dbEditBirth, 3) self.mapper.addMapping(self.ui.comboBoxProvince, 4) self.mapper.addMapping(self.ui.dbComboDep, 5) self.mapper.addMapping(self.ui.dbSpinSalary, 6) self.mapper.addMapping(self.ui.dbEditMemo, 7) self.mapper.toFirst()#移动首记录 self.selModel=QItemSelectionModel(self.tabModel)#选择模型 self.ui.tableView.setSelectionModel(self.selModel)#设置选择模型 self.selModel.currentChanged.connect(self.do_currentChanged)#当前项变化时触发 self.selModel.currentRowChanged.connect(self.do_currentRowChanged)#选择行变化时 strList=("男", "女") self.__delegateSex=QmyComboBoxDelegate() self.__delegateSex.setItems(strList, False) self.ui.tableView.setItemDelegateForColumn(self.fldNum["Gender"], self.__delegateSex) strList=("浙江","蒙古","陆西","吉林","广东","新疆") self.__delegateProvince=QmyComboBoxDelegate() self.__delegateProvince.setItems(strList,True) self.ui.tableView.setItemDelegateForColumn(self.fldNum["Province"], self.__delegateProvince) strList=("销售部", "技术部","生产部","行政部") self.__delegateDepart=QmyComboBoxDelegate() self.__delegateDepart.setItems(strList, True) self.ui.tableView.setItemDelegateForColumn(self.fldNum["Department"], self.__delegateDepart) #添加按钮状态 #数据表成功打开后,打开按钮失效,添加,插入,删除,涨工资相关按钮能按了 self.ui.actOpenDB.setEnabled(False) self.ui.actRecAppend.setEnabled(True) self.ui.actRecInsert.setEnabled(True) self.ui.actRecDelete.setEnabled(True) self.ui.actScan.setEnabled(True) #一开始,排序和数据过滤这两个groupBox里面的组件是不能选的。因为数据表没有打开, #没法数据排序和数据过滤。 #数据表一旦打开,这两个GroupBox应该enabled self.ui.groupBoxSort.setEnabled(True) self.ui.groupBoxFilter.setEnabled(True) def do_currentChanged(self, current, previous):#更新actPost和actCancel状态 self.ui.actSubmit.setEnabled(self.tabModel.isDirty())#有未保存修改时可用 self.ui.actRevert.setEnabled(self.tabModel.isDirty()) def do_currentRowChanged(self, current, previous):#行切换时状态控制 self.mapper.setCurrentIndex(current.row())#更新数据映射的行号 #点降序。注意不改变EmpNo,直接点降序那个radio Button是没反应的,因为不会触发#currentindexChanged信号。 #只有在排序字段先点了降序,然后修改了ComboBox的索引,比如改到Salary,然后选回 #EmpNo,再次点击降序,才会变化。可以理解一下。只有ComboBox里面的索引改变了, #才会触发槽函数。 @pyqtSlot(int)##排序字段变化 def on_comboFields_currentIndexChanged(self, index): if self.ui.radioBtnAscend.isChecked(): self.tabModel.setSort(index, Qt.SortOrder.AscendingOrder) else: self.tabModel.setSort(index, Qt.SortOrder.DescendingOrder) self.tabModel.select() @pyqtSlot() def on_radioBtnAscend_clicked(self):#升序 self.tabModel.setSort(self.ui.comboFields.currentIndex(), Qt.SortOrder.AscendingOrder) self.tabModel.select() @pyqtSlot() def on_radioBtnDescend_clicked(self):#降序 self.tabModel.setSort(self.ui.comboFields.currentIndex(), Qt.SortOrder.DescendingOrder) self.tabModel.select() @pyqtSlot() def on_radioBtnMan_clicked(self):#数据过滤,男 self.tabModel.setFilter("Gender='男'") @pyqtSlot() def on_radioBtnWoman_clicked(self):#数据过滤,女 self.tabModel.setFilter("Gender='女'") @pyqtSlot()#取消数据过滤 def on_radioBtnBoth_clicked(self): self.tabModel.setFilter("") @pyqtSlot() ##添加记录 def on_actRecAppend_triggered(self): self.tabModel.insertRow(self.tabModel.rowCount(), QModelIndex())#在末尾添加一个记录 curIndex=self.tabModel.index(self.tabModel.rowCount()-1, 1)#创建最后一行的ModelIndex self.selModel.clearSelection()#清空选择项 self.selModel.setCurrentIndex(curIndex,QItemSelectionModel.SelectionFlag.Select)#设置刚插入的行为当前选择行 currow=curIndex.row()#获得当前行 self.tabModel.setData(self.tabModel.index(currow,self.fldNum["EmpNo"]), 2000+self.tabModel.rowCount())#自动生成编号 self.tabModel.setData(self.tabModel.index(currow,self.fldNum["Gender"]),"男") @pyqtSlot()##插入记录 def on_actRecInsert_triggered(self): curIndex=self.ui.tableView.currentIndex() #QModelIndex self.tabModel.insertRow(curIndex.row(), QModelIndex()) self.selModel.clearSelection()#清楚已有选择 self.selModel.setCurrentIndex(curIndex, QItemSelectionModel.SelectionFlag.Select) currow=curIndex.row() #获得当前行 self.tabModel.setData(self.tabModel.index(currow,self.fldNum["EmpNo"]), 2000+self.tabModel.rowCount())#自动生成编号 self.tabModel.setData(self.tabModel.index(currow,self.fldNum["Gender"]),"男") @pyqtSlot() def on_actRecDelete_triggered(self):##删除记录 curIndex=self.selModel.currentIndex() self.tabModel.removeRow(curIndex.row()) @pyqtSlot()##涨工资,遍历数据表所有数据 def on_actScan_triggered(self): if(self.tabModel.rowCount()==0): return for i in range(self.tabModel.rowCount()): aRec=self.tabModel.record(i)#获取当前记录 salary=aRec.value("Salary") salary=salary*1.1 aRec.setValue("Salary", salary) self.tabModel.setRecord(i, aRec) @pyqtSlot()##edit策略需要手工submit def on_actSubmit_triggered(self): res=self.tabModel.submitAll() if(res==False): QMessageBox.information(self, "消息", "数据保存错误,错误信息\n"+self.tabModel.lastError().text()) else: self.ui.actSubmit.setEnabled(False) self.ui.actRevert.setEnabled(False) @pyqtSlot()##取消修改 def on_actRevert_triggered(self): self.tabModel.revertAll() self.ui.actSubmit.setEnabled(False) self.ui.actRevert.setEnabled(False) if(self.tabModel.submitAll()): QMessageBox.information(self, "消息", "涨工资计算完毕") if __name__=="__main__": #用于当前窗体测试 app=QApplication(sys.argv) #创建GUI应用程序 form=QmyMainWindow() #创建窗体 form.show() sys.exit(app.exec())
我们发现在数据库中将性别修改为不合理的X,可以成功保存
而右侧框,因为有下拉框限定,不会修改为X
怎么解决这个问题呢?
要对tableview里面数据的修改进行限制。
tableview默认的单元格编辑组件是QlineEdit,对输入的数据无法限制。可以为某列设置自定义代理组件,比如OcommoBox。在上面的bua中,希望把性别的编辑组件改成QcommoBox,只能选择某些项(比如男和女),而不能随便输入。
新建文件myDelegates.py,处理浮点数和下拉框的限定数据数据
注:每个自定义代理组件必须继承4个函数:
(1)createEditor:创建用于编辑模型数据的widget组件
(2) setEditData:从数据模型获取数据,供widget组件进行编辑
(3)setModelData:将widget上的数据更新到数据模型
(4)updateEditorGeometry:给widet组件设置合适的大小
在这个类中,用itemList来获取可以选的所有选项,比如男、女。然后限定只能使用这些选项。
from PyQt6.QtWidgets import QStyledItemDelegate,QDoubleSpinBox,QComboBox from PyQt6.QtCore import Qt ##======================基于QComboBox的代理组件==== class QmyComboBoxDelegate(QStyledItemDelegate): def __init__(self,parent=None): super().__init__(parent) self.__itemList=[] self.__isEditable=False def setItems(self, itemList, isEditable=False): self.__itemList=itemList self.__isEditable=isEditable #自定义代理组件必须继承一下4个函数 def createEditor(self, parent, option,index): editor=QComboBox(parent) editor.setFrame(False) editor.setEditable(self.__isEditable) editor.addItems(self.__itemList) return editor def setEditorData(self, editor, index): model=index.model() text=model.data(index, Qt.ItemDataRole.EditRole) editor.setCurrentText(text) def setModelData(self, editor,model, index): text=editor.currentText() model.setData(index, text, Qt.ItemDataRole.EditRole) def updateEditorGeometry(self,editor,option,index): editor.setGeometry(option.rect) ##====================基于QDoubleSpinbox的代理组件======= #在这个类中,定义了_min,_max,_decimals,然后限定范围只能在_min和_max之间 class QmyFloatSpinDelegate(QStyledItemDelegate): def __init__(self,minV=0, maxV=10000, digi=2, parent=None): super().__init__(parent) self. __min=minV self.__max=maxV self.__decimals=digi def createEditor(self, parent, option,index): editor=QDoubleSpinBox(parent) editor.setFrame(False) editor.setRange(self.__min, self.__max) editor.setDecimals(self.__decimals) return editor def setEditorData(self,editor,index): model=index.model()#关联的数据模型 text=model.data(index, Qt.EditRole)#单元格文字 editor.setValue(float(text)) def setModelData(self,editor,model,index): value=editor.value() model.setData(index,value, Qt.EditRole) def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect)
注:从myDelegates.py文件中,import QmyComboBoxDelegate 这个类。
在myMainWindow中添加如下代码,上面的myMainWindow已经添加了,是完整的代码!
strList=("男", "女") self.__delegateSex=QmyComboBoxDelegate() self.__delegateSex.setItems(strList, False) self.ui.tableView.setItemDelegateForColumn(self.fldNum["Gender"], self.__delegateSex) strList=("浙江","蒙古","陆西","吉林","广东","新疆") self.__delegateProvince=QmyComboBoxDelegate() self.__delegateProvince.setItems(strList,True) self.ui.tableView.setItemDelegateForColumn(self.fldNum["Province"], self.__delegateProvince) strList=("销售部", "技术部","生产部","行政部") self.__delegateDepart=QmyComboBoxDelegate() self.__delegateDepart.setItems(strList, True) self.ui.tableView.setItemDelegateForColumn(self.fldNum["Department"], self.__delegateDepart)
注:在Ui_MainWindow中需加入import res_rc
同时将res_rc.py中的
from PySide6 import QtCore
改为:from PyQt6 import QtCore
这样才能在运行窗口中显示图标