今回はセルに罫線をセットします。初めに単一のセルに罫線を引くプログラムを作り、次回にセル範囲を対象に罫線を引くプログラムを作ります。


C:\Users\Public\Documents\openxmlsdk16.xlsx

C列, G列、I列のセルに罫線を引くサンプルです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;

namespace OpenXMLSDK_Sample9 {
    class Program {
        static void Main(string[] args) {
            const string FILE = @"C:\Users\Public\Documents\openxmlsdk16.xlsx";
            const string SHEET_NAME = "Sheet1";

            using (SpreadsheetDocument excelDoc = SpreadsheetDocument.Open(FILE, true)) {
                WorkbookPart bookPart = excelDoc.WorkbookPart;
                int sheet_index = GetWorksheetIndex(bookPart, SHEET_NAME);
                WorksheetPart sheet_part = GetWorksheetPart(bookPart, sheet_index);

                //罫線を引く
                Border BorderC2 = new Border();
                BorderC2.TopBorder = new TopBorder() { Style = BorderStyleValues.Thin };
                AppendCellBorder(sheet_part, "C2", BorderC2);

                Border BorderC3 = new Border();
                BorderC3.RightBorder = new RightBorder() { Style = BorderStyleValues.Thin };
                AppendCellBorder(sheet_part, "C3", BorderC3);

                Border BorderC4 = new Border();
                BorderC4.BottomBorder = new BottomBorder() { Style = BorderStyleValues.Thin };
                AppendCellBorder(sheet_part, "C4", BorderC4);

                Border BorderC5 = new Border();
                BorderC5.LeftBorder = new LeftBorder() { Style = BorderStyleValues.Thin };
                AppendCellBorder(sheet_part, "C5", BorderC5);

                Border BorderC6 = new Border();
                BorderC6.DiagonalBorder = new DiagonalBorder() { Style = BorderStyleValues.Thin };
                BorderC6.DiagonalUp = new BooleanValue(true);
                BorderC6.DiagonalDown = new BooleanValue(false);
                AppendCellBorder(sheet_part, "C6", BorderC6);

                Border BorderC7 = new Border();
                BorderC7.DiagonalBorder = new DiagonalBorder() { Style = BorderStyleValues.Thin };
                BorderC7.DiagonalUp = new BooleanValue(false);
                BorderC7.DiagonalDown = new BooleanValue(true);
                AppendCellBorder(sheet_part, "C7", BorderC7);

                Border BorderC8 = new Border();
                BorderC8.DiagonalBorder = new DiagonalBorder() { Style = BorderStyleValues.Thin };
                BorderC8.DiagonalUp = new BooleanValue(true);
                BorderC8.DiagonalDown = new BooleanValue(true);
                AppendCellBorder(sheet_part, "C8", BorderC8);

                //罫線を追加
                Border BorderG2 = new Border();
                BorderG2.TopBorder = new TopBorder() { Style = BorderStyleValues.Medium };
                AppendCellBorder(sheet_part, "G2", BorderG2);

                Border BorderG3 = new Border();
                BorderG3.RightBorder = new RightBorder() { Style = BorderStyleValues.Medium };
                AppendCellBorder(sheet_part, "G3", BorderG3);

                Border BorderG4 = new Border();
                BorderG4.BottomBorder = new BottomBorder() { Style = BorderStyleValues.Medium };
                AppendCellBorder(sheet_part, "G4", BorderG4);

                Border BorderG5 = new Border();
                BorderG5.LeftBorder = new LeftBorder() { Style = BorderStyleValues.Medium };
                AppendCellBorder(sheet_part, "G5", BorderG5);

                Border BorderG6 = new Border();
                BorderG6.DiagonalBorder = new DiagonalBorder() { Style = BorderStyleValues.Medium };
                BorderG6.DiagonalUp = new BooleanValue(true);
                BorderG6.DiagonalDown = new BooleanValue(false);
                AppendCellBorder(sheet_part, "G6", BorderG6);

                Border BorderG7 = new Border();
                BorderG7.DiagonalBorder = new DiagonalBorder() { Style = BorderStyleValues.Medium };
                BorderG7.DiagonalUp = new BooleanValue(false);
                BorderG7.DiagonalDown = new BooleanValue(true);
                AppendCellBorder(sheet_part, "G7", BorderG7);

                Border BorderG8 = new Border();
                BorderG8.DiagonalBorder = new DiagonalBorder() { Style = BorderStyleValues.Medium };
                BorderG8.DiagonalUp = new BooleanValue(true);
                BorderG8.DiagonalDown = new BooleanValue(true);
                AppendCellBorder(sheet_part, "G8", BorderG8);

                //くねくね
                for (int i = 0; i < 8; i++) {
                    Border border = new Border();
                    if (i % 2 == 0) {
                        border.RightBorder = new RightBorder() { Style = BorderStyleValues.Thick, Color = new Color() { Rgb = new HexBinaryValue("FFFF0000") } };
                    } else {
                        border.LeftBorder = new LeftBorder() { Style = BorderStyleValues.Thick, Color = new Color() { Rgb = new HexBinaryValue("FF0000FF") } };
                    }
                    border.BottomBorder = new BottomBorder() { Style = BorderStyleValues.Thick, Color = new Color() { Rgb = new HexBinaryValue("FF00FF00") } };

                    AppendCellBorder(sheet_part, string.Format("I{0}", 2 + i), border);
                }

            }
            //ファイルを開く
            System.Diagnostics.Process.Start(FILE);
        }

