上一篇,我们已经搞定了学生信息的“展示”功能,看着表格里整齐的数据,是不是有点小成就感?

  但一个只能看、不能改的系统,是没有灵魂的。

  今天,我们来攻克最关键的一环: “新增”与“编辑” 。我们要点击按钮,弹出一个优雅的对话框,填入信息,然后丝滑地保存到数据库。如下图所示:

  Python GUI 实战:只需3步,让你的学生管理系统从“能用”变成“好用”

  今天,我们来攻克最关键的一环: “新增”与“编辑” 。我们要点击按钮,弹出一个优雅的对话框,填入信息,然后丝滑地保存到数据库。

  很多同学上手就想直接写两个窗口:一个用来“加”,一个用来“改”。 停!千万别这么干。

  写代码最忌讳的就是“体力活”。今天带你看看,如何用架构思维,优雅地解决这个问题。

01 顶级程序员的思维:找不同

  在动手写代码前,我们先做个“找不同”的游戏。

  仔细看,“新增学生”和“编辑学生”这两个界面,区别在哪?

  你会发现: 它们长得一模一样! 唯一的区别只是:

  新增时 ,表单是空的,等你填。

  编辑时 ,表单里填好了数据,等你改。

  Python GUI 实战:只需3步,让你的学生管理系统从“能用”变成“好用”

  Python GUI 实战:只需3步,让你的学生管理系统从“能用”变成“好用”

  既然长相一样,我们为什么要写两份代码?

  这时候,面向对象编程里最性感的一个概念出场了—— 继承(Inheritance) 。

  我们可以造一个“父类”(基类),把它当成一个通用的模具。以后无论是“新增”还是“编辑”,都直接复用这个模具。

  少写代码,多做复用,这就是高手的“偷懒”艺术。

  在开发过程中,为了简化代码并提高复用性,我们可以通过创建一个GUI基类来实现相同功能的不同窗口逻辑复用。具体而言,可以为添加和修改操作分别创建类,这些类继承自同一个基类或父类,从而实现代码的复用与维护上的便利性。

02 造一个高颜值的“模具”

  在学生管理的场景中,我们可以创建一个基类,针对“添加”操作实现空表单,针对“修改”操作从数据库中获取当前用户信息并填充表单。首先,我们在 student 模块中创建一个新文件,命名为 student_dialog 。目录结构如下图所示:

  Python GUI 实战:只需3步,让你的学生管理系统从“能用”变成“好用”

  在这里,我们以 Fluent UI 中提供的弹窗效果为参考,使用类似的样式进行实现。在 Fluent UI 的文档中,可以找到一个名为 dialog_flyout/custom_message_box 文件夹下的自定义弹窗示例,其弹窗效果包含输入框,这个自定义弹窗类继承是 MessageBoxBase 这个父类,我们将基于这个示例编写自己的窗口样式,如下图所示:

  Python GUI 实战:只需3步,让你的学生管理系统从“能用”变成“好用”

  接下来我们在 student/student_dialog.py 文件中创建一个名为 BaseStudentDialog 的类,并让它继承自 MessageBoxBase 。需要对 MessageBoxBase 模块进行必要的导入。 在类中定义初始化方法 __init__ ,接收 title parent 两个参数,默认 parent=None 。通过调用父类的初始化方法,完成基础设置。此外, title 参数用于区分窗口的用途,例如“添加学生信息”或“修改学生信息”,代码如下:

  # student/student_dialog.py 文件中fromqfluentwidgetsimportMessageBoxBase # 导入 qfluentwidgets 模块中的 MessageBoxBase 作为弹窗的基类classBaseStudentDialog(MessageBoxBase): # 定义一个学生信息弹窗的基类,继承自 MessageBoxBasedef__init__(self, title, parent=None):super.__init__(parent) # 调用父类的初始化方法,设置父窗口self.title = title # 保存弹窗标题,用于区分弹窗用途(如添加或修改学生信息)self.setup_ui # 调用界面设置方法,初始化弹窗界面

  编写一个 setup_ui 函数,用于定义窗口界面。我们采用 viewLayout 作为主布局,这是 MessageBoxBase 提供的默认布局,包含 OK Cancel 按钮,无需重复创建。我们使用栅格布局( QGridLayout )绘制窗口表单。栅格布局适用于对齐内容。

