简体   繁体   中英

Apply borders to all cells in a range with openpyxl

I have a script that takes a pandas dataframe and chops it up into several hundred chunks and saves each chunk as a separate excel file. Each chunk will have the same number of columns but the number of rows varies. I've figured out how to apply all the other necessary formatting to these files with openpyxl, but I haven't yet determined the fastest way to apply borders. Also, I think I'm just not applying borders correctly, because the code below (which I suspect shouldn't need to loop over each cell individually) doesn't apply any borders.

from openpyxl.style import Border

wb = load_workbook(filename = _fname)
ws = wb.worksheets[0]  
for _row in ws.range('A1:L'+str(ws.get_highest_row() ) ):
    for _cell in _row:
            _cell.style.borders.left.border_style = Border.BORDER_THIN
            _cell.style.borders.right.border_style = Border.BORDER_THIN
            _cell.style.borders.top.border_style = Border.BORDER_THIN
            _cell.style.borders.bottom.border_style = Border.BORDER_THIN
wb.save(_fname)

So this code works , but it doesn't apply the border I expect (the default border in excel) and it takes a lot more steps than I'd prefer. My expectation is that I should be able to do something like this:

from openpyxl.style import Border

wb = load_workbook(filename = _fname)
ws = wb.worksheets[0]

_range = ws.some_range_func('A1:L'+str(ws.get_highest_row() ) ):
    _range.style.borders.all_borders = Borders.BORDER_THIN

Does this functionality exist? If not, can someone please be so kind as to at least explain how to apply the default border style and not this slightly thicker border? None of Border.BORDER_THICK, Border.BORDER_MEDIUM, Border.BORDER_THIN, or Border.BORDER_HAIR seem correct.

Thanks!

In more pythonic way for openpyxl==3.0.5:

from openpyxl.styles import Border, Side

def set_border(ws, cell_range):
    thin = Side(border_style="thin", color="000000")
    for row in ws[cell_range]:
        for cell in row:
            cell.border = Border(top=thin, left=thin, right=thin, bottom=thin)

set_border(worksheet, 'A5:C10') 

May be this is handy:

from openpyxl.reader.excel import load_workbook
from openpyxl.style import Border

def set_border(ws, cell_range):
    rows = was[cell_range]
    for row in rows:
        row[0].style.borders.left.border_style = Border.BORDER_THIN
        row[-1].style.borders.right.border_style = Border.BORDER_THIN
    for c in rows[0]:
        c.style.borders.top.border_style = Border.BORDER_THIN
    for c in rows[-1]:
        c.style.borders.bottom.border_style = Border.BORDER_THIN

#usage example:
ws = load_workbook('example.xlsx').get_active_sheet()
set_broder(ws, "C3:H10")

It performs reasonably fast.

There is a slight modification to answer from @Karimov
Below is how your code should be

from openpyxl.styles import Border, Side, Font, Alignment
def __format_ws__(self, ws, cell_range):
     border = Border(left=Side(border_style='thin', color='000000'),
                    right=Side(border_style='thin', color='000000'),
                    top=Side(border_style='thin', color='000000'),
                    bottom=Side(border_style='thin', color='000000'))

      rows = ws[cell_range]
      for row in rows:
          for cell in row:
              cell.border = border

A much faster way that uses list comprehension is below:

 def __format_ws__(self, ws, cell_range):

        #applying border and alignment
        font = Font(size=9)
        align=Alignment(horizontal='left', vertical='center')
        border = Border(left=Side(border_style='thin', color='000000'),
                        right=Side(border_style='thin', color='000000'),
                        top=Side(border_style='thin', color='000000'),
                        bottom=Side(border_style='thin', color='000000'))

        rows = [rows for rows in ws[cell_range]]
        flattened = [item for sublist in rows for item in sublist]
        [(setattr(cell,'border',border), setattr(cell,'font',font), setattr(cell,'alignment',align)) for cell in flattened]

The way you use it is:

self.__format_ws__(ws=writer.book.worksheets[0], cell_range='A1:G10')

Decision that works on openpyxl 2.3.5

from openpyxl.styles import Border, Side

def set_border(ws, cell_range):
    border = Border(left=Side(border_style='thin', color='000000'),
                right=Side(border_style='thin', color='000000'),
                top=Side(border_style='thin', color='000000'),
                bottom=Side(border_style='thin', color='000000'))

    rows = ws.iter_rows(cell_range)
    for row in rows:
        for cell in row:
            cell.border = border

