第05章-Excel样式与格式化

第五章:Excel样式与格式化

5.1 样式基础概念

5.1.1 样式对象结构

在NPOI中,样式通过ICellStyle接口管理,包含以下主要组成部分:

ICellStyle
├── 字体 (IFont)
│   ├── 字体名称
│   ├── 字号
│   ├── 加粗/斜体/下划线
│   └── 颜色
├── 对齐方式
│   ├── 水平对齐
│   ├── 垂直对齐
│   └── 文本换行
├── 边框
│   ├── 边框样式
│   └── 边框颜色
├── 填充
│   ├── 前景色
│   ├── 背景色
│   └── 填充模式
└── 数据格式
    └── 数字/日期格式

5.1.2 创建和应用样式

using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;

IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = workbook.CreateSheet("样式示例");

// 创建样式
ICellStyle style = workbook.CreateCellStyle();

// 应用样式到单元格
IRow row = sheet.CreateRow(0);
ICell cell = row.CreateCell(0);
cell.SetCellValue("带样式的文本");
cell.CellStyle = style;

5.1.3 样式复用的重要性

重要提示:NPOI中样式对象的数量是有限的(约4000个),应该复用样式而不是为每个单元格创建新样式。

// 错误做法:为每个单元格创建新样式
for (int i = 0; i < 10000; i++)
{
    ICellStyle style = workbook.CreateCellStyle();  // 可能超出限制!
    cell.CellStyle = style;
}

// 正确做法:复用样式
ICellStyle sharedStyle = workbook.CreateCellStyle();
for (int i = 0; i < 10000; i++)
{
    cell.CellStyle = sharedStyle;  // 复用同一个样式
}

5.2 字体设置

5.2.1 基本字体属性

IWorkbook workbook = new XSSFWorkbook();

// 创建字体
IFont font = workbook.CreateFont();

// 字体名称
font.FontName = "微软雅黑";
// 常用字体:宋体、黑体、楷体、Arial、Times New Roman

// 字号(以点为单位)
font.FontHeightInPoints = 12;
// 或使用 FontHeight(以 1/20 点为单位)
font.FontHeight = 240;  // 12点 = 240

// 加粗
font.IsBold = true;

// 斜体
font.IsItalic = true;

// 下划线
font.Underline = FontUnderlineType.Single;
// FontUnderlineType: None, Single, Double, SingleAccounting, DoubleAccounting

// 删除线
font.IsStrikeout = true;

// 上标/下标
font.TypeOffset = FontSuperScript.Super;  // 上标
font.TypeOffset = FontSuperScript.Sub;    // 下标
font.TypeOffset = FontSuperScript.None;   // 正常

// 创建样式并应用字体
ICellStyle style = workbook.CreateCellStyle();
style.SetFont(font);

5.2.2 字体颜色

// 使用索引颜色(兼容性好)
font.Color = IndexedColors.Red.Index;
font.Color = IndexedColors.Blue.Index;
font.Color = IndexedColors.Green.Index;

// 常用索引颜色
var colors = new Dictionary<string, short>
{
    { "黑色", IndexedColors.Black.Index },
    { "白色", IndexedColors.White.Index },
    { "红色", IndexedColors.Red.Index },
    { "绿色", IndexedColors.Green.Index },
    { "蓝色", IndexedColors.Blue.Index },
    { "黄色", IndexedColors.Yellow.Index },
    { "橙色", IndexedColors.Orange.Index },
    { "紫色", IndexedColors.Violet.Index },
    { "灰色", IndexedColors.Grey50Percent.Index },
    { "深灰", IndexedColors.Grey80Percent.Index },
    { "浅灰", IndexedColors.Grey25Percent.Index }
};

// 使用自定义RGB颜色(仅XSSF支持)
XSSFFont xssfFont = (XSSFFont)workbook.CreateFont();
xssfFont.SetColor(new XSSFColor(new byte[] { 255, 128, 0 }));  // 橙色 RGB

5.2.3 字体工厂类

/// <summary>
/// 字体工厂类
/// </summary>
public class FontFactory
{
    private readonly IWorkbook _workbook;
    private readonly Dictionary<string, IFont> _fontCache = new();
    
    public FontFactory(IWorkbook workbook)
    {
        _workbook = workbook;
    }
    
    /// <summary>
    /// 获取或创建字体
    /// </summary>
    public IFont GetFont(
        string fontName = "宋体",
        short fontSize = 11,
        bool isBold = false,
        bool isItalic = false,
        short color = 0,
        FontUnderlineType underline = FontUnderlineType.None)
    {
        string key = $"{fontName}_{fontSize}_{isBold}_{isItalic}_{color}_{underline}";
        
        if (!_fontCache.TryGetValue(key, out IFont font))
        {
            font = _workbook.CreateFont();
            font.FontName = fontName;
            font.FontHeightInPoints = fontSize;
            font.IsBold = isBold;
            font.IsItalic = isItalic;
            if (color > 0) font.Color = color;
            font.Underline = underline;
            
            _fontCache[key] = font;
        }
        
        return font;
    }
    
    /// <summary>
    /// 创建标题字体
    /// </summary>
    public IFont CreateTitleFont(short fontSize = 16)
    {
        return GetFont("微软雅黑", fontSize, true, false);
    }
    
    /// <summary>
    /// 创建正文字体
    /// </summary>
    public IFont CreateBodyFont()
    {
        return GetFont("宋体", 11, false, false);
    }
    
    /// <summary>
    /// 创建链接字体
    /// </summary>
    public IFont CreateLinkFont()
    {
        return GetFont("宋体", 11, false, false, 
            IndexedColors.Blue.Index, FontUnderlineType.Single);
    }
}

5.3 对齐方式

5.3.1 水平对齐

ICellStyle style = workbook.CreateCellStyle();

// 水平对齐方式
style.Alignment = HorizontalAlignment.Left;      // 左对齐
style.Alignment = HorizontalAlignment.Center;    // 居中
style.Alignment = HorizontalAlignment.Right;     // 右对齐
style.Alignment = HorizontalAlignment.Justify;   // 两端对齐
style.Alignment = HorizontalAlignment.Fill;      // 填充
style.Alignment = HorizontalAlignment.General;   // 常规(默认)
style.Alignment = HorizontalAlignment.CenterSelection;  // 跨列居中
style.Alignment = HorizontalAlignment.Distributed;       // 分散对齐

5.3.2 垂直对齐

// 垂直对齐方式
style.VerticalAlignment = VerticalAlignment.Top;       // 顶端对齐
style.VerticalAlignment = VerticalAlignment.Center;    // 居中
style.VerticalAlignment = VerticalAlignment.Bottom;    // 底端对齐
style.VerticalAlignment = VerticalAlignment.Justify;   // 两端对齐
style.VerticalAlignment = VerticalAlignment.Distributed; // 分散对齐

5.3.3 文本控制

// 自动换行
style.WrapText = true;

// 文本旋转角度(-90 到 90 度)
style.Rotation = 45;  // 旋转45度

// 缩进级别
style.Indention = 2;  // 缩进2个字符

// 缩小字体填充
style.ShrinkToFit = true;

5.3.4 对齐方式示例

public static void CreateAlignmentDemo(IWorkbook workbook)
{
    ISheet sheet = workbook.CreateSheet("对齐示例");
    
    // 设置列宽
    sheet.SetColumnWidth(0, 20 * 256);
    sheet.SetColumnWidth(1, 30 * 256);
    
    string[] alignments = { "左对齐", "居中", "右对齐", "两端对齐", "分散对齐" };
    HorizontalAlignment[] hAligns = {
        HorizontalAlignment.Left,
        HorizontalAlignment.Center,
        HorizontalAlignment.Right,
        HorizontalAlignment.Justify,
        HorizontalAlignment.Distributed
    };
    
    for (int i = 0; i < alignments.Length; i++)
    {
        IRow row = sheet.CreateRow(i);
        row.HeightInPoints = 30;
        
        // 标签
        ICell labelCell = row.CreateCell(0);
        labelCell.SetCellValue(alignments[i]);
        
        // 示例
        ICell demoCell = row.CreateCell(1);
        demoCell.SetCellValue("这是示例文本 This is sample text");
        
        ICellStyle style = workbook.CreateCellStyle();
        style.Alignment = hAligns[i];
        style.VerticalAlignment = VerticalAlignment.Center;
        style.WrapText = true;
        demoCell.CellStyle = style;
    }
}

5.4 边框设置

5.4.1 边框样式

ICellStyle style = workbook.CreateCellStyle();

// 设置四边边框样式
style.BorderTop = BorderStyle.Thin;
style.BorderBottom = BorderStyle.Thin;
style.BorderLeft = BorderStyle.Thin;
style.BorderRight = BorderStyle.Thin;

// 边框样式枚举
public enum BorderStyle
{
    None,           // 无边框
    Thin,           // 细线
    Medium,         // 中等
    Dashed,         // 虚线
    Dotted,         // 点线
    Thick,          // 粗线
    Double,         // 双线
    Hair,           // 发丝线
    MediumDashed,   // 中等虚线
    DashDot,        // 点划线
    MediumDashDot,  // 中等点划线
    DashDotDot,     // 双点划线
    MediumDashDotDot, // 中等双点划线
    SlantedDashDot  // 斜点划线
}

5.4.2 边框颜色

// 设置边框颜色
style.TopBorderColor = IndexedColors.Black.Index;
style.BottomBorderColor = IndexedColors.Black.Index;
style.LeftBorderColor = IndexedColors.Red.Index;
style.RightBorderColor = IndexedColors.Red.Index;