创建栅格布局

  创建一个名为 grid_layout 的栅格布局实例,并将其添加到主布局 viewLayout 中。

   defsetup_ui(self): # 定义设置用户界面的方法grid_layout = QGridLayout # 创建一个栅格布局对象,用于对控件进行行列排布self.viewLayout.addLayout(grid_layout) # 将栅格布局添加到主视图布局中定义表单字段

  需要定义表单中的各项字段,包括姓名、学号、性别、班级以及语文、数学、英语成绩。每个字段的输入组件如下:

  姓名 :单行文本输入框,使用 QLineEdit

  学号 :单行文本输入框,同样使用 QLineEdit

  性别 :下拉菜单,使用 QComboBox ,添加“男”和“女”两个选项。

  班级 :下拉菜单,与性别类似。

  三科成绩 :单行文本输入框,分别定义语文、数学和英语成绩输入。

  示例代码如下:

   self.nameInput = LineEdit(self) # 创建一个单行文本输入框用于输入姓名self.numberInput = LineEdit(self) # 创建一个单行文本输入框用于输入学号self.genderCombo = ComboBox(self) # 创建一个下拉框组件用于选择性别self.genderCombo.addItems(['男', '女']) # 为下拉框添加两个选项:男和女self.classCombo = ComboBox(self) # 创建一个下拉框组件用于选择班级self.chineseInput = LineEdit(self) # 创建一个单行文本输入框用于输入语文成绩self.mathInput = LineEdit(self) # 创建一个单行文本输入框用于输入数学成绩self.englishInput = LineEdit(self) # 创建一个单行文本输入框用于输入英语成绩添加字段到网格布局

  为简化布局,我们可以通过字典形式管理字段,并一一添加到栅格布局中。为了让弹窗的界面更加美观,我们使用了 QFluent 中的 StrongBodyLabel 样式来修饰文本标签。这一样式能够实现加粗效果,使界面更直观,代码如下:

  # 定义字段标签与控件的映射fields = [("姓名:", self.nameInput), # 姓名字段与对应的输入框("学号:", self.numberInput), # 学号字段与对应的输入框("性别:", self.genderCombo), # 性别字段与对应的下拉框("班级:", self.classCombo), # 班级字段与对应的下拉框("语文:", self.chineseInput), # 语文字段与对应的输入框("数学:", self.mathInput), # 数学字段与对应的输入框("英语:", self.englishInput) # 英语字段与对应的输入框]# 遍历字段,添加到栅格布局中forrow, (label_text, widget)inenumerate(fields): # 使用 enumerate 获取字段的行号label = StrongBodyLabel(label_text, self) # 创建加粗的标签grid_layout.addWidget(label, row,0) # 将标签添加到栅格布局的第一列grid_layout.addWidget(widget, row,1) # 将控件添加到栅格布局的第二列布局的优化和对齐方式

  为了保证界面在弹窗尺寸变化时保持整齐,我们设置了栅格的列拉伸属性,使其可以根据弹窗大小动态调整。我们还将所有字段设置为左对齐,确保布局一致:

   # 设置列拉伸grid_layout.setColumnStretch(1,1) # 设置第二列的列拉伸系数为 1,确保控件能够自动适应窗口大小# 设置标签对齐方式grid_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) # 将控件对齐到左侧确认和取消按钮的文本修改

  弹窗中的按钮通常会有默认文本(如“OK”和“Cancel”),我们根据需求对其文本内容进行了自定义:

   # 修改按钮文本self.yesButton.setText('确定') # 将确认按钮文本设置为“确定”self.cancelButton.setText('取消') # 将取消按钮文本设置为“取消”

  在 student/student_dialog.py 文件中界面布局整体代码如下:

  # student/student_dialog.py文件中fromPyQt6.QtCoreimportQt # 导入 PyQt6 模块中的 Qt 类,用于对齐方式等常量fromPyQt6.QtWidgetsimportQGridLayout # 导入 PyQt6 模块中的 QGridLayout,用于栅格布局fromqfluentwidgetsimportMessageBoxBase, ComboBox, LineEdit, StrongBodyLabel # 导入 qfluentwidgets 模块中的组件classBaseStudentDialog(MessageBoxBase): # 定义一个学生信息弹窗的基类,继承自 MessageBoxBasedef__init__(self, title, parent=None): # 初始化方法,接收弹窗标题和父窗口作为参数super.__init__(parent) # 调用父类的初始化方法,设置父窗口self.title = title # 保存弹窗标题,用于区分弹窗用途(如添加或修改学生信息)self.setup_ui # 调用界面设置方法,初始化弹窗界面defsetup_ui(self): # 定义设置用户界面的方法grid_layout = QGridLayout # 创建一个栅格布局对象,用于对控件进行行列排布self.viewLayout.addLayout(grid_layout) # 将栅格布局添加到主视图布局中# 创建输入控件self.nameInput = LineEdit(self) # 创建一个单行文本输入框用于输入姓名self.numberInput = LineEdit(self) # 创建一个单行文本输入框用于输入学号self.genderCombo = ComboBox(self) # 创建一个下拉框组件用于选择性别self.genderCombo.addItems(['男', '女']) # 为下拉框添加两个选项:男和女self.classCombo = ComboBox(self) # 创建一个下拉框组件用于选择班级self.chineseInput = LineEdit(self) # 创建一个单行文本输入框用于输入语文成绩self.mathInput = LineEdit(self) # 创建一个单行文本输入框用于输入数学成绩self.englishInput = LineEdit(self) # 创建一个单行文本输入框用于输入英语成绩# 定义字段标签与控件的映射fields = [("姓名:", self.nameInput), # 姓名字段与对应的输入框("学号:", self.numberInput), # 学号字段与对应的输入框("性别:", self.genderCombo), # 性别字段与对应的下拉框("班级:", self.classCombo), # 班级字段与对应的下拉框("语文:", self.chineseInput), # 语文字段与对应的输入框("数学:", self.mathInput), # 数学字段与对应的输入框("英语:", self.englishInput) # 英语字段与对应的输入框]# 遍历字段,添加到栅格布局中forrow, (label_text, widget)inenumerate(fields): # 使用 enumerate 获取字段的行号label = StrongBodyLabel(label_text, self) # 创建加粗的标签grid_layout.addWidget(label, row,0) # 将标签添加到栅格布局的第一列grid_layout.addWidget(widget, row,1) # 将控件添加到栅格布局的第二列# 设置列拉伸grid_layout.setColumnStretch(1,1) # 设置第二列的列拉伸系数为 1,确保控件能够自动适应窗口大小# 设置标签对齐方式grid_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) # 将控件对齐到左侧# 修改按钮文本self.yesButton.setText('确定') # 将确认按钮文本设置为“确定”self.cancelButton.setText('取消') # 将取消按钮文本设置为“取消”03 赋予灵魂:从“展示”到“交互”

  接下来我们创建添加和编辑功能的类:为“添加学生”功能创建了一个 AddStudentDialog 类,该类继承自基础弹窗类 BaseStudentDialog 。在这个类中,我们重写了父类的一些方法,并根据具体需求设置标题和字段内容:

  # student/student_dialog.py文件中fromPyQt6.QtCoreimportQt # 导入 PyQt6 模块中的 Qt 类,用于对齐方式等常量fromPyQt6.QtWidgetsimportQGridLayout # 导入 PyQt6 模块中的 QGridLayout,用于栅格布局fromqfluentwidgetsimportMessageBoxBase, ComboBox, LineEdit, StrongBodyLabel # 导入 qfluentwidgets 模块中的组件classBaseStudentDialog(MessageBoxBase): # 定义一个学生信息弹窗的基类,继承自 MessageBoxBasedef__init__(self, title, parent=None): # 初始化方法,接收弹窗标题和父窗口作为参数super.__init__(parent) # 调用父类的初始化方法,设置父窗口self.title = title # 保存弹窗标题,用于区分弹窗用途(如添加或修改学生信息)self.setup_ui # 调用界面设置方法,初始化弹窗界面defsetup_ui(self): # 定义设置用户界面的方法grid_layout = QGridLayout # 创建一个栅格布局对象,用于对控件进行行列排布self.viewLayout.addLayout(grid_layout) # 将栅格布局添加到主视图布局中# 创建输入控件self.nameInput = LineEdit(self) # 创建一个单行文本输入框用于输入姓名self.numberInput = LineEdit(self) # 创建一个单行文本输入框用于输入学号self.genderCombo = ComboBox(self) # 创建一个下拉框组件用于选择性别self.genderCombo.addItems(['男', '女']) # 为下拉框添加两个选项:男和女self.classCombo = ComboBox(self) # 创建一个下拉框组件用于选择班级self.chineseInput = LineEdit(self) # 创建一个单行文本输入框用于输入语文成绩self.mathInput = LineEdit(self) # 创建一个单行文本输入框用于输入数学成绩self.englishInput = LineEdit(self) # 创建一个单行文本输入框用于输入英语成绩# 定义字段标签与控件的映射fields = [("姓名:", self.nameInput), # 姓名字段与对应的输入框("学号:", self.numberInput), # 学号字段与对应的输入框("性别:", self.genderCombo), # 性别字段与对应的下拉框("班级:", self.classCombo), # 班级字段与对应的下拉框("语文:", self.chineseInput), # 语文字段与对应的输入框("数学:", self.mathInput), # 数学字段与对应的输入框("英语:", self.englishInput) # 英语字段与对应的输入框]# 遍历字段,添加到栅格布局中forrow, (label_text, widget)inenumerate(fields): # 使用 enumerate 获取字段的行号label = StrongBodyLabel(label_text, self) # 创建加粗的标签grid_layout.addWidget(label, row,0) # 将标签添加到栅格布局的第一列grid_layout.addWidget(widget, row,1) # 将控件添加到栅格布局的第二列# 设置列拉伸grid_layout.setColumnStretch(1,1) # 设置第二列的列拉伸系数为 1,确保控件能够自动适应窗口大小# 设置标签对齐方式grid_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) # 将控件对齐到左侧# 修改按钮文本self.yesButton.setText('确定') # 将确认按钮文本设置为“确定”self.cancelButton.setText('取消') # 将取消按钮文本设置为“取消”classAddStudentDialog(BaseStudentDialog): # 定义一个用于添加学生的弹窗类,继承自 BaseStudentDialogdef__init__(self, parent=None): # 初始化方法,接收父窗口作为参数,默认为 Nonesuper.__init__('添加学生', parent) # 调用父类的初始化方法,设置弹窗标题为“添加学生”,并传递父窗口self.student_id =None# 初始化学生 ID 属性,默认为 None,表示新建学生时不需要指定 IDself.yesButton.setText('添加') # 设置确认按钮的文本为“添加”,以明确功能04 创建添加学生方法

  接下来,我们将对代码进行改进。在点击“添加”按钮时,我们需要在 student_interface.py 文件中将其与一个事件关联,代码如下:

  self.addButton.clicked.connect(self.add_student)# 将“添加”按钮的点击事件与 add_student 方法连接

  接下来我们需要要在 student_interface.py 文件中创建一个 add_student 方法。在这个方法内,我们首先定义了 add_student ,我们创建一个变量 dialog ,并判断 dialog.exec 的执行结果。如果执行成功,则打印:用户点击了确定按钮,在弹出框中,如果用户点击了取消按钮,我们会提示用户已点击取消。代码如下:

  # student_interface.py 文件importsys # 导入 sys 模块,用于处理系统相关的功能fromPyQt6.QtCoreimportQt # 导入 Qt,用于设置对齐方式# 从 PyQt6.QtWidgets 模块中导入 QWidget、QVBoxLayout、QHBoxLayout、QApplication 和 QHeaderView、QCheckBox、QTableWidgetItem# 分别用于创建窗口部件、垂直布局、水平布局、应用程序、表格头部视图、复选框和表格项fromPyQt6.QtWidgetsimportQWidget, QVBoxLayout, QHBoxLayout, QApplication, QHeaderView, QCheckBox, QTableWidgetItem# 从 qfluentwidgets 库中导入 CardWidget、PushButton、SearchLineEdit、TableWidget、setCustomStyleSheet# 用于创建卡片样式的组件、按钮、搜索输入框、表格组件以及设置自定义样式fromqfluentwidgetsimportCardWidget, PushButton, SearchLineEdit, TableWidget, setCustomStyleSheet# 从自定义模块 database.student_db 中导入 StudentDB 类,用于操作学生相关的数据库数据fromdatabase.student_dbimportStudentDB# 从 student.student_dialog 模块导入 AddStudentDialog 类fromstudent.student_dialogimportAddStudentDialog# 导入自定义样式,用于“新增”和“批量删除”按钮fromutils.custom_styleimportADD_BUTTON_STYLE, BATCH_DELETE_BUTTON_STYLE# 定义 StudentInterface 类,继承自 QWidgetclassStudentInterface(QWidget):def__init__(self): # 初始化方法super.__init__ # 调用父类 QWidget 的初始化方法self.setObjectName("studentInterface") # 设置当前界面的对象名称self.students = # 用于存储学生数据的列表self.setup_ui # 调用 setup_ui 方法,配置界面布局和组件self.load_data # 调用 load_data 方法,加载学生数据self.populate_table # 调用 populate_table 方法,填充表格defsetup_ui(self): # 定义 setup_ui 方法,用于设置界面的布局和组件layout = QVBoxLayout(self) # 创建垂直布局,并将其设置为当前窗口的主布局# 顶部按钮组card_widget = CardWidget(self) # 创建卡片样式的组件作为按钮组容器,并设置父组件为当前窗口buttons_layout = QHBoxLayout(card_widget) # 创建水平布局,用于放置按钮,并将其设置为 card_widget 的布局self.addButton = PushButton("新增", self) # 创建一个“新增”按钮,并将其设置为当前窗口的子组件setCustomStyleSheet(self.addButton, ADD_BUTTON_STYLE, ADD_BUTTON_STYLE) # 应用自定义样式self.addButton.clicked.connect(self.add_student) # 将“添加”按钮的点击事件与 add_student 方法连接# 创建搜索输入框self.searchInput = SearchLineEdit(self) # 创建一个搜索输入框,并将其设置为当前窗口的子组件self.searchInput.setPlaceholderText("搜索学生姓名或学号...") # 设置占位提示文字self.searchInput.setFixedWidth(500) # 设置输入框固定宽度为 500# 创建“批量删除”按钮并应用自定义样式self.batchDeleteButton = PushButton("批量删除", self)setCustomStyleSheet(self.batchDeleteButton, BATCH_DELETE_BUTTON_STYLE, BATCH_DELETE_BUTTON_STYLE)# 将按钮和搜索框添加到按钮组的水平布局中buttons_layout.addWidget(self.addButton) # 添加“新增”按钮buttons_layout.addWidget(self.searchInput) # 添加搜索输入框buttons_layout.addStretch(1) # 添加弹性空间,将按钮推向两端buttons_layout.addWidget(self.batchDeleteButton) # 添加“批量删除”按钮# 将按钮组的卡片组件添加到主布局中layout.addWidget(card_widget)# 表格组件(创建空表格,后续填充内容)self.table_widget = TableWidget(self)self.table_widget.setBorderVisible(True) # 设置表格边框可见self.table_widget.setBorderRadius(8) # 设置表格圆角self.table_widget.setWordWrap(False) # 禁用自动换行# 设置表头填充模式和列数self.table_widget.horizontalHeader.setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # 表头填充模式为 Stretch,使表头填满整个表格,并实现列宽自适应屏幕缩放的效果self.table_widget.setColumnCount(11) # 设置表格列数为 11 列self.table_widget.setHorizontalHeaderLabels(["", "学生ID", "姓名", "学号", "性别", "班级", "语文", "数学", "英语", "总分", "操作"]) # 设置表头名称# 将表格组件添加到主布局中layout.addWidget(self.table_widget)self.setStyleSheet("StudentInterface{background: white}") # 设置界面背景为白色self.resize(1280,760) # 设置窗口大小为 1280x760defload_data(self): # 定义 load_data 方法,用于加载学生数据withStudentDBasdb:self.students = db.fetch_students # 调用 fetch_students 方法从数据库中获取学生数据,并将结果赋值给 self.studentsdefpopulate_table(self): # 定义 populate_table 方法,用于填充表格数据self.table_widget.setRowCount(len(self.students)) # 设置表格行数为学生数据的数量forrow, student_infoinenumerate(self.students): # 遍历学生数据并设置每行的内容self.setup_table_row(row, student_info) # 调用 setup_table_row 方法,设置每一行的数据defsetup_table_row(self, row, student_info): # 定义 setup_table_row 方法,用于设置表格行数据checkbox = QCheckBox # 创建复选框self.table_widget.setCellWidget(row,0, checkbox) # 将复选框添加到表格的第一列# 遍历学生数据的各个字段并添加到表格中forcol, keyinenumerate(['student_id', 'student_name', 'student_number', 'gender', 'class_name', 'chinese_score', 'math_score','english_score', 'total_score']):value = student_info.get(key, '') # 获取对应字段的值ifkey == 'gender': # 若字段为性别,进行性别的转换显示value = '男'ifvalue ==1else'女'ifvalue ==2else'未知'item = QTableWidgetItem(str(value)) # 创建表格项item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) # 设置文本居中self.table_widget.setItem(row, col +1, item) # 将表格项添加到表格中对应的列defadd_student(self): # 添加学生信息方法dialog = AddStudentDialog(self) # 创建一个 AddStudentDialog 对话框实例,并将当前窗口作为父窗口传递ifdialog.exec: # 执行对话框,弹出窗口等待用户操作print("用户点击了确定,处理添加学生逻辑") # 如果用户点击了确定按钮,执行此代码else:print("用户取消了操作") # 如果用户点击了取消按钮,执行此代码# 主程序入口if__name__ == '__main__':app = QApplication(sys.argv) # 创建应用程序实例window = StudentInterface # 创建 StudentInterface 窗口实例window.show # 显示窗口sys.exit(app.exec) # 进入应用程序主循环,并在退出时返回状态码

  运行程序如下图所示:

  Python GUI 实战:只需3步,让你的学生管理系统从“能用”变成“好用”05 细节决定成败:从“能用”到“好用”

  很多程序员的工作到上面那一步就结束了。 但产品经理会拿着刀走过来告诉你:

      窗口怎么这么细长?像根面条。

      标题呢?用户怎么知道他在干嘛?

      最不能忍的是,一打开窗口,焦点竟然在“取消”按钮上?

  用户体验,往往就是多想这一步。

  我们要对代码进行“精装修”。

  优化一:调整身材 给输入框定个宽,别让它缩成一团。

  优化二:聚焦视线 打开窗口时,光标应该自动跳到“姓名”栏,而不是傻傻地停在按钮上。

  优化三:摆正位置 把标题居中,这才是正宫该有的气场。

  我们可以在 student_dialog.py 文件中将每个输入框的宽度设置为 200、取消“确定”按钮的默认焦点,使其不会自动获取焦点,并将焦点设置到第一个输入框,即“姓名”输入框、添加标题,并使其居中。代码如下:

  # student_interface.py 文件importsys # 导入 sys 模块,用于处理系统相关的功能fromPyQt6.QtCoreimportQt # 导入 Qt,用于设置对齐方式# 从 PyQt6.QtWidgets 模块中导入 QWidget、QVBoxLayout、QHBoxLayout、QApplication 和 QHeaderView、QCheckBox、QTableWidgetItem# 分别用于创建窗口部件、垂直布局、水平布局、应用程序、表格头部视图、复选框和表格项fromPyQt6.QtWidgetsimportQWidget, QVBoxLayout, QHBoxLayout, QApplication, QHeaderView, QCheckBox, QTableWidgetItem# 从 qfluentwidgets 库中导入 CardWidget、PushButton、SearchLineEdit、TableWidget、setCustomStyleSheet# 用于创建卡片样式的组件、按钮、搜索输入框、表格组件以及设置自定义样式fromqfluentwidgetsimportCardWidget, PushButton, SearchLineEdit, TableWidget, setCustomStyleSheet# 从自定义模块 database.student_db 中导入 StudentDB 类,用于操作学生相关的数据库数据fromdatabase.student_dbimportStudentDB# 从 student.student_dialog 模块导入 AddStudentDialog 类fromstudent.student_dialogimportAddStudentDialog# 导入自定义样式,用于“新增”和“批量删除”按钮fromutils.custom_styleimportADD_BUTTON_STYLE, BATCH_DELETE_BUTTON_STYLE# 定义 StudentInterface 类,继承自 QWidgetclassStudentInterface(QWidget):def__init__(self): # 初始化方法super.__init__ # 调用父类 QWidget 的初始化方法self.setObjectName("studentInterface") # 设置当前界面的对象名称self.students = # 用于存储学生数据的列表self.setup_ui # 调用 setup_ui 方法,配置界面布局和组件self.load_data # 调用 load_data 方法,加载学生数据self.populate_table # 调用 populate_table 方法,填充表格defsetup_ui(self): # 定义 setup_ui 方法,用于设置界面的布局和组件layout = QVBoxLayout(self) # 创建垂直布局,并将其设置为当前窗口的主布局# 顶部按钮组card_widget = CardWidget(self) # 创建卡片样式的组件作为按钮组容器,并设置父组件为当前窗口buttons_layout = QHBoxLayout(card_widget) # 创建水平布局,用于放置按钮,并将其设置为 card_widget 的布局self.addButton = PushButton("新增", self) # 创建一个“新增”按钮,并将其设置为当前窗口的子组件setCustomStyleSheet(self.addButton, ADD_BUTTON_STYLE, ADD_BUTTON_STYLE) # 应用自定义样式self.addButton.clicked.connect(self.add_student) # 将“添加”按钮的点击事件与 add_student 方法连接# 创建搜索输入框self.searchInput = SearchLineEdit(self) # 创建一个搜索输入框,并将其设置为当前窗口的子组件self.searchInput.setPlaceholderText("搜索学生姓名或学号...") # 设置占位提示文字self.searchInput.setFixedWidth(500) # 设置输入框固定宽度为 500# 创建“批量删除”按钮并应用自定义样式self.batchDeleteButton = PushButton("批量删除", self)setCustomStyleSheet(self.batchDeleteButton, BATCH_DELETE_BUTTON_STYLE, BATCH_DELETE_BUTTON_STYLE)# 将按钮和搜索框添加到按钮组的水平布局中buttons_layout.addWidget(self.addButton) # 添加“新增”按钮buttons_layout.addWidget(self.searchInput) # 添加搜索输入框buttons_layout.addStretch(1) # 添加弹性空间,将按钮推向两端buttons_layout.addWidget(self.batchDeleteButton) # 添加“批量删除”按钮# 将按钮组的卡片组件添加到主布局中layout.addWidget(card_widget)# 表格组件(创建空表格,后续填充内容)self.table_widget = TableWidget(self)self.table_widget.setBorderVisible(True) # 设置表格边框可见self.table_widget.setBorderRadius(8) # 设置表格圆角self.table_widget.setWordWrap(False) # 禁用自动换行# 设置表头填充模式和列数self.table_widget.horizontalHeader.setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # 表头填充模式为 Stretch,使表头填满整个表格,并实现列宽自适应屏幕缩放的效果self.table_widget.setColumnCount(11) # 设置表格列数为 11 列self.table_widget.setHorizontalHeaderLabels(["", "学生ID", "姓名", "学号", "性别", "班级", "语文", "数学", "英语", "总分", "操作"]) # 设置表头名称# 将表格组件添加到主布局中layout.addWidget(self.table_widget)self.setStyleSheet("StudentInterface{background: white}") # 设置界面背景为白色self.resize(1280,760) # 设置窗口大小为 1280x760defload_data(self): # 定义 load_data 方法,用于加载学生数据withStudentDBasdb:self.students = db.fetch_students # 调用 fetch_students 方法从数据库中获取学生数据,并将结果赋值给 self.studentsdefpopulate_table(self): # 定义 populate_table 方法,用于填充表格数据self.table_widget.setRowCount(len(self.students)) # 设置表格行数为学生数据的数量forrow, student_infoinenumerate(self.students): # 遍历学生数据并设置每行的内容self.setup_table_row(row, student_info) # 调用 setup_table_row 方法,设置每一行的数据defsetup_table_row(self, row, student_info): # 定义 setup_table_row 方法,用于设置表格行数据checkbox = QCheckBox # 创建复选框self.table_widget.setCellWidget(row,0, checkbox) # 将复选框添加到表格的第一列# 遍历学生数据的各个字段并添加到表格中forcol, keyinenumerate(['student_id', 'student_name', 'student_number', 'gender', 'class_name', 'chinese_score', 'math_score','english_score', 'total_score']):value = student_info.get(key, '') # 获取对应字段的值ifkey == 'gender': # 若字段为性别,进行性别的转换显示value = '男'ifvalue ==1else'女'ifvalue ==2else'未知'item = QTableWidgetItem(str(value)) # 创建表格项item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) # 设置文本居中self.table_widget.setItem(row, col +1, item) # 将表格项添加到表格中对应的列defadd_student(self): # 添加学生信息方法dialog = AddStudentDialog(self) # 创建一个 AddStudentDialog 对话框实例,并将当前窗口作为父窗口传递ifdialog.exec: # 执行对话框,弹出窗口等待用户操作print("用户点击了确定,处理添加学生逻辑") # 如果用户点击了确定按钮,执行此代码else:print("用户取消了操作") # 如果用户点击了取消按钮,执行此代码# 主程序入口if__name__ == '__main__':app = QApplication(sys.argv) # 创建应用程序实例window = StudentInterface # 创建 StudentInterface 窗口实例window.show # 显示窗口sys.exit(app.exec) # 进入应用程序主循环,并在退出时返回状态码

  完成以上调整后,重新运行程序 student_interface.py 文件,点击“新增”按钮,弹出框的宽度变宽,光标自动聚焦到姓名输入框,且标题居中显示。如下图所示:

  Python GUI 实战:只需3步,让你的学生管理系统从“能用”变成“好用”

  再次运行,哪怕是同样的逻辑, 体验感瞬间提升了几个Level:

  窗口宽敞大气,光标跃跃欲试,标题清晰明了。 这才是一个成熟的系统该有的样子。

  今天我们不仅实现了一个“新增”功能,更重要的是,我们实践了两个重要的编程哲学:

      DRY原则 (Don't Repeat Yourself): 用继承消灭重复代码。

      UX思维 (User Experience): 代码写完了只是开始,好用才是终点。

  下一步,我们将挑战“批量删除”和“数据入库”,让数据真正流动起来。