博客 / 詳情

返回

在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()  # 刷新顯示

通過上面的簡單封裝,我們就可以很容易的記得相關的處理函數,並且儘可能的減少了相關的參數傳遞,這樣我們在使用的時候,更加方便靈活了。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.