記事内に広告が含まれています

【ワレコの講座】EXCEL VSTOアドインで選択セル情報取得(2/10)

この記事は約27分で読めます。
スポンサーリンク

写真 ワレコのサイトを見ながらVSTOプログラミングを勉強中の人(カフェで)

全国のワレコEXCEL VSTOアドイン講座ファンの皆さんの為に、第二回目の講座を執筆した。

今回は、当ブログのコメント欄からお寄せ頂いた近藤さんのお問い合わせに対する回答と言う形でEXCEL VSTOアドインを作成してみよう。

近藤 より:
2018年3月7日 1:45 AM (編集)
VS2017でEXCEL VSTOアドインで
選択中セル位置の取り込みが出来ません。

アドバイス願えるとありがたいです。

なるほど。

 

近藤さんの質問は、「選択中セル位置の取得方法が分からない」と言う事だ。

と言う事で、今回の講座では、以下の情報をEXCEL VSTOアドインで操作してみよう。

  • 現在選択されている領域を取得する方法
  • 複数領域が選択されている場合の対策(Area)
  • その選択域の中にあるセルの値の読み取り
  • 別のセルにその値をコピーする

では、本題に入ろう。

ちなみに、ワテの環境は以下の通り。

  • 自作パソコン(Core i7-4770K, 32GBメモリ, SSD750GB, HDD3TB,5TB)
  • Windows 10 Pro(64)
  • Visual Studio 2017 Community, 全パッケージをインストール(約100GBのSSD使う)
  • OFFICE Professional 2013
スポンサーリンク
スポンサーリンク

第二回講座で作成したVSTOアドインの解説

前回と同じく、C#でExcel VSTOアドインを作成する。

テンプレート

  Visual C#

    Office/SharPoint

      VSTOアドイン

        Excel 2013と2016 VSTOアドイン

あとは第一回目で説明した手順でリボンを追加する。

ワテの場合はExcel2013用のVSTOアドインを作成する。ちなみにVSTOアドインとはCOMアドインと呼ばれる場合もある。まあ、どちらも同じ物だ。

Ribbonのデザイン

リボンにはgroup1を追加して、その中にボタンを三つ(button1, button2, button3)を追加する。

それぞれのボタンのタイトルは以下の通り。

button1  ①データ書き込み

button2  ②複数レンジ選択

button3  ③複数レンジの値取得と書き込み

 

次に、各ボタンのクリックイベントハンドラを以下のように定義した。

第二回講座で作成した全C#ソースコード

冒頭で using Excel を定義しておくと、ソースコードの中で Microsoft.Office.Interop.Excel を Excel と短縮できるので便利だ。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Office.Tools.Ribbon;
using Excel = Microsoft.Office.Interop.Excel;
 
namespace MyExcelAddIn1
{
  public partial class Ribbon1
  {
    private void Ribbon1_Load(object sender, RibbonUIEventArgs e)
    {
 
    }
 
    private void button1_Click(object sender, RibbonControlEventArgs e)
    {
      // セルに値を書き込む
 
      var activeSheet = MyExcelAddIn1.Globals.ThisAddIn.Application.ActiveSheet
        as Microsoft.Office.Interop.Excel.Worksheet;
 
      activeSheet.Cells[1, 2] = "1_2"; activeSheet.Cells[1, 3] = "1_3";
      activeSheet.Cells[2, 2] = "2_2"; activeSheet.Cells[2, 3] = "2_3";
      activeSheet.Cells[3, 2] = "3_2"; activeSheet.Cells[3, 3] = "3_3";
      activeSheet.Cells[4, 2] = "4_2"; activeSheet.Cells[4, 3] = "4_3";
      activeSheet.Cells[5, 2] = "5_2"; activeSheet.Cells[5, 3] = "5_3";
      activeSheet.Cells[6, 2] = "6_2"; activeSheet.Cells[6, 3] = "6_3";
      activeSheet.Cells[6, 2].Select();
    }
 
