WxPython跨平台开发框架之表格数据导出到Excel并打开

在 Python 中使用 wxPython 导出实体类列表数据到 Excel,通常可以借助 openpyxlpandas 库来实现。本篇随笔由浅入深,逐步介绍导出Excel文件的操作,然后结合跨平台项目的实现,根据抽象继承的方式,对不同业务模块的通用导出Excel文件功能,以及跨平台的打开处理方式的实现进行介绍。

以下是一个基本示例,展示如何将实体类的列表数据导出到 Excel 文件。

1、使用pandas 库导出Excel

import wx
import pandas as pd

# 假设这是你的实体类
class Person:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

    def to_dict(self):
        return {"Name": self.name, "Age": self.age, "Email": self.email}

# 用一个 wxPython 窗口展示如何导出数据
class MyFrame(wx.Frame):
    def __init__(self, parent, title):
        super().__init__(parent, title=title, size=(300, 200))
        
        self.panel = wx.Panel(self)
        self.button = wx.Button(self.panel, label="导出到Excel", pos=(50, 50))
        
        # 创建一些实体类数据
        self.person_list = [
            Person("Alice", 30, "alice@example.com"),
            Person("Bob", 25, "bob@example.com"),
            Person("Charlie", 35, "charlie@example.com")
        ]
        
        self.Bind(wx.EVT_BUTTON, self.export_to_excel, self.button)
        self.Show()

    def export_to_excel(self, event):
        # 将实体类列表转换为字典列表
        data = [person.to_dict() for person in self.person_list]
        
        # 使用 pandas 导出到 Excel
        df = pd.DataFrame(data)
        df.to_excel("exported_data.xlsx", index=False)
        
        wx.MessageBox("导出成功!", "信息", wx.OK | wx.ICON_INFORMATION)

# 启动 wxPython 应用
app = wx.App(False)
frame = MyFrame(None, "导出实体类数据到 Excel")
app.MainLoop()

实体类 (Person):包含一些字段,如 nameageemail,并定义了 to_dict 方法,将实体对象转换为字典格式,以便更容易处理。

export_to_excel:这个方法将实体类列表转换为字典列表,使用 pandas 库将数据导出为 Excel 文件。

如果你需要更多的功能(如自定义 Excel 格式,单元格样式等),可以进一步扩展 openpyxlxlsxwriter 来提供更复杂的导出选项。

 

2、使用 openpyxl 库导出Excel

要在 Excel 导出时为标题加粗并设置背景色,你可以使用 openpyxl 库,它提供了丰富的功能来设置单元格样式(如字体、背景色等)。

以下是一个更新的示例,演示如何在导出数据时,设置 Excel 标题行的加粗和背景色。

import wx
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill

# 假设这是你的实体类
class Person:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

    def to_dict(self):
        return {"Name": self.name, "Age": self.age, "Email": self.email}

# 用一个 wxPython 窗口展示如何导出数据
class MyFrame(wx.Frame):
    def __init__(self, parent, title):
        super().__init__(parent, title=title, size=(300, 200))
        
        self.panel = wx.Panel(self)
        self.button = wx.Button(self.panel, label="导出到Excel", pos=(50, 50))
        
        # 创建一些实体类数据
        self.person_list = [
            Person("Alice", 30, "alice@example.com"),
            Person("Bob", 25, "bob@example.com"),
            Person("Charlie", 35, "charlie@example.com")
        ]
        
        self.Bind(wx.EVT_BUTTON, self.export_to_excel, self.button)
        self.Show()

    def export_to_excel(self, event):
        # 将实体类列表转换为字典列表
        data = [person.to_dict() for person in self.person_list]
        
        # 创建 Excel 工作簿和工作表
        wb = Workbook()
        ws = wb.active
        ws.title = "People Data"
        
        # 设置标题行并加粗背景色
        titles = ["Name", "Age", "Email"]
        ws.append(titles)
        
        # 设置标题样式:加粗和背景色
        title_font = Font(bold=True)
        title_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")  # 黄色背景
        for cell in ws[1]:
            cell.font = title_font
            cell.fill = title_fill
        
        # 填充数据
        for person in data:
            ws.append([person["Name"], person["Age"], person["Email"]])
        
        # 保存到文件
        wb.save("exported_data_with_styles.xlsx")
        
        wx.MessageBox("导出成功!", "信息", wx.OK | wx.ICON_INFORMATION)

# 启动 wxPython 应用
app = wx.App(False)
frame = MyFrame(None, "导出实体类数据到 Excel")
app.MainLoop()

Excel 在 openpyxl 中可以设置自适应列宽或者指定具体的列宽,甚至可以设置框架(边框样式)。

虽然 openpyxl 本身并没有直接提供“自动调整列宽”的功能,但我们可以通过遍历列中的所有单元格来计算每列的最大宽度,然后动态调整列宽。

调整后的代码如下所示。

import wx
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Border, Side

# 假设这是你的实体类
class Person:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

    def to_dict(self):
        return {"Name": self.name, "Age": self.age, "Email": self.email}

# 用一个 wxPython 窗口展示如何导出数据
class MyFrame(wx.Frame):
    def __init__(self, parent, title):
        super().__init__(parent, title=title, size=(300, 200))
        
        self.panel = wx.Panel(self)
        self.button = wx.Button(self.panel, label="导出到Excel", pos=(50, 50))
        
        # 创建一些实体类数据
        self.person_list = [
            Person("Alice", 30, "alice@example.com"),
            Person("Bob", 25, "bob@example.com"),
            Person("Charlie", 35, "charlie@example.com")
        ]
        
        self.Bind(wx.EVT_BUTTON, self.export_to_excel, self.button)
        self.Show()

    def export_to_excel(self, event):
        # 将实体类列表转换为字典列表
        data = [person.to_dict() for person in self.person_list]
        
        # 创建 Excel 工作簿和工作表
        wb = Workbook()
        ws = wb.active
        ws.title = "People Data"
        
        # 设置标题行并加粗背景色
        titles = ["Name", "Age", "Email"]
        ws.append(titles)
        
        # 设置标题样式:加粗和背景色
        title_font = Font(bold=True)
        title_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")  # 黄色背景
        for cell in ws[1]:
            cell.font = title_font
            cell.fill = title_fill
        
        # 填充数据
        for person in data:
            ws.append([person["Name"], person["Age"], person["Email"]])
        
        # 设置列宽(手动指定或根据内容自适应)
        # 自动调整列宽
        for col in ws.columns:
            max_length = 0
            column = col[0].column_letter  # 获取列字母
            for cell in col:
                try:
                    if len(str(cell.value)) > max_length:
                        max_length = len(cell.value)
                except:
                    pass
            adjusted_width = (max_length + 2)
            ws.column_dimensions[column].width = adjusted_width
        
        # 设置框架(边框)
        border = Border(
            left=Side(border_style="thin"),
            right=Side(border_style="thin"),
            top=Side(border_style="thin"),
            bottom=Side(border_style="thin")
        )
        for row in ws.iter_rows():
            for cell in row:
                cell.border = border
        
        # 保存到文件
        wb.save("exported_data_with_styles_and_borders.xlsx")
        
        wx.MessageBox("导出成功!", "信息", wx.OK | wx.ICON_INFORMATION)

# 启动 wxPython 应用
app = wx.App(False)
frame = MyFrame(None, "导出实体类数据到 Excel")
app.MainLoop()

点击“导出到Excel”按钮后,程序将生成一个包含:

  • 自动调整列宽的 Excel 文件;
  • 每个单元格的边框;
  • 标题行加粗并带黄色背景色的 Excel 文件。

为了实现一个通用的导出函数,根据 display_columnscolumn_mapping 设置导出的字段,并映射标题名称,你可以设计一个灵活的函数,接收这些参数并根据需要导出数据到 Excel。

  • display_columns:一个字符串,指定需要导出的字段(如 name,age,email)。
  • column_mapping:一个字典,指定字段到显示名称的映射。
  • list:包含实体类数据的列表,每个实体类需要提供 to_dict() 方法,将数据转换为字典格式。
  • filename:保存的文件名。
import wx
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Border, Side

def export_to_excel(list_data, display_columns, column_mapping, filename):
    # 解析 display_columns 为列表
    display_columns = display_columns.split(',')
    
    # 获取映射后的标题
    headers = [column_mapping.get(col, col) for col in display_columns]

    # 创建 Excel 工作簿和工作表
    wb = Workbook()
    ws = wb.active
    ws.title = "Data"

    # 设置标题行并加粗背景色
    ws.append(headers)
    title_font = Font(bold=True)
    title_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")  # 黄色背景
    for cell in ws[1]:
        cell.font = title_font
        cell.fill = title_fill

    # 填充数据
    for data_item in list_data:
        row = [data_item.get(col) for col in display_columns]
        ws.append(row)

    # 设置列宽(自动调整)
    for col in ws.columns:
        max_length = 0
        column = col[0].column_letter  # 获取列字母
        for cell in col:
            try:
                if len(str(cell.value)) > max_length:
                    max_length = len(cell.value)
            except:
                pass
        adjusted_width = (max_length + 2)
        ws.column_dimensions[column].width = adjusted_width

    # 设置框架(边框)
    border = Border(
        left=Side(border_style="thin"),
        right=Side(border_style="thin"),
        top=Side(border_style="thin"),
        bottom=Side(border_style="thin")
    )
    for row in ws.iter_rows():
        for cell in row:
            cell.border = border

    # 保存到文件
    wb.save(filename)
    return f"导出成功!文件已保存为 {filename}"

调用代码如下所示

    def export(self, event):
        display_columns = "name,age,email"  # 需要导出的字段
        column_mapping = { 
            "age": "年龄", 
            "email": "电子邮箱", 
            "name": "显示名称"
        }
        filename = "exported_data.xlsx"  # 保存的文件名
        result = export_to_excel(
            [person.to_dict() for person in self.person_list], 
            display_columns, 
            column_mapping, 
            filename
        )
        wx.MessageBox(result, "信息", wx.OK | wx.ICON_INFORMATION)

你只需调用 export_to_excel 函数并传递数据、要导出的字段(display_columns)、字段映射(column_mapping)和保存的文件名(filename)。它会生成一个 Excel 文件,并按要求设置样式。

openpyxl 中,自动调整列宽是通过检查列中内容的最大长度来实现的。如果你发现某一列(例如“年龄”列)的宽度过窄,可能是因为该列中的数据(例如数字)被视为较短的字符串,导致列宽过小。

为了解决这个问题,您可以通过设置列宽时为数字列提供额外的宽度补偿,或者通过在计算列宽时增加一个常量来确保列宽更合适。

    # 设置列宽(自动调整)
    for col in ws.columns:
        max_length = 0
        column = col[0].column_letter  # 获取列字母
        for cell in col:
            try:
                if cell.value:
                    cell_value = str(cell.value)
                    # 增加补偿宽度:如果是数字列,增加额外宽度
                    if isinstance(cell.value, (int, float)):
                        max_length = max(max_length, len(cell_value) + 2)  # 数字列增加 2 的宽度
                    else:
                        max_length = max(max_length, len(cell_value))
            except:
                pass
        adjusted_width = (max_length + 2)  # 留出一些额外的空间
        ws.column_dimensions[column].width = max(adjusted_width, 12)  # 设置最小宽度为 12
  • 列宽补偿:对于数字列(如 age 列),在计算最大长度时增加一个 +2 的补偿。这将确保数字列的列宽足够显示数字值。
  • 最小列宽:为了避免列过窄,我设置了 max(adjusted_width, 12),确保列宽至少为 12。如果计算出的列宽小于 12,将强制设置为 12。
  • 自动列宽:自动计算每列的最大长度,并为每列分配合适的宽度。

如果我们需要再实际业务中导出数据,如对于用户信息,实体类为UserDto,那么我们需要在导出数据之前,将 UserDto 类型的对象转换为字典格式。

在每个 DTO 对象中,添加一个 to_dict 方法,用于将对象的属性转换为字典。to_dict 方法可以返回一个字典,其中每个键是类属性的名称,每个值是对应的属性值。

不过我的跨平台框架中的UserDto对象是与Pydantic模型的,因此它可以通过函数 model_dump  进行通用处理为字典对象。

使用 model_dump 方法可以很方便地将 Python 类对象(特别是 Pydantic 模型或者具备 model_dump 方法的类)转换为字典。如果你正在使用 Pydantic 或者使用了某些自定义实现了 model_dump 方法的类,可以直接调用该方法来完成对象到字典的转换。

假设你有一个 UserDto 类,它是一个 Pydantic 模型:

from pydantic import BaseModel

class UserDto(BaseModel):
    name: str
    age: int
    email: str

# 创建一个 UserDto 实例
user = UserDto(name="Alice", age=30, email="alice@example.com")

# 使用 model_dump 将对象转换为字典
user_dict = user.model_dump()

print(user_dict)

在这个例子中,model_dump 会自动将 Pydantic 模型实例转换为字典,所有字段(即类的属性)都会成为字典的键,属性值成为字典的值。

如果你有嵌套的 Pydantic 模型,model_dump 会自动递归地将嵌套模型转换为字典。如果你的项目中使用了 Pydantic,这种方法将非常简便高效。

3、在项目列表基类中增加导出功能

通过上面的封装测试,我们可以把导出Excel的功能做的很不错了,因此把它整合到列表基类里面,通过基类界面中增加一个导出按钮即可实现所有业务模块的数据导出,不用每个页面都实现,简化了操作。

基类界面实现导出功能

添加按钮采用通用辅助函数参加按钮及图标,并增加导出的处理函数,如下代码所示。

  btn_export = ControlUtil.create_button(
      pane, "导出Excel", "xls", handler=self.OnExport, is_async=True
  )

 

OnExport函数的实现如下所示。

    async def OnExport(self, event: wx.Event) -> None:
        """导出数据"""
        # 检查数据是否是一个 Pydantic 实体
        export_list = []
        for item in self.data:
            if hasattr(item, "model_dump"):
                export_item = item.model_dump()
                export_list.append(export_item)
            else:
                export_list.append(item)

        # print(export_list)

        filename = FileDialogUtil.save_excel(self)
        if not filename:
            return

        result = ExcelUtil.export_to_excel(
            export_list, self.display_columns, self.column_mapping, filename
        )
        if result:
            if (
                MessageUtil.show_confirm(self, "导出成功,是否打开文件?", "导出成功")
                == wx.ID_YES
            ):
                ExcelUtil.open_file(filename)
        else:
            MessageUtil.show_error(self, "导出失败")

在MacOS上弹出导出提示,如下所示。

确认后提示,是否打开文件如下。

导出的文件打开后,可以看到效果如下所示

 

4、文件的打开方式和跨平台打开实现

注意,不同平台打开文件进行查看,操作方式有所不同。

如果你想用默认应用(如 Excel 或 Numbers)直接打开 .xls 文件,你可以通过 Python 的 subprocess 模块调用 macOS 上的应用程序来打开文件。

在 macOS 中,open 命令可以用来打开任何文件。如果你希望在默认的应用程序中打开 .xls 文件(例如 Excel 或 Numbers),可以使用如下方法:

import subprocess

# 打开 .xls 文件
subprocess.run(["open", "your_file.xls"])

macOS 支持通过 os 模块调用系统命令来打开文件。可以通过 os.system()subprocess.run() 来调用 open 命令,在 macOS 上打开文件。

import os

# 打开 .xls 文件
os.system("open your_file.xls")

通过 os.system("open your_file.xls")subprocess.run(["open", "your_file.xls"]),你可以在 macOS 上使用默认的应用程序(如 Excel 或 Numbers)打开 .xls 文件。这些方法非常简单且不需要依赖外部库。

os.startfile() 是 Windows 系统中的一个特定方法,用于打开文件并在关联的默认应用程序中显示它。然而,os.startfile()macOSLinux 系统中不可用。因此,在 macOS 上使用该方法会导致错误。

为了使代码在不同平台上都能工作,您可以编写一个条件判断,区分操作系统并使用合适的命令来打开文件。

import os
import subprocess
import platform

def open_file(file_path):
    system = platform.system()
    
    if system == "Darwin":  # macOS
        subprocess.run(["open", file_path])
    elif system == "Windows":
        os.startfile(file_path)
    elif system == "Linux":
        subprocess.run(["xdg-open", file_path])
    else:
        raise NotImplementedError(f"Unsupported operating system: {system}")

# 示例调用
open_file("your_file.xls")

说明:

  • macOS (Darwin):使用 open 命令。
  • Windows:使用 os.startfile(),这是 Windows 特有的方法。
  • Linux:使用 xdg-open 命令,适用于大多数 Linux 发行版。

对于跨平台执行系统命令,推荐使用 subprocess 模块,它提供了更强大的功能,并且更灵活和安全。subprocess.run() 方法是一个更通用的替代方案。

因此结合通用的导出Excel和打开文件,就可以实现Excel文件的导出打开操作了,各个业务列表模块度是基于列表基础页面的,因此自动具有导出的功能,当然,我们也可以根据一些条件进行判断是否使用导出按钮即可。

列表界面继承基类,从而可以大幅度的利用相应的规则和实现。

 

 如对于两个例子窗体:系统类型定义,客户信息,其中传如对应的DTO信息和参数即可。

子类继承基类列表页面,并传入对应的参数即可具体化相关的业务功能了。

以上就是根据抽象继承的方式,对不同业务模块的通用导出Excel文件功能,以及跨平台的打开处理方式的实现。

posted on 2024-12-09 22:05  伍华聪  阅读(302)  评论(0编辑  收藏  举报

导航