Epplus设置位置图片问题

8
我正在使用Epplus库在Asp.Net C#中生成Excel 2010及更高版本兼容的文件。我正在使用最新版本3.1.2。
在添加任何图片之前,我首先设置行高,就像这样:
ExcelPackage pck = new ExcelPackage();
var ws = pck.Workbook.Worksheets.Add("sheet 1");
while (i < dt.Rows.Count + offset)
{
    ws.Row(i).Height = 84;
    i++;
}

dt 是我的 DataTable,其中包含 DataRows。 设置高度后,我再次循环遍历行以添加图片。

while (i < dt.Rows.Count + offset)
{
    var prodImg = ws.Drawings.AddPicture(dr["code"].ToString(), new FileInfo(path));
    prodImg.SetPosition(i - 1, 0, 14, 0);
    prodImg.SetSize(75);
}

这个可以工作,但这个不行:

var prodImg = ws.Drawings.AddPicture(dr["code"].ToString(), new FileInfo(path));
int w = prodImg.Image.Width;
int h = prodImg.Image.Height;

if (h > 140) // because height of 84 is 140 pixels in excel
{
    double scale = h / 140.0;
    w = (int)Math.Floor(w / scale);
    h = 140;
}

int xOff = (150 - w) / 2;
int yOff = (140 - h) / 2;

prodImg.SetPosition(i - 1, xOff, 11, yOff);
prodImg.SetSize(w, h);

这会导致图片偏离中心且未经调整大小的图像。而以下代码位于同一循环中:
var prodImgDm = ws.Drawings.AddPicture("bcdm" + dr["code"].ToString(), new FileInfo(pathDm));
prodImgDm.SetPosition(i - 1, 25, 15, 40);
prodImgDm.SetSize(100);

有时候这个方法是有效的。图片prodImgDm是数据矩阵图像,具有固定的宽度和高度,不需要调整大小,因为它们总是很小/微小。所以即使在某些行中没有SetSize,它也可以工作,在其他一些行中则无法工作。真的很奇怪,因为代码是相同的。可能是库和/或Excel中的问题。也许我使用得不对?有没有epplus图片专家?
提前感谢!!
编辑有时候一张图片胜过千言万语,所以这里是截图。正如您所看到的,产品图片在单元格中不是水平和垂直对齐的。右侧最远处的数据矩阵有时会缩放约120%,即使我设置了SetSize(100),所以对我来说真的很奇怪。所以最后一个数据矩阵有正确的大小……我已经找到了this SO thread,但那并不能帮助我,我想。

epplus images

编辑 2013/04/09 Essenpillai 给了我一个提示来设置

pck.DoAdjustDrawings = false;

但这给了我更奇怪的图像:

doadjustdrawings

数据矩阵在行的基础上仍在变化。其中一行是可以的,而另一行则不行。而且EAN13码太宽了。
3个回答

3
public static void CreatePicture(ExcelWorksheet worksheet, string name, Image image, int firstColumn, int lastColumn, int firstRow, int lastRow, int defaultOffsetPixels)
        {
            int columnWidth = GetWidthInPixels(worksheet.Cells[firstRow, firstColumn]);
            int rowHeight = GetHeightInPixels(worksheet.Cells[firstRow, firstColumn]);

            int totalColumnWidth = columnWidth * (lastColumn - firstColumn + 1);
            int totalRowHeight = rowHeight * (lastRow - firstRow + 1);
            double cellAspectRatio = Convert.ToDouble(totalColumnWidth) / Convert.ToDouble(totalRowHeight);

            int imageWidth = image.Width;
            int imageHeight = image.Height;
            double imageAspectRatio = Convert.ToDouble(imageWidth) / Convert.ToDouble(imageHeight);

            int pixelWidth;
            int pixelHeight;
            if (imageAspectRatio > cellAspectRatio)
            {
                pixelWidth = totalColumnWidth - defaultOffsetPixels * 2;
                pixelHeight = pixelWidth * imageHeight / imageWidth;
            }
            else
            {
                pixelHeight = totalRowHeight - defaultOffsetPixels * 2;
                pixelWidth = pixelHeight * imageWidth / imageHeight;
            }

            int rowOffsetPixels = (totalRowHeight - pixelHeight) / 2;
            int columnOffsetPixels = (totalColumnWidth - pixelWidth) / 2;

            int rowOffsetCount = 0;
            int columnOffsetCount = 0;

            if (rowOffsetPixels > rowHeight)
            {
                rowOffsetCount = (int)Math.Floor(Convert.ToDouble(rowOffsetPixels) / Convert.ToDouble(rowHeight));
                rowOffsetPixels -= rowHeight * rowOffsetCount;
            }

            if (columnOffsetPixels > columnWidth)
            {
                columnOffsetCount = (int)Math.Floor(Convert.ToDouble(columnOffsetPixels) / Convert.ToDouble(columnWidth));
                columnOffsetPixels -= columnWidth * columnOffsetCount;
            }

            int row = firstRow + rowOffsetCount - 1;
            int column = firstColumn + columnOffsetCount - 1;

            ExcelPicture pic = worksheet.Drawings.AddPicture(name, image);
            pic.SetPosition(row, rowOffsetPixels, column, columnOffsetPixels);
            pic.SetSize(pixelWidth, pixelHeight);
        }

        public static int GetHeightInPixels(ExcelRange cell)
        {
            using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero))
            {
                float dpiY = graphics.DpiY;
                return (int)(cell.Worksheet.Row(cell.Start.Row).Height * (1 / 72.0) * dpiY);
            }
        }

        public static float MeasureString(string s, Font font)
        {
            using (var g = Graphics.FromHwnd(IntPtr.Zero))
            {
                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
                return g.MeasureString(s, font, int.MaxValue, StringFormat.GenericTypographic).Width;
            }
        }

        public static int GetWidthInPixels(ExcelRange cell)
        {
            double columnWidth = cell.Worksheet.Column(cell.Start.Column).Width;
            Font font = new Font(cell.Style.Font.Name, cell.Style.Font.Size, FontStyle.Regular);
            double pxBaseline = Math.Round(MeasureString("1234567890", font) / 10);
            return (int)(columnWidth * pxBaseline);
        }

点击此处查看图片描述

这是一个关于IT技术的内容。

这是我们用EPPLUS控件封装的一个插入图片的方法,希望对你有帮助。 - Sheng Gao
2
虽然这段代码片段可能是解决方案,但包括解释真的有助于提高您的帖子质量。请记住,您正在回答未来读者的问题,而这些人可能不知道您的代码建议原因。 - Johan
很好。对我有效。谢谢 :) - Ghanshyam Shukla
抱歉,我的英语很差,不太会说英语。 - Sheng Gao
我正在寻求帮助。我想要使用您的代码示例,但请指导我如何计算 firstColumn、lastColumn、firstRow、lastRow 和 defaultOffsetPixels - user15940620
@ShengGao 请查看此帖子 https://dev59.com/y6f1oIgBc1ULPQZFv1ZA - user15940620

2
我遇到了与 Epplus 库 相同的问题。
在找不到解决方法后,我查看了该库的 源代码。 Epplus 始终将 Excel 图片创建为 twoCellAnchor 绘图。在 xlsx 文件中,您可以找到带有此代码的 drawingXYZ.xml:
<xdr:twoCellAnchor editAs="oneCell">
  <xdr:from> ... </xdr:from>
  <xdr:to> ... </xdr:to>
  <xdr:pic>
  ...
</xdr:twoCellAnchor>

因此,图片始终与两个单元格相关联,这不是 Epplus 库的可变部分。您可以在 ExcelDrawing.cs 文件中找到此代码部分。
  XmlElement drawNode = _drawingsXml.CreateElement(
    "xdr", "twoCellAnchor", ExcelPackage.schemaSheetDrawings);
  colNode.AppendChild(drawNode);

您可以轻松创建自己的该dll副本。好消息是,您只需要修改两个文件即可解决此问题。所以...
此网站下载Epplus库的源代码,并在Visual Studio中打开。
我们需要将drawing代码生成为oneCellAnchor,因此我们必须删除图片的<xdr:to>元素,并创建具有图片尺寸作为参数的<xdr:ext />元素。
新的xml结构将如下所示:
<xdr:oneCellAnchor editAs="oneCell">
  <xdr:from> ... </xdr:from>
  <xdr:ext cx="1234567" cy="7654321" />
  <xdr:pic>
  ...