set_border(worksheet, 'A5:C10')

@user698585 your approach seems nice but it doesn't work anymore as the present version of the openpyxl change the implementation. So this should be updated into eg

    ws.cell(row=1, column=1).style.border.top.border_style = borders.BORDER_MEDIUM

but it results with an error that changing the style is not allowed. As a workaround I just defined a dedicated styles, but they are just a duplication of the present styles plus border definition - not so good solution as works only if you know what style has the cell under the change.

    border_style = Style(font=Font(name='Console', size=10, bold=False,
                         color=Color(openpyxl.styles.colors.BLACK)),
                         fill=PatternFill(patternType='solid', fgColor=Color(rgb='00C5D9F1')),
                         border=Border(bottom=Side(border_style='medium', color=Color(rgb='FF000000'))))

if you need styling (borders...) for pandas excel dataframe my fork just got merged into master https://github.com/pydata/pandas/pull/2370#issuecomment-10898427

as for you borders problems. setting all borders at once does not seam to work in openpyxl.

In [34]: c.style.borders.all_borders.border_style = openpyxl.style.Border.BORDER_THIN

In [36]: c.style
'Calibri':11:False:False:False:False:'none':False:'FF000000':'none':0:'FFFFFFFF':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':0:'thin':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':'general':'bottom':0:False:False:0:'General':0:'inherit':'inherit'

setting individually works ('thin':'FF000000')

In [37]: c.style.borders.top.border_style = openpyxl.style.Border.BORDER_THIN

In [38]: c.style
Out[38]: 'Calibri':11:False:False:False:False:'none':False:'FF000000':'none':0:'FFFFFFFF':'FF000000':'none':'FF000000':'none':'FF000000':'thin':'FF000000':'none':'FF000000':'none':'FF000000':0:'thin':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':'general':'bottom':0:False:False:0:'General':0:'inherit':'inherit'

maybe a bug in openpyxl. but no big deal just wrap setting bottom, top, left, right in function

Had the same issue but couldn't find anything that fixes this problem for 2019 because of depreciation. I have something that works below.. could be better but works for all intends and purposes.

def set_border(ws, cell_range):
    rows = ws[cell_range]
    for row in rows:
        if row == rows[0][0] or row == rows[0][-1] or row == rows[-1][0] or row == rows[-1][-1]:
            pass
        else:
            row[0].border = Border(left=Side(style='thin'))
            row[-1].border = Border(right=Side(style='thin'))
        for c in rows[0]:
            c.border = Border(top=Side(style='thin'))
        for c in rows[-1]:
            c.border = Border(bottom=Side(style='thin'))
    rows[0][0].border = Border(left=Side(style='thin'), top=Side(style='thin'))
    rows[0][-1].border = Border(right=Side(style='thin'), top=Side(style='thin'))
    rows[-1][0].border = Border(left=Side(style='thin'), bottom=Side(style='thin'))
    rows[-1][-1].border = Border(right=Side(style='thin'), bottom=Side(style='thin'))
def set_border(ws, cell_range, style='thin'):
rows = ws[cell_range]
for row in rows:
    temp_row = copy(row[0].border)
    row[0].border = Border(left=Side(style=style), right=temp_row.right, top=temp_row.top, bottom=temp_row.bottom)

    temp_row = copy(row[-1].border)
    row[-1].border = Border(right=Side(style=style), left=temp_row.left, top=temp_row.top, bottom=temp_row.bottom)
for c in rows[0]:
    temp_row = copy(c.border)
    c.border = Border(top=Side(style=style), left=temp_row.left, bottom=temp_row.bottom, right=temp_row.right)
for c in rows[-1]:
    temp_row = copy(c.border)
    c.border = Border(bottom=Side(style=style), left=temp_row.left, top=temp_row.top, right=temp_row.right)

This keeps the existing borders of the side and you can also style your border

seems there is no built-in for this task, and we have to make some steps ourselves, like:

#need make conversion from alphabet to number due to range function
def A2N(s,e):
    return range(ord(s), ord(e)+1)
#B1 is the border you defined
#Assume you trying border A1-Q1 ... A3-Q3
X = A2N('A','Q')
#print X    
your_desired_sheet_range_rows = range(1,4)
#need two loop to go through cells
for row in your_desired_sheet_rows:
    for col in X:
        ca = chr(col)
        sheet[ca+str(row)].border=B1

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM