Python自动办公工具06-设置Word文档中表格的格式

一:效果展示:

本项目是基于PyQt5python-docx库开发的图形化应用程序,用于对Word文档中的表格进行格式化设置。用户可以通过直观的界面调整表格的各种格式属性,包括基本格式、条件格式、边框和背景等


二:功能描述:

1. 核心功能

(1)文件操作模块
  • 打开文件 :允许用户选择并加载.docx文件,自动检测文件中的第一个表格
  • 保存文件:将修改后的文档保存为新文件(在原文件名后添加"-整理"后缀)
  • 文件状态显示:在界面顶部显示当前打开的文件名
(2)基本格式设置
  1. 行高设置:
  • 默认行高:设置表格数据行的默认高度(单位:磅)
  • 表头行数:指定表格中表头的行数(表头行高固定为1cm)
  1. 对齐方式:
  • 水平对齐:居中/左对齐/右对齐
  • 垂直对齐:居中/顶部/底部
  1. 字体设置:
  • 字体名称:下拉选择常见中英文字体(包括系统默认字体)
  • 字体大小:8-72磅可调
  • 字体样式:加粗/斜体复选框
  • 字体颜色:通过颜色选择器设置
(3)条件格式设置
  1. 条件1设置:
  • 应用列:指定要应用条件的列号
  • 操作符:>=, >, =, <=, <, <>
  • 比较值:数值比较
  • 背景色:满足条件时单元格的背景颜色
  1. 条件2设置:
  • 应用列:指定要应用条件的列号
  • 操作符:>=, >, =, <=, <, <>
  • 比较值:支持数值和文本比较
  • 背景色:满足条件时单元格的背景颜色
(4)边框和背景设置
  1. 边框设置:
  • 边框样式:单线/虚线/点线/双线/无线条
  • 边框宽度:1-24磅可调
  • 边框颜色:通过颜色选择器设置
  • 应用边框:可单独选择应用左边框、上边框、右边框或下边框
  1. 背景设置:
  • 背景颜色:通过颜色选择器设置
  • 应用范围:所有单元格/表头/数据行/特定列
  • 列选择:当选择 "++特定列++" 时指定列号

2. 功能实现细节

(1)表格格式化核心类 - DocxTableEditor
  • set_cell_border :使用python-docx的底层XML操作设置单元格边框样式
  • set_background_color :通过XML操作设置单元格背景颜色
  • set_cell_font:设置单元格内文本的字体属性(名称、大小、加粗、斜体、颜色)
(2)条件格式实现
  • 支持数值比较(条件1)和文本比较(条件2)
  • 只有当用户选择了背景颜色时才应用条件格式
  • 自动跳过表头行(根据表头行数设置)
(3)用户界面特性
  • 使用选项卡组织不同功能模块
  • 颜色选择后实时预览(标签显示颜色并设置背景色)
  • 智能字体选择(包含常见中英文字体并尝试检测系统默认字体)
  • 详细的错误处理和用户反馈(通过QMessageBox

3. 使用流程

(1)打开文档:

点击"打开文件"按钮选择包含表格的Word文档

(2)设置格式
  • 在"基本格式"选项卡中设置表格的基础样式
  • 在"条件格式"选项卡中设置基于单元格值的条件格式
  • 在"边框和背景"选项卡中设置边框样式和单元格背景
(3)保存文档

点击"保存文件"按钮将修改保存为新文档

4. 技术特点

  1. 跨平台性 :基于PyQt5开发,可在WindowsmacOSLinux上运行
  2. 非破坏性编辑:保留原始文档内容,只修改指定格式属性
  3. 直观的用户界面:通过图形化控件简化复杂的表格格式设置

5. 适用场景

  1. 批量处理报表文档的格式
  2. 标准化不同来源的Word表格样式
  3. 通过条件格式可突出显示特定数据

三:完整代码:

python 复制代码
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QLabel, QPushButton, QFileDialog, QGroupBox, QGridLayout,
                             QCheckBox, QComboBox, QLineEdit, QSpinBox, QColorDialog,
                             QMessageBox, QTabWidget, QFormLayout)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor
from docx import Document
from docx.shared import Cm, Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_ALIGN_VERTICAL
from docx.oxml import OxmlElement
from docx.oxml.ns import qn, nsdecls
from docx.oxml import parse_xml

