
Andrew Whitechapel's 2005 "Getting the Application Object in a Shimmed Automation Add-in" - now only available via the Wayback Machine - was an early code source and inspiration when I start creating Excel files in .NET over 15 years ago.
For many years the COM Interop approach facilitated by the code above was the main technique I used for generating Excel files - but by the mid-2010s it was clear to me that using libraries like ClosedXML, which don't require the Excel application to be installed, are a better approach.
At work the task of moving many years of Excel COM Interop based reporting to ClosedXML is an ongoing, long-term, low-priority task.
At the end of 2022 I tackled converting a heavily used report where the final step of the report creation is splitting and positioning the user's initial view. (Note here that 'splitting' is not the same as 'freezing'...)
I couldn't find a way to do this in ClosedXML... So I decided that after writing the file to disk with ClosedXML I would use OpenXML to setup the split. A simple enough approach (that eventually worked), but working directly thru OpenXML is not my usual code path and the first few failures made it clear that I was going to need some reference material on what properties to set with what values.
I used the one trick I remembered for these files to help me look at the underlying XML - I renamed the file with a .zip extension and then navigated into it. This works and it is a good trick because it doesn't require any additional programs/tooling - if I had managed to immediately intuit the correct code changes I wouldn't have given this hack a second thought... But I didn't get the code right in the first few tries and as I tested code changes I very quickly wanted a better way to take a direct look at the files' XML.
After a little searching I found the Borislav Ivanov's Open XML Package Editor Power Tool for Visual Studio. Once installed in Visual Studio you can drop an Open XML/Excel file into Visual Studio and start exploring. This was fantastic and let me find the last detail I needed...
The code below has so far stood up to some light testing - presented as research and reading material more than code you should copy and paste...
/// <summary>
/// Splits the first sheet in an OpenXML Excel File into 2 vertical panes.
/// If the file, workbook or worksheet don't exist the method will exit
/// without throwing an exception.
/// </summary>
/// <param name="filename">Full Path and Filename of the OpenXML Excel Files</param>
/// <param name="verticalSplitHeight">Value in 1/20th of a point</param>
/// <param name="topLeftCellAddressForUpperPane">
/// In A1 style - upper left corner of the top pane, left column for both
/// panes
/// </param>
/// <param name="topRowForLowerPane">Top row for the lower pane</param>
/// <returns>The Filename</returns>
public static string ExcelFileFirstSheetTwoSplitVerticalPanes(this string filename, double verticalSplitHeight,
string topLeftCellAddressForUpperPane, int topRowForLowerPane)
{
//1/1/2023 - As far as I can tell ClosedXML doesn't offer options to create a Vertical
//Split (not Freeze) with the TopLeft Cell set for both panes.
//
//Let me know if this is incorrect and this functionality is available - it would be
//better not to re-open and re-save the file...But all things considered getting the
//correct view setup for a user can be an important enough
//feature to merit this approach.
if (!File.Exists(filename)) return filename;
using var xl = SpreadsheetDocument.Open(filename, true);
var workbook = xl.WorkbookPart;
var worksheet = workbook?.WorksheetParts.FirstOrDefault();
if (worksheet?.Worksheet.SheetViews?.FirstOrDefault() is not SheetView view) return filename;
//Clear the view for the new Pane and Selections created below
view.RemoveAllChildren();
//This will set the upper pane's top left cell
view.TopLeftCell = topLeftCellAddressForUpperPane;
//Setup the split - note that VerticalSplit is the number of rows when Freezing
//but is "Vertical position of the split, in 1/20th of a point" when splitting.
// https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.pane?view=openxml-2.8.1
var newPane = new Pane
{
VerticalSplit = verticalSplitHeight,
TopLeftCell = $"A{topRowForLowerPane}",
ActivePane = PaneValues.BottomLeft,
State = PaneStateValues.Split
};
//Set a reasonable Selection
var lowerPaneTopLeftCellAddress =
$"{string.Concat(topLeftCellAddressForUpperPane.Where(x => !char.IsDigit(x)))}{topRowForLowerPane}";
var selectionSor = new ListValue<StringValue>();
selectionSor.Items.Add(lowerPaneTopLeftCellAddress);
var selection = new Selection
{
SequenceOfReferences = selectionSor,
ActiveCell = lowerPaneTopLeftCellAddress,
Pane = PaneValues.BottomLeft
};
//Add the Pane and Selection - save the file.
view.Append(newPane);
view.Append(selection);
worksheet.Worksheet.Save();
workbook.Workbook.Save();
xl.Close();
return filename;
}

PS - COM Interop is still a key desktop technology especially if you want to read data from, or write data to, a file the user has open in Excel. For a modern update to Andrew Whitechapel's approach see Jamie Faix's Automate multiple Excel instances on Codeproject. I have lightly modified Faix's code for the ExcelInteropExtensions in my Pointless Waymarks Project.