在PySide6/PyQt6的项目中封装一些基础类库,包括文件对话框、字体对话框、颜色对话框、消息对话框等内容

在我们实际开发项目的时候,有时候为了使用方便,会针对一些常用到的内容进行一定的封装处理,以降低使用的难度和减少相关代码,本篇随笔介绍在PySide6/PyQt6的项目中封装一些基础类库,包括文件对话框、字体对话框、颜色对话框、消息对话框等内容。

1、常用对话框处理封装的优点

对常用对话框的调用(包括文件对话框、字体对话框、颜色对话框、消息对话框等内容),可能调用的时候,会遇到一些问题,如对于常用的文件目录对话框,可能会出现下面一些问题:

  • 参数顺序难记

  • 单选 / 多选 / 保存 / 目录 API 不统一

  • 最近路径不好维护

  • 每个窗口都写一遍

如果对它进行一定的封装,可以实现更多可选参数的设置以及更好的支持:

✅ 打开单文件
✅ 打开多文件
✅ 保存文件
✅ 选择目录
✅ 文件过滤器常量
✅ 最近目录记忆(进程级 / 可扩展到配置)
✅ Windows / macOS / Linux 兼容

如果对这些对话框进行辅助类的统一封装,会具有以下是一些主要的优点:

1)代码复用

封装常用对话框可以避免重复代码。你可以定义一个统一的函数或类来处理所有常用对话框操作,从而在多个地方复用这段代码。

2)一致性

通过封装,你可以确保所有常用对话框的外观和行为一致。这有助于提高用户体验,使用户在应用程序中获得统一的交互方式。

3) 简化调用

封装可以简化调用过程。你可以将常用的参数设置(如标题、图标、按钮类型等)预先定义好,从而在调用时减少参数输入。

4)易于维护

当需要更改对话框的行为或样式时,只需在封装函数中进行修改,而不必在应用程序中的每个调用点进行更改。这使得维护变得更加简单和高效。

5)增强可读性

通过使用封装的函数或类,代码变得更易读。其他开发者可以一眼看出对话框的作用,而不必深入了解其具体实现。

6)集中管理

封装有助于集中管理对话框的逻辑,比如处理用户输入、响应用户选择等。这样可以更方便地进行逻辑更新或错误处理。

7) 扩展性

如果将来需要增加新的对话框或修改现有对话框的逻辑,封装使得扩展更加容易。你可以在封装的基础上进行扩展,而不影响现有的代码结构。

2、文件对话框的封装

如果我们对文件对话框进行封装,那么需要考虑打开、保存文件对话框等常规操作,而文件的格式有多种多样,我们为了方便,可以提供更细节的函数选择具体的文件,如文本文件、Excel文件等。

class FileDialogUtil:
    """文件、目录对话框工具类"""

    # 定义文件过滤器
    all_filter = "All File (*.*);;*.*"
    word_filter = "Word (*.doc);;*.doc;;Word (*.docx);;*.docx;;All File (*.*);;*.*"
    excel_filter = "Excel (*.xls);;*.xls;;Excel (*.xlsx);;*.xlsx;;All File (*.*);;*.*"
    pdf_filter = "PDF (*.pdf);;*.pdf;;All File (*.*);;*.*"
    image_filter = "Image Files (*.BMP;*.bmp;*.JPG;*.jpg;*.GIF;*.gif;*.png;*.PNG);;*.BMP;*.bmp;*.JPG;*.jpg;*.GIF;*.gif;*.png;*.PNG;;All File (*.*);;*.*"
    html_filter = "HTML files (*.html;*.htm);;*.html;*.htm;;All files (*.*);;*.*"
    access_filter = "Access (*.mdb);;*.mdb;;All File (*.*);;*.*"
    zip_filter = "Zip (*.zip);;*.zip;;Rar (*.rar);;*.rar;;All files (*.*);;*.*"
    config_filter = "Configuration Files (*.cfg);;*.cfg;;All File (*.*);;*.*"
    txt_filter = "Text (*.txt);;*.txt;;All files (*.*);;*.*"
    xml_filter = "XML Files (*.xml);;*.xml;;All files (*.*);;*.*"
    rar_filter = "Rar (*.rar);;*.rar;;All files (*.*);;*.*"
    sqlite_filter = "Sqlite Files (*.db);;*.db;;All files (*.*);;*.*"
    python_filter = "Python Files (*.py);;*.py;;All files (*.*);;*.*"
    csv_filter = "CSV Files (*.csv);;*.csv;;All files (*.*);;*.*"

    @staticmethod
    def open_file(
        parent: QWidget = None,
        multiple: bool = False,
        title: str = "打开文件",
        filter: str = all_filter,
        filename: str = "",
        initial_directory: str = os.getcwd(),
    ) -> str:
        """
        打开文件对话框

        :param parent: 父窗口
        :param multiple: 是否多选
        :param title: 对话框标题
        :param filename: 默认文件名
        :param filter: 文件过滤器
        :param initial_directory: 默认目录
        :return: 选中的文件路径,如果是多选,则返回以逗号分隔的多个文件路径
        """

        # 创建文件对话框
        dialog = QFileDialog(parent)
        dialog.setWindowTitle(title)
        dialog.setFileMode(
            QFileDialog.FileMode.ExistingFiles
            if multiple
            else QFileDialog.FileMode.ExistingFile
        )
        dialog.setNameFilter(filter)
        dialog.setDirectory(initial_directory)
        dialog.selectFile(filename)

        # 执行对话框并获取文件路径
        result = ""
        if dialog.exec():
            if multiple:
                file_paths = dialog.selectedFiles()
                result = ",".join(file_paths)  # 将文件路径连接成一个字符串
            else:
                result = dialog.selectedFiles()[0]  # 获取单个文件路径

        return result

