Search code examples
c#.netopenxmlopenxml-sdk

Add Columns to Existing Excel 2007 workbook using Open Xml


I have a predefined Excel workbook with all sheets in place and I need to write content to it. I succesfully write to cells.

The problem is in a particular worksheet that i need to add three columns to it. In the code bellow, first i'm grabbing the Worksheet and then i proceed to add columns. This code runs fine, i mean, no exception is thrown, but then I get an error when I try to open the Excel file, stating that there are some content that cannot be read and all the content of this particular worksheet is cleared.

I know that the problem is with this operation because if I comment out those lines that add columns, the workbook opens just fine with all the cells values I write from code in place.

This is the relevant code, for testing purpose I'm trying to add 3 columns:

using (SpreadsheetDocument document = SpreadsheetDocument.Open(outputPath, true)){
        Sheet sheet2 = document.WorkbookPart.Workbook.Descendants<Sheet>().Single( s => s.Name == "Miscellaneous Credit" );

        Worksheet workSheet2 = ( (WorksheetPart)document.WorkbookPart.GetPartById( sheet2.Id ) ).Worksheet;

        Columns cs = new Columns();
        for ( var y = 1; y <= 3; y++ ) {                    
            Column c = new Column()
            {
                Min = (UInt32Value)1U,
                Max = (UInt32Value)1U,
                Width = 44.33203125D,
                CustomWidth = true
            };
            cs.Append( c );
        }
        workSheet2.Append( cs );
    }

EDIT : As per Chris's explanation about columns's concept

using (SpreadsheetDocument document = SpreadsheetDocument.Open(outputPath, true)){
        Sheet sheet2 = document.WorkbookPart.Workbook.Descendants<Sheet>().Single( s => s.Name == "Miscellaneous Credit" );

        Worksheet workSheet2 = ( (WorksheetPart)document.WorkbookPart.GetPartById( sheet2.Id ) ).Worksheet;

       // Check if the column collection exists
            Columns cs = workSheet2.Elements<Columns>().FirstOrDefault();
            if ( ( cs == null ) ) {
                // If Columns appended to worksheet after sheetdata Excel will throw an error.
                SheetData sd = workSheet2.Elements<SheetData>().FirstOrDefault();
                if ( ( sd != null ) ) {
                    cs = workSheet2.InsertBefore( new Columns(), sd );
                }
                else {
                    cs = new Columns();
                    workSheet2.Append( cs );
                }
            }

            //create a column object to define the width of columns 1 to 3  
            Column c = new Column
            {
                Min = (UInt32Value)1U,
                Max = (UInt32Value)3U,
                Width = 44.33203125,
                CustomWidth = true
            };
            cs.Append( c );
    }

Solution

  • This first part of answer deals about how to set columns width (based on the initial sample code, I was thinking that you wanted only define the width of the columns).

    • First, it seems you misunderstood what are Min and Max properties of the Column object. They represent respectively First and Last column affected by this 'column info' record. So if you have a set of contiguous columns with the same width, you can set that width using one Column class. In your snippet you define 3 times the width of the same column (Index 1).

    • Then, you presume Columns collection doesn't exist yet...

    • And finally, the main point is that if the Columns collection is appended after SheetData, Excel will throw error.

    Final code that work for me (Open XML SDK 2.0)

    using (SpreadsheetDocument document = SpreadsheetDocument.Open(outputPath, true)) {
        Sheet sheet2 = document.WorkbookPart.Workbook.Descendants<Sheet>().Single(s => s.Name == "Your sheet name");
    
        Worksheet workSheet2 = ((WorksheetPart)document.WorkbookPart.GetPartById(sheet2.Id)).Worksheet;
    
        // Check if the column collection exists
        Columns cs = workSheet2.Elements<Columns>().FirstOrDefault();
    
        if ((cs == null)) {
            // If Columns appended to worksheet after sheetdata Excel will throw an error.
            SheetData sd = workSheet2.Elements<SheetData>().FirstOrDefault();
            if ((sd != null)) {
                cs = workSheet2.InsertBefore(new Columns(), sd);
            } else {
                cs = new Columns();
                workSheet2.Append(cs);
            }
        }
    
        //create a column object to define the width of columns 1 to 3  
        Column c = new Column {
            Min = (UInt32Value)1U,
            Max = (UInt32Value)3U,
            Width = 44.33203125,
            CustomWidth = true
        };
        cs.Append(c);
    
    }
    

    I'm still confused on how to perform column insert. Says I have columns A, B and C, I want to insert three columns between B and C, ending up with columns A,B,C,D,E,F. How can i achieve it?

    The Columns object in OpenXml SDK is here to store styles and width informations for the columns. Inserting a Column in the collection won't "insert" a column in the sheet.

    "Inserting" a column like you mean is a very large and complex task with OpenXmlSDK.

    From my understanding of the problem, it means you will have to find all cells and shift them by changing their reference (ex. a cell with ref "B1" would become "F1" after inserting 3 columns, etc ...). And it means you will have to change a lot of other things (reference of cell in formulas for example).

    This kind of task could be easily done with Office.Interop or probably with libraries like EEPlus or ClosedXml.