        public static void AppendCellBorder(WorksheetPart target_ws_part, string cell_address, Border border) {
            Cell cell = GetCell(target_ws_part, cell_address);
            WorkbookPart bookPart = target_ws_part.GetParentParts().First() as WorkbookPart;

            CellFormat cell_format;
            if (cell.StyleIndex == null) {
                cell_format = new CellFormat();
            } else {
                cell_format = bookPart.WorkbookStylesPart.Stylesheet.CellFormats.ElementAt((int)cell.StyleIndex.Value) as CellFormat;
            }

            Border merged_border;
            if (cell_format.BorderId == null) {
                merged_border = border;
            } else {
                Border current_border = bookPart.WorkbookStylesPart.Stylesheet.Borders.ElementAt((int)cell_format.BorderId.Value) as Border;
                merged_border = MergeBorder(current_border, border);
            }

            Border same_border = bookPart.WorkbookStylesPart.Stylesheet.Borders.FirstOrDefault(b => b.OuterXml == merged_border.OuterXml) as Border;
            int border_id;
            if (same_border == null) {
                bookPart.WorkbookStylesPart.Stylesheet.Borders.Append(merged_border);
                bookPart.WorkbookStylesPart.Stylesheet.Borders.Count++; //Count属性を更新
                border_id = bookPart.WorkbookStylesPart.Stylesheet.Borders.Count() - 1;
            } else {
                border_id = bookPart.WorkbookStylesPart.Stylesheet.Borders.ToList().IndexOf(same_border);
            }

            //border_idを持ち、かつその他の属性が「cell_formatのborder以外」と一致するようなCellFormatを探す
            IEnumerable<Cellformat> cell_format_enumerable = bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Elements<Cellformat>();

            CellFormat target_format = cell_format_enumerable.FirstOrDefault(xf =>
                xf.GetAttributes().Where(at => at.LocalName != "borderId").SequenceEqual(cell_format.GetAttributes().Where(at => at.LocalName != "borderId"))
                && (xf.BorderId != null && xf.BorderId == border_id) && xf.InnerXml == cell_format.InnerXml);

            if (target_format == null) {
                target_format = cell_format.CloneNode(true) as CellFormat;
                if (target_format.BorderId == null) {
                    target_format.BorderId = new UInt32Value((uint)border_id);
                } else {
                    target_format.BorderId.Value = (uint)border_id;
                }

                bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Append(target_format);
                bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Count++;

                cell.StyleIndex = new UInt32Value((uint)bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Count() - 1);
            } else {
                cell.StyleIndex = new UInt32Value((uint)(cell_format_enumerable.ToList().IndexOf(target_format)));
            }
        }

        private static Border MergeBorder(Border border_modified, Border border_append) {
            Border merged_border = border_modified.CloneNode(true) as Border;

            //TopBorder
            if (border_append.TopBorder != null && border_append.TopBorder.HasAttributes) {
                merged_border.TopBorder = border_append.TopBorder.CloneNode(true) as TopBorder;
            }

            //LeftBorder
            if (border_append.LeftBorder != null && border_append.LeftBorder.HasAttributes) {
                merged_border.LeftBorder = border_append.LeftBorder.CloneNode(true) as LeftBorder;
            }

            //BottomBorder
            if (border_append.BottomBorder != null && border_append.BottomBorder.HasAttributes) {
                merged_border.BottomBorder = border_append.BottomBorder.CloneNode(true) as BottomBorder;
            }

            //RightBorder
            if (border_append.RightBorder != null && border_append.RightBorder.HasAttributes) {
                merged_border.RightBorder = border_append.RightBorder.CloneNode(true) as RightBorder;
            }

            //DiagonalBorder
            if (border_append.DiagonalBorder != null && border_append.DiagonalBorder.HasAttributes) {
                merged_border.DiagonalBorder = border_append.DiagonalBorder.CloneNode(true) as DiagonalBorder;
            }

            //Diagonal attribute
            if (border_append.DiagonalDown != null) {
                merged_border.DiagonalDown = border_append.DiagonalDown.Clone() as BooleanValue;
            }

            if (border_append.DiagonalUp != null) {
                merged_border.DiagonalUp = border_append.DiagonalUp.Clone() as BooleanValue;
            }

            return merged_border;
        }
    // 以下省略...
    }
}

