VSTO:使用C#開發Excel、Word【17】


使用Range對象
Range對象表示電子表格中的單元格范圍。范圍可以包含一個單元格,多個連續的單元格,甚至多個不連續的單元格。您可以在Excel中選擇時按住Ctrl鍵選擇多個不連續的單元格。

獲取特定Cell或Cells范圍對象
Excel提供了多種獲取Range對象的方法。 Range對象是您要在Excel工作表中處理單元格或單元格范圍時使用的對象。在Application對象的描述中提到了兩種獲取Range對象的方法。 Application.ActiveCell在活動窗口中返回活動選擇的左上角單元格。 Application.Selection返回一個表示活動窗口中活動選擇的對象。如果活動選擇是單元格范圍,則可以將Application.Selection轉換為Range對象。如果在活動窗口(例如形狀或圖表)中選擇了其他選項,Application.Selection將返回所選對象。

工作表還提供了幾種獲取Range對象的方法。 Worksheet.get_Range方法是從工作表獲取Range對象的最常用方法。此方法接收可以傳遞字符串的必需對象參數。它具有可以傳遞第二個字符串的第二個可選參數。您傳遞的字符串是所謂的A1樣式參考格式。解釋A1樣式參考格式的最簡單的方法是給出幾個例子。

參考A1指定A列1行的單元格。參考D22指定D列22行的單元格。參考AA11指定行11,列AA(第27列)處的單元格。

參考$ A $ 1也指的是第1行列A中的單元格。如果在A1樣式引用中使用$符號,則它們將被忽略。

您可以使用范圍運算符(:)來指定單元格范圍,其中第一個A1樣式引用是范圍的左上角,后跟一個冒號運算符,之后是右下角的第二個A1樣式引用的范圍。參考A1:B1指的是行1,列A和列1,列B的兩個單元格。參考A1:AA11是指塊中的所有297個單元格,其左上角位於第1列,第A列和下側右角位於第11列,AA欄(第27列)。

您可以使用聯合運算符(,)來指定可能是不連續的多個單元格。例如,參考A1,C4指定了第一個單元格位於第1列,第A列,第二個單元格位於第4列第C列的兩個單元格的范圍。用戶可以通過按住Ctrl鍵來選擇單元格的不連續范圍因為他們選擇各種細胞。參考A1,C4,C8,C10是指定四個不同單元格的另一個有效的A1樣式參考。

交點運算符(一個空格)可以指定單元格的交集。例如,參考A1:A10 A5:A15解析為從行5,列A開始並以行A,列A開始的相交六個單元。參考A1:A10 A5:A15 A5分解為第5行的單個單元格,列A.

您還可以使用您在A1樣式參考中在工作表中定義的任何名稱。例如,假設您定義了指向單元格A1的名為foo的名稱范圍。使用您的名稱的一些有效的A1樣式的引用將包括foo:A2,其引用行1,列A和行2,列A的單元格。引用foo,A5:A6是指第1行,第A列;第5行,A列;和第6列,列A。

如前所述,get_Range方法采用第二個可選參數,您可以傳遞第二個A1樣式的引用字符串。使用范圍運算符有效地組合第一個參數和第二個參數。因此,當調用get_Range(“A1”,“A2”)時,get_Range返回的范圍等同於調用get_Range(“A1:A2”,Type.Missing)時獲得的范圍。

獲取Range對象的第二種方法是使用Worksheet.Cells屬性,該屬性返回工作表中所有單元格的范圍。然后,您可以在返回的Range對象上使用相同的get_Range方法,並以與使用Worksheet對象中的get_Range相同的方式傳遞A1樣式引用以選擇單元格。所以Cells.get_Range(“A1:A2”,Type.Missing)等價於get_Range(“A1:A2”,Type.Missing)。使用Cells屬性的更常見的用法是將其與Range的get_Item屬性結合使用,該屬性將使用行索引和可選的列索引。使用get_Item是一種在不使用A1樣式引用的情況下訪問特定單元格的方法。所以Cells.get_Item(1,1)相當於get_Range(“A1”,Type.Missing)。

獲取Range對象的另一種方法是使用Worksheet.Rows或Worksheet.Columns屬性。這些返回一個與其他Range對象不同的范圍。例如,如果您采用Column返回的范圍並顯示范圍內的單元格數,則返回256列數。但是如果您在返回的范圍內調用Select方法,Excel將在工作表中選擇所有16,772,216個單元格。考慮Rows和Columns返回的范圍的最簡單的方法是它們的行為與列和行標題在Excel中的行為相似。

