使用Open XML 2.0在Excel中获取单元格背景颜色

9

我正在尝试获取Excel电子表格中单元格的背景颜色。我正在使用Open XML 2.0 SDK,并且能够打开*.xlsx文件并获取单元格值。以下是我获取背景颜色的代码:

   public BackgroundColor GetCellBackColor(Cell theCell, SpreadsheetDocument document)
    {
        BackgroundColor backGroundColor = null;
        WorkbookStylesPart styles = SpreadsheetReader.GetWorkbookStyles(document);
        int cellStyleIndex = (int)theCell.StyleIndex.Value;
        CellFormat cellFormat = (CellFormat)styles.Stylesheet.CellFormats.ChildElements[cellStyleIndex];
        Fill fill = (Fill)styles.Stylesheet.Fills.ChildElements[(int)cellFormat.FillId.Value];
        backGroundColor = fill.PatternFill.BackgroundColor;

        return backGroundColor;
    }

我的问题是PatternFill.BackgroundColor只返回自然数,我认为它是样式的ID。我的问题在于以下代码行:

DocumentFormat.OpenXml.Spreadsheet.Color c = (DocumentFormat.OpenXml.Spreadsheet.Color)styles.Stylesheet.Colors.ChildElements[Int32.Parse(backGroundColor.InnerText)];

返回错误,因为Stylesheet.Colorsnull... ...也许是因为我在Excel中使用了“内置”颜色而不是自定义颜色?!

有什么办法可以从“backGroundColor-Value”中“计算”真实的颜色编号吗?


1
类SpreadsheetReader在OpenXML 2.5中不存在。 - Elmue
@Elmue 为了使用SpreadsheetReader,您还需要安装DocumentFormat.OpenXML.Extensions - Unknown
3个回答

14
一个Excel电子表格中单元格的填充模式由两种颜色组成:背景颜色和前景颜色。在这里,“前景颜色”这个术语有点误导。它不是字体的颜色,而是图案填充的前景颜色。
例如,如果您用纯色填充单元格的背景,则单元格的相关PatternFill对象的ForegroundColor属性将设置为所选的纯色值,而BackgroundColor对象则设置为系统前景色。PatternFill对象的PatternType属性设置为PatternValues.Solid。
因此,要获取单元格背景(纯色填充)的颜色值,您必须分析相关PatternFill对象的ForegroundColor属性。您必须确定实例表示的“颜色类型”:
1.自动颜色和系统相关颜色 2.索引颜色。 3.ARGB颜色(Alpha、Red、Green和Blue) 4.基于主题的颜色。 5.应用于颜色的色调值。

关于不同的“颜色类型”更多信息,请参见以下link

请注意,ForegroundColorBackgroundColor类的InnerText属性的含义取决于颜色类型。例如,在基于主题的颜色的情况下,InnerText属性设置为索引到ColorScheme集合中。

以下示例打印电子表格文档中所有单元格的所有背景颜色信息:

public static PatternFill GetCellPatternFill(Cell theCell, SpreadsheetDocument document)
{ 
  WorkbookStylesPart styles = SpreadsheetReader.GetWorkbookStyles(document);

  int cellStyleIndex;
  if (theCell.StyleIndex == null) // I think (from testing) if the StyleIndex is null
  {                               // then this means use cell style index 0.
    cellStyleIndex = 0;           // However I did not found it in the open xml 
  }                               // specification.
  else
  {
    cellStyleIndex = (int)theCell.StyleIndex.Value;
  }      

  CellFormat cellFormat = (CellFormat)styles.Stylesheet.CellFormats.ChildElements[cellStyleIndex];

  Fill fill = (Fill)styles.Stylesheet.Fills.ChildElements[(int)cellFormat.FillId.Value];
  return fill.PatternFill;  
}

private static void PrintColorType(SpreadsheetDocument sd, DocumentFormat.OpenXml.Spreadsheet.ColorType ct)
{
  if (ct.Auto != null)
  {
    Console.Out.WriteLine("System auto color");
  }

  if (ct.Rgb != null)
  {
    Console.Out.WriteLine("RGB value -> {0}", ct.Rgb.Value);
  }

  if (ct.Indexed != null)
  {
    Console.Out.WriteLine("Indexed color -> {0}", ct.Indexed.Value);

    //IndexedColors ic = (IndexedColors)styles.Stylesheet.Colors.IndexedColors.ChildElements[(int)bgc.Indexed.Value];         
  }

  if (ct.Theme != null)
  {
    Console.Out.WriteLine("Theme -> {0}", ct.Theme.Value);

    Color2Type c2t = (Color2Type)sd.WorkbookPart.ThemePart.Theme.ThemeElements.ColorScheme.ChildElements[(int)ct.Theme.Value];

    Console.Out.WriteLine("RGB color model hex -> {0}", c2t.RgbColorModelHex.Val);
  }

  if (ct.Tint != null)
  {
    Console.Out.WriteLine("Tint value -> {0}", ct.Tint.Value);
  }
}

static void ReadAllBackgroundColors()
{
  using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open("c:\\temp\\bgcolor.xlsx", false))
  {
    WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart;
    foreach(WorksheetPart worksheetPart in workbookPart.WorksheetParts)
    {
      SheetData sheetData = worksheetPart.Worksheet.Elements<SheetData>().First();

      foreach (Row r in sheetData.Elements<Row>())
      {
        foreach (Cell c in r.Elements<Cell>())
        {            
          Console.Out.WriteLine("----------------");
          PatternFill pf = GetCellPatternFill(c, spreadsheetDocument);        

          Console.Out.WriteLine("Pattern fill type -> {0}", pf.PatternType.Value);

          if (pf.PatternType == PatternValues.None)
          {
            Console.Out.WriteLine("No fill color specified");
            continue;
          }

          Console.Out.WriteLine("Summary foreground color:");
          PrintColorType(spreadsheetDocument, pf.ForegroundColor);
          Console.Out.WriteLine("Summary background color:");
          PrintColorType(spreadsheetDocument, pf.BackgroundColor);                          
        }
      }     
    }
  }
}

static void Main(string[] args)
{ 
  ReadAllBackgroundColors();
}

谢谢您的出色回答。只有一个要点需要补充:如果我不改变单元格的样式,那么行int cellStyleIndex = (int)theCell.StyleIndex.Value;会导致空异常。我怎样才能确定它实际上是默认样式以及这是什么样式(颜色等)?提前感谢! - basti
1
@chiffre:我认为(我进行了一些测试),如果StyleIndex为空,您必须使用单元格样式索引0。但是我在Open XML规范中没有找到相关说明。 - Hans
感谢您的详细解释,但我发现使用ColorType.Theme作为索引无法可靠地从ThemeElements.ColorScheme中获取颜色:索引0和1(通常是黑色/白色)交换了位置,还有索引2和3。这些是主题的“Dark1”/“Light1”/“Dark2”/“Light2”颜色。我们能否指望这些索引始终被交换,或者我们需要以其他方式找到主题颜色? - Sphinxxx
相同的代码也适用于字体属性。感谢您的帮助。记录一下:提取字体ID并从Fonts属性获取字体(类型为Font)。 - Bjoern
这就是为什么我不喜欢OpenXML。它太笨拙了!微软怎么可能没有实现获取单元格颜色这样基本任务的函数呢? - Elmue
显示剩余2条评论

1

我有一个类似的用例,需要测试哪种RGB颜色被应用为单元格的背景颜色。只需在您的函数中添加代码即可。

backGroundColor = fill.PatternFill.BackgroundColor.Rgb.Value;
return backgroundColor;

这将返回单元格背景中使用的 RGB 颜色值。

0

Hans的回答非常好!我想补充一下,因为我不得不解析一些列的渐变填充。这是我的解决方案:

我只想获取rgb字符串,如果您的要求不同,您将不得不相应地更改返回类型。

public static IEnumerable<string> GetCellFillColors(string sheetName,
     string addressName, WorkbookPart wbPart)
    {
        WorkbookStylesPart styles = wbPart.WorkbookStylesPart;

        Sheet theSheet = wbPart.Workbook.Descendants<Sheet>().
          Where(s => s.Name == sheetName).FirstOrDefault();

        // Throw an exception if there is no sheet.
        if (theSheet == null)
        {
            throw new ArgumentException("sheetName");
        }

        // Retrieve a reference to the worksheet part.
        WorksheetPart wsPart =
            (WorksheetPart)(wbPart.GetPartById(theSheet.Id));

        // Use its Worksheet property to get a reference to the cell 
        // whose address matches the address you supplied.
        Cell theCell = wsPart.Worksheet.Descendants<Cell>().
          Where(c => c.CellReference == addressName).FirstOrDefault();

        int cellStyleIndex;
        if (theCell.StyleIndex == null) // I think (from testing) if the StyleIndex is null
        {                               // then this means use cell style index 0.
            cellStyleIndex = 0;           // However I did not found it in the open xml 
        }                               // specification.
        else
        {
            cellStyleIndex = (int)theCell.StyleIndex.Value;
        }

        CellFormat cellFormat = (CellFormat)styles.Stylesheet.CellFormats.ChildElements[cellStyleIndex];

        DocumentFormat.OpenXml.Spreadsheet.Fill fill = (DocumentFormat.OpenXml.Spreadsheet.Fill)styles.Stylesheet.Fills.ChildElements[(int)cellFormat.FillId.Value];

        if (fill.PatternFill != null)
        {
            return fill.PatternFill?.BackgroundColor?.Rgb?.ToString() != null ?
            new List<string>()
            {
                fill.PatternFill?.BackgroundColor?.Rgb?.ToString()
            } : new List<string>();
        }

        if (fill.GradientFill != null)
        {
            var colors = fill.GradientFill.ChildElements.Select(e => (DocumentFormat.OpenXml.Spreadsheet.GradientStop)e);
            return colors?.Select(c => c?.Color.Rgb?.ToString());

        }

        return null;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接