当然上面 open_file 传入的是所有文件的格式,如果我们需要跟进一步选择Excel格式,就需要传入对应的后缀名参数常量即可。如下所示。

image

 而如果要弹出保存文件对话框的操作,那么我们也是如法炮制即可。

    @staticmethod
    def save_file(
        parent: QWidget = None,
        title: str = "保存文件",
        filter: str = all_filter,
        filename: str = "",
        initial_directory: str = os.getcwd(),
    ) -> str:
        """以指定的标题弹出保存文件对话框

        :param title: 对话框标题
        :param filename: 默认文件名
        :param filter: 文件过滤器
        :param initial_directory: 默认目录
        :return: 选中的文件路径
        """
        # 创建保存文件对话框
        dialog = QFileDialog(parent)
        dialog.setWindowTitle(title)
        dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)  # 设置为保存模式
        dialog.setNameFilter(filter)
        dialog.setDirectory(initial_directory)
        dialog.selectFile(filename)

        # 执行对话框并获取文件路径
        result = ""
        if dialog.exec():
            result = dialog.selectedFiles()[0]  # 获取选中的文件路径

        return result

其他类型格式的,只需要传入对应的filter格式即可。

image

 选择目录也是类似的处理

    @staticmethod
    def open_dir(
        parent: QWidget = None,
        title: str = "选择目录",
        initial_directory: str = os.getcwd(),
    ) -> str:
        """显示目录选择对话框"""

        # 创建文件对话框
        dialog = QFileDialog(parent)
        dialog.setWindowTitle(title)
        dialog.setFileMode(QFileDialog.FileMode.Directory)  # 设置为目录选择模式
        dialog.setOption(QFileDialog.Option.ShowDirsOnly, True)  # 只显示目录
        dialog.setDirectory(initial_directory)

        # 执行对话框并获取选中的目录
        result = ""
        if dialog.exec():
            result = dialog.selectedFiles()[0]  # 获取选中的目录路径

        print(result)
        return result

选择多个目录,如下效果

image

这样我们在一些窗体上使用保存Excel或者PDF文件的时候,直接使用它的函数调用即可,比较简单了。

    def export_to_pdf(self, setting: PrintSetting) -> str:
        """将 QTableView 的数据导出为 PDF, 成功返回文件路径,失败返回空字符串"""

        pdf_file = FileDialogUtil.save_pdf(filename=f"{setting.print_title}.pdf")
        if not pdf_file:
            return ""

        # 创建 QPrinter,PDF格式
        printer = QPrinter(QPrinter.PrinterMode.HighResolution)
        printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat)
        printer.setOutputFileName(pdf_file)  # 输出 PDF 文件

        # 打印的处理
        printer.setPageSize(setting.page_size)
        printer.setPageOrientation(setting.page_orientation)
        self.print_cols = setting.print_cols  # 打印指定列的索引列表
        self.print_title = setting.print_title  # 打印标题
        self.settings = setting  # 保存打印设置

        # 打印输出
        self.print_preview_paint(printer)
        return pdf_file

 

3、封装常用消息对话框

封装的消息提示对话框包括个各种常用的对话框,如下所示:

首先我们需要定义一个独立的消息对话框类MessageUtil,如下所示。