// XSSF支持自定义RGB颜色
XSSFCellStyle xssfStyle = (XSSFCellStyle)workbook.CreateCellStyle();
xssfStyle.SetBorderColor(BorderSide.TOP, 
    new XSSFColor(new byte[] { 0, 128, 0 }));  // 绿色

5.4.3 边框辅助方法

/// <summary>
/// 边框设置辅助类
/// </summary>
public static class BorderHelper
{
    /// <summary>
    /// 设置所有边框
    /// </summary>
    public static void SetAllBorders(ICellStyle style, BorderStyle borderStyle, 
        short color = 0)
    {
        style.BorderTop = borderStyle;
        style.BorderBottom = borderStyle;
        style.BorderLeft = borderStyle;
        style.BorderRight = borderStyle;
        
        if (color > 0)
        {
            style.TopBorderColor = color;
            style.BottomBorderColor = color;
            style.LeftBorderColor = color;
            style.RightBorderColor = color;
        }
    }
    
    /// <summary>
    /// 为区域设置外边框
    /// </summary>
    public static void SetRegionBorder(ISheet sheet, CellRangeAddress region,
        BorderStyle borderStyle, short color, IWorkbook workbook)
    {
        // 上边框
        for (int col = region.FirstColumn; col <= region.LastColumn; col++)
        {
            IRow row = sheet.GetRow(region.FirstRow) ?? sheet.CreateRow(region.FirstRow);
            ICell cell = row.GetCell(col) ?? row.CreateCell(col);
            ICellStyle style = workbook.CreateCellStyle();
            if (cell.CellStyle != null) style.CloneStyleFrom(cell.CellStyle);
            style.BorderTop = borderStyle;
            style.TopBorderColor = color;
            cell.CellStyle = style;
        }
        
        // 下边框
        for (int col = region.FirstColumn; col <= region.LastColumn; col++)
        {
            IRow row = sheet.GetRow(region.LastRow) ?? sheet.CreateRow(region.LastRow);
            ICell cell = row.GetCell(col) ?? row.CreateCell(col);
            ICellStyle style = workbook.CreateCellStyle();
            if (cell.CellStyle != null) style.CloneStyleFrom(cell.CellStyle);
            style.BorderBottom = borderStyle;
            style.BottomBorderColor = color;
            cell.CellStyle = style;
        }
        
        // 左边框
        for (int r = region.FirstRow; r <= region.LastRow; r++)
        {
            IRow row = sheet.GetRow(r) ?? sheet.CreateRow(r);
            ICell cell = row.GetCell(region.FirstColumn) ?? 
                row.CreateCell(region.FirstColumn);
            ICellStyle style = workbook.CreateCellStyle();
            if (cell.CellStyle != null) style.CloneStyleFrom(cell.CellStyle);
            style.BorderLeft = borderStyle;
            style.LeftBorderColor = color;
            cell.CellStyle = style;
        }
        
        // 右边框
        for (int r = region.FirstRow; r <= region.LastRow; r++)
        {
            IRow row = sheet.GetRow(r) ?? sheet.CreateRow(r);
            ICell cell = row.GetCell(region.LastColumn) ?? 
                row.CreateCell(region.LastColumn);
            ICellStyle style = workbook.CreateCellStyle();
            if (cell.CellStyle != null) style.CloneStyleFrom(cell.CellStyle);
            style.BorderRight = borderStyle;
            style.RightBorderColor = color;
            cell.CellStyle = style;
        }
    }
}

5.5 填充与背景色

5.5.1 填充模式

ICellStyle style = workbook.CreateCellStyle();

// 设置填充模式
style.FillPattern = FillPattern.SolidForeground;  // 纯色填充

// 填充模式枚举
public enum FillPattern
{
    NoFill,           // 无填充
    SolidForeground,  // 纯色填充
    FineDots,         // 细点
    AltBars,          // 交替条纹
    SparseDots,       // 稀疏点
    ThickHorizontalBands,  // 粗水平条纹
    ThickVerticalBands,    // 粗垂直条纹
    ThickBackwardDiagonals, // 粗反向对角线
    ThickForwardDiagonals,  // 粗正向对角线
    BigSpots,         // 大斑点
    Bricks,           // 砖块
    ThinHorizontalBands,   // 细水平条纹
    ThinVerticalBands,     // 细垂直条纹
    ThinBackwardDiagonals, // 细反向对角线
    ThinForwardDiagonals,  // 细正向对角线
    Squares,          // 方格
    Diamonds,         // 菱形
    LessDots,         // 较少点
    LeastDots         // 最少点
}

5.5.2 填充颜色

// 使用索引颜色
style.FillForegroundColor = IndexedColors.LightBlue.Index;
style.FillPattern = FillPattern.SolidForeground;

// 设置前景色和背景色(用于图案填充)
style.FillForegroundColor = IndexedColors.Red.Index;
style.FillBackgroundColor = IndexedColors.Yellow.Index;
style.FillPattern = FillPattern.ThinHorizontalBands;

// XSSF支持自定义RGB颜色
XSSFCellStyle xssfStyle = (XSSFCellStyle)workbook.CreateCellStyle();
xssfStyle.FillPattern = FillPattern.SolidForeground;
xssfStyle.SetFillForegroundColor(new XSSFColor(new byte[] { 200, 230, 255 }));

5.5.3 常用颜色配色方案

/// <summary>
/// 颜色配色方案
/// </summary>
public static class ColorSchemes
{
    // 表头颜色
    public static readonly short HeaderBlue = IndexedColors.LightBlue.Index;
    public static readonly short HeaderGreen = IndexedColors.LightGreen.Index;
    public static readonly short HeaderGrey = IndexedColors.Grey25Percent.Index;
    
    // 交替行颜色
    public static readonly short AlternateRow1 = IndexedColors.White.Index;
    public static readonly short AlternateRow2 = IndexedColors.Grey25Percent.Index;
    
    // 状态颜色
    public static readonly short Success = IndexedColors.LightGreen.Index;
    public static readonly short Warning = IndexedColors.LightYellow.Index;
    public static readonly short Error = IndexedColors.Rose.Index;
    
    // 自定义RGB颜色(XSSF)
    public static XSSFColor CreateColor(int r, int g, int b)
    {
        return new XSSFColor(new byte[] { (byte)r, (byte)g, (byte)b });
    }
    
    // 预定义RGB颜色
    public static readonly XSSFColor SoftBlue = CreateColor(173, 216, 230);
    public static readonly XSSFColor SoftGreen = CreateColor(144, 238, 144);
    public static readonly XSSFColor SoftYellow = CreateColor(255, 255, 224);
    public static readonly XSSFColor SoftPink = CreateColor(255, 182, 193);
}

5.6 数字格式

5.6.1 内置数字格式

ICellStyle style = workbook.CreateCellStyle();
IDataFormat format = workbook.CreateDataFormat();

// 常用内置格式
style.DataFormat = format.GetFormat("General");     // 常规
style.DataFormat = format.GetFormat("0");           // 整数
style.DataFormat = format.GetFormat("0.00");        // 两位小数
style.DataFormat = format.GetFormat("#,##0");       // 千位分隔符
style.DataFormat = format.GetFormat("#,##0.00");    // 千位分隔符+两位小数
style.DataFormat = format.GetFormat("0%");          // 百分比整数
style.DataFormat = format.GetFormat("0.00%");       // 百分比两位小数
style.DataFormat = format.GetFormat("$#,##0.00");   // 美元
style.DataFormat = format.GetFormat("¥#,##0.00");   // 人民币
style.DataFormat = format.GetFormat("@");           // 文本

5.6.2 日期时间格式

// 日期格式
style.DataFormat = format.GetFormat("yyyy-MM-dd");
style.DataFormat = format.GetFormat("yyyy/MM/dd");
style.DataFormat = format.GetFormat("yyyy年MM月dd日");
style.DataFormat = format.GetFormat("MM-dd");
style.DataFormat = format.GetFormat("yyyy-MM");

// 时间格式
style.DataFormat = format.GetFormat("HH:mm:ss");
style.DataFormat = format.GetFormat("HH:mm");
style.DataFormat = format.GetFormat("h:mm AM/PM");

// 日期时间格式
style.DataFormat = format.GetFormat("yyyy-MM-dd HH:mm:ss");
style.DataFormat = format.GetFormat("yyyy/MM/dd HH:mm");

5.6.3 自定义数字格式

// 条件格式
// 正数显示绿色,负数显示红色(带括号),零显示横线
style.DataFormat = format.GetFormat("[绿色]#,##0.00;[红色](#,##0.00);-");

// 显示正负号
style.DataFormat = format.GetFormat("+#,##0.00;-#,##0.00;0");

// 四舍五入到千
style.DataFormat = format.GetFormat("#,##0,千元");

// 科学计数法
style.DataFormat = format.GetFormat("0.00E+00");

// 分数
style.DataFormat = format.GetFormat("# ?/?");

// 文本前缀
style.DataFormat = format.GetFormat("\"编号:\"@");

// 数字前补零
style.DataFormat = format.GetFormat("000000");  // 6位数字,不足补零

// 电话号码格式
style.DataFormat = format.GetFormat("000-0000-0000");

5.6.4 格式化辅助类

/// <summary>
/// 数字格式辅助类
/// </summary>
public static class NumberFormatHelper
{
    private static readonly Dictionary<string, short> _formatCache = new();
    
    /// <summary>
    /// 获取格式代码
    /// </summary>
    public static short GetFormat(IWorkbook workbook, string formatString)
    {
        if (!_formatCache.TryGetValue(formatString, out short format))
        {
            IDataFormat dataFormat = workbook.CreateDataFormat();
            format = dataFormat.GetFormat(formatString);
            _formatCache[formatString] = format;
        }
        return format;
    }
    
    // 预定义格式
    public static short Integer(IWorkbook wb) => GetFormat(wb, "0");
    public static short Decimal2(IWorkbook wb) => GetFormat(wb, "0.00");
    public static short Thousands(IWorkbook wb) => GetFormat(wb, "#,##0");
    public static short ThousandsDecimal2(IWorkbook wb) => GetFormat(wb, "#,##0.00");
    public static short Percent(IWorkbook wb) => GetFormat(wb, "0%");
    public static short PercentDecimal2(IWorkbook wb) => GetFormat(wb, "0.00%");
    public static short Currency(IWorkbook wb) => GetFormat(wb, "¥#,##0.00");
    public static short DateOnly(IWorkbook wb) => GetFormat(wb, "yyyy-MM-dd");
    public static short DateTime(IWorkbook wb) => GetFormat(wb, "yyyy-MM-dd HH:mm:ss");
    public static short TimeOnly(IWorkbook wb) => GetFormat(wb, "HH:mm:ss");
    public static short Text(IWorkbook wb) => GetFormat(wb, "@");
}

5.7 样式管理器

5.7.1 完整的样式管理器实现

/// <summary>
/// Excel样式管理器
/// </summary>
public class ExcelStyleManager
{
    private readonly IWorkbook _workbook;
    private readonly Dictionary<string, ICellStyle> _styleCache = new();
    private readonly Dictionary<string, IFont> _fontCache = new();
    
    public ExcelStyleManager(IWorkbook workbook)
    {
        _workbook = workbook;
    }
    
    #region 字体管理
    
    /// <summary>
    /// 获取或创建字体
    /// </summary>
    public IFont GetFont(string name = "宋体", short size = 11, 
        bool bold = false, bool italic = false, 
        short color = 0, FontUnderlineType underline = FontUnderlineType.None)
    {
        string key = $"F_{name}_{size}_{bold}_{italic}_{color}_{underline}";
        
        if (!_fontCache.TryGetValue(key, out IFont font))
        {
            font = _workbook.CreateFont();
            font.FontName = name;
            font.FontHeightInPoints = size;
            font.IsBold = bold;
            font.IsItalic = italic;
            if (color > 0) font.Color = color;
            font.Underline = underline;
            _fontCache[key] = font;
        }
        
        return font;
    }
    
    #endregion
    
    #region 预定义样式
    
    /// <summary>
    /// 表头样式
    /// </summary>
    public ICellStyle HeaderStyle
    {
        get
        {
            const string key = "Header";
            if (!_styleCache.TryGetValue(key, out ICellStyle style))
            {
                style = _workbook.CreateCellStyle();
                style.SetFont(GetFont("微软雅黑", 11, true));
                style.Alignment = HorizontalAlignment.Center;
                style.VerticalAlignment = VerticalAlignment.Center;
                style.FillForegroundColor = IndexedColors.Grey25Percent.Index;
                style.FillPattern = FillPattern.SolidForeground;
                BorderHelper.SetAllBorders(style, BorderStyle.Thin, 
                    IndexedColors.Black.Index);
                _styleCache[key] = style;
            }
            return style;
        }
    }
    
    /// <summary>
    /// 正文样式
    /// </summary>
    public ICellStyle BodyStyle
    {
        get
        {
            const string key = "Body";
            if (!_styleCache.TryGetValue(key, out ICellStyle style))
            {
                style = _workbook.CreateCellStyle();
                style.SetFont(GetFont("宋体", 11));
                style.VerticalAlignment = VerticalAlignment.Center;
                BorderHelper.SetAllBorders(style, BorderStyle.Thin, 
                    IndexedColors.Black.Index);
                _styleCache[key] = style;
            }
            return style;
        }
    }
    
    /// <summary>
    /// 标题样式
    /// </summary>
    public ICellStyle TitleStyle
    {
        get
        {
            const string key = "Title";
            if (!_styleCache.TryGetValue(key, out ICellStyle style))
            {
                style = _workbook.CreateCellStyle();
                style.SetFont(GetFont("微软雅黑", 16, true));
                style.Alignment = HorizontalAlignment.Center;
                style.VerticalAlignment = VerticalAlignment.Center;
                _styleCache[key] = style;
            }
            return style;
        }
    }
    
    /// <summary>
    /// 日期样式
    /// </summary>
    public ICellStyle DateStyle
    {
        get
        {
            const string key = "Date";
            if (!_styleCache.TryGetValue(key, out ICellStyle style))
            {
                style = _workbook.CreateCellStyle();
                style.CloneStyleFrom(BodyStyle);
                IDataFormat format = _workbook.CreateDataFormat();
                style.DataFormat = format.GetFormat("yyyy-MM-dd");
                _styleCache[key] = style;
            }
            return style;
        }
    }
    
    /// <summary>
    /// 金额样式
    /// </summary>
    public ICellStyle CurrencyStyle
    {
        get
        {
            const string key = "Currency";
            if (!_styleCache.TryGetValue(key, out ICellStyle style))
            {
                style = _workbook.CreateCellStyle();
                style.CloneStyleFrom(BodyStyle);
                style.Alignment = HorizontalAlignment.Right;
                IDataFormat format = _workbook.CreateDataFormat();
                style.DataFormat = format.GetFormat("¥#,##0.00");
                _styleCache[key] = style;
            }
            return style;
        }
    }
    
    /// <summary>
    /// 百分比样式
    /// </summary>
    public ICellStyle PercentStyle
    {
        get
        {
            const string key = "Percent";
            if (!_styleCache.TryGetValue(key, out ICellStyle style))
            {
                style = _workbook.CreateCellStyle();
                style.CloneStyleFrom(BodyStyle);
                style.Alignment = HorizontalAlignment.Right;
                IDataFormat format = _workbook.CreateDataFormat();
                style.DataFormat = format.GetFormat("0.00%");
                _styleCache[key] = style;
            }
            return style;
        }
    }
    
    /// <summary>
    /// 整数样式
    /// </summary>
    public ICellStyle IntegerStyle
    {
        get
        {
            const string key = "Integer";
            if (!_styleCache.TryGetValue(key, out ICellStyle style))
            {
                style = _workbook.CreateCellStyle();
                style.CloneStyleFrom(BodyStyle);
                style.Alignment = HorizontalAlignment.Right;
                IDataFormat format = _workbook.CreateDataFormat();
                style.DataFormat = format.GetFormat("#,##0");
                _styleCache[key] = style;
            }
            return style;
        }
    }
    
    #endregion
    
    #region 自定义样式构建器
    
    /// <summary>
    /// 创建自定义样式
    /// </summary>
    public StyleBuilder CreateStyle()
    {
        return new StyleBuilder(this, _workbook.CreateCellStyle());
    }
    
    /// <summary>
    /// 基于现有样式创建
    /// </summary>
    public StyleBuilder CreateStyleFrom(ICellStyle baseStyle)
    {
        ICellStyle newStyle = _workbook.CreateCellStyle();
        newStyle.CloneStyleFrom(baseStyle);
        return new StyleBuilder(this, newStyle);
    }
    
    #endregion
}

/// <summary>
/// 样式构建器(流式API)
/// </summary>
public class StyleBuilder
{
    private readonly ExcelStyleManager _manager;
    private readonly ICellStyle _style;
    
    internal StyleBuilder(ExcelStyleManager manager, ICellStyle style)
    {
        _manager = manager;
        _style = style;
    }
    
    public StyleBuilder Font(string name = "宋体", short size = 11, 
        bool bold = false, bool italic = false, short color = 0)
    {
        _style.SetFont(_manager.GetFont(name, size, bold, italic, color));
        return this;
    }
    
    public StyleBuilder HorizontalAlign(HorizontalAlignment align)
    {
        _style.Alignment = align;
        return this;
    }
    
    public StyleBuilder VerticalAlign(VerticalAlignment align)
    {
        _style.VerticalAlignment = align;
        return this;
    }
    
    public StyleBuilder Center()
    {
        _style.Alignment = HorizontalAlignment.Center;
        _style.VerticalAlignment = VerticalAlignment.Center;
        return this;
    }
    
    public StyleBuilder WrapText(bool wrap = true)
    {
        _style.WrapText = wrap;
        return this;
    }
    
    public StyleBuilder Border(BorderStyle borderStyle, short color = 0)
    {
        BorderHelper.SetAllBorders(_style, borderStyle, 
            color > 0 ? color : IndexedColors.Black.Index);
        return this;
    }
    
    public StyleBuilder Background(short color)
    {
        _style.FillForegroundColor = color;
        _style.FillPattern = FillPattern.SolidForeground;
        return this;
    }
    
    public StyleBuilder DataFormat(string format, IWorkbook workbook)
    {
        IDataFormat df = workbook.CreateDataFormat();
        _style.DataFormat = df.GetFormat(format);
        return this;
    }
    
    public ICellStyle Build()
    {
        return _style;
    }
}

5.8 条件格式

5.8.1 基于值的条件格式

using NPOI.SS.UserModel;
using NPOI.SS.Util;
using NPOI.XSSF.UserModel;