「以下省略」とした部分に定義されているメソッドについては「セルの書式設定 - 表示形式」など、以前の記事をご覧ください。

罫線を引くセルが多いのでサンプルプログラムが長くなっていますが、「引きたいBorderを作って、指定されたセルの罫線に追加」という処理を繰り返しているだけです。

「引きたいBorderを作って、指定されたセルの罫線に追加」するために今回AppendCellBorderを定義します。(108行目~159行目)

セルのStyleIndexから現在のCellFormatを取得します(112行目~117行目)。

CellFormat cell_format;
if (cell.StyleIndex == null) {
    cell_format = new CellFormat();
} else {
    cell_format = bookPart.WorkbookStylesPart.Stylesheet.CellFormats.ElementAt((int)cell.StyleIndex.Value) as CellFormat;
}

つづいてセットするBorderを準備し(詳しくは後述)、Border以外の書式を保持するために「セットするBorder以外が現在の書式と一致する」ようなCellFormatを探します(139行目~143行目)。

//border_idを持ち、かつその他の属性が「cell_formatのborder以外」と一致するようなCellFormatを探す
IEnumerable<Cellformat> cell_format_enumerable = bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Elements<Cellformat>();
 
CellFormat target_format = cell_format_enumerable.FirstOrDefault(xf =>
    xf.GetAttributes().Where(at => at.LocalName != "borderId").SequenceEqual(cell_format.GetAttributes().Where(at => at.LocalName != "borderId"))
    && (xf.BorderId != null && xf.BorderId == border_id) && xf.InnerXml == cell_format.InnerXml);

もし見つかればそのスタイルをセット、見つからなければ新たにCellFormatを定義します(145行目~159行目)。

if (target_format == null) {
    target_format = cell_format.CloneNode(true) as CellFormat;
    if (target_format.BorderId == null) {
        target_format.BorderId = new UInt32Value((uint)border_id);
    } else {
        target_format.BorderId.Value = (uint)border_id;
    }
 
    bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Append(target_format);
    bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Count++;
 
    cell.StyleIndex = new UInt32Value((uint)bookPart.WorkbookStylesPart.Stylesheet.CellFormats.Count() - 1);
} else {
    cell.StyleIndex = new UInt32Value((uint)(cell_format_enumerable.ToList().IndexOf(target_format)));
}

処理の流れとしては、前回の記事「セルの書式設定 - セルの背景色」とほぼ同じです。

背景色の代わりに罫線をセットする(119行目~136行目)のですが、引数に指定したborderをそのまま適用すると、元々引かれていた罫線と置き換わってしまいます。

そこで元々引かれていた罫線に引数borderを反映する(上書きする)よう、MergeBorderを定義して元々の罫線を生かすように工夫しています。

Border merged_border;
if (cell_format.BorderId == null) {
    merged_border = border;
} else {
    Border current_border = bookPart.WorkbookStylesPart.Stylesheet.Borders.ElementAt((int)cell_format.BorderId.Value) as Border;
    merged_border = MergeBorder(current_border, border);
}

こうして合成したmerged_borderが既に定義されていればそのidを、定義されていなければ新たに作成します。(127行目~136行目)

Border same_border = bookPart.WorkbookStylesPart.Stylesheet.Borders.FirstOrDefault(b => b.OuterXml == merged_border.OuterXml) as Border;
int border_id;
if (same_border == null) {
    bookPart.WorkbookStylesPart.Stylesheet.Borders.Append(merged_border);
    bookPart.WorkbookStylesPart.Stylesheet.Borders.Count++; //Count属性を更新
    border_id = bookPart.WorkbookStylesPart.Stylesheet.Borders.Count() - 1;
} else {
    border_id = bookPart.WorkbookStylesPart.Stylesheet.Borders.ToList().IndexOf(same_border);
}

このコードを実行すると、C列、G列、I列のセルに以下のような罫線が引かれます。G列のセルに元々ひかれていた右罫線はこのプログラムによって上書きされないことを確認してください。

次回はセル範囲を指定してセルの罫線を引くコードに取り組みます。