如何结合Excel Interop与ClosedXml实现单元格样式还原?
解决ClosedXml与Excel Interop格式枚举不兼容的问题
我之前在做类似的VSTO插件时也踩过这个坑——ClosedXml和Excel Interop的格式枚举体系完全是两套,直接赋值肯定会报0x800A03EC错误(本质是枚举类型不匹配)。咱们一步步拆解问题,给出可行的解决方案:
错误根源分析
你代码里的两个报错点,本质都是属性对应错误或者枚举值不匹配:
- 填充Pattern报错:你把ClosedXml的
PatternColor(图案颜色)赋值给了Interop的Interior.Pattern(图案样式枚举),完全搞反了属性!Interop的Pattern需要的是填充样式(比如纯色、灰度50%),而不是颜色。 - 边框LineStyle报错:ClosedXml的
XLBorderStyleValues枚举和Interop的XlLineStyle枚举值不一一对应,比如ClosedXml的Thin是一个枚举值,而Interop里的细边框是xlContinuous样式+xlThin粗细,直接赋值类型不兼容。
解决方案:枚举映射+属性正确对应
我们需要手动做枚举值的映射,同时修正属性的对应关系。
1. 编写枚举映射方法
先写几个工具方法,把ClosedXml的格式枚举转换成Interop对应的枚举:
// 映射ClosedXml填充图案到Excel Interop的XlPattern枚举 private Excel.XlPattern MapFillPattern(XLFillPatternType cxPattern) { return cxPattern switch { XLFillPatternType.None => Excel.XlPattern.xlPatternNone, XLFillPatternType.Solid => Excel.XlPattern.xlPatternSolid, XLFillPatternType.Gray50 => Excel.XlPattern.xlPatternGray50, XLFillPatternType.Gray75 => Excel.XlPattern.xlPatternGray75, XLFillPatternType.Gray25 => Excel.XlPattern.xlPatternGray25, XLFillPatternType.HorizontalStripe => Excel.XlPattern.xlPatternHorizontal, XLFillPatternType.VerticalStripe => Excel.XlPattern.xlPatternVertical, // 其他需要的图案样式可以继续补充 _ => Excel.XlPattern.xlPatternNone }; } // 映射ClosedXml边框样式到Excel Interop的XlLineStyle枚举 private Excel.XlLineStyle MapBorderStyle(XLBorderStyleValues cxBorder) { return cxBorder switch { XLBorderStyleValues.None => Excel.XlLineStyle.xlLineStyleNone, XLBorderStyleValues.Thin => Excel.XlLineStyle.xlContinuous, XLBorderStyleValues.Medium => Excel.XlLineStyle.xlContinuous, XLBorderStyleValues.Thick => Excel.XlLineStyle.xlContinuous, XLBorderStyleValues.Dashed => Excel.XlLineStyle.xlDash, XLBorderStyleValues.Dotted => Excel.XlLineStyle.xlDot, XLBorderStyleValues.DashDot => Excel.XlLineStyle.xlDashDot, XLBorderStyleValues.DashDotDot => Excel.XlLineStyle.xlDashDotDot, XLBorderStyleValues.Double => Excel.XlLineStyle.xlDouble, _ => Excel.XlLineStyle.xlLineStyleNone }; } // 映射ClosedXml边框粗细到Excel Interop的XlBorderWeight枚举 private Excel.XlBorderWeight MapBorderWeight(XLBorderStyleValues cxBorder) { return cxBorder switch { XLBorderStyleValues.Thin => Excel.XlBorderWeight.xlThin, XLBorderStyleValues.Medium => Excel.XlBorderWeight.xlMedium, XLBorderStyleValues.Thick => Excel.XlBorderWeight.xlThick, _ => Excel.XlBorderWeight.xlThin }; }
2. 修正格式赋值代码
把原来的格式处理代码替换成下面的逻辑,注意属性的正确对应和枚举映射:
var xl = Globals.ThisAddIn.Application; var dest = xl.ActiveWorkbook; try { var org = new XLWorkbook(pfad); foreach (IXLWorksheet sheet in org.Worksheets) { var usedRange = sheet.RangeUsed(true); Excel.Worksheet dsheet = dest.Sheets[sheet.Name]; foreach (IXLCell cel in usedRange.Cells(false)) { var adr = cel.Address.ToStringFixed(); // 确保地址格式和Interop兼容 var destRange = dsheet.Range[adr]; // --- 处理单元格填充格式 --- var cxFill = cel.Style.Fill; var interior = destRange.Interior; // 处理背景色(区分主题色和普通颜色) if (cxFill.BackgroundColor.IsTheme) { interior.ThemeColor = (Excel.XlThemeColor)(int)cxFill.BackgroundColor.Theme; interior.TintAndShade = cxFill.BackgroundColor.Tint; } else { interior.Color = cxFill.BackgroundColor.Color; } // 处理填充图案 interior.Pattern = MapFillPattern(cxFill.PatternType); if (cxFill.PatternType != XLFillPatternType.None) { // 只有非无图案时才设置图案颜色 if (cxFill.PatternColor.IsTheme) { interior.PatternThemeColor = (Excel.XlThemeColor)(int)cxFill.PatternColor.Theme; interior.PatternTintAndShade = cxFill.PatternColor.Tint; } else { interior.PatternColor = cxFill.PatternColor.Color; } } // --- 处理边框格式 --- var cxBorder = cel.Style.Border; var borders = destRange.Borders; // 处理顶部边框 var topBorder = borders[Excel.XlBordersIndex.xlEdgeTop]; topBorder.LineStyle = MapBorderStyle(cxBorder.TopBorder); topBorder.Weight = MapBorderWeight(cxBorder.TopBorder); if (cxBorder.TopBorder != XLBorderStyleValues.None) { if (cxBorder.TopBorderColor.IsTheme) { topBorder.ThemeColor = (Excel.XlThemeColor)(int)cxBorder.TopBorderColor.Theme; topBorder.TintAndShade = cxBorder.TopBorderColor.Tint; } else { topBorder.Color = cxBorder.TopBorderColor.Color; } } // 同理处理左、右、底部、内部边框(xlEdgeLeft、xlEdgeRight、xlEdgeBottom、xlInsideHorizontal等) // ... 这里可以复制顶部边框的逻辑,替换对应的边框枚举即可 } } org.Dispose(); } catch (Exception ex) { log.WriteLine(ex.Message); }
3. 额外优化建议
- 减少Interop调用次数:逐个单元格处理性能还是有点慢,你可以尝试按行/列的区域批量复制格式,比如先获取ClosedXml中某一行的所有格式,然后一次性应用到Interop的对应行。
- 主题色兼容:上面的代码已经处理了主题色和TintAndShade,这是很容易被忽略的点,确保格式还原的完整性。
- 异常处理:可以给枚举映射加上默认值,避免出现未定义的枚举值导致崩溃。
内容的提问来源于stack exchange,提问作者LocEngineer