class DocxTableEditor:
    @staticmethod
    def set_cell_border(cell, **kwargs):
        tc = cell._tc
        tcPr = tc.get_or_add_tcPr()

        tcBorders = tcPr.first_child_found_in("w:tcBorders")
        if tcBorders is None:
            tcBorders = OxmlElement('w:tcBorders')
            tcPr.append(tcBorders)

        for edge in ('start', 'top', 'end', 'bottom', 'insideH', 'insideV'):
            edge_data = kwargs.get(edge)
            if edge_data:
                tag = 'w:{}'.format(edge)

                element = tcBorders.find(qn(tag))
                if element is None:
                    element = OxmlElement(tag)
                    tcBorders.append(element)

                for key in ["sz", "val", "color", "space", "shadow"]:
                    if key in edge_data:
                        element.set(qn('w:{}'.format(key)), str(edge_data[key]))

    @staticmethod
    def set_background_color(cell, rgb_color):
        if isinstance(rgb_color, QColor):
            rgb_color = rgb_color.name()[1:]  
        elif isinstance(rgb_color, str) and rgb_color.startswith('#'):
            rgb_color = rgb_color[1:]
        rgb_color = f"{rgb_color:0>6}"

        shading_elm = parse_xml(r'<w:shd {} w:fill="{color_value}"/>'.format(nsdecls('w'), color_value=rgb_color))
        cell._tc.get_or_add_tcPr().append(shading_elm)

    @staticmethod
    def set_cell_font(cell, font_name=None, font_size=None, bold=None, italic=None, color=None):
        for paragraph in cell.paragraphs:
            for run in paragraph.runs:
                if font_name:
                    run.font.name = font_name
                if font_size:
                    run.font.size = Pt(font_size)
                if bold is not None:
                    run.font.bold = bold
                if italic is not None:
                    run.font.italic = italic
                if color:
                    if isinstance(color, QColor):
                        run.font.color.rgb = RGBColor(color.red(), color.green(), color.blue())
                    elif isinstance(color, str):
                        if color.startswith('#'):
                            color = color[1:]
                        try:
                            r = int(color[0:2], 16)
                            g = int(color[2:4], 16)
                            b = int(color[4:6], 16)
                            run.font.color.rgb = RGBColor(r, g, b)
                        except (ValueError, IndexError):
                            run.font.color.rgb = RGBColor(0, 0, 0)
                    else:
                        run.font.color.rgb = RGBColor(0, 0, 0)

class TableFormatterApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("设置Word文档表格格式")
        self.setGeometry(100, 100, 800, 600)
        self.doc = None
        self.table = None
        self.file_path = ""
        self.font_color_label = QLabel("未选择")
        self.cond1_color_label = QLabel("未选择")
        self.cond2_color_label = QLabel("未选择")
        self.border_color_label = QLabel("未选择")
        self.bg_color_label = QLabel("未选择")
        self.init_ui()

    def init_ui(self):
        main_widget = QWidget()
        main_layout = QVBoxLayout()
        file_group = QGroupBox("文件操作")
        file_layout = QHBoxLayout()
        self.file_label = QLabel("未选择文件")
        self.file_label.setAlignment(Qt.AlignCenter)
        open_btn = QPushButton("打开文件")
        open_btn.clicked.connect(self.open_file)
        save_btn = QPushButton("保存文件")
        save_btn.clicked.connect(self.save_file)
        file_layout.addWidget(self.file_label)
        file_layout.addWidget(open_btn)
        file_layout.addWidget(save_btn)
        file_group.setLayout(file_layout)
        tab_widget = QTabWidget()
        basic_tab = QWidget()
        basic_layout = QVBoxLayout()
        row_group = QGroupBox("行高设置")
        row_form = QFormLayout()
        self.row_height_spin = QSpinBox()
        self.row_height_spin.setRange(10, 100)
        self.row_height_spin.setValue(30)
        self.row_height_spin.setSuffix(" 磅")
        self.header_row_spin = QSpinBox()
        self.header_row_spin.setRange(0, 10)
        self.header_row_spin.setValue(1)
        row_form.addRow("默认行高:", self.row_height_spin)
        row_form.addRow("表头行数:", self.header_row_spin)
        row_group.setLayout(row_form)
        align_group = QGroupBox("对齐方式")
        align_layout = QHBoxLayout()
        self.align_combo = QComboBox()
        self.align_combo.addItems(["居中", "左对齐", "右对齐"])
        self.valign_combo = QComboBox()
        self.valign_combo.addItems(["居中", "顶部", "底部"])
        align_layout.addWidget(QLabel("水平对齐:"))
        align_layout.addWidget(self.align_combo)
        align_layout.addWidget(QLabel("垂直对齐:"))
        align_layout.addWidget(self.valign_combo)
        align_group.setLayout(align_layout)
        font_group = QGroupBox("字体设置")
        font_layout = QFormLayout()
        self.font_name_combo = QComboBox()
        common_fonts = [
            "默认字体",  
            "宋体", "黑体", "楷体", "仿宋", "微软雅黑", "Arial", "Times New Roman",
            "Calibri", "Courier New", "Verdana", "Tahoma", "Helvetica"
        ]
        self.font_name_combo.addItems(common_fonts)

        try:
            import matplotlib.font_manager as fm
            default_font = fm.FontProperties().get_name()
            if default_font not in common_fonts:
                self.font_name_combo.insertItem(1, default_font)
        except:
            pass

        self.font_size_spin = QSpinBox()
        self.font_size_spin.setRange(8, 72)
        self.font_size_spin.setValue(11)
        self.bold_check = QCheckBox("加粗")
        self.italic_check = QCheckBox("斜体")
        font_color_layout = QHBoxLayout()
        self.font_color_btn = QPushButton("字体颜色")
        self.font_color_btn.clicked.connect(self.choose_font_color)
        font_color_layout.addWidget(self.font_color_btn)
        font_color_layout.addWidget(self.font_color_label)
        font_layout.addRow("字体名称:", self.font_name_combo)
        font_layout.addRow("字体大小:", self.font_size_spin)
        font_layout.addRow(self.bold_check)
        font_layout.addRow(self.italic_check)
        font_layout.addRow("字体颜色:", font_color_layout)
        font_group.setLayout(font_layout)
        apply_basic_btn = QPushButton("应用基本格式")
        apply_basic_btn.clicked.connect(self.apply_basic_formatting)
        basic_layout.addWidget(row_group)
        basic_layout.addWidget(align_group)
        basic_layout.addWidget(font_group)
        basic_layout.addWidget(apply_basic_btn)
        basic_tab.setLayout(basic_layout)
        cond_tab = QWidget()
        cond_layout = QVBoxLayout()
        cond_group = QGroupBox("条件格式设置")
        cond_grid = QGridLayout()
        cond_grid.addWidget(QLabel("条件1:"), 0, 0)
        self.cond1_col_spin = QSpinBox()
        self.cond1_col_spin.setRange(1, 20)
        self.cond1_col_spin.setValue(6)  # 默认第6列(数量列)
        cond_grid.addWidget(QLabel("列号:"), 0, 1)
        cond_grid.addWidget(self.cond1_col_spin, 0, 2)
        self.cond1_op_combo = QComboBox()
        self.cond1_op_combo.addItems([">=", ">", "=", "<=", "<", "<>"])
        cond_grid.addWidget(QLabel("操作符:"), 0, 3)
        cond_grid.addWidget(self.cond1_op_combo, 0, 4)
        self.cond1_value_spin = QSpinBox()
        self.cond1_value_spin.setRange(0, 10000)
        self.cond1_value_spin.setValue(85)
        cond_grid.addWidget(QLabel("值:"), 0, 5)
        cond_grid.addWidget(self.cond1_value_spin, 0, 6)
        cond1_color_layout = QHBoxLayout()
        self.cond1_color_btn = QPushButton("背景色")
        self.cond1_color_btn.clicked.connect(lambda: self.choose_condition_color(1))
        cond1_color_layout.addWidget(self.cond1_color_btn)
        cond1_color_layout.addWidget(self.cond1_color_label)
        cond_grid.addLayout(cond1_color_layout, 0, 7)
        cond_grid.addWidget(QLabel("条件2:"), 1, 0)
        self.cond2_col_spin = QSpinBox()
        self.cond2_col_spin.setRange(1, 20)
        cond_grid.addWidget(QLabel("列号:"), 1, 1)
        cond_grid.addWidget(self.cond2_col_spin, 1, 2)
        self.cond2_op_combo = QComboBox()
        self.cond2_op_combo.addItems([">=", ">", "=", "<=", "<", "<>"])
        cond_grid.addWidget(QLabel("操作符:"), 1, 3)
        cond_grid.addWidget(self.cond2_op_combo, 1, 4)
        self.cond2_value_edit = QLineEdit()
        self.cond2_value_edit.setPlaceholderText("数值或文本")
        cond_grid.addWidget(QLabel("值:"), 1, 5)
        cond_grid.addWidget(self.cond2_value_edit, 1, 6)
        cond2_color_layout = QHBoxLayout()
        self.cond2_color_btn = QPushButton("背景色")
        self.cond2_color_btn.clicked.connect(lambda: self.choose_condition_color(2))
        cond2_color_layout.addWidget(self.cond2_color_btn)
        cond2_color_layout.addWidget(self.cond2_color_label)
        cond_grid.addLayout(cond2_color_layout, 1, 7)
        cond_group.setLayout(cond_grid)
        apply_cond_btn = QPushButton("应用条件格式")
        apply_cond_btn.clicked.connect(self.apply_condition_formatting)
        cond_layout.addWidget(cond_group)
        cond_layout.addWidget(apply_cond_btn)
        cond_tab.setLayout(cond_layout)
        border_tab = QWidget()
        border_layout = QVBoxLayout()
        border_group = QGroupBox("边框设置")
        border_form = QFormLayout()
        self.border_style_combo = QComboBox()
        self.border_style_combo.addItems(["single", "dashed", "dotted", "double", "none"])
        self.border_style_combo.setCurrentText("single")
        self.border_width_spin = QSpinBox()
        self.border_width_spin.setRange(1, 24)
        self.border_width_spin.setValue(4)
        self.border_width_spin.setSuffix(" 磅")
        border_color_layout = QHBoxLayout()
        self.border_color_btn = QPushButton("边框颜色")
        self.border_color_btn.clicked.connect(self.choose_border_color)
        border_color_layout.addWidget(self.border_color_btn)
        border_color_layout.addWidget(self.border_color_label)
        self.border_sides_check = []
        side_layout = QHBoxLayout()
        sides = ["左边框", "上边框", "右边框", "下边框"]
        for side in sides:
            check = QCheckBox(side)
            check.setChecked(True)
            self.border_sides_check.append(check)
            side_layout.addWidget(check)

        border_form.addRow("边框样式:", self.border_style_combo)
        border_form.addRow("边框宽度:", self.border_width_spin)
        border_form.addRow("边框颜色:", border_color_layout)
        border_form.addRow("应用边框:", side_layout)
        border_group.setLayout(border_form)
        bg_group = QGroupBox("背景设置")
        bg_layout = QHBoxLayout()
        bg_color_layout = QHBoxLayout()
        self.bg_color_btn = QPushButton("选择背景色")
        self.bg_color_btn.clicked.connect(self.choose_bg_color)
        bg_color_layout.addWidget(self.bg_color_btn)
        bg_color_layout.addWidget(self.bg_color_label)
        self.bg_range_combo = QComboBox()
        self.bg_range_combo.addItems(["所有单元格", "表头", "数据行", "特定列"])
        self.bg_col_spin = QSpinBox()
        self.bg_col_spin.setRange(1, 20)
        self.bg_col_spin.setValue(1)
        bg_layout.addLayout(bg_color_layout)
        bg_layout.addWidget(QLabel("应用范围:"))
        bg_layout.addWidget(self.bg_range_combo)
        bg_layout.addWidget(QLabel("列号:"))
        bg_layout.addWidget(self.bg_col_spin)
        bg_group.setLayout(bg_layout)
        apply_border_btn = QPushButton("应用边框和背景")
        apply_border_btn.clicked.connect(self.apply_border_and_background)
        border_layout.addWidget(border_group)
        border_layout.addWidget(bg_group)
        border_layout.addWidget(apply_border_btn)
        border_tab.setLayout(border_layout)
        tab_widget.addTab(basic_tab, "基本格式")
        tab_widget.addTab(cond_tab, "条件格式")
        tab_widget.addTab(border_tab, "边框和背景")
        main_layout.addWidget(file_group)
        main_layout.addWidget(tab_widget)
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)

    def open_file(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "打开Word文件", "", "Word文件 (*.docx)")
        if file_path:
            self.file_path = file_path
            self.file_label.setText(file_path.split("/")[-1])
            try:
                self.doc = Document(file_path)
                if len(self.doc.tables) > 0:
                    self.table = self.doc.tables[0]
                    QMessageBox.information(self, "成功", "文件加载成功!")
                else:
                    QMessageBox.warning(self, "警告", "文件中没有表格!")
                    self.table = None
            except Exception as e:
                QMessageBox.critical(self, "错误", f"无法打开文件:\n{str(e)}")
                self.table = None

    def save_file(self):
        if not self.doc:
            QMessageBox.warning(self, "警告", "请先打开文件!")
            return

        if not self.file_path:
            save_path, _ = QFileDialog.getSaveFileName(self, "保存Word文件", "", "Word文件 (*.docx)")
            if not save_path:
                return
        else:
            save_path = self.file_path.replace(".docx", "-整理.docx")

        try:
            self.doc.save(save_path)
            QMessageBox.information(self, "成功", f"文件已保存到:\n{save_path}")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"保存文件失败:\n{str(e)}")

    def choose_font_color(self):
        color = QColorDialog.getColor()
        if color.isValid():
            self.font_color = color
            self.font_color_label.setText(color.name())
            self.font_color_label.setStyleSheet(f"background-color: {color.name()}; color: white;")

    def choose_condition_color(self, cond_num):
        color = QColorDialog.getColor()
        if color.isValid():
            if cond_num == 1:
                self.cond1_color = color.name()[1:]
                self.cond1_color_label.setText(color.name())
                self.cond1_color_label.setStyleSheet(f"background-color: {color.name()}; color: black;")
            else:
                self.cond2_color = color.name()[1:]
                self.cond2_color_label.setText(color.name())
                self.cond2_color_label.setStyleSheet(f"background-color: {color.name()}; color: black;")

    def choose_border_color(self):
        color = QColorDialog.getColor()
        if color.isValid():
            self.border_color = color.name()[1:]
            self.border_color_label.setText(color.name())
            self.border_color_label.setStyleSheet(f"background-color: {color.name()}; color: black;")

    def choose_bg_color(self):
        color = QColorDialog.getColor()
        if color.isValid():
            self.bg_color = color.name()[1:]
            self.bg_color_label.setText(color.name())
            self.bg_color_label.setStyleSheet(f"background-color: {color.name()}; color: black;")

    def apply_basic_formatting(self):
        if not self.table:
            QMessageBox.warning(self, "警告", "请先打开文件!")
            return

        try:
            align_map = {"居中": WD_ALIGN_PARAGRAPH.CENTER, "左对齐": WD_ALIGN_PARAGRAPH.LEFT,
                         "右对齐": WD_ALIGN_PARAGRAPH.RIGHT}
            valign_map = {"居中": WD_ALIGN_VERTICAL.CENTER, "顶部": WD_ALIGN_VERTICAL.TOP, "底部": WD_ALIGN_VERTICAL.BOTTOM}

            align = align_map[self.align_combo.currentText()]
            valign = valign_map[self.valign_combo.currentText()]
            font_name = self.font_name_combo.currentText() if self.font_name_combo.currentText() != "默认字体" else None
            font_size = self.font_size_spin.value() if self.font_size_spin.value() > 0 else None
            bold = self.bold_check.isChecked()
            italic = self.italic_check.isChecked()
            header_rows = self.header_row_spin.value()
            default_row_height = self.row_height_spin.value()

            for i, row in enumerate(self.table.rows):
                if i < header_rows:
                    row.height = Cm(1)  
                else:
                    row.height = Pt(default_row_height)

                for cell in row.cells:
                    for paragraph in cell.paragraphs:
                        paragraph.alignment = align
                    cell.vertical_alignment = valign
                    DocxTableEditor.set_cell_font(
                        cell,
                        font_name=font_name,
                        font_size=font_size,
                        bold=bold,
                        italic=italic,
                        color=self.font_color  
                    )

            QMessageBox.information(self, "成功", "基本格式应用成功!")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"应用基本格式时出错:\n{str(e)}")

    def apply_condition_formatting(self):
        if not self.table:
            QMessageBox.warning(self, "警告", "请先打开文件!")
            return

        try:
            cond1_col = self.cond1_col_spin.value() - 1
            cond1_op = self.cond1_op_combo.currentText()
            cond1_value = self.cond1_value_spin.value()
            cond1_color = self.cond1_color if hasattr(self, 'cond1_color') else None
            cond2_col = self.cond2_col_spin.value() - 1
            cond2_op = self.cond2_op_combo.currentText()
            cond2_value = self.cond2_value_edit.text()
            cond2_color = self.cond2_color if hasattr(self, 'cond2_color') else None

            for row in self.table.rows[self.header_row_spin.value():]:
                try:
                    cell_text = row.cells[cond1_col].text.strip()
                    if cell_text and cond1_color: 
                        try:
                            cell_value = float(cell_text)
                            condition_met = self.evaluate_condition(cell_value, cond1_op, cond1_value)

                            if condition_met:
                                DocxTableEditor.set_background_color(row.cells[cond1_col], cond1_color)
                        except ValueError:
                            pass

                    if cond2_value and cond2_color:  
                        cell_text = row.cells[cond2_col].text.strip()
                        try:
                            cell_value = float(cell_text)
                            cond2_value_float = float(cond2_value)
                            condition_met = self.evaluate_condition(cell_value, cond2_op, cond2_value_float)
                        except ValueError:
                            condition_met = self.evaluate_text_condition(cell_text, cond2_op, cond2_value)

                        if condition_met:
                            DocxTableEditor.set_background_color(row.cells[cond2_col], cond2_color)

                except (ValueError, IndexError):
                    continue

            QMessageBox.information(self, "成功", "条件格式应用成功!")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"应用条件格式时出错:\n{str(e)}")

    def evaluate_condition(self, value, op, compare_value):
        if op == ">=":
            return value >= compare_value
        elif op == ">":
            return value > compare_value
        elif op == "=":
            return value == compare_value
        elif op == "<=":
            return value <= compare_value
        elif op == "<":
            return value < compare_value
        elif op == "<>":
            return value != compare_value
        return False

    def evaluate_text_condition(self, text, op, compare_text):
        if op == "=":
            return text == compare_text
        elif op == "<>":
            return text != compare_text
        return False

    def apply_border_and_background(self):
        if not self.table:
            QMessageBox.warning(self, "警告", "请先打开文件!")
            return

        try:
            border_style = self.border_style_combo.currentText()
            border_width = self.border_width_spin.value()
            border_color = self.border_color

            sides = []
            if self.border_sides_check[0].isChecked(): sides.append('start')
            if self.border_sides_check[1].isChecked(): sides.append('top')
            if self.border_sides_check[2].isChecked(): sides.append('end')
            if self.border_sides_check[3].isChecked(): sides.append('bottom')

            bg_range = self.bg_range_combo.currentText()
            bg_col = self.bg_col_spin.value() - 1  
            header_rows = self.header_row_spin.value()

            for i, row in enumerate(self.table.rows):
                for j, cell in enumerate(row.cells):
                    if sides:
                        border_settings = {}
                        for side in sides:
                            border_settings[side] = {
                                "val": border_style,
                                "sz": border_width,
                                "color": border_color
                            }
                        DocxTableEditor.set_cell_border(cell, **border_settings)

                    if self.bg_color:
                        apply_bg = False

                        if bg_range == "所有单元格":
                            apply_bg = True
                        elif bg_range == "表头" and i < header_rows:
                            apply_bg = True
                        elif bg_range == "数据行" and i >= header_rows:
                            apply_bg = True
                        elif bg_range == "特定列" and j == bg_col:
                            apply_bg = True

                        if apply_bg:
                            DocxTableEditor.set_background_color(cell, self.bg_color)

            QMessageBox.information(self, "成功", "边框和背景应用成功!")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"应用边框和背景时出错:\n{str(e)}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = TableFormatterApp()
    window.show()
    sys.exit(app.exec_())

四:代码分析:

1. DocxTableEditor 工具类

python 复制代码
class DocxTableEditor:
    """用于操作Word表格样式的工具类"""
    
    @staticmethod
    def set_cell_border(cell, **kwargs):
        """设置单元格边框样式
        
        Args:
            cell: 表格单元格对象
            **kwargs: 边框参数,如 start/top/end/bottom/insideH/insideV 等边的样式
                     每边参数格式: {'val': 'single', 'sz': 4, 'color': 'FF0000'}
        """
        tc = cell._tc
        tcPr = tc.get_or_add_tcPr()
        tcBorders = tcPr.first_child_found_in("w:tcBorders")
        
        # 如果不存在边框定义则创建
        if tcBorders is None:
            tcBorders = OxmlElement('w:tcBorders')
            tcPr.append(tcBorders)

        # 遍历所有指定的边
        for edge in ('start', 'top', 'end', 'bottom', 'insideH', 'insideV'):
            edge_data = kwargs.get(edge)
            if edge_data:
                tag = 'w:{}'.format(edge)
                element = tcBorders.find(qn(tag))
                
                # 如果不存在该边的定义则创建
                if element is None:
                    element = OxmlElement(tag)
                    tcBorders.append(element)

                # 设置边框属性
                for key in ["sz", "val", "color", "space", "shadow"]:
                    if key in edge_data:
                        element.set(qn('w:{}'.format(key)), str(edge_data[key]))

    @staticmethod
    def set_background_color(cell, rgb_color):
        """设置单元格背景色
        
        Args:
            cell: 表格单元格对象
            rgb_color: 颜色值,可以是QColor对象或十六进制字符串(带#或不带)
        """
        # 统一颜色格式为6位十六进制字符串
        if isinstance(rgb_color, QColor):
            rgb_color = rgb_color.name()[1:]  # 去掉QColor返回的#前缀
        elif isinstance(rgb_color, str) and rgb_color.startswith('#'):
            rgb_color = rgb_color[1:]
        rgb_color = f"{rgb_color:0>6}"  # 确保6位长度

        # 创建shading元素并设置填充色
        shading_elm = parse_xml(r'<w:shd {} w:fill="{color_value}"/>'.format(
            nsdecls('w'), color_value=rgb_color))
        cell._tc.get_or_add_tcPr().append(shading_elm)

    @staticmethod
    def set_cell_font(cell, font_name=None, font_size=None, bold=None, italic=None, color=None):
        """设置单元格内文字样式
        
        Args:
            cell: 表格单元格对象
            font_name: 字体名称
            font_size: 字体大小(pt)
            bold: 是否加粗
            italic: 是否斜体
            color: 字体颜色(QColor/十六进制字符串)
        """
        for paragraph in cell.paragraphs:
            for run in paragraph.runs:
                if font_name:
                    run.font.name = font_name
                if font_size:
                    run.font.size = Pt(font_size)
                if bold is not None:
                    run.font.bold = bold
                if italic is not None:
                    run.font.italic = italic
                if color:
                    # 处理不同颜色输入格式
                    if isinstance(color, QColor):
                        run.font.color.rgb = RGBColor(color.red(), color.green(), color.blue())
                    elif isinstance(color, str):
                        if color.startswith('#'):
                            color = color[1:]
                        try:
                            r = int(color[0:2], 16)
                            g = int(color[2:4], 16)
                            b = int(color[4:6], 16)
                            run.font.color.rgb = RGBColor(r, g, b)
                        except (ValueError, IndexError):
                            run.font.color.rgb = RGBColor(0, 0, 0)
                    else:
                        run.font.color.rgb = RGBColor(0, 0, 0)

2. 主窗口初始化 (TableFormatterApp 类)

python 复制代码
class TableFormatterApp(QMainWindow):
    """主应用程序窗口"""
    
    def __init__(self):
        super().__init__()
        self.setWindowTitle("设置Word文档表格格式")
        self.setGeometry(100, 100, 800, 600)
        self.doc = None  # 当前打开的Document对象
        self.table = None  # 当前操作的表格对象
        self.file_path = ""  # 当前文件路径
        
        # 初始化颜色标签(用于显示当前选择的颜色)
        self.font_color_label = QLabel("未选择")
        self.cond1_color_label = QLabel("未选择")
        self.cond2_color_label = QLabel("未选择")
        self.border_color_label = QLabel("未选择")
        self.bg_color_label = QLabel("未选择")
        
        self.init_ui()  # 初始化界面

    def init_ui(self):
        """初始化用户界面"""
        main_widget = QWidget()
        main_layout = QVBoxLayout()

        # ========== 文件操作区域 ==========
        file_group = QGroupBox("文件操作")
        file_layout = QHBoxLayout()
        self.file_label = QLabel("未选择文件")
        self.file_label.setAlignment(Qt.AlignCenter)
        
        open_btn = QPushButton("打开文件")
        open_btn.clicked.connect(self.open_file)
        
        save_btn = QPushButton("保存文件")
        save_btn.clicked.connect(self.save_file)
        
        file_layout.addWidget(self.file_label)
        file_layout.addWidget(open_btn)
        file_layout.addWidget(save_btn)
        file_group.setLayout(file_layout)

        # ========== 选项卡区域 ==========
        tab_widget = QTabWidget()
        
        # 基本格式选项卡
        basic_tab = self._create_basic_tab()
        # 条件格式选项卡
        cond_tab = self._create_cond_tab()
        # 边框和背景选项卡
        border_tab = self._create_border_tab()
        
        tab_widget.addTab(basic_tab, "基本格式")
        tab_widget.addTab(cond_tab, "条件格式")
        tab_widget.addTab(border_tab, "边框和背景")

        # ========== 主布局组装 ==========
        main_layout.addWidget(file_group)
        main_layout.addWidget(tab_widget)
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)

3. 核心功能方法

python 复制代码
def open_file(self):
    """打开Word文件并加载第一个表格"""
    file_path, _ = QFileDialog.getOpenFileName(self, "打开Word文件", "", "Word文件 (*.docx)")
    if file_path:
        self.file_path = file_path
        self.file_label.setText(file_path.split("/")[-1])
        try:
            self.doc = Document(file_path)
            if len(self.doc.tables) > 0:
                self.table = self.doc.tables[0]
                QMessageBox.information(self, "成功", "文件加载成功!")
            else:
                QMessageBox.warning(self, "警告", "文件中没有表格!")
                self.table = None
        except Exception as e:
            QMessageBox.critical(self, "错误", f"无法打开文件:\n{str(e)}")
            self.table = None

def save_file(self):
    """保存修改后的Word文件"""
    if not self.doc:
        QMessageBox.warning(self, "警告", "请先打开文件!")
        return

    if not self.file_path:
        save_path, _ = QFileDialog.getSaveFileName(self, "保存Word文件", "", "Word文件 (*.docx)")
        if not save_path:
            return
    else:
        save_path = self.file_path.replace(".docx", "-整理.docx")

    try:
        self.doc.save(save_path)
        QMessageBox.information(self, "成功", f"文件已保存到:\n{save_path}")
    except Exception as e:
        QMessageBox.critical(self, "错误", f"保存文件失败:\n{str(e)}")

def apply_basic_formatting(self):
    """应用基本格式设置到表格"""
    if not self.table:
        QMessageBox.warning(self, "警告", "请先打开文件!")
        return

    try:
        # 获取对齐方式设置
        align_map = {"居中": WD_ALIGN_PARAGRAPH.CENTER, 
                    "左对齐": WD_ALIGN_PARAGRAPH.LEFT,
                    "右对齐": WD_ALIGN_PARAGRAPH.RIGHT}
        valign_map = {"居中": WD_ALIGN_VERTICAL.CENTER, 
                     "顶部": WD_ALIGN_VERTICAL.TOP, 
                     "底部": WD_ALIGN_VERTICAL.BOTTOM}

        align = align_map[self.align_combo.currentText()]
        valign = valign_map[self.valign_combo.currentText()]
        
        # 获取字体设置
        font_name = self.font_name_combo.currentText() if self.font_name_combo.currentText() != "默认字体" else None
        font_size = self.font_size_spin.value() if self.font_size_spin.value() > 0 else None
        bold = self.bold_check.isChecked()
        italic = self.italic_check.isChecked()
        header_rows = self.header_row_spin.value()
        default_row_height = self.row_height_spin.value()

        # 遍历表格应用设置
        for i, row in enumerate(self.table.rows):
            # 设置行高(表头行固定1cm,其他行使用指定磅值)
            if i < header_rows:
                row.height = Cm(1)
            else:
                row.height = Pt(default_row_height)

            # 设置单元格格式
            for cell in row.cells:
                # 设置段落对齐
                for paragraph in cell.paragraphs:
                    paragraph.alignment = align
                
                # 设置垂直对齐
                cell.vertical_alignment = valign
                
                # 设置字体
                DocxTableEditor.set_cell_font(
                    cell,
                    font_name=font_name,
                    font_size=font_size,
                    bold=bold,
                    italic=italic,
                    color=getattr(self, 'font_color', None)  # 使用getattr避免未定义属性错误
                )

        QMessageBox.information(self, "成功", "基本格式应用成功!")
    except Exception as e:
        QMessageBox.critical(self, "错误", f"应用基本格式时出错:\n{str(e)}")

def apply_condition_formatting(self):
    """应用条件格式到表格"""
    if not self.table:
        QMessageBox.warning(self, "警告", "请先打开文件!")
        return

    try:
        # 获取条件1设置
        cond1_col = self.cond1_col_spin.value() - 1  # 转换为0-based索引
        cond1_op = self.cond1_op_combo.currentText()
        cond1_value = self.cond1_value_spin.value()
        cond1_color = getattr(self, 'cond1_color', None)
        
        # 获取条件2设置
        cond2_col = self.cond2_col_spin.value() - 1
        cond2_op = self.cond2_op_combo.currentText()
        cond2_value = self.cond2_value_edit.text()
        cond2_color = getattr(self, 'cond2_color', None)

        # 跳过表头行
        for row in self.table.rows[self.header_row_spin.value():]:
            try:
                # 处理条件1(数值比较)
                cell_text = row.cells[cond1_col].text.strip()
                if cell_text and cond1_color:
                    try:
                        cell_value = float(cell_text)
                        if self.evaluate_condition(cell_value, cond1_op, cond1_value):
                            DocxTableEditor.set_background_color(row.cells[cond1_col], cond1_color)
                    except ValueError:
                        pass  # 非数值内容跳过

                # 处理条件2(支持文本比较)
                if cond2_value and cond2_color:
                    cell_text = row.cells[cond2_col].text.strip()
                    try:
                        # 尝试数值比较
                        cell_value = float(cell_text)
                        cond2_value_float = float(cond2_value)
                        condition_met = self.evaluate_condition(cell_value, cond2_op, cond2_value_float)
                    except ValueError:
                        # 回退到文本比较
                        condition_met = self.evaluate_text_condition(cell_text, cond2_op, cond2_value)

                    if condition_met:
                        DocxTableEditor.set_background_color(row.cells[cond2_col], cond2_color)

            except (ValueError, IndexError):
                continue  # 跳过格式错误的单元格

        QMessageBox.information(self, "成功", "条件格式应用成功!")
    except Exception as e:
        QMessageBox.critical(self, "错误", f"应用条件格式时出错:\n{str(e)}")

def evaluate_condition(self, value, op, compare_value):
    """数值条件判断"""
    if op == ">=":
        return value >= compare_value
    elif op == ">":
        return value > compare_value
    elif op == "=":
        return value == compare_value
    elif op == "<=":
        return value <= compare_value
    elif op == "<":
        return value < compare_value
    elif op == "<>":
        return value != compare_value
    return False

def evaluate_text_condition(self, text, op, compare_text):
    """文本条件判断"""
    if op == "=":
        return text == compare_text
    elif op == "<>":
        return text != compare_text
    return False

def apply_border_and_background(self):
    """应用边框和背景设置"""
    if not self.table:
        QMessageBox.warning(self, "警告", "请先打开文件!")
        return

    try:
        # 获取边框设置
        border_style = self.border_style_combo.currentText()
        border_width = self.border_width_spin.value()
        border_color = getattr(self, 'border_color', None)
        
        # 获取要设置边框的边
        sides = []
        for idx, check in enumerate(self.border_sides_check):
            if check.isChecked():
                side_map = ['start', 'top', 'end', 'bottom']
                sides.append(side_map[idx])

        # 获取背景设置
        bg_range = self.bg_range_combo.currentText()
        bg_col = self.bg_col_spin.value() - 1
        header_rows = self.header_row_spin.value()

        # 遍历表格应用设置
        for i, row in enumerate(self.table.rows):
            for j, cell in enumerate(row.cells):
                # 应用边框
                if sides and border_color:
                    border_settings = {}
                    for side in sides:
                        border_settings[side] = {
                            "val": border_style,
                            "sz": border_width,
                            "color": border_color
                        }
                    DocxTableEditor.set_cell_border(cell, **border_settings)

                # 应用背景色
                if hasattr(self, 'bg_color') and self.bg_color:
                    apply_bg = False
                    
                    # 判断是否在指定范围内
                    if bg_range == "所有单元格":
                        apply_bg = True
                    elif bg_range == "表头" and i < header_rows:
                        apply_bg = True
                    elif bg_range == "数据行" and i >= header_rows:
                        apply_bg = True
                    elif bg_range == "特定列" and j == bg_col:
                        apply_bg = True

                    if apply_bg:
                        DocxTableEditor.set_background_color(cell, self.bg_color)

        QMessageBox.information(self, "成功", "边框和背景应用成功!")
    except Exception as e:
        QMessageBox.critical(self, "错误", f"应用边框和背景时出错:\n{str(e)}")
相关推荐
p***c94932 分钟前
PHP在电商中的电商系统
开发语言·php
Z***258039 分钟前
JavaScript在Node.js中的Deno
开发语言·javascript·node.js
花酒锄作田1 小时前
[python]FastAPI-Tracking ID 的设计
python·fastapi
AI-智能1 小时前
别啃文档了!3 分钟带小白跑完 Dify 全链路:从 0 到第一个 AI 工作流
人工智能·python·自然语言处理·llm·embedding·agent·rag
a***56061 小时前
Windows上安装Go并配置环境变量(图文步骤)
开发语言·windows·golang
San30.1 小时前
ES6+ 新特性解析:让 JavaScript 开发更优雅高效
开发语言·javascript·es6
烤麻辣烫2 小时前
黑马程序员苍穹外卖(新手)DAY6
java·开发语言·学习·spring·intellij-idea
d***95622 小时前
爬虫自动化(DrissionPage)
爬虫·python·自动化
友友马2 小时前
『QT』窗口 (一)
开发语言·数据库·qt