清單5-27顯示了使用get_Range方法和Cells,Rows和Columns屬性的幾個示例。我們使用范圍的Value2屬性將范圍中的每個單元格設置為指定的字符串值。程序的運行結果如圖5-7所示

清單5-27   獲取Range對象的VSTO定制

private void Sheet1_Startup(object sender, System.EventArgs e)
{
  Excel.Range r1 = this.get_Range("A1", missing);
  r1.Value2 = "r1";

  Excel.Range r2 = this.get_Range("B7:C9", missing);
  r2.Value2 = "r2";

  Excel.Range r3 = this.get_Range("C1,C3,C5", missing);
  r3.Value2 = "r3";

  Excel.Range r4 = this.get_Range("A1:A10 A5:A15", missing);
  r4.Value2 = "r4";

  Excel.Range r5 = this.get_Range("F4", "G8");
  r5.Value2 = "r5";

  Excel.Range r6 = this.Rows.get_Item(12, missing)
    as Excel.Range;

  r6.Value2 = "r6";

  Excel.Range r7 = this.Columns.get_Item(5, missing)
    as Excel.Range;

  r7.Value2 = "r7";
}

圖5-7  運行結果清單5-27

使用Address
給定一個Range對象,你經常需要確定它所指的單元格。 get_Address方法返回A1樣式或R1C1樣式范圍的地址。 您已經了解了A1樣式的引用。 R1C1樣式的引用支持與A1樣式引用(范圍為冒號,聯合逗號和交叉空間)討論的所有相同的運算符。 R1C1樣式的引用分別以R和C開頭的行和列號。 所以R1C1風格的單元格A4將是R4C1。 圖5-8顯示了我們在本節中考慮的三個方面的范圍。


圖5-8  具有三個不連續區域的范圍

 

圖5-8中范圍的地址以A1樣式和R1C1樣式顯示:

$A$15:$F$28,$H$3:$J$9,$L$1
R15C1:R28C6,R3C8:R9C10,R1C12

獲取地址的另一個選擇是獲取外部引用還是本地引用。 我們已經在圖5-8中顯示的地址是本地引用。 外部引用包括范圍所在的工作簿和工作表的名稱。 在圖5-8中,與A1風格和R1C1風格的外部參考相同。

 

[Book1]Sheet1!$A$15:$F$28,$H$3:$J$9,$L$1
[Book1]Sheet1!R15C1:R28C6,R3C8:R9C10,R1C12

對於我們的示例,我們創建的范圍的工作簿未保存。 當我們將它保存為Book1.xls時,地址如下所示:

[Book1.xls]Sheet1!$A$15:$F$28,$H$3:$J$9,$L$1
[Book1.xls]Sheet1!R15C1:R28C6,R3C8:R9C10,R1C12

獲取地址的另一個選擇是使用絕對地址還是使用相對地址。 我們已經考慮過的地址是絕對的。 相對格式(相對於單元格A1)的相同地址如下所示:

R[14]C:R[27]C[5],R[2]C[7]:R[8]C[9],RC[11]
A15:F28,H3:J9,L1

對於R1C1樣式的地址,您還可以指定希望地址相對的單元格。 如果我們在圖5-4中相對於單元格B2獲得了R1C1樣式,我們得到以下結果:

R[13]C[-1]:R[26]C[4],R[1]C[6]:R[7]C[8],R[-1]C[10]

get_Address方法使用五個可選參數來控制引用的返回方式,如表5-17所示。

表5-17   get_Address的可選參數

Parameter Name

Type

What It Does

RowAbsolute

object

通過TRue將地址的行部分作為絕對引用返回($ A $ 1)。 如果您傳遞false,行參考將不會是絕對的($ A1)。 默認值為true。

ColumnAbsolute

object

通過TRue將地址的列部分作為絕對引用返回($ A $ 1)。 如果你傳遞錯誤,列參考將不是絕對的(A $ 1)。 默認值為true。

ReferenceStyle

XlReferenceStyle

通過xlA1返回A1樣式的引用。 通過xlR1C1返回R1C1樣式的引用。

External

object

傳遞真的返回外部引用。 默認值為false。

RelativeTo

object

傳遞一個表示您希望R1C1樣式引用相對於單元格的Range對象。 與A1樣式引用一起使用時不起作用。

 

清單5-28顯示了使用示例范圍的get_Address的幾個示例。

清單5-28  使用get_Address的VSTO自定義