public static void ApplyConditionalFormatting(ISheet sheet, IWorkbook workbook)
{
    ISheetConditionalFormatting cf = sheet.SheetConditionalFormatting;
    
    // 定义应用范围
    CellRangeAddress[] ranges = { CellRangeAddress.ValueOf("B2:B100") };
    
    // 创建条件规则:大于80显示绿色
    IConditionalFormattingRule rule1 = cf.CreateConditionalFormattingRule(
        ComparisonOperator.GreaterThan, "80", null);
    
    IPatternFormatting pf1 = rule1.CreatePatternFormatting();
    pf1.FillBackgroundColor = IndexedColors.LightGreen.Index;
    pf1.FillPattern = FillPattern.SolidForeground;
    
    // 创建条件规则:小于60显示红色
    IConditionalFormattingRule rule2 = cf.CreateConditionalFormattingRule(
        ComparisonOperator.LessThan, "60", null);
    
    IPatternFormatting pf2 = rule2.CreatePatternFormatting();
    pf2.FillBackgroundColor = IndexedColors.Rose.Index;
    pf2.FillPattern = FillPattern.SolidForeground;
    
    // 应用条件格式
    cf.AddConditionalFormatting(ranges, new[] { rule1, rule2 });
}

5.8.2 数据条

public static void ApplyDataBar(ISheet sheet)
{
    XSSFSheetConditionalFormatting cf = 
        (XSSFSheetConditionalFormatting)sheet.SheetConditionalFormatting;
    
    CellRangeAddress[] ranges = { CellRangeAddress.ValueOf("C2:C100") };
    
    // 创建数据条规则
    XSSFConditionalFormattingRule rule = 
        (XSSFConditionalFormattingRule)cf.CreateConditionalFormattingRule(
            ComparisonOperator.Between, "0", "100");
    
    XSSFDataBarFormatting dataBar = (XSSFDataBarFormatting)rule.DataBarFormatting;
    if (dataBar != null)
    {
        dataBar.Color = new XSSFColor(new byte[] { 99, 190, 123 });  // 绿色
    }
    
    cf.AddConditionalFormatting(ranges, rule);
}

5.8.3 图标集

public static void ApplyIconSet(XSSFSheet sheet)
{
    XSSFSheetConditionalFormatting cf = 
        (XSSFSheetConditionalFormatting)sheet.SheetConditionalFormatting;
    
    CellRangeAddress[] ranges = { CellRangeAddress.ValueOf("D2:D100") };
    
    // 这需要直接操作XML,NPOI对图标集的支持有限
    // 建议使用Excel模板预设图标集条件格式
}

5.9 综合示例

5.9.1 创建格式化报表

using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using NPOI.SS.Util;
using System;
using System.IO;

public class FormattedReportExample
{
    public static void CreateSalesReport()
    {
        IWorkbook workbook = new XSSFWorkbook();
        var styleManager = new ExcelStyleManager(workbook);
        ISheet sheet = workbook.CreateSheet("销售报表");
        
        int rowIndex = 0;
        
        // 1. 创建报表标题
        IRow titleRow = sheet.CreateRow(rowIndex++);
        titleRow.HeightInPoints = 30;
        ICell titleCell = titleRow.CreateCell(0);
        titleCell.SetCellValue("2024年第一季度销售报表");
        titleCell.CellStyle = styleManager.TitleStyle;
        sheet.AddMergedRegion(new CellRangeAddress(0, 0, 0, 5));
        
        // 空行
        rowIndex++;
        
        // 2. 创建表头
        IRow headerRow = sheet.CreateRow(rowIndex++);
        headerRow.HeightInPoints = 25;
        string[] headers = { "序号", "产品名称", "销售数量", "单价", "销售额", "占比" };
        for (int i = 0; i < headers.Length; i++)
        {
            ICell cell = headerRow.CreateCell(i);
            cell.SetCellValue(headers[i]);
            cell.CellStyle = styleManager.HeaderStyle;
        }
        
        // 3. 创建数据行
        var salesData = new[]
        {
            new { Name = "产品A", Quantity = 1500, UnitPrice = 99.99m },
            new { Name = "产品B", Quantity = 2300, UnitPrice = 149.99m },
            new { Name = "产品C", Quantity = 800, UnitPrice = 299.99m },
            new { Name = "产品D", Quantity = 3200, UnitPrice = 49.99m },
            new { Name = "产品E", Quantity = 1100, UnitPrice = 199.99m }
        };
        
        decimal totalSales = salesData.Sum(x => x.Quantity * x.UnitPrice);
        
        for (int i = 0; i < salesData.Length; i++)
        {
            IRow row = sheet.CreateRow(rowIndex++);
            var data = salesData[i];
            decimal sales = data.Quantity * data.UnitPrice;
            
            // 序号
            row.CreateCell(0).SetCellValue(i + 1);
            row.GetCell(0).CellStyle = styleManager.BodyStyle;
            
            // 产品名称
            row.CreateCell(1).SetCellValue(data.Name);
            row.GetCell(1).CellStyle = styleManager.BodyStyle;
            
            // 销售数量
            row.CreateCell(2).SetCellValue(data.Quantity);
            row.GetCell(2).CellStyle = styleManager.IntegerStyle;
            
            // 单价
            row.CreateCell(3).SetCellValue((double)data.UnitPrice);
            row.GetCell(3).CellStyle = styleManager.CurrencyStyle;
            
            // 销售额
            row.CreateCell(4).SetCellValue((double)sales);
            row.GetCell(4).CellStyle = styleManager.CurrencyStyle;
            
            // 占比
            row.CreateCell(5).SetCellValue((double)(sales / totalSales));
            row.GetCell(5).CellStyle = styleManager.PercentStyle;
        }
        
        // 4. 创建汇总行
        IRow summaryRow = sheet.CreateRow(rowIndex++);
        ICell summaryLabel = summaryRow.CreateCell(0);
        summaryLabel.SetCellValue("合计");
        
        ICellStyle summaryStyle = styleManager.CreateStyleFrom(styleManager.BodyStyle)
            .Font("宋体", 11, true)
            .Background(IndexedColors.LightYellow.Index)
            .Build();
        
        summaryLabel.CellStyle = summaryStyle;
        sheet.AddMergedRegion(new CellRangeAddress(rowIndex - 1, rowIndex - 1, 0, 1));
        
        // 数量合计
        ICell sumQuantity = summaryRow.CreateCell(2);
        sumQuantity.SetCellFormula($"SUM(C4:C{rowIndex - 1})");
        ICellStyle summaryIntStyle = workbook.CreateCellStyle();
        summaryIntStyle.CloneStyleFrom(styleManager.IntegerStyle);
        summaryIntStyle.FillForegroundColor = IndexedColors.LightYellow.Index;
        summaryIntStyle.FillPattern = FillPattern.SolidForeground;
        IFont boldFont = workbook.CreateFont();
        boldFont.IsBold = true;
        summaryIntStyle.SetFont(boldFont);
        sumQuantity.CellStyle = summaryIntStyle;
        
        // 空单元格
        summaryRow.CreateCell(3).CellStyle = summaryStyle;
        
        // 销售额合计
        ICell sumSales = summaryRow.CreateCell(4);
        sumSales.SetCellFormula($"SUM(E4:E{rowIndex - 1})");
        ICellStyle summaryCurrencyStyle = workbook.CreateCellStyle();
        summaryCurrencyStyle.CloneStyleFrom(styleManager.CurrencyStyle);
        summaryCurrencyStyle.FillForegroundColor = IndexedColors.LightYellow.Index;
        summaryCurrencyStyle.FillPattern = FillPattern.SolidForeground;
        summaryCurrencyStyle.SetFont(boldFont);
        sumSales.CellStyle = summaryCurrencyStyle;
        
        // 占比合计
        ICell sumPercent = summaryRow.CreateCell(5);
        sumPercent.SetCellFormula($"SUM(F4:F{rowIndex - 1})");
        ICellStyle summaryPercentStyle = workbook.CreateCellStyle();
        summaryPercentStyle.CloneStyleFrom(styleManager.PercentStyle);
        summaryPercentStyle.FillForegroundColor = IndexedColors.LightYellow.Index;
        summaryPercentStyle.FillPattern = FillPattern.SolidForeground;
        summaryPercentStyle.SetFont(boldFont);
        sumPercent.CellStyle = summaryPercentStyle;
        
        // 5. 设置列宽
        sheet.SetColumnWidth(0, 8 * 256);   // 序号
        sheet.SetColumnWidth(1, 15 * 256);  // 产品名称
        sheet.SetColumnWidth(2, 12 * 256);  // 销售数量
        sheet.SetColumnWidth(3, 12 * 256);  // 单价
        sheet.SetColumnWidth(4, 15 * 256);  // 销售额
        sheet.SetColumnWidth(5, 10 * 256);  // 占比
        
        // 6. 冻结首行
        sheet.CreateFreezePane(0, 3);
        
        // 7. 保存文件
        using (FileStream fs = new FileStream("销售报表.xlsx", FileMode.Create))
        {
            workbook.Write(fs);
        }
        
        Console.WriteLine("格式化报表创建成功!");
    }
}

5.10 本章小结

本章详细介绍了NPOI中的样式和格式化功能。通过本章学习,你应该掌握:

  • 样式对象的结构和复用机制
  • 字体的各种属性设置(名称、大小、颜色、加粗、斜体等)
  • 对齐方式(水平、垂直、文本换行、旋转)
  • 边框设置(样式、颜色、区域边框)
  • 填充和背景色设置
  • 数字格式(内置格式、日期格式、自定义格式)
  • 样式管理器的设计和使用
  • 条件格式的基本应用

样式和格式化是创建专业Excel报表的关键,合理使用样式缓存可以提高性能并避免样式数量限制问题。


下一章预告:第六章将介绍Excel公式与函数,包括公式的插入、引用、求值和常用函数的使用。

posted @ 2025-12-29 10:47  我才是银古  阅读(2)  评论(0)    收藏  举报