class MessageUtil:
    """
    封装了常用的消息对话框,以方便使用常用对话框消息。
    包括提示信息、警告信息、错误信息、确认信息、询问信息、输入信息、
    选择信息、多选信息、文件选择信息、目录选择信息、字体选择信息、颜色选择信息、进度条信息等。
    """

    # 常用消息对话框的标题
    CAPTION_TIPS = "提示信息"
    CAPTION_WARNING = "警告信息"
    CAPTION_ERROR = "错误信息"
    CAPTION_CONFIRM = "确认信息"
 
   @staticmethod
    def show_message(
        message: str,
        title: str = CAPTION_TIPS,
        extended_message=None,
        parent=None,
        icon: QMessageBox.Icon = QMessageBox.Icon.Information,
        buttons: QMessageBox.StandardButton = QMessageBox.StandardButton.Ok,
    ) -> QMessageBox.StandardButton:
        """
        通用消息框显示函数
        """
        msg_box = QMessageBox(parent)
        msg_box.setWindowTitle(title)
        msg_box.setText(message)

        # 设置详细信息,在消息框的底部显示。
        if extended_message:
            # 创建 TextSplitter 实例,设置每行最大字符数为 30
            splitter = TextSplitter(max_line_length=80)
            # 使用 splitter 来分割文本
            extended_message = splitter.split(extended_message)
            msg_box.setDetailedText(extended_message)

        msg_box.setDetailedText(extended_message)
        msg_box.setIcon(icon)
        msg_box.setStandardButtons(buttons)
        # 设置窗口图标
        app_icon = QIcon("app/images/app.ico")
        msg_box.setWindowIcon(app_icon)
        return msg_box.exec()

然后统一对常用的消息进行函数封装,如一般消息、警告消息、错误消息,提示消息等,只需要对上面函数的简单调用,传递不同的参数就可以。

image

有了这样的封装,我们可以在窗体中直接调用,不需要记住太多的参数了,比较简单,如下面的删除操作处理。

    @asyncSlot()
    async def OnDelete(self):
        """弹出删除对话框"""
        selected_rows = self.table_view.selectionModel().selectedRows()
        if not selected_rows:
            MessageUtil.show_info("请选择要操作的行")
            return
        # 确认删除
        result = MessageUtil.show_confirm("确认删除选中的行?", "确认删除")
        if result:
            # 遍历选定的行,删除主键值对应的记录
            list = []
            for index in selected_rows:
                entity_id = self.table_model.GetPrimaryKeyValue(index.row())
                if entity_id:
                    list.append(entity_id)
            try:
                await self.OnDeleteByIdList(list)
            except Exception as e:
                MessageUtil.show_error(f"删除失败:{e}")

 为了了解常用对话框的操作,我们还编写一个简单的测试界面来展示效果。

image

 

字体对话框

image

封装字体对话框函数如下:

    @staticmethod
    def show_font_dialog(parent=None, font=None) -> tuple[QFont, bool]:
        """
        显示字体选择对话框
        :param parent: 父窗口
        :param font: 默认字体,如果没有提供,使用系统默认字体
        :return: 选择的字体,成功时返回 (QFont, True),失败时返回 (None, False)
        """
        # 如果没有提供默认字体,则使用系统默认字体
        if font is None:
            font = QApplication.font()  # 获取系统默认字体

        # 打开字体选择对话框
        ok, selected_font = QFontDialog.getFont(font, parent)

        # 返回选择的字体和是否成功
        return selected_font, ok

调用代码如下所示。

    def on_select_font(self):
        myfont = self.message.font()  # 获取当前字体
        font_data = MessageUtil.show_font_dialog(self, myfont)[0]  # 打开字体选择对话框
        if font_data:
            self.message.setFont(
                QFont(font_data.family(), font_data.pointSize())
            )  # 设置字体
            self.message.update()  # 刷新显示

 

颜色对话框

image

封装颜色对话框函数如下:

    def show_colour_dialog(parent=None, colour=None) -> tuple[QColor, bool]:
        """
        显示颜色选择对话框
        :param parent: 父窗口
        :param colour: 默认颜色,如果没有提供,使用黑色
        :return: 选择的颜色,成功时返回 (QColor, True),失败时返回 (None, False)
        """
        # 如果没有提供默认颜色,则使用黑色
        if colour is None:
            colour = QColor(Qt.GlobalColor.black)

        # 打开颜色选择对话框
        selected_colour = QColorDialog.getColor(colour, parent)

        # 返回选择的颜色和是否成功
        return selected_colour, selected_colour.isValid()

调用代码如下所示。

    def on_select_colour(self):
        color = self.message.palette().color(QPalette.ColorRole.WindowText)
        color_data = MessageUtil.show_colour_dialog(self, color)[0]
        if color_data:
            self.message.setStyleSheet(f"color: {color_data.name()};")
            self.message.update()  # 刷新显示

通过上面的简单封装,我们就可以很容易的记得相关的处理函数,并且尽可能的减少了相关的参数传递,这样我们在使用的时候,更加方便灵活了。

posted on 2025-12-21 14:14  伍华聪  阅读(36)  评论(0)    收藏  举报

导航