private void Sheet1_Startup(object sender, System.EventArgs e)
{
  Excel.Range range1 = this.get_Range(
    "$A$15:$F$28,$H$3:$J$9,$L$1", missing);

  System.Text.StringBuilder sb = new System.Text.StringBuilder();
  sb.AppendLine("A1-Style Addresses:");
  sb.AppendFormat("Default: {0}\n", range1.get_Address(
    missing, missing, Excel.XlReferenceStyle.xlA1,
    missing, missing));

  sb.AppendFormat("Relative rows: {0}\n",
    range1.get_Address(false, missing,
    Excel.XlReferenceStyle.xlA1, missing, missing));

  sb.AppendFormat("Row & Column Relative: {0}\n",
    range1.get_Address(false, false,
    Excel.XlReferenceStyle.xlA1, missing, missing));

  sb.AppendFormat("External: {0}\n", range1.get_Address(
    missing, missing, Excel.XlReferenceStyle.xlA1,
    true, missing));

  sb.AppendLine();
  sb.AppendLine("R1C1-Style Addresses:");
  sb.AppendFormat("Default: {0}\n", range1.get_Address(
    missing, missing, Excel.XlReferenceStyle.xlR1C1,
    missing, missing));

  sb.AppendFormat("Row & Column Relative to C5: {0}\n",
    range1.get_Address(false, false,
    Excel.XlReferenceStyle.xlR1C1, missing,
    this.get_Range("C5", missing)));

  sb.AppendFormat("External: {0}", range1.get_Address(
    missing, missing, Excel.XlReferenceStyle.xlR1C1,
    true, missing));

  MessageBox.Show(sb.ToString());
}

使用運算符方法創建新的范圍
我們討論了可以在地址字符串中使用的幾個“運算符”,包括聯合運算符(逗號)和交集運算符(空格)。 您還可以通過Application.Union和Application.Intersection方法應用這些操作符。

也可以通過使用get_Offset方法取一個范圍並獲得一個與之相距一些行和列的新范圍。 該方法采用行和列值來偏移給定范圍並返回新的偏移范圍。 所以在圖5-8的示例范圍中調用get_Offset(5,5)返回一個這樣的A1樣式地址的范圍:

"$F$20:$K$33,$M$8:$O$14,$Q$6"

清單5-29顯示了使用這些運算符的示例。 請注意,聯合和交點需要很多可選參數,允許您聯合或相交多於兩個范圍。

清單5-29  使用Union,Intersection和get_Offset的VSTO自定義

private void Sheet1_Startup(object sender, System.EventArgs e)
{
  Excel.Application app = this.Application;

  Excel.Range range1 = this.get_Range("$A$15:$F$28", missing);
  Excel.Range range2 = this.get_Range("$H$3:$J$9", missing);
  Excel.Range range3 = this.get_Range("$L$1", missing);
  Excel.Range range4 = this.get_Range("$A$11:$G$30", missing);

  Excel.Range rangeUnion = app.Union(range1, range2,
    range3, missing, missing, missing, missing, missing,
    missing, missing, missing, missing, missing, missing,
    missing, missing, missing, missing, missing, missing,
    missing, missing, missing, missing, missing, missing,
    missing, missing, missing, missing);

  Excel.Range rangeIntersection = app.Intersect(range1,
    range4, missing, missing, missing, missing, missing,
    missing, missing, missing, missing, missing, missing,
    missing, missing, missing, missing, missing, missing,
    missing, missing, missing, missing, missing, missing,
    missing, missing, missing, missing, missing);

  Excel.Range rangeOffset = rangeUnion.get_Offset(5, 5);

  MessageBox.Show(String.Format("Union: {0}",
    rangeUnion.get_Address(missing, missing,
    Excel.XlReferenceStyle.xlA1, missing, missing)));

  MessageBox.Show(String.Format("Intersection: {0}",
    rangeIntersection.get_Address(missing, missing,
    Excel.XlReferenceStyle.xlA1, missing, missing)));

  MessageBox.Show(String.Format("Offset: {0}",
    rangeOffset.get_Address(missing, missing,
    Excel.XlReferenceStyle.xlA1, missing, missing)));
}

使用Area
當一個范圍內存在多個不連續的單元格范圍時,每個不連續的范圍稱為一個區域。 如果Range中有多個不連續的區域,請使用Areas屬性通過Areas集合訪問每個區域(作為Range)。 Areas集合具有一個Areas.Count屬性和一個Areas.get_Item方法,它將一個表示基於1的索引的int參數作為數組。 清單5-30顯示了一個迭代示例范圍(有三個區域)並打印每個區域的地址的示例。

清單5-30   適用於區域的VSTO定制

private void Sheet1_Startup(object sender, System.EventArgs e)
{
  Excel.Range range1 = this.get_Range(
    "$A$15:$F$28,$H$3:$J$9,$L$1", missing);

  MessageBox.Show(String.Format("There are {0} areas",
    range1.Areas.Count));

  foreach (Excel.Range area in range1.Areas)
  {
    MessageBox.Show(String.Format("Area address is {0}",
      area.get_Address(missing, missing,
      Excel.XlReferenceStyle.xlA1, missing, missing)));
  }
}