    private void button2_Click(object sender, RibbonControlEventArgs e)
    {
      // 複数レンジを選択する。
 
      var activeSheet = MyExcelAddIn1.Globals.ThisAddIn.Application.ActiveSheet
         as Microsoft.Office.Interop.Excel.Worksheet;
 
      activeSheet.Range["B1:C3,B5:C6"].Cells.Select();
    }
    private void button3_Click(object sender, RibbonControlEventArgs e)
    {
      // 現在選択されているエリアを順番にループして、その中味を別のセルに書き出す
 
      var selectedRange = MyExcelAddIn1.Globals.ThisAddIn.Application.Selection as Microsoft.Office.Interop.Excel.Range;
      var selectedAreas = selectedRange.Areas as Microsoft.Office.Interop.Excel.Areas;
      if (true)
      {
        int i = 0;
        foreach (Excel.Range area_i in selectedAreas)
        {
          int r = 1;
          foreach (Excel.Range row in area_i.Rows)
          {
            int c = 1;
            foreach (Excel.Range col in row.Columns)
            {
              // Globals.ThisAddIn.Application.ActiveSheet.Cells[i * 7 + r + 10, c] = r * 1000 + c;
              Globals.ThisAddIn.Application.ActiveSheet.Cells[i * 7 + r + 10, c] = col.Address + "の値は" + col.Value;
              var a = area_i[r, c].Value;
              c++;
            }
            r++;
          }
          i++;
        }
      }
 
      if (true)
      {
        int i = 0;
        foreach (Excel.Range area_i in selectedAreas)
        {
          for (int r = 1; r <= area_i.Rows.Count; r++)
          {
            for (int c = 1; c <= area_i.Columns.Count; c++)
            {
              var area_r_c = area_i[r, c] as Excel.Range;
              var val_r_c = area_r_c.Value;
              var add_r_c = area_r_c.Address;
              var add_r_c_2 = area_r_c.Address[
                                RowAbsolute: true,
                                ColumnAbsolute: true,
                                ReferenceStyle: Excel.XlReferenceStyle.xlR1C1,
                                External: true,
                                RelativeTo: null
                              ];
 
              {
                // 必要ならこう言う属性も取得出来る。
                var add_r_c_row = area_r_c.Row;
                var add_r_c_col = area_r_c.Column;
 
                Debug.WriteLine("r=" + r + "c=" + c);
                Debug.WriteLine("add_r_c_row=" + add_r_c_row);
                Debug.WriteLine("add_r_c_col=" + add_r_c_col);
                Debug.WriteLine("  add_r_c_2=" + add_r_c_2);
              }
 
 
              // Globals.ThisAddIn.Application.ActiveSheet.Cells[i * 7 + r + 10, c] = r * 1000 + c;
              Globals.ThisAddIn.Application.ActiveSheet.Cells[i * 7 + r + 10, c + 3] = add_r_c_2 + "の値は" + val_r_c;
              var a = area_i[r, c].Value;
            }
          }
          i++;
        }
      }
 
 
      if (false)
      {
        // 必要なら行、列の幅を最適化する
 
        Globals.ThisAddIn.Application.ActiveSheet.Cells.Select();
        Globals.ThisAddIn.Application.ActiveSheet.Cells.EntireColumn.AutoFit();
        Globals.ThisAddIn.Application.ActiveSheet.Cells.EntireRow.AutoFit();
      }

 
    }
  }
}

 

ソースコードの説明

まず、ボタン1は、クリックすると複数のセルに値を書き込むだけの単純な処理だ。

button1

    private void button1_Click(object sender, RibbonControlEventArgs e)
    {
      // セルに値を書き込む
 
      var activeSheet = MyExcelAddIn1.Globals.ThisAddIn.Application.ActiveSheet
        as Microsoft.Office.Interop.Excel.Worksheet;
 
      activeSheet.Cells[1, 2] = "1_2"; activeSheet.Cells[1, 3] = "1_3";
      activeSheet.Cells[2, 2] = "2_2"; activeSheet.Cells[2, 3] = "2_3";
      activeSheet.Cells[3, 2] = "3_2"; activeSheet.Cells[3, 3] = "3_3";
      activeSheet.Cells[4, 2] = "4_2"; activeSheet.Cells[4, 3] = "4_3";
      activeSheet.Cells[5, 2] = "5_2"; activeSheet.Cells[5, 3] = "5_3";
      activeSheet.Cells[6, 2] = "6_2"; activeSheet.Cells[6, 3] = "6_3";
      activeSheet.Cells[6, 2].Select();
    }

上の例では文字列をセルに書き込んでいるが、数値、日付なども書き込める。

その辺りは通常のEXCELの操作、あるいはEXCEL VBAでの操作と同じだ。

 

button2

ボタン2をクリックすると、二カ所の領域を飛び地で選択する。

    private void button2_Click(object sender, RibbonControlEventArgs e)
    {
      // 複数レンジを選択する。
 
      var activeSheet = MyExcelAddIn1.Globals.ThisAddIn.Application.ActiveSheet
         as Microsoft.Office.Interop.Excel.Worksheet;
 
      activeSheet.Range["B1:C3,B5:C6"].Cells.Select();
    }

単一のセルを選択する場合なら以下のように数値で指定出来る。

activeSheet.Cells[6, 1].Select();

一方、複数のセルやレンジ(長方形領域)を選択する場合には、以下のように文字列で指定すると可能だ。

activeSheet.Range["A1:B3,A5:B6"].Cells.Select();

これを文字列では無くてRow, ColumnのIndex数値で指定する事も可能だ。

プログラミングの観点ではA1形式の文字列でRangeを指定するよりも、R1C1形式で指定出来るほうが好都合の場合も多い。

その方法がこれだ。

複数のRangeを選択する手法(文字列で与える場合と数値で与える場合)

  private void button2_Click(object sender, RibbonControlEventArgs e)
  {
    // 複数レンジを選択する。
 
    var activeSheet = MyExcelAddIn1.Globals.ThisAddIn.Application.ActiveSheet
       as Microsoft.Office.Interop.Excel.Worksheet;
 
    // rangeを文字列で指定する方式
    //activeSheet.Range["B1:C3,B5:C6"].Cells.Select();
 
    //     row    col
    // B1   1     2
    // C3   3     3
    // B5   5     2
    // C6   6     3
 
    // rangeを数字で指定する方式
    var excelApp = Globals.ThisAddIn.Application as Excel.Application;
 
    var range1a = activeSheet.Range[activeSheet.Cells[1, 2], activeSheet.Cells[3, 3]] as Excel.Range;
    var range1b = activeSheet.Range[activeSheet.Cells[5, 2], activeSheet.Cells[6, 3]] as Excel.Range;
    var result1 = excelApp.Union(range1a, range1b) as Excel.Range;
    result1.Select();
 
    // 一箇所だけ選択するならこれで良い。
    activeSheet.Range[activeSheet.Cells[1, 2], activeSheet.Cells[3, 3]].Cells.Select();

  }

Index数字で複数のRangeを選択する手法の要点は、二つのRange(range1aとrange1b)を作成して、それらをExcelのUnion関数を使って合体するのだ。

あとはUnion合体したresult1というRangeをSelectすれば良い。

button3

ボタン1をクリック、ボタン2をクリックして、ボタン3をクリックすると以下の処理が実行される。

二つのforループブロックがあるが、内容は殆ど同じ。

前者はforeachループ、後者はforループだ。

なので、どちらでも良い。この例では両方を実行している。

それぞれのループで、出力先が異なる。

    private void button3_Click(object sender, RibbonControlEventArgs e)
    {
      // 現在選択されているエリアを順番にループして、その中味を別のセルに書き出す
 
      var selectedRange = MyExcelAddIn1.Globals.ThisAddIn.Application.Selection as Microsoft.Office.Interop.Excel.Range;
      var selectedAreas = selectedRange.Areas as Microsoft.Office.Interop.Excel.Areas;
      if (true)
      {
        int i = 0;
        foreach (Excel.Range area_i in selectedAreas)
        {
          int r = 1;
          foreach (Excel.Range row in area_i.Rows)
          {
            int c = 1;
            foreach (Excel.Range col in row.Columns)
            {
              // Globals.ThisAddIn.Application.ActiveSheet.Cells[i * 7 + r + 10, c] = r * 1000 + c;
              Globals.ThisAddIn.Application.ActiveSheet.Cells[i * 7 + r + 10, c] = col.Address + "の値は" + col.Value;
              var a = area_i[r, c].Value;
              c++;
            }
            r++;
          }
          i++;
        }
      }
 
      if (true)
      {
        int i = 0;
        foreach (Excel.Range area_i in selectedAreas)
        {
          for (int r = 1; r <= area_i.Rows.Count; r++)
          {
            for (int c = 1; c <= area_i.Columns.Count; c++)
            {
              var area_r_c = area_i[r, c] as Excel.Range;
              var val_r_c = area_r_c.Value;
              var add_r_c = area_r_c.Address;
              var add_r_c_2 = area_r_c.Address[
                                RowAbsolute: true,
                                ColumnAbsolute: true,
                                ReferenceStyle: Excel.XlReferenceStyle.xlR1C1,
                                External: true,
                                RelativeTo: null
                              ];
 
              {
                // 必要ならこう言う属性も取得出来る。
                var add_r_c_row = area_r_c.Row;
                var add_r_c_col = area_r_c.Column;
 
                Debug.WriteLine("r=" + r + "c=" + c);
                Debug.WriteLine("add_r_c_row=" + add_r_c_row);
                Debug.WriteLine("add_r_c_col=" + add_r_c_col);
                Debug.WriteLine("  add_r_c_2=" + add_r_c_2);
              }
 
 
              // Globals.ThisAddIn.Application.ActiveSheet.Cells[i * 7 + r + 10, c] = r * 1000 + c;
              Globals.ThisAddIn.Application.ActiveSheet.Cells[i * 7 + r + 10, c + 3] = add_r_c_2 + "の値は" + val_r_c;
              var a = area_i[r, c].Value;
            }
          }
          i++;
        }
      }
   
 
      if (false)
      {
        // 必要なら行、列の幅を最適化する
 
        Globals.ThisAddIn.Application.ActiveSheet.Cells.Select();
        Globals.ThisAddIn.Application.ActiveSheet.Cells.EntireColumn.AutoFit();
        Globals.ThisAddIn.Application.ActiveSheet.Cells.EntireRow.AutoFit();
      }
    }

 

一つ目のforループでは、$A$1形式のアドレスを取得してみた。

 Globals.ThisAddIn.Application.ActiveSheet.Cells[i * 7 + r + 10, c] 
   = col.Address + "の値は" + col.Value; 

.Addressで取得すると、得られるアドレスは$A$1形式(絶対参照)になるようだ。

 

二つ目のループでは、R1C1形式でアドレスを取得してみた。

 var add_r_c = area_r_c.Address;
 var add_r_c_2 = area_r_c.Address[
    RowAbsolute: true,
    ColumnAbsolute: true,
    ReferenceStyle: Excel.XlReferenceStyle.xlR1C1,
    External: true,
    RelativeTo: null
 ];

 Globals.ThisAddIn.Application.ActiveSheet.Cells[i * 7 + r + 10, c + 3] 
  = add_r_c_2 + "の値は" + val_r_c; 

Address[・・・]には上に示すように五つのパラメータを指定出来る。この辺りもVBAと全く同じだ。

各パラメータをtrue/falseなど切り替えてみて、得られるアドレス形式がどうなるのか実験してみると良いだろう。

なお、上のforループにおいて、一番外側のAreaに関するループ部分

foreach (Excel.Range area_i in selectedAreas)
{
   ・・・
}

をインデックス方式の、

 var selectedAreas = selectedRange.Areas as Microsoft.Office.Interop.Excel.Areas;
 var cntSelectedAreas = selectedAreas.Count;
 for (int i = 0; i < cntSelectedAreas; i++)
 {
    var area_i = selectedAreas[i]; // ここで例外が出る。
   ・・・
 }

にすると例外が出た。.Countは取得出来るのだが。

selectedAreas[i]でのアクセスが出来ない理由は知らない。

Areasオブジェクトはインデックスではアクセス出来ないのかな?

実行結果

三つのボタンを上から順にクリックすると以下のようになるはずだ。

選択した二つの領域の中のセルの値とアドレスを読み取って、一つ目のループでは$B$1のアドレスを取得している。

同じく二番目のループでは、R5C3形式のアドレスを取得している。

C#でVSTOを開発するコツ

C#のVSTOで使えるコマンドはVBAで使えるコマンド群と同じだと思う。

完全に一致しているのかどうかは、ワテは未確認だが、ワテの経験では、全く同じだと思う。

従って、C#でこんな事をやりたい場合、どんな風に書けば良いのかな?と思った場合には、EXCEL VBAを起動してみて、VBAでコードを書いてみると良い。

例えば今回も、即席でこんなコードを書いてみた。

適当にエクセルのセルを選択して実行すると、現在の選択域のアドレス情報を確認出来る。

Sub aa()

  Dim rngActiveRange1 As Excel.Range
  Dim rngActiveRange As Excel.Range

' Selection プロパティから返された Range オブジェクトです。
  Set rngActiveRange1 = Selection
  Call PrintRangeInfo(rngActiveRange1)
' ActiveCell プロパティから返された Range オブジェクトです。
  Set rngActiveRange = ActiveCell
  Call PrintRangeInfo(rngActiveRange)

  a = rngActiveRange1.Address
  b = rngActiveRange1.Offset
  c = rngActiveRange1.Columns.Address
  d = rngActiveRange1.Rows.Address

End Sub

Sub PrintRangeInfo(rngCurrent As Excel.Range)
 Dim rngTemp As Excel.Range
 Dim strValue As String
 Dim strRangeName As String
 Dim strAddress As String
 Dim strFormula As String
 
 On Error Resume Next
 
 strRangeName = rngCurrent.Name.Name _
 & " (" & rngCurrent.Name.RefersTo & ")"
 strAddress = rngCurrent.Address
 
 For Each rngTemp In rngCurrent.Cells
 If IsEmpty(rngTemp) Then
 strValue = strValue & "Cell(" & rngTemp.Address _
 & ") Is empty." & vbCrLf
 Else
 strValue = strValue & "Cell(" & rngTemp.Address _
 & ") = " & rngTemp.Value _
 & " Formula " & rngTemp.Formula & vbCrLf
 End If
 Next rngTemp
 Debug.Print IIf(Len(strRangeName) > 0, "Range Name = " _
 & strRangeName, "Range not named") & vbCrLf & "Address = " _
 & strAddress & vbCrLf & strValue
 Debug.Print "**********************************"
 Debug.Print
 If Err > 0 Then Err = 0
End Sub

Sub PrintRangeInfo(rngCurrent As Excel.Range)と言う関数はマイクロソフトのサイトで見付けた関数だ。

上のコードは、長方形選択域Selectionや単一セル選択域ActiveCellに対して、どんなプロパティがあるのか思い出す為に即席作ったものだ。

なお、注意事項としては、C#の中では、例えば以下のように as Excel.Range とすればインテリセンス機能が働くので、.Valueプロパティや .Addressプロパティが自動で表示される。

 var area_r_c = area_i[r, c] as Excel.Range;
 var val_r_c = area_r_c.Value;
 var add_r_c = area_r_c.Address;

ところが、 as Excel.Range を省略しても動く。

かつ、その時に例えばタイプミスして、Addressをaddressのように

 var add_r_c = area_r_c.address;

を小文字にしても動いた。理由は知らない。

でもそうすると混乱の元なので、出来る限り変数には型宣言をしておくと良い。

型宣言して有れば .address と入力するとエラーになるので分かり易いからだ。

と言うか、実際は逆で、当初は  as Excel.Range 無しで .address で実行出来ていた。

その後から、 as Excel.Range を付けたのだが .address がエラー表示になる。

それでワテは、「あれれ?おかしいぞ、どないなってんねん?」

と少々混乱したのだ。

まとめ

EXCEL VSTO講座の第二回目では、選択セルの位置情報を取得する方法を紹介した。

これが、近藤さんの

「選択中セル位置の取得方法が分からない」

と言うご質問の回答になっていれば良いのだが。

 

なお、その後、近藤さんからは音信不通だ。

大丈夫ですか、近藤さん!

 

最新状況

その後、近藤さんからお礼のお返事を頂きました。

安心しました。

質問募集中

当ページにお越し頂いた皆さんも、もしEXCEL VBA、EXCEL VSTOアドイン、EXCEL XLLアドインに関して何か質問などありましたら、下にあるコメント欄からお気軽にお問い合わせください。

ワテが出来る範囲でお答えします。

本を読む

ちなみにワテの場合はどの本も読んでいない。

なので良い本かどうかは未確認だ。

第一回講座はこちら
【ワレコの講座】VS2017でEXCEL VSTOアドイン作成 – リボン編(1/10)
EXCELアドインには三種類ある。アドインの種類開発言語説明などxlamアドインVBA歴史は古いが、開発環境が古臭い。COMアドインC#、VB.NETVSTOアドインとも言うXLLアドインVisual Studioの C、C++高速動作。ワ...
第三回講座を作成した
【ワレコの講座】EXCEL VSTOアドインを他のPCに配布する(3/10)
写真 VSTOプログラミングをバリバリに出来る美人OLの人、でもパソコンの電源がOFF!さて、ワテのブログ記事の中で最大の人気を誇る【ワレコの講座】EXCEL VSTOアドイン シリーズ全10回予定であるが、昨日、熱烈な読者の方からこんなコ...
関連記事
【ワレコのEXCEL】VBAでリボンを作成して使う方法【ワテ流】
EXCELのアドインには三種類ある。 EXCEL VSTOアドイン(EXCEL COMアドインとも呼ばれる) EXCEL XLLアドイン EXCEL VBAで作成するxlamアドインだ。マイクロソフト社の統合開発環境Visual Studi...
スポンサーリンク
コメントを読む

この記事には読者の方からコメントが 3件あります。
興味ある人はこのページ下部にあるコメントを参照下さい。

C#ExcelVSTO
スポンサーリンク
シェアする
warekoをフォローする
スポンサーリンク

コメント

  1. 近藤 より:

    近藤です。

    ご丁寧にアドバイス頂きありがとうございました。

  2. いけやま より:

    仕事でエクセル表をパワポに張りたくなるときがあるのですが、
    きれいに見せるために、エクセルで任意に選択した範囲のセルをオブジェクトで作成するマクロを作成しました。
    (9セル選択してたら9個オブジェクト作り、個々のオブジェクトはセルの書式に合わせるようなモノ)
    アドイン化したものの、再起動でアドインメニューが消えるので、対策として、
    Visual studio を使ってアドイン化させようとしてますが、VTSOアドインでオブジェクト作成は出来るのでしょうか・・・。

    • wareko より:

      いけやま様

      この度は小生のサイトへの訪問並びに質問ありがとうございます。

      さて、お問い合わせの件ですが、私はこのところエクセルプログラミングからはかなり遠ざかっていて、用語が良く理解できないところがありました。
      幾つか確認させて下さい。

      >仕事でエクセル表をパワポに張りたくなるときがあるのですが、
      >きれいに見せるために、エクセルで任意に選択した範囲のセルをオブジェクトで作成するマクロを作成しました。

      「オブジェクト」と言うのは「挿入」->「図形」で描けるどれかの図形の事でしょうか?

      >(9セル選択してたら9個オブジェクト作り、個々のオブジェクトはセルの書式に合わせるよう>なモノ)
      >アドイン化したものの、再起動でアドインメニューが消えるので、

      そのアドインは、xlamアドインでしょうか?
      もしそうなら、アドインとして登録しておけば、エクセルを起動する度に自動で読み込まれると思うのですが。エクセルの再起動でアドインメニューが消える事自体、何か別の問題のような気がします。

      >対策として、
      >Visual studio を使ってアドイン化させようとしてますが、VTSOアドインでオブジェクト作成は出来るのでしょうか・・・。

      もしその「オブジェクト」と言うのがエクセルやパワーポイントの「図形」であれば、VSTOアドインで自由に作成出来ます。
      似た様な事は、私自身、以前試した事があります。
      私の理解では、VBAで出来る事はVSTOでも出来ます(たぶんですが)。