</xdr:oneCellAnchor>

好的,那么如何操作呢?
Epplus代码中的更改
ExcelDrawings.cs(链接到文件这里
  1. 首先,我们修改ExcelDrawings.cs中的CreateDrawingXml()方法。为了保留原始功能,我们添加一个可选参数(如果创建oneCellAnchor),默认值为true。在该方法中,基于此参数,我们创建一个或两个单元格锚定,并创建或不创建元素。
此方法代码的重要部分:
private XmlElement CreateDrawingXml(bool twoCell = true) { 
  if (DrawingXml.OuterXml == "") 
  { ... } // not changed
  XmlNode colNode= _drawingsXml.SelectSingleNode("//xdr:wsDr", NameSpaceManager);
  //First change in method code
  XmlElement drawNode;
  if (twoCell)
    drawNode = _drawingsXml.CreateElement(
      "xdr", "twoCellAnchor", ExcelPackage.schemaSheetDrawings);
  else
    drawNode = _drawingsXml.CreateElement(
      "xdr", "oneCellAnchor", ExcelPackage.schemaSheetDrawings);
  colNode.AppendChild(drawNode);

  //Add from position Element; // Not changed
  XmlElement fromNode = _drawingsXml.CreateElement(
    "xdr", "from", ExcelPackage.schemaSheetDrawings);
  drawNode.AppendChild(fromNode);
  fromNode.InnerXml = "<xdr:col>0</xdr:col><xdr:colOff>0</xdr:colOff>"
    + "<xdr:row>0</xdr:row><xdr:rowOff>0</xdr:rowOff>";

  //Add to position Element;
  //Second change in method
  if (twoCell)
  {
    XmlElement toNode = _drawingsXml.CreateElement(
      "xdr", "to", ExcelPackage.schemaSheetDrawings);
    drawNode.AppendChild(toNode);
    toNode.InnerXml = "<xdr:col>10</xdr:col><xdr:colOff>0</xdr:colOff>"
      + "<xdr:row>10</xdr:row><xdr:rowOff>0</xdr:rowOff>";
  }
  return drawNode;
}

然后我们在同一个文件中修改了两个AddPicture方法:
public ExcelPicture AddPicture(string Name, Image image, Uri Hyperlink)
{
  if (image != null) {
    if (_drawingNames.ContainsKey(Name.ToLower())) {
      throw new Exception("Name already exists in the drawings collection");
    }
    XmlElement drawNode = CreateDrawingXml(false);
    // Change: we need create element with dimensions
    // like: <xdr:ext cx="3857625" cy="1047750" />
    XmlElement xdrext = _drawingsXml.CreateElement(
      "xdr", "ext", ExcelPackage.schemaSheetDrawings);
    xdrext.SetAttribute("cx", 
      (image.Width * ExcelDrawing.EMU_PER_PIXEL).ToString());
    xdrext.SetAttribute("cy", 
      (image.Height * ExcelDrawing.EMU_PER_PIXEL).ToString());
    drawNode.AppendChild(xdrext);
    // End of change, next part of method is the same:
    drawNode.SetAttribute("editAs", "oneCell");
    ...
  }
}

这个方法的输入参数是 FileInfo
public ExcelPicture AddPicture(string Name, FileInfo ImageFile, Uri Hyperlink)
{
  if (ImageFile != null) {
    if (_drawingNames.ContainsKey(Name.ToLower())) {
      throw new Exception("Name already exists in the drawings collection");
    }
    XmlElement drawNode = CreateDrawingXml(false);
    // Change: First create ExcelPicture object and calculate EMU dimensions
    ExcelPicture pic = new ExcelPicture(this, drawNode, ImageFile, Hyperlink);
    XmlElement xdrext = _drawingsXml.CreateElement(
      "xdr", "ext", ExcelPackage.schemaSheetDrawings);
    xdrext.SetAttribute("cx", 
      (pic.Image.Width * ExcelDrawing.EMU_PER_PIXEL).ToString());
    xdrext.SetAttribute("cy", 
      (pic.Image.Height * ExcelDrawing.EMU_PER_PIXEL).ToString());
    drawNode.AppendChild(xdrext);
    // End of change, next part of method is the same (without create pic object)
    drawNode.SetAttribute("editAs", "oneCell");
    ...
  }
}

所以,这些都是重要的代码。现在我们必须改变搜索节点的代码并保持元素的顺序。
在`private void AddDrawings()`中,我们将`xpath`从以下内容更改为:
XmlNodeList list = _drawingsXml.SelectNodes(
  "//xdr:twoCellAnchor", NameSpaceManager);

这个可以翻译为:“对于此事:”。
XmlNodeList list = _drawingsXml.SelectNodes(
  "//(xdr:twoCellAnchor or xdr:oneCellAnchor)", NameSpaceManager);

这些都在这个文件中,现在我们要修改 ExcelPicture.cs在这里添加文件链接)。
原始代码在构造函数中查找节点以便附加下一个代码,例如:
node.SelectSingleNode("xdr:to",NameSpaceManager);

因为我们并不总是创建<xdr:to>元素,所以我们改变了这段代码:
internal ExcelPicture(ExcelDrawings drawings, XmlNode node
  , Image image, Uri hyperlink) 
  : base(drawings, node, "xdr:pic/xdr:nvPicPr/xdr:cNvPr/@name")
{
  XmlElement picNode = node.OwnerDocument.CreateElement(
    "xdr", "pic", ExcelPackage.schemaSheetDrawings);
  // Edited: find xdr:to, or xdr:ext if xdr:to not exists
  XmlNode befor = node.SelectSingleNode("xdr:to",NameSpaceManager);
  if (befor != null && befor.Name == "xdr:to")
    node.InsertAfter(picNode, befor);
  else {
    befor = node.SelectSingleNode("xdr:ext", NameSpaceManager);
    node.InsertAfter(picNode, befor);
  }
  // End of change, next part of constructor is unchanged
  _hyperlink = hyperlink;
  ...
}

第二个构造函数同样以FileInfo作为输入参数:
internal ExcelPicture(ExcelDrawings drawings, XmlNode node
  , FileInfo imageFile, Uri hyperlink) 
  : base(drawings, node, "xdr:pic/xdr:nvPicPr/xdr:cNvPr/@name")
{
  XmlElement picNode = node.OwnerDocument.CreateElement(
    "xdr", "pic", ExcelPackage.schemaSheetDrawings);
  // Edited: find xdr:to, or xdr:ext if xdr:to not exists
  XmlNode befor = node.SelectSingleNode("xdr:to", NameSpaceManager);
  if (befor != null && befor.Name == "xdr:to")
    node.InsertAfter(picNode, befor);
  else {
    befor = node.SelectSingleNode("xdr:ext", NameSpaceManager);
    node.InsertAfter(picNode, befor);
  }
  // End of change, next part of constructor is unchanged
  _hyperlink = hyperlink;
  ...

现在,图片被创建为oneCellAnchor。如果需要的话,您可以为两种变体创建多个AddPicture方法。最后一步是构建此项目并创建自己的自定义EPPlus.dll。然后关闭使用此dll的项目,并将新文件EPPlus.dllEPPlus.pdbEPPlus.XML复制到您的项目中(备份并替换您原始的dll文件),放置在同一位置(这样您就不需要对项目引用或设置进行任何更改)。
然后打开并重新构建您的项目,尝试查看是否解决了您的问题。

看起来这是一个很棒的项目拉取请求。https://epplus.codeplex.com/sourcecontrol/list/contributions - JP Hellemons
1
我找到了这个资源https://epplus.codeplex.com/workitem/14675,它建议绘图应该设置editas属性。在那里你可以选择absolute、onecell、twocell。 - JP Hellemons

1
也许我来晚了,但这是我的回答... 您也可以在CodePlex问题上阅读它(https://epplus.codeplex.com/workitem/14846)。

我也遇到了这个问题。

经过一些研究,我找出了错误所在。

它在 ExcelRow.cs 文件的代码第 149 行的 ExcelRow 类中。

有一个错误,当行高改变时,重新计算所有图片的高度,但使用的是图片的宽度而不是高度,所以很容易修复。

只需更改该行:

var pos = _worksheet.Drawings.GetDrawingWidths();

var pos = _worksheet.Drawings.GetDrawingHeight();

查看图片上的代码更改

P.S. 适用于版本4.0.4


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