使用Cells
Count屬性返回給定范圍內的單元格數。 您可以使用get_Item方法獲取范圍內的特定單元格范圍。 get_Item方法接受所需的行索引和可選的列索引。 當范圍是單元格的一維數組時,可以省略列索引,因為在這種情況下,它只有一列或一列的單元格,所以稱為RowIndex的參數真的像數組索引一樣。 如果范圍有多個區域,則必須首先獲取要處理的區域,get_Item只會返回單元格的范圍中的第一個區域。

清單5-31顯示了使用get_Item的示例。

清單5-31  使用get_Item的VSTO自定義

private void Sheet1_Startup(object sender, System.EventArgs e)
{
  Excel.Range range1 = this.get_Range("$A$15:$F$28", missing);

  int rowCount = range1.Rows.Count;
  int columnCount = range1.Columns.Count;

  for (int i = 1; i <= rowCount; i++)
  {
    for (int j = 1; j <= columnCount; j++)
    {
      Excel.Range cell = range1.get_Item(i, j) as Excel.Range;
      string address = cell.get_Address(missing,
        missing, Excel.XlReferenceStyle.xlA1,
        missing, missing);

      cell.Value2 = String.Format("get_Item({0},{1})", i, j);
    }
  }
}

使用rows和columns
給定一個Range對象,您可以使用Row和Column屬性來確定其第一個區域的左上角的行和列號。行和列號作為int值返回。

您還可以使用“行”和“列”屬性來確定第一個區域中的行和列的總數。這些屬性返回特殊范圍,您可以將其視為對應於與范圍相關聯的行或列標題。當我們從圖5-8中的示例范圍獲取Rows.Count時,它返回14,Columns.Count返回6.這是有道理的,因為我們選擇的第一個區域(A15:F28)跨越6列和14行。

要獲取第一個區域右下角的行和列位置,可以使用清單5-32所示的相當尷尬的表達式。清單5-32還說明了使用get_Item,它使用行和列索引(相對於給定范圍的頂部),並返回該行和列索引處的單元格(作為范圍)。當您獲得一個Rows或Columns范圍時,這些范圍是一維的,在這種情況下,稱為RowIndex的參數像數組索引一樣。

清單5-32  獲取行和列位置的VSTO自定義

private void Sheet1_Startup(object sender, System.EventArgs e)
{
  Excel.Range range1 = this.get_Range(
    "$A$15:$F$28,$H$3:$J$9,$L$1", missing);
  Excel.Range area = range1.Areas.get_Item(1);

  int topLeftColumn = area.Column;
  int topLeftRow = area.Row;
  int bottomRightColumn = ((Excel.Range)area.Columns.
    get_Item(area.Columns.Count, missing)).Column;

  int bottomRightRow = ((Excel.Range)area.Rows.
    get_Item(area.Rows.Count, missing)).Row;

  MessageBox.Show(String.Format(
    "Area Top Left Column {0} and Row {1}",
    topLeftColumn, topLeftRow));
  MessageBox.Show(String.Format(
    "Area Bottom Right Column {0} and Row {1}",
    bottomRightColumn, bottomRightRow));

  MessageBox.Show(String.Format(
    "Total Rows in Area = {0}", area.Rows));
  MessageBox.Show(String.Format(
    "Total Columns in Area = {0}", area.Columns));
}

使用regions
CurrentRegion屬性返回一個范圍,該范圍將擴展為包含所有單元格,直到空白行和空白列。這個擴展的范圍被稱為一個區域。所以,例如,你可能有一個范圍,它包含一個表格中的幾個單元格,以獲得包含整個表格的范圍(假設該表由空白的行和列組成),您將使用較小范圍的CurrentRegion屬性返回整個桌子

get_End方法是對與Range相關聯的區域起作用的方法。 get_End方法接受XlDirection枚舉的成員:xlDown,xlUp,xlToLeft或xlToRight。當xlUp傳遞的方法返回與Range范圍左上角的單元格相同的列中的最上面的單元格。當通過xlDown時,它返回與Range的左上角單元格相同的列中的最下面的單元格。當通過xlToLeft時,它返回與Range的左上角單元格相同行中的最左邊的單元格。當通過xlToRight時,它將返回與Range的左上角單元格相同行中的最右邊的單元格。

選擇Range
您可以使用范圍上的選擇方法使范圍當前選擇。記住,撥打選擇會更改用戶的當前選擇,這不是一件很好的事情,沒有很好的理由。然而,在某些情況下,您希望將用戶的注意力吸引到某些情況下,在選擇范圍的情況下可以做到這一點。

編輯Range的值
通常使用兩種方法來獲取和設置范圍內的值。第一種方法是使用get_Value和set_Value方法。第二種方法是使用屬性Value2。 Value2和get_Value的區別在於,Value2屬性返回的元素是貨幣或日期作為雙重值。而且,get_Value也接受XlRangeValueDataType類型的可選參數。如果您傳遞XlRangeValueData.xlRangeValueDefault,您將返回一個表示單個單元格范圍單元格值的對象。對於Value2和get_Value,如果Range包含多個單元格,則將返回與Range中單元格相對應的對象數組。

清單5-33顯示了使用Value2的幾個示例,包括將值數組傳遞給Value2的示例。通過數組一次設置范圍內的單元格的值比通過多個調用單獨設置每個單元格更有效。

清單5-33 使用Value2的VSTO定制

private void Sheet1_Startup(object sender, System.EventArgs e)
{
  Excel.Range range1 = this.get_Range("$A$15:$F$28", missing);
  range1.Value2 = "Test";

  int rowCount = range1.Rows.Count;
  int columnCount = range1.Columns.Count;

  object[,] array = new object[rowCount, columnCount];

  for (int i = 0; i < rowCount; i++)
  {
    for (int j = 0; j < columnCount; j++)
    {
      array[i, j] = i * j;
    }
  }

  range1.Value2 = array;
}

復制,清除和刪除Range
Excel提供了一些復制,清除和刪除范圍的方法。復制方法采用Destination參數,您可以傳遞復制范圍的目的地。 Clear方法清除范圍內的單元格的內容和格式。 ClearContents只清除范圍內單元格的值,而ClearFormats僅清除格式。刪除方法刪除單元格的范圍,並作為參數移動單元格替換已刪除的單元格的方向。該方向作為XlDeleteShiftDirection枚舉的成員傳遞:xlShiftToLeft或xlShiftUp。

在Range內查找文本
Find方法允許您查找范圍中的文本,並返回范圍內的單元格,其中找到文本。查找方法對應於查找和替換對話框,如圖5-9所示。如果在調用Find方法時省略參數,它將使用上次使用Find對話框時用戶設置的任何設置。此外,當您指定參數時,指定的設置將在下次用戶打開時顯示在“查找”對話框中。

圖5-9  查找和替換對話框

Find方法采用表5-18中描述的許多參數。 Find返回一個Range對象,如果它成功,如果找不到任何東西,則返回null。 您可以使用FindNext方法找到與您的查找條件匹配的下一個單元格。 FindNext需要一個可選的After參數,您需要傳遞上一個找到的范圍,以確保您不會再一次找到相同的單元格。 清單5-34顯示了使用Find和FindNext方法的示例,其中我們搜索包含字符“2”的任何單元格,並加粗這些單元格。

表5-18  查找方法的參數

 

Parameter Name

Type

What It Does

What

object

Pass the data to search for as a required string.

After

object

Pass a single cell after which you want the search to begin as a Range. The default is the top-left cell if this omitted.

LookIn

object

Pass the type to search.

LookAt

XlLookAt

Pass xlWhole to match the whole cell contents, xlPart to match parts of the cell contents.

SearchOrder

XlSearchOrder

Pass xlByRows to search by rows, xlByColumns to search by columns.

SearchDirection

XlSearch-Direction

Pass xlNext to search forward, xlPrevious to search backward.

MatchCase

object

Pass true to match case.

MatchByte

object

Pass true to have double-byte characters match only double-byte characters.

SearchFormat

object

Set to true if you want the search to respect the FindFormat options. You can change the FindFormat options by using the Application.FindFormat.

 

 清單5-34  使用Find和FindNext的VSTO定制

private void Sheet1_Startup(object sender, System.EventArgs e)
{
  Excel.Range range1 = this.get_Range("$A$15:$F$28", missing);

  int rowCount = range1.Rows.Count;
  int columnCount = range1.Columns.Count;

  object[,] array = new object[rowCount, columnCount];

  for (int i = 0; i < rowCount; i++)
  {
    for (int j = 0; j < columnCount; j++)
    {
      array[i, j] = i * j;
    }
  }
  range1.Value2 = array;

  Excel.Range foundRange = range1.Find("2",
    range1.get_Item(1, 1), missing,
    Excel.XlLookAt.xlPart, missing,
    Excel.XlSearchDirection.xlNext,
    missing, missing, missing);

  while (foundRange != null)
  {
    foundRange.Font.Bold = true;
    foundRange = range1.FindNext(foundRange);
  }
}

