dotnet OpenXML 解析 PPT 圖表 面積圖入門

来源:https://www.cnblogs.com/lindexi/archive/2022/08/05/16552973.html
-Advertisement-
Play Games

1、await和.result/ .getwaiter() .getresult()的區別 await:Task.Run裡面的邏輯是新開的線程去執行的,await Task.Run後面邏輯都在新開的線程去執行。 private async void MainWindow_Loaded(object ...


本文告訴大家如何使用 OpenXML 解析 PPT 的圖表,以面積圖為入門例子告訴大家 OpenXML 的存儲

在 PPT 裡面,有強大的圖表功能,可以聯動 Excel 展示數據。在 PPT 裡面的圖表和 Excel 的圖表稍微有一些差別,本文只聊 PPT 的圖表

如下圖是本文將作為例子的圖表

對應的數據如圖

如上圖可以看到在 PPT 裡面的圖表是可以使用 Excel 的數據,將 Excel 文件內嵌到 PPT 裡面。但這不代表要解析圖表的數據就一定需要先瞭解 Excel 的內容,本文將繞過對 Excel 的任何讀取,通過 PPT 裡面的內容拿到圖表的數據

圖表的組成

開始之前,還請先讓我告訴大家一個圖表元素包含的基礎組件部分,也就是圖表元素由哪些部分組成

橫坐標軸 類別坐標軸數據

對於面積圖來說,預設的面積圖的橫坐標就是類別的坐標軸數據,對應的 Excel 表格的第一列的內容,也就是 A B C D E 這些數據

在 OpenXML SDK 裡面,採用 DocumentFormat.OpenXml.Drawing.Charts.CategoryAxisData 存放

本文以下將會告訴大家獲取方法,這裡只是寫上類型,方便大家瞭解

縱坐標軸

對於預設面積圖來說,縱坐標屬於一個運行時屬性,不會存放在 OpenXML 文檔裡面,需要根據每個系列的數值的最大值和最小值以及配置,計算出來縱坐標的內容,本文不會涉及具體的坐標軸計算方法

數據系列

在圖表裡面有數據系列的概念,每個系列的數據組成一個個的數據系列。對於大部分圖表來說,數據層都是由一個個數據系列組成的

每個數據系列可以有自己的系列名稱

系列名稱大部分時候都放在圖例裡面,也就是圖例裡面的內容就是由系列名稱提供的

在 OpenXML SDK 裡面,採用 DocumentFormat.OpenXml.Drawing.Charts.SeriesText 存放

在圖表裡面,核心就是對數據的處理,系列的數據內容就是核心的

如圖,面積圖有兩個數據系列,通過上面的 Excel 內容可以瞭解到兩個系列的數據分別如下

系列 1:32,32,28,12,15
系列 2:12,12,12,21,28

本文將重點告訴大家如何解析圖表的數據

效果

以下是本文的解析效果,可以解析出來圖表的類別坐標軸數據,和各個系列的系列名稱和系列數據

下麵將告訴大家如何根據 OpenXML SDK 提供的方法讀取到圖表的內容

讀取圖表

在開始之前,還請大家先瞭解 OpenXml 讀取 PPT 的基礎。本文將在 C# dotnet 使用 OpenXml 解析 PPT 文件 的基礎上進行開發

先讀取 PPT 文檔

            var file = new FileInfo("Test.pptx");

            using var presentationDocument = PresentationDocument.Open(file.FullName, false);

本文的測試文件和所有代碼都可以在本文最後獲取

在這份 Test.pptx 的圖表是放在第一個頁面,先獲取頁面,通過頁面的元素獲取到圖表

            var slide = presentationDocument.PresentationPart!.SlideParts.First().Slide;

在 OpenXML 裡面的頁面存放的圖表的代碼如下

 <p:cSld>
   <p:spTree>
     <p:graphicFrame>
       ...
     </p:graphicFrame>
   </p:spTree>
 </p:cSld>

圖表也是一個元素,放在 SharpTree (p:spTree) 裡面,作為 GraphicFrame (p:graphicFrame) 存放。但不能說 GraphicFrame 就是圖表元素,在 OpenXML 的 GraphicFrame 是一個很通用的元素,如 OLE 元素或公式都會用到此元素

讀取 GraphicFrame 的內容,如果能讀取到 ChartReference (c:chart) 那就證明這個元素是圖表元素

            // 獲取圖表元素,在這份課件里,有一個面積圖。以下使用 First 忽略細節,獲取圖表
            var graphicFrame = slide.Descendants<GraphicFrame>().First();
            // 獲取到對應的圖表信息,圖表是引用的,內容不是放在 Slide 頁面裡面,而是放在獨立的圖表 xml 文件里
            var graphic = graphicFrame.Graphic;
            var graphicData = graphic?.GraphicData;
            var chartReference = graphicData?.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.ChartReference>();

在 OpenXML 里,圖表是引用的,內容不是放在 Slide 頁面裡面,而是放在獨立的圖表 xml 文件里。頁面的代碼如下

   <p:graphicFrame>
     <a:graphic>
       <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/chart">
         <c:chart xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:id="rId2" />
       </a:graphicData>
     </a:graphic>
   </p:graphicFrame>

根據 dotnet OpenXML 為什麼資源使用 Relationship 引用 可以瞭解到,這裡的圖表引用,需要到 rels 文件裡面獲取關聯的內容。在 OpenXml SDK 里,封裝好了獲取方法,獲取時需要有兩個參數,一個是 id 另一個是去哪裡獲取的 Part 內容。獲取 id 的方法如下

            // 獲取到 id 也就是 `r:id="rId2"` 根據 Relationship 的描述,可以知道去 rels 文件裡面獲取關聯的內容。在 OpenXml SDK 里,封裝好了獲取方法,獲取時需要有兩個參數,一個是 id 另一個是去哪裡獲取的 Part 內容
            var id = chartReference?.Id?.Value;

在這份課件是圖表元素放在頁面上,可以通過頁面去獲取到圖表元素的存儲。在實際項目里,需要判斷圖表元素所在的是頁面還是頁面模版等,不能和以下代碼寫固定從頁面獲取

            // 如果是放在模版裡面,記得要用模版的 Part 去獲取
            var currentPart = slide.SlidePart!;

            if (!currentPart.TryGetPartById(id!, out var openXmlPart))
            {
                // 在這份課件里,一定不會進入此分支
                // 一定能從頁面找到對應的資源內容也就是圖表
                return;
            }

這裡拿到的 openXmlPart 是 ChartPart 對象,這裡面就存放了圖表的信息

            if (openXmlPart is not ChartPart chartPart)
            {
                // 這裡拿到的一定是 ChartPart 對象,一定不會進入此分支。但是在實際項目的代碼,還是要做這個判斷
                return;
            }

這裡的 ChartPart 對應的就是 charts\chartN.xml 文件。這裡的 chartN.xml 表示的是 chart1.xml 或 chart2.xml 等文件

這個文件的存儲內容大概如下

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <c:chartSpace>
   <c:chart>
     ...
     <c:plotArea>
       ...
     </c:plotArea>
   </c:chart>
 </c:chartSpace>

讀取圖表首先需要獲取 ChartSpace 對象,再獲取到 Chart 對象。在 OpenXML SDK 裡面,定義了很多個 Chart 類型,放在不同的命名空間,在獲取時,推薦寫全命名空間

using Chart = DocumentFormat.OpenXml.Drawing.Charts.Chart;

            // 這裡的 ChartPart 對應的就是 charts\chartN.xml 文件。這裡的 chartN.xml 表示的是 chart1.xml 或 chart2.xml 等文件
            var chartSpace = chartPart.ChartSpace;

            // 這裡的 Chart 是 DocumentFormat.OpenXml.Drawing.Charts.Chart 類型,在 OpenXmlSDK 裡面,有多個同名的 Chart 類型,還請看具體的命名空間
            /*
            <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
            <c:chartSpace>
              <c:chart>
                ...
                <c:plotArea>
                  ...
                </c:plotArea>
              </c:chart>
            </c:chartSpace>
             */
            var chart = chartSpace.GetFirstChild<Chart>();

接著獲取 PlotArea 對象,這裡面就存放了圖表的內容

using PlotArea = DocumentFormat.OpenXml.Drawing.Charts.PlotArea;

            var chart = chartSpace.GetFirstChild<Chart>();
            var plotArea = chart?.GetFirstChild<PlotArea>();

如本文的面積圖就放在 PlotArea 元素里

 <c:plotArea>
   <c:areaChart>
     ...
   </c:areaChart>
 </c:plotArea>

在 Chart 里,有不同的圖表類型,例如 BarChart Bar3DChart LineChart PieChart Pie3DChart OfPieChart 不水字數了,就是很多不同的圖表。本文這裡只獲取面積圖

            var areaChart = plotArea?.GetFirstChild<AreaChart>();

            if (areaChart == null)
            {
                // 在這份課件里,一定存在面積圖,一定不會進入此分支
                return;
            }

獲取到面積圖,接下來就是讀取面積圖的數據系列

數據系列的存儲代碼如下

  <c:plotArea>
    <c:areaChart>
      <c:ser>
        ...
      </c:ser>
      <c:ser>
        ...
      </c:ser>
    </c:areaChart>
  </c:plotArea>

每個 DocumentFormat.OpenXml.Drawing.Charts.AreaChartSeries (c:ser) 就是一個系列的內容。一個圖表裡面可以有多個系列,每個系列包含下麵數據

  • 系列名
  • 系列數據
  • 類別軸上的數據
  • 樣式信息

樣式信息裡面包含了填充的畫刷,如純色填充。類別軸上的數據是面積圖橫坐標軸顯示內容,每個系列都有,這是重覆的數據,在 PPT 里,只取第一個系列的數據

數據系列里的橫坐標軸的類別坐標軸數據,在 OpenXML 裡面,是 DocumentFormat.OpenXml.Drawing.Charts.CategoryAxisData 類型,對應 c:cat 的內容

讀取類別軸上的數據方法如下

            foreach (var areaChartChildElement in areaChart.ChildElements)
            {
                // 獲取系列
                /*
                    <c:plotArea>
                      <c:areaChart>
                        <c:ser>
                          ...
                        </c:ser>
                        <c:ser>
                          ...
                        </c:ser>
                      </c:areaChart>
                    </c:plotArea>
                 */
                if (areaChartChildElement is DocumentFormat.OpenXml.Drawing.Charts.AreaChartSeries areaChartSeries)
                {
                    // 類別軸上的數據 橫坐標軸上的數據
                    var categoryAxisData = areaChartSeries.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.CategoryAxisData>()!;
                }
            }

在 OpenXML SDK 的存儲如下

 <c:plotArea>
   <c:areaChart>
     <c:ser>
       <c:cat>
       </c:cat>         
     </c:ser>
   </c:areaChart>
 </c:plotArea>

在類別軸上的數據存放的是數據引用,數據引用在 OpenXML 裡面有多個不同的存儲類型。例如 NumberReference 類型表示的是數值引用,例如 StringReference 表示字元串引用類型,在這份課件裡面存放的是 StringReference 類型,以下代碼只演示採用 StringReference 類型的讀取方式,還請在具體項目,自行判斷

var categoryAxisData = areaChartSeries.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.CategoryAxisData>()!;
var categoryAxisDataStringReference = categoryAxisData.GetFirstChild<StringReference>();

在 StringReference 裡面,大部分都有兩個部分,一個是公式,表示如何引用 Excel 的數據。通過公式讀取 Excel 可以獲取到正確的數據,但缺點是比較複雜。可以通過第二部分,也就是緩存數據部分讀取,雖然讀取緩存也許不對,不過優點在於簡單

存儲的代碼如下

  <c:cat>
   <c:strRef>
     <c:f>Sheet1!$A$2:$A$6</c:f>
     <c:strCache>
       <c:ptCount val="5" />
       <c:pt idx="0">
         <c:v>A</c:v>
       </c:pt>
       <c:pt idx="1">
         <c:v>B</c:v>
       </c:pt>
       <c:pt idx="2">
         <c:v>C</c:v>
       </c:pt>
       <c:pt idx="3">
         <c:v>D</c:v>
       </c:pt>
       <c:pt idx="4">
         <c:v>E</c:v>
       </c:pt>
     </c:strCache>
   </c:strRef>
 </c:cat>

獲取公式的代碼如下

 var categoryAxisDataStringReference = categoryAxisData.GetFirstChild<StringReference>();
 if (categoryAxisDataStringReference != null)
 {
     // 這個公式表示是從 Excel 哪個數據獲取的,獲取的方式比較複雜。這裡還是先從緩存獲取
     var categoryAxisDataFormula = categoryAxisDataStringReference.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.Formula>();
 }

讀取緩存的方法如下

  // 讀取緩存
  var categoryAxisDataStringCache = categoryAxisDataStringReference.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.StringCache>()!;

讀取類別軸上的數據

 var list = new List<string>();
 foreach (var stringPoint in categoryAxisDataStringCache.Elements<DocumentFormat.OpenXml.Drawing.Charts.StringPoint>())
 {
     // 以下的 類別軸上的數據 橫坐標軸上的數據,各個列項的名稱
     // 對於面積圖來說,多個系列的列項都是相同的。儘管在 OpenXml 存儲裡面存放了兩份,但以第零個系列的為準
     var text = stringPoint.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.NumericValue>()!.Text;

     list.Add(text);
 }

上面代碼的 list 就存放了讀取類別軸上的數據,也就是 A B C D E 字元串

繼續讀取第二部分內容,系列的系列名稱,也就是系列標題

系列標題在 OpenXML 里,使用 DocumentFormat.OpenXml.Drawing.Charts.SeriesText 表示,對應 c:tx 類型。在圖表裡面的數據大部分都採用引用的方式,引用裡面基本都有兩個部分,如 類別軸上的數據 有引用 Excel 的公式,和緩存

這裡讀取系列標題也是通過緩存讀取,不會去解析 Excel 內容

  // 獲取系列標題,放心,可以不讀取 Excel 的內容,通過緩存內容即可。但是緩存內容也許和 Excel 內容不對應
  /*
      <c:plotArea>
        <c:areaChart>
          <c:ser>
            <c:tx>
              <c:strRef>
                <c:f>Sheet1!$B$1</c:f>
                <c:strCache>
                  <c:ptCount val="1" />
                  <c:pt idx="0">
                    <c:v>系列 1</c:v>
                  </c:pt>
                </c:strCache>
              </c:strRef>
            </c:tx>
            ...
          </c:ser>
        </c:areaChart>
      </c:plotArea>
   */
  var seriesText = areaChartSeries.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.SeriesText>()!;
  var seriesTextStringReference = seriesText.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.StringReference>()!;
  // 這個公式表示是從 Excel 哪個數據獲取的,獲取的方式比較複雜。這裡還是先從緩存獲取
  var seriesTextFormula = seriesTextStringReference.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.Formula>();

使用緩存獲取系列名稱

 // 有緩存的話,從緩存獲取就可以,緩存內容也許和 Excel 內容不對應
 /*
     <c:strCache>
       <c:ptCount val="1" />
       <c:pt idx="0">
         <c:v>系列 1</c:v>
       </c:pt>
     </c:strCache>
  */
 var seriesTextStringCache = seriesTextStringReference.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.StringCache>();
 if (seriesTextStringCache != null)
 {
     var seriesTextStringPoint = seriesTextStringCache.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.StringPoint>();

     var numericValue = seriesTextStringPoint!.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.NumericValue>();
     // 系列1 標題
     var title = numericValue!.Text;

 }

上面的 title 就是系列的標題,如上面圖表,拿到的就是 系列1系列2 字元串

完成獲取系列的標題獲取,下麵開始獲取系列的樣式。系列的樣式如系列的填充畫刷,畫刷是一個比較大的話題,本文使用的例子只用到純色畫刷

圖表的系列樣式存儲採用的是 DocumentFormat.OpenXml.Drawing.Charts.ChartShapeProperties 類型,圖表的形狀屬性的內容和 形狀屬性 的內容是差不多的

  <c:plotArea>
    <c:areaChart>
      <c:ser>
        <c:tx>
          ...
        </c:tx>
        <c:spPr>
          <a:solidFill>
            <a:srgbClr val="FF0000" />
          </a:solidFill>
        </c:spPr>
      </c:ser>
    </c:areaChart>
  </c:plotArea>

獲取系列的填充顏色

  // 圖表的形狀屬性的內容和 形狀屬性 的內容是差不多的
  /*
      <c:plotArea>
        <c:areaChart>
          <c:ser>
            <c:tx>
              ...
            </c:tx>
            <c:spPr>
              <a:solidFill>
                <a:srgbClr val="FF0000" />
              </a:solidFill>
            </c:spPr>
          </c:ser>
        </c:areaChart>
      </c:plotArea>
   */
  var chartShapeProperties = areaChartSeries.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.ChartShapeProperties>()!;
  // 獲取畫刷,畫刷有好多不同的類型,這個課件只用了純色
  var solidFill = chartShapeProperties.GetFirstChild<SolidFill>()!;
  // 畫刷純色顏色有很多個顏色表示方法,這個課件只用了 RGB 的純色
  var rgbColorModelHex = solidFill.GetFirstChild<DocumentFormat.OpenXml.Drawing.RgbColorModelHex>()!;
  // 這就是這個系列的顏色
  var colorValue = rgbColorModelHex.Val!.Value;

以上的 colorValue 就是這個系列的填充。不同的系列可以有不同的填充

接下來獲取圖表最核心的內容,系列的數據

在 PPT 裡面,是允許數據為空的,如果是空,行為就是不繪製系列內容。本文使用的例子是存在數據,就沒有判斷數據為空

 // 獲取系列的值
 /*
     <c:plotArea>
       <c:areaChart>
         <c:ser>
           <c:tx>
             ...
           </c:tx>
           <c:cat>
             ...
           </c:cat>
           <c:val>
             <c:numRef>
               <c:f>Sheet1!$B$2:$B$6</c:f>
               <c:numCache>
                 <c:formatCode>General</c:formatCode>
                 <c:ptCount val="5" />
                 <c:pt idx="0">
                   <c:v>32</c:v>
                 </c:pt>
                 <c:pt idx="1">
                   <c:v>32</c:v>
                 </c:pt>
                 <c:pt idx="2">
                   <c:v>28</c:v>
                 </c:pt>
                 <c:pt idx="3">
                   <c:v>12</c:v>
                 </c:pt>
                 <c:pt idx="4">
                   <c:v>15</c:v>
                 </c:pt>
               </c:numCache>
             </c:numRef>
           </c:val>
         </c:ser>
         <c:ser>
           ...
         </c:ser>
       </c:areaChart>
     </c:plotArea>
  */
 // 這就是系列裡面最重要的數據。然而在 PPT 裡面,是允許為空的,如果是空,行為就是不繪製系列內容
 var valueList = new List<string>();
 var values = areaChartSeries.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.Values>();

在面積圖,數據理論上是數值類型。對應的是 NumberReference 引用,同樣可以使用公式引用 Excel 數據,也可以採用緩存獲取

  var valuesNumberReference = values?.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.NumberReference>();
  if (valuesNumberReference != null)
  {
      /*
           <c:val>
             <c:numRef>
               <c:f>Sheet1!$B$2:$B$6</c:f>
               <c:numCache>
                 <c:formatCode>General</c:formatCode>
                 <c:ptCount val="5" />
                 <c:pt idx="0">
                   <c:v>32</c:v>
                 </c:pt>
                 <c:pt idx="1">
                   <c:v>32</c:v>
                 </c:pt>
                 <c:pt idx="2">
                   <c:v>28</c:v>
                 </c:pt>
                 <c:pt idx="3">
                   <c:v>12</c:v>
                 </c:pt>
                 <c:pt idx="4">
                   <c:v>15</c:v>
                 </c:pt>
               </c:numCache>
             </c:numRef>
           </c:val>
       */
      // 這份課件一定存在 values 內容
      // 和其他的一樣,存在引用 Excel 的內容。這裡同樣也是採用緩存
      var valuesFormula = valuesNumberReference.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.Formula>();

本文只採用讀取緩存的方式。在緩存也有一個數據,表示數據如何格式化顯示,例如通過格式化字元串告訴 PPT 如何格式化日期內容等。本文使用的例子寫的是 General 表示不需要格式化

  var valuesNumberingCache = valuesNumberReference.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.NumberingCache>()!;

  // 通過 FormatCode 決定界面效果。這份課件是 General 表示不用格式化
  var formatCode = valuesNumberingCache.FormatCode;
  Debug.Assert(formatCode?.Text == "General");

接下來繼續獲取數據

 var valueList = new List<string>();
 foreach (var numericPoint in valuesNumberingCache.Elements<DocumentFormat.OpenXml.Drawing.Charts.NumericPoint>())
 {
     var numericValue = numericPoint.GetFirstChild<DocumentFormat.OpenXml.Drawing.Charts.NumericValue>()!;
     var numericValueText = numericValue.Text;

     valueList.Add(numericValueText);
 }

通過上面例子,無論數據引用是數值引用還是字元串引用,具體的內容都是 DocumentFormat.OpenXml.Drawing.Charts.NumericValue 類型。如果不需要準確判斷內容,可以採用獲取此類型,簡化邏輯

上面代碼的 valueList 存放了系列數據內容

這就完成了讀取圖表的大部分數據內容

數據存儲

本文期望大家瞭解 OpenXML 里對圖表的存儲方式。在 OpenXML 裡面,圖表是放在頁面的一個元素,但是數據不放在頁面上,頁面上放的是引用。通過引用獲取到圖表的內容,對應的數據存儲如下

    <c:plotArea>
      <c:areaChart>
        <c:ser>
          <!-- 系列的數據 -->
        </c:ser>
        <c:ser>
          <c:tx>
            <!-- 系列標題 -->
          </c:tx>
          <c:spPr>
            <!-- 系列樣式 -->
          </c:spPr>
          <c:cat>
            <!-- 類別軸上的數據 -->
          </c:cat>
          <c:val>
            <!-- 系列數據 -->
          </c:val>
        </c:ser>
      </c:areaChart>
    </c:plotArea>

以上是面積圖的存儲,面積圖裡面由多個系列組成。對於圖表來說,最重要的數據就是每個系列的內容。系列裡面包含了系列標題,系列樣式,和類別軸上的數據和系列數據。其中類別軸上的數據只有第零個系列的有用,但是在 OpenXML 里每個系列都重覆存放一份

在圖表裡存放的數據使用的是引用,可以用公式讀取 Excel 的數據,也可以使用緩存。如果想要數據正確,是需要通過公式讀取 Excel 的數據,如果想要讀取 Excel 的數據,前置的是讀取 PPT 裡面內嵌的 Excel 內容,請看 dotnet OpenXML 讀取 PPT 內嵌 xlsx 格式 Excel 表格的信息

圖表還有其他的內容,如圖表標題和樣式等。以及圖表的數據格式化展示邏輯,日期計算方法等,這些都沒有放在本文告訴大家。將在後續博客告訴大家這些內容和行為,請看 Office 使用 OpenXML SDK 解析文檔博客目錄

代碼

本文以上的測試文件和代碼放在githubgitee 歡迎訪問

可以通過如下方式獲取本文的源代碼,先創建一個空文件夾,接著使用命令行 cd 命令進入此空文件夾,在命令行裡面輸入以下代碼,即可獲取到本文的代碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 2f266d20916f784662d84a98d60b7e1bd097d11d

以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

獲取代碼之後,進入 MainWindow.xaml.cs 文件,在這個文件里就是本文的例子代碼

更多

更多請看 Office 使用 OpenXML SDK 解析文檔博客目錄

博客園博客只做備份,博客發佈就不再更新,如果想看最新博客,請到 https://blog.lindexi.com/

知識共用許可協議
本作品採用知識共用署名-非商業性使用-相同方式共用 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發佈,但務必保留文章署名[林德熙](http://blog.csdn.net/lindexi_gd)(包含鏈接:http://blog.csdn.net/lindexi_gd ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請與我[聯繫](mailto:[email protected])。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在上一遍文章中已經介紹了PixelShaderEffect 用hlsl(著色器) 可以實現各種自定義濾鏡效果了,本文將用 "ThresholdEffect" 來講解如何編寫,編譯hlsl,然後使用PixelShaderEffect製作自定義濾鏡。 效果圖: 一.hlsl幫助程式介紹 在寫hlsl 代 ...
  • 簡介 FTP是FileTransferProtocol(文件傳輸協議)的英文簡稱,而中文簡稱為“文傳協議”。用於Internet上的控制文件的雙向傳輸。同時,它也是一個應用程式(Application)。基於不同的操作系統有不同的FTP應用程式,而所有這些應用程式都遵守同一種協議以傳輸文件。 FTP ...
  • 在繼承中,派生類可以拿到基類的方法,若是派生類很多,且有時某部分派生類的部分實現邏輯是一樣的,但其他的派生類又用不到,這個時候這些邏輯若是全部寫到派生類中,就會導致產生很多的重覆邏輯,但是若是寫到基類中就會導致其他用不到當前邏輯的派生類也能調用,這樣就會導致代碼維護出現了問題。由此產生了介面。 在C ...
  • WPF 截圖控制項之移除控制項(九)「仿微信」 WPF 截圖控制項之移除控制項(九)「仿微信」 作者:WPFDevelopersOrg 原文鏈接: https://github.com/WPFDevelopersOrg/WPFDevelopers 框架使用大於等於.NET40; Visual Studio ...
  • 之前寫的DBHelper,名稱確實太Low,就改了個名,叫LiteSql,本來想叫SqlShuttle(SQL一把梭),奈何單詞太長。 有兩個版本,一個是LiteSql,一個是Dapper.LiteSql,LiteSql底層用的是ADO.NET,Dapper.LiteSql底層用的是Dapper,提 ...
  • 從零開始搭建基於 ABP Framework 分層架構解決方案,快速集成框架內置應用模塊。探索基於 ABP Framework 極速開發的最佳路徑,構建一個模塊完備、可開發、可調試、可發佈和部署的分層架構解決方案。 ...
  • 一、前言 之前分享過一期關於DrawingVisual來繪製高性能曲線的博客,今天再分享一篇通過另一種方式來繪製高性能曲線的方法,也就是通過WriteableBitmap的方式;具體的一些細節這裡就不啰嗦了,同樣是局部繪製的思想,滾動條拖動到哪裡,就只繪製那一部分的曲線,直接貼代碼;(該程式在英特爾 ...
  • 規則,點擊投籃目標點,就會有一個球沿著相關拋物線,然後,判斷是否進入籃子里,其實就是一個矩形,直接是按照碰撞檢測來的,碰到就算進去了,對其增加了一個分數統計等功能。 ...
一周排行
    -Advertisement-
    Play Games
  • ## 引言 最近發現自己喜歡用的 Todo 軟體總是差點意思,畢竟每個人的習慣和工作流不太一樣,我就想著自己寫一個小的[Todo 項目]( https://github.com/circler3/TodoTrack ),核心的功能是自動記錄 Todo 執行過程中消耗的時間(尤其面向程式員),按照自己 ...
  • ### 前言 當我們編寫 C# 代碼時,經常需要處理大量的數據集合。在傳統的方式中,我們往往需要先將整個數據集合載入到記憶體中,然後再進行操作。但是如果數據集合非常大,這種方式就會導致記憶體占用過高,甚至可能導致程式崩潰。 C# 中的`yield return`機制可以幫助我們解決這個問題。通過使用`y ...
  • 1. ADO.NET的前世今生 ADO.NET的名稱起源於ADO(ActiveX Data Objects),是一個COM組件庫,用於在以往的Microsoft技術中訪問數據。之所以使用ADO.NET名稱,是因為Microsoft希望表明,這是在NET編程環境中優先使用的數據訪問介面。 ADO.NE ...
  • 1. 為什麼需要單元測試 在我們之前,測試某些功能是否能夠正常運行時,我們都將代碼寫到Main方法中,當我們測試第二個功能時,我們只能選擇將之前的代碼清掉,重新編寫。此時,如果你還想重新測試你之前的功能時,這時你就顯得有些難為情了,因為代碼都被你清掉了。當然你完全可以把代碼寫到一個記事本中進行記錄, ...
  • 1. 透過現象看本質 反射被譽為是 c#中的黑科技 ,在很多領域中都有反射的身影,例如,我們經常使用的ORM框架,ABP框架 等。 反射指程式可以訪問、檢測和修改它本身狀態或行為的一種能力。. 程式集包含模塊,而模塊包含類型,類型又包含成員。. 反射則提供了封裝程式集、模塊和類型的對象。. 您可以使 ...
  • # Rust Web 全棧開發之 Web Service 中的錯誤處理 ## Web Service 中的統一錯誤處理 ### Actix Web Service 自定義錯誤類型 -> 自定義錯誤轉為 HTTP Response - 資料庫 - 資料庫錯誤 - 串列化 - serde 錯誤 - I/ ...
  • 在前面的幾篇文章中,詳細地給大家介紹了Java里的集合。但在介紹集合時,我們涉及到了泛型的概念卻並沒有詳細學習,所以今天我們要花點時間給大家專門講解什麼是泛型、泛型的作用、用法、特點等內容 ...
  • ###BIO:同步阻塞 主線程發起io請求後,需要等待當前io操作完成,才能繼續執行。 ###NIO:同步非阻塞 引入selector、channel、等概念,當主線程發起io請求後,輪詢的查看系統是否準備好執行io操作,沒有準備好則主線程不會阻塞會繼續執行,準備好主線程會阻塞等待io操作完成。 # ...
  • 摘要:在讀多寫少的環境中,有沒有一種比ReadWriteLock更快的鎖呢?有,那就是JDK1.8中新增的StampedLock! 本文分享自華為雲社區《【高併發】高併發場景下一種比讀寫鎖更快的鎖》,作者: 冰 河。 什麼是StampedLock? ReadWriteLock鎖允許多個線程同時讀取共 ...
  • ## 併發與並行😣 ### 併發與並行的概念和區別 並行:同一個時間段內多個任務同時在不同的CPU核心上執行。強調同一時刻多個任務之間的”**同時執行**“。 併發:同一個時間段內多個任務都在進展。強調多個任務間的”**交替執行**“。 ![](https://img2023.cnblogs.co ...