在 Openpyxl (Python) 中绘制边框而不覆盖之前的边框

问题描述 投票:0回答:5

我最近在一个项目中使用了 openpyxl 3.0.6,想要在 Excel 2016 中自动绘制一些复杂的表格并调整其大小。一切都很顺利,直到我尝试勾勒出一系列单元格的轮廓,并且之前的边框被覆盖。我的问题与覆盖先前的边框有关。比如说

import openpyxl

workbook = openpyxl.load_workbook("Borders Test.xlsx")
sheet = workbook.active
cell = sheet["B6"]

border = openpyxl.styles.borders.Side(style = "thick")

# Draw a border on top of cell B6 #
cell.border = openpyxl.styles.Border(top = border)

# Draw a border on the bottom of cell B6, overwrites the above border #
cell.border = openpyxl.styles.Border(bottom = border)
workbook.save("Borders Test.xlsx")
workbook.close()

只会在“B6”的底部设置边框,因为这是最后一个,但我想要的输出是单元格 B6,顶部和底部都有边框。注意,在围绕某个范围绘制边框的情况下,我们可以在外侧绘制边框,因此将 B5 的下边框和 B7 的上边框设置为粗即可得到所需的结果,但这并不能解决覆盖问题问题。我也尝试修改“cell.border”这样

cell.border.left = openpyxl.styles.Border(left = border)

但我收到错误消息:

'AttributeError:样式对象是不可变的,无法更改。 用副本重新指定样式'

虽然这是一个选项,但有点糟糕,我想看看是否有更好的方法。

我在 Windows 10 中使用 64 位 python,如果这很重要,则使用 python 3.7.8。

提前感谢您的帮助,希望我的第一篇文章不会太糟糕!

python excel openpyxl
5个回答
1
投票

我设法复制当前边框。

例如,如果您只想更改单元格 H10 的左边框:

cell = worksheet['H10']

# copy other cell borders
right_s = Side(**d.border.right.__dict__)
top_s = Side(**d.border.top.__dict__)
bottom_s = Side(**d.border.bottom.__dict__)

# set style of left border
left_s = Side(border_style="thin", color="000000")

# reassign border
border = Border(left=left_s, right=right_s, top=top_s, bottom=bottom_s)
cell.border = border

1
投票

这是我对这个问题的解决方案:

  • 获取现有边框属性

  • 仅在属性中定义的侧面替换它们。

    def add_border(c, b, overwrite=True):
       """Add a border to a cell.
    
       :param c: the cell to apply the new border
       :param b: the new border of type openpyxl.styles.borders.Border
       :param overwrite: (OPTIONAL) remove existing borders on sides not defined in b (default True)
       """
    
       def get_border_attr(b):
           return {
              'left': getattr(b, 'left'),
              'right': getattr(b, 'right'),
              'top': getattr(b, 'top'),
              'bottom': getattr(b, 'bottom'),
          }
    
       if overwrite:
           c.border = b
       else:
           saved_border = get_border_attr(c.border)
           new_border = get_border_attr(b)
           c.border = Border(
               left = new_border['left'] if new_border['left'] else saved_border['left'],
               right = new_border['right'] if new_border['right'] else saved_border['right'],
               top = new_border['top'] if new_border['top'] else saved_border['top'],
               bottom = new_border['bottom'] if new_border['bottom'] else saved_border['bottom'],
          )
    

然后,您可以在代码中使用此函数在一系列单元格周围添加一条粗线,并在顶部单元格的底部添加一条细线:

import openpyxl
from openpyxl.styles import Side, Border

wb = openpyxl.Workbook()
ws = wb.create_sheet("My sheet")

thin = Side(border_style="thin", color="000000")
thick = Side(border_style="thick", color="000000")

# Not directly related to the question but nice to have
def set_cell_range_border(ws, cell_range, border_style, overwrite=True):
    rows = ws[cell_range]
    for row in rows:
        add_border(row[0], Border(left=border_style), overwrite)
        add_border(row[-1], Border(right=border_style), overwrite)
    for c in rows[0]:
        add_border(c, Border(top=border_style), overwrite)
    for c in rows[-1]:
        add_border(c, Border(bottom=border_style), overwrite)
    add_border(rows[0][0], Border(left=border_style, top=border_style), overwrite)
    add_border(rows[0][-1], Border(right=border_style, top=border_style), overwrite)
    add_border(rows[-1][0], Border(left=border_style, bottom=border_style), overwrite)
    add_border(rows[-1][-1], Border(right=border_style, bottom=border_style), overwrite)

# Apply thick border around A1:E5
set_cell_range_border(ws, 'A1:E5', thick)

# Add a thin line under the top row cells without removing the thick borders previously set
for c in ws['A1:E1'][0]
     add_border(c, Border(bottom=thin), overwrite=False)

0
投票

对边界的两项更改应同时进行。 Border 可以采用一个元组,这样可以避免设置边框然后覆盖它。

代替:

# Draw a border on top of cell B6 #
cell.border = openpyxl.styles.Border(top = border)

# Draw a border on the bottom of cell B6, overwrites the above border #
cell.border = openpyxl.styles.Border(bottom = border)

替换为:

cell.border = openpyxl.styles.Border(top = border, bottom = border)

0
投票

虽然艾伦说可以这样做是正确的,但我的目标(我表达得不好)是能够在第一次作业后回来进行编辑,而不丢失之前的边框。这是我提出的一个丑陋的解决方案:

__elements__ = ('left', 'right', 'top', 'bottom')
def drawSafely(cell, __side = Thick_side, _where = "left"):
   ### Overwrites only the value at _where
   kwargs = {element:getattr(cell.border,element) for element in __elements__}
   kwargs[_where] = __side
   border = openpyxl.styles.Border(**kwargs)
   cell.border = border

其中单元格与问题陈述中相同,“Thick_side”的类型为“openpyxl.styles.borders.Side”。这只是将旧数据保存在字典中,更新字典,重新制作边框,并将该值分配给 cell.border。另外,“_where”必须位于

openpyxl.styles.borders.Side.__elements__

(而不是我的解决方案中的那个,它只是一个子集)。再说一次,可能会更好,但它确实有效。


0
投票

我没有找到@Romn 的工作答案。这是一个对我有用的代码片段。它确实使用了OP确实提到的“会很糟糕”的“复制”,但是它很短并且可以完成工作。

def add_border(cell: Cell, left: Union[Side,None]=None, right: Union[Side,None]=None, top: Union[Side,None]=None, bottom: Union[Side,None]=None) -> None:

    border_copy: Border = copy(cell.border)

    if left:
        border_copy.left = left
    if right:
        border_copy.right = right
    if top:
        border_copy.top = top
    if bottom:
        border_copy.bottom = bottom
    
    cell.border = border_copy

它只会覆盖给定的面。

© www.soinside.com 2019 - 2024. All rights reserved.