格式化一系列單元格
Excel提供了幾種方法和屬性來格式化一系列單元格。最有用的是NumberFormat屬性,您可以將其設置為格式化與“格式化單元格”對話框的“自定義”類別中的字符串相對應的字符串。例如,您可以將NumberFormat設置為“常規”,不設置特定的數字格式。將NumberFormat設置為m / d / yyyy設置日期格式,0%將格式設置為百分比格式。當使用NumberFormat時,如果您正在構建控制台應用程序或加載項,請務必考慮本章后面的“特殊Excel問題”一節中討論的區域設置問題,因為讀取和設置此字符串可能會在不同的運行時導致問題語言環境。如果您在工作簿或模板項目后面使用VSTO代碼,則無需擔心語言環境問題。

Font屬性返回一個Font對象,可用於將Font設置為各種大小和樣式。清單5-34顯示了用於加粗單元格字體的Font對象的示例。

Excel還允許您創建與工作簿相關聯的樣式,並將這些樣式應用於范圍。您可以使用Workbook.Styles創建樣式。清單5-35顯示了創建樣式並將其應用於Range的示例。

清單5-35 創建和應用樣式的VSTO自定義

private void Sheet1_Startup(object sender, System.EventArgs e)
{
  Excel.Range range1 = this.get_Range("$A$15:$F$28", missing);
  range1.Value2 = "Hello";

  Excel.Style style = Globals.ThisWorkbook.Styles.Add(
    "My Style", missing);

  style.Font.Bold = true;
  style.Borders.LineStyle = Excel.XlLineStyle.xlDash;
  style.Borders.ColorIndex = 3;
  style.NumberFormat = "General";

  range1.Style = "My Style";
}

特殊Excel問題
在.NET中使用Excel對象模型時,需要注意幾個特殊的注意事項。 本節將檢查兩個最重要的內容:使用多個區域設置並使用Excel日期。

自動化可執行文件和COM加載項的Excel區域問題
當使用自動化可執行文件或COM加載項中的托管代碼對Excel對象模型進行編程時,根據當前線程的區域設置,Excel方法和屬性的行為可能會有所不同。 請注意,使用VSTO構建的文檔解決方案后面的代碼中不會出現此問題。 例如,如果要為范圍設置公式並且位於法語區域設置中,則Excel要求您使用本地化的法語公式名稱和格式:

 

sheet.get_Range("A1", Type.Missing).Formula = "=SOMME(3; 4)";

這種行為與獨立於語言環境的解決方案背后的VBA和VSTO代碼不同。 VBA和VSTO總是告訴Excel,該語言環境是美國英語(locale id 1033)。 在解決方案中的VBA和VSTO代碼中,與Excel進行交談時,您不必考慮區域設置。 您可以編寫此代碼,並使其在法國語言環境中工作:

sheet.get_Range("A1", Type.Missing).Formula = "=SUM(3, 4)";

當托管代碼調用到Excel對象模型中時,它會告知Excel它正在運行的區域設置(當前線程的區域設置),這將導致Excel期望您將以該區域設置的本地化格式提供公式和其他值。 Excel還將以該區域設置的本地化格式返回公式和其他值。 Excel期望本地化的字符串,例如日期格式,與Range關聯的NumberFormat字符串,與NumberFormat字符串相關聯的顏色名稱和公式名稱。

使用DateTime for Dates
作為一個例子,如果你不考慮這個問題,可以考慮下列代碼:

sheet.get_Range("A1", Type.Missing).Value2 = "03/11/02";

該值可能由2002年3月11日,2002年11月3日,或2003年11月2日由Excel解釋,具體取決於當前線程的區域設置。

對於日期,您有一個明確的解決方法。 不要將日期作為文字字符串傳遞給Excel。 相反,使用System.DateTime對象構建日期,並使用DateTime的ToOADate方法將其傳遞給Excel,如代碼清單5-36所示。 ToOADate方法將DateTime轉換為OLE自動化日期,這是Excel對象模型期望的日期格式。

清單5-36  適當地將日期傳遞給Excel的VSTO定制

 

private void Sheet1_Startup(object sender, System.EventArgs e)
{
  Excel.Range range1 = this.get_Range("$A$1", missing);

  // March 11, 2002
  System.DateTime date = new System.DateTime(2002, 3, 11);
  range1.Value2 = date.ToOADate();
}

將線程區域切換為英文和后退不推薦
您可能會認為與設置或獲取Range.NumberFormat和Range.Formula相關的問題的解決方案是保存線程的區域設置,臨時將線程的區域設置切換為英語(區域設置ID 1033),執行設置的代碼或獲取受區域設置影響的屬性(如NumberFormat或Formula),然后切換回保存的區域設置。不建議使用此方法,因為它會影響不期望本地交換機的其他加載項。

請考慮以下示例。您的加載項正在法國機器上運行。您的加載項將區域設置切換到1033並設置公式值。另一個加載項是處理Change事件並顯示一個對話框。該對話框以英文而不是法語顯示。因此,通過更改線程區域設置,您已經改變了另一個加載項的行為,並且一般是不良的Office公民。

使用反思來解決地區問題
遇到區域設置問題的COM加載項或自動化可執行文件的建議解決方法(訪問受當前語言環境影響的屬性(如NumberFormat或Formula屬性)時)是通過反射訪問這些屬性。反射使您能夠指定Excel的英文區域設置,並編寫無論當前線程區域設置如何的代碼。清單5-37說明了如何使用反射來設置NumberFormat和Formula屬性。

清單5-37 使用反思來解決Excel中的區域問題

 

static void Main(string[] args)
{
  Excel.Application application = new Excel.Application();
  application.Visible = true;
  object missing = Type.Missing;

  Excel.Workbook workbook = application.Workbooks.Add(missing);
  Excel.Worksheet sheet = (Excel.Worksheet)workbook.Worksheets.Add(missing,
missing, missing, missing);
  Excel.Range range1 = sheet.get_Range("$A$1", missing);

  // Set Formula in English (US) using reflection
  typeof(Excel.Range).InvokeMember("Formula",
    System.Reflection.BindingFlags.Public |
    System.Reflection.BindingFlags.Instance |
    System.Reflection.BindingFlags.SetProperty,
    null, range1,
    new object[] {"=SUM(12, 34)" },
    System.Globalization.CultureInfo.GetCultureInfo(1033));

  // Set NumberFormat in English (US) using reflection
  typeof(Excel.Range).InvokeMember("NumberFormat",
    System.Reflection.BindingFlags.Public |
    System.Reflection.BindingFlags.Instance |
    System.Reflection.BindingFlags.SetProperty,
    null, rangel,
    new object[] {"General" },
    System.Globalization.CultureInfo.GetCultureInfo(1033));
}

舊格式或無效的類型庫錯誤
Excel語言環境問題進一步復雜化的第二個問題是,在將語言環境設置為非英語區域設置的計算機上的英文Excel安裝中使用Excel對象模型時,可能會收到“舊格式或無效類型庫”錯誤。 Excel正在程序文件\ Microsoft Office \ OFFICE11 \ 1033中找到一個名為xllex.dll的文件,它找不到。解決此問題的方法是安裝xllex.dll文件或安裝Office的MUI語言包。您還可以復制excel.exe,將其重命名為xllex.dll,並將其復制到1033目錄。

VSTO和Excel語言環境問題
文檔解決方案后的VSTO代碼通過使用位於您和Excel對象模型之間的透明代理對象來解決Excel區域設置問題。此代理總是告訴Excel,該語言環境是美國英語(locale id 1033),這有效地使VSTO匹配VBA行為。如果您在文檔解決方案后面使用VSTO代碼,則會為您解決Excel語言環境問題,您不必再進一步擔心。如果您正在為Excel或自動化可執行文件構建托管COM加載項,則問題仍然存在。

VSTO對Excel語言環境問題的解決方案有一些注意事項。 VSTO透明代理可以稍微減慢代碼的速度。它也會導致Excel對象在調試器中檢查時顯示略有不同。最后,如果您使用Equals運算符將代理的Excel對象(如Application)與未使用的Application對象進行比較,則它們將不會被評估為相等。

如果要繞過特定對象的VSTO透明代理,可以使用Microsoft.Office.Tools.Excel.ExcelLocale1033Proxy.Unwrap方法,並傳遞要繞過代理的Excel對象。此方法將刪除代理並返回原始的PIA對象,再次暴露您的區域設置問題。您還可以將VSTO項目的AssemblyInfo.cs文件中的程序集屬性ExcelLocale1033設置為false,以便關閉整個Excel解決方案的透明代理。

如果您導航到另一個PIA的對象,然后再次導航回Excel PIA,則可能會丟失透明代理。例如,如果從Application.CommandBars集合中的Microsoft.Office.Core PIA命名空間獲取CommandBar對象,然后使用CommandBar.Application屬性返回到Excel Application對象,則現在已丟失代理和區域設置問題會再次發生。

最后,如果您從解決方案中的Word VSTO代碼創建了一個新的Excel實例,那么您直接與Excel PIA進行交談,而沒有透明的代理對象,並且區域設置問題將繼續有效。

將Excel日期轉換為DateTime
Excel可以以兩種格式表示日期:1900格式或1904格式。 1900格式基於一個系統,當轉換為一個數字時,它代表從1900年1月1日起的已過去的天數。1904格式是基於一個系統,當轉換為一個數字時,它表示已過的天數自1904年1月1日起,1904年格式由早期的Macintosh計算機引入,因為我們稍后描述的1900格式的問題。您可以通過檢查Workbook.Date1904屬性來確定工作簿正在使用的格式,如果工作簿使用1904格式,則返回true。

如果Excel工作簿使用1904格式,並將日期從該工作簿轉換為DateTime直接,則會得到錯誤的值。由於DateTime期望1900年的格式,由數字代表的Excel日期的值是1900年1月1日以后,而不是1904年1月1日之前的經過天數,因此將關閉4年和2個閏年。因此,此代碼如果您在工作簿中使用1904格式,將會給出錯誤的日期時間。

object excelDate = myRange.get_value(Type.Missing);
DateTime possiblyBadDateIfExcelIsIn1904Mode = (DateTime)excelDate;

要獲得1904格式的日期為DateTime格式,您必須添加1904格式日期4年和2個閏天(以彌補1904年在1904年而不是1900年的0)。 所以,如果你編寫代碼,並使用函數Convert-ExcelDateToDate在清單5-38中,你將得到正確的結果,如果使用1904日期系統。

object excelDate = myRange.get_value(Type.Missing);
DateTime goodDate = ConvertExcelDateToDate(excelDate);

清單5-38  將Excel日期轉換為DateTime並再次返回

static readonly DateTime march1st1900 = new DateTime(1900, 03, 01);
static readonly DateTime december31st1899 = new DateTime(1899, 12, 31);
static readonly DateTime january1st1904 = new DateTime(1904, 01, 01);
static readonly TimeSpan date1904adjustment = new TimeSpan(4 * 365 + 2, 0, 0,0, 0);
static readonly TimeSpan before1stMarchAdjustment = new TimeSpan(1, 0, 0, 0);
bool date1904 = ActiveWorkbook.Date1904;

object ConvertDateToExcelDate(DateTime date)
{
    LanguageSettings languageSettings = Application.LanguageSettings;
    int lcid = languageSettings.get_LanguageID(
      MsoAppLanguageID.msoLanguageIDUI);
    CultureInfo officeUICulture = new CultureInfo(lcid);
    DateTimeFormatInfo dateFormatProvider = officeUICulture.
      DateTimeFormat;
    string dateFormat = dateFormatProvider.ShortDatePattern;

    if (date1904)
    {
        if (date >= january1st1904)
            return date - date1904adjustment;
        else
            return date.ToString(dateFormat, dateFormatProvider);
    }
    if (date >= march1st1900)
        return date;
    if (date < march1st1900 && date > december31st1899)
        return date - before1stMarchAdjustment;
    return date.ToString(dateFormat, dateFormatProvider);
}

DateTime ConvertExcelDateToDate(object excelDate)
{
    DateTime date = (DateTime)excelDate;
    if (date1904)
        return date + date1904adjustment;
    if (date < march1st1900)
        return date + before1stMarchAdjustment;
    return date;
}

清單5-38還對1900格式日期進行了更正。 事實證明,當Lotus 1-2-3寫成時,程序員錯誤地認為1900年是一個閏年。 當Microsoft寫Excel時,他們希望確保它們與現有的Lotus 1-2-3電子表格保持兼容,使其能夠計算自1899年12月31日以來的天數,而不是1900年1月1日。當DateTime為 寫的,它的創作者沒有試圖回溯到1899年12月31日,它是從1900年1月1日開始計算的。所以為了將1900年3月1日之前的1900年格式的Excel日期正確地轉換成DateTime,你必須添加一天。

最后,Excel不能代表1900年1月1日之前1900年格式的日子,1904年1月1日之前的日期,以1904年格式。 因此,當您將DateTime轉換為Excel日期時,必須傳遞一個字符串而不是表示date的數字,因為這些日期不能在Excel中表示為日期(僅作為字符串)。

結論
本章探討了Excel對象模型中的一些最重要的對象。 我們在后續章節的Excel示例中使用了許多這些對象。 在第21章“在Excel中使用XML”中,我們還考慮了一些用於在Excel中使用XML的其他Excel對象模型對象。

本章描述了由Excel的主要互操作程序集定義的這些對象。 您應該注意到,VSTO擴展了這些對象(Workbook,Worksheet,Range,Chart,ChartObject和ListObject),以添加一些附加功能,如數據綁定支持。 本書第三部分考察了這些擴展。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM