「Excel VBA Tips」ではExcel VBAを使用していて気付いたことを取り上げて行きます。
今回は「Application.Selectionプロパティの使い方」を中心に、Range.CountLargeプロパティの使い方や描画オブジェクトの中のグラフ判定方法ついても調べてみました。
なおSelectionプロパティにはセル・セル範囲も描画オブジェクトも両方含まれおり、説明が少し複雑になっていますのでお含み置きください。
また前回の「VBA ShapesオブジェクトとItemメソッド・Rangeプロパティ」でご説明した内容を引用する箇所がありますので、合わせてご参照いただければ幸いです。
※動作は32bit版Excel 2016の バージョン2208(ビルド 15601.20088)を使用して検証しています。
SelectionはApplicationオブジェクトのプロパティ
Selectionは「アクティブなワークシートで現在選択されているオブジェクト」を表すプロパティですが、特定の状況下ではIndex(添え字)を付けたItemメゾットを使用する事ができます。
※「特定の状況下」の詳細は後段でご説明いたします。
なおMicrosoft Officeドキュメントのサイトは下記になります。
このSelectionドキュメントの中に下記の一文があります。
TypeName関数を使用して、選択されているオブジェクトの種類を検出します。
「選択されているオブジェクト」とは「Selectionが指し示しているオブジェクト」であり、「その種類はTypeName関数を使用して検出する」という、とても重要な情報なのですが、詳細は後段でご説明いたします。
以下はApplicationオブジェクトのプロパティについての余談にはなります。
前回の「VBA ShapesオブジェクトとItemメソッド・Rangeプロパティ」で使用した「ActiveSheet」や「Cells」もApplicationオブジェクトのプロパティです。
なお「ActiveSheet」や、この後でてくる「ActiveChart」はWorkbookオブジェクトのプロパティにも属しています。
この他にApplicationオブジェクトやWorkbookオブジェクトには特定のオブジェクトやオブジェクトの集まりを返してくれるものがありますので、「何か使えるものが無いか?」一度探してみるのも良いかもしれません。
Selectionのセル範囲ではCountLargeプロパティを使った方が無難だが描画オブジェクトには使えない
本題からは少し離れますが、章題の件をご説明いたします。
冒頭で「Selectionプロパティにはセル・セル範囲も描画オブジェクトも両方含まれている」とご説明しましたが、セル範囲が選択されている場合はCountプロパティよりもCountLargeプロパティを使った方が「無難」という意味になります。
Selectionプロパティがセル範囲の場合
セル範囲の場合、例えばシートを全選択した状態がSelectionの対象になっている事があり得るのですが、この場合はCountプロパティではオーバーフローのエラーになってしまいます。
そこでこのようなエラーを回避するためにはRangeオブジェクトのCountLargeプロパティを使用して置いた方が無難です。
Microsoft Officeのドキュメントのページは下記になります。
イミディエイトウィンドウで次のように打ち込むと最大値の1,7179,869,184が結果に返ります。
? activesheet.cells.countlarge
もちろんシート全体を選択してから次のように入力しても同じ結果になります。
? selection.cells.countlarge または ? selection.countlarge
ただしSelectionプロパティが描画オブジェクトの場合はCountLargeは使えない
描画オブジェクトの場合は、そもそも「それほど多数の描画オブジェクトを一度に選択する事は無い」と想定されているのか、CountLargeプロパティを使う事ができません。
- 描画オブジェクトを複数選択してイミディエイトウィンドウで次のように打ち込むと「実行時エラー:438 オブジェクトは、このプロパティまたはメソッドをサポートしていません。」のエラーが表示されます。
? selection.shaperange.countlarge または ? selection.countlarge
- 描画オブジェクトを複数選択した上でCountLargeからCountプロパティに書き換えれる事で正しい結果が返ります。
※「複数選択」に赤いアンダーラインを引いていますが、この件につきましては次章でご説明いたします。
以上の事からSelectionプロパティでCountLargeを使うためには、Selectionが「セル範囲」なのか?「複数選択された描画オブジェクト」なのかを事前に判定する必要がある事が解ります。
※Excelでは「セル範囲」と「描画オブジェクト」を同時に選択する事はできないので、どちらかを判定する事になります。
なおVBAでSelectionを扱う時には「どんな選択状態なのか?」をエラーにならずに判断できる事が望ましいのですが、実はこれが結構大変な話なので、以降順を追ってご説明いたします。
Selectionだけでは描画オブジェクトを単独で処理する事ができない
Selectionプロパティがセル・セル範囲(1つのセル・複数のセル)を選択している場合は問題なく処理する事ができます。
ところが描画オブジェクトが選択されている時は話がまったく異なります。
前回の「VBA ShapesオブジェクトとItemメソッド・Rangeプロパティ」で描画オブジェクには様々な種類がある事をご説明しましたが、次のようなシートを作ってそれぞれの描画オブジェクについて単独・複数(2個)選択をして、Selection.Countが動くか?を確認して行きます。
【動作確認に使用するシート】
次の①から⑨の良く使われそうな描画オブジェクトをExcelの挿入タブメニュの中などにあるさまざまなアイコンから選出しています。
①入力規則、②コメント、③フォームコントロール、④テキストボックス、⑤オートシェープ、⑥グラフ、⑦図表(Smart Art)、⑧画像、⑨グループ化
※残りのOLEオブジェクトと3Dモデルとワードアート オブジェクトについては最後の章でまとめて述べます。
【補足】
なるべく同じ種類を2個つづ配置するようにしています。
①入力規則は「データの入力規則がリストの場合」に限定されます。
「⑨グループ化」の対象は「⑤オートシェープ」だけにしていますが、他の描画オブジェクトでも問題ありません。
【Excelのタブメニュでの位置】
①入力規則は「データ」タブメニュの「データツール」
②コメントは「挿入」タブメニュの「コメント」
③フォームコントロールは「開発」タブメニュの「コントロール」
④オートシェープ、⑦図表(Smart Art)、⑧画像(他に3Dモデル)は「挿入」タブメニュの「図」
⑤テキストボックス(他にワードアートとOLEオブジェクト)は「挿入」タブメニュの「テキスト」
⑥グラフは「挿入」タブメニュの「グラフ」
グループ化の中にグループ化した描画オブジェクトを含める事はできますが、Selectionプロパティに含める事はできません。
「グループ化された図形の中にあるグループ化図形は選択できない」というのがExcelの仕様です。
イミディエイトウィンドウでSelection.Countを動作検証する
図形を選択した上でイミディエイトウィンドウで「? selection.count」を実行します。
※複数の描画オブジェクトを選択するには1個選択した上でShiftキーを押しながら、もう一つの図形をクリックします。
以下は一覧表の表示項目の説明になります。
- 「単 ×」
- 「実行時エラー:438 オブジェクトは、このプロパティまたはメソッドをサポートしていません。」が返る事を表しています。
- 「複 -」
- Excelの仕様でそのような選択ができない事を表します。
- 別の描画オブジェクトを選択した上でコメントを入力状態にするとテキストボックスが選択状態になりますが、これは「入力状態を表しているだけ」でキストボックスの選択とは見なさない認識です。
- 「複(β)」
- 2つ以上β個の図形を選択している事を表します。
- 「子-単」
- 親図形が単独で選択された状態でその子図形(要素)が1つ選択されている事を表します。
- 「子-複(β)」
- 親図形が単独で選択された状態でその子図形(要素)が2つ以上β個選択されている事を表します。
No | 選択 | 値 | 選択 | 値 |
---|---|---|---|---|
① | 単 | 1 | 複(β) | β |
- 描画オブジェクトではなくセル・セル範囲として処理されていると推察されます。
④ | 単 | × | 複(β) | β |
⑦ | 親-単 | × | 親-複(β) | β |
子-単 | × | |||
子-複(β) | β |
- 親の複数選択だけでなく子図形を複数選択しても正しい値が返ります。
No | 選択 | 値 | 選択 | 値 |
---|---|---|---|---|
② | 単 | × | 複 | - |
- コメント吹き出しのテキストボックスは同時に複数選択できません。(Excelの仕様)
⑤ | 単 | × | 複(β) | β |
⑧ | 単 | × | 複(β) | β |
No | 選択 | 値 | 選択 | 値 |
---|---|---|---|---|
③ | 単 | × | 複(β) | β |
⑥ | 親-単 | × | 親-複(β) | β |
子-単 | × | |||
子-複 | - |
- グラフの子要素は複数選択できません。
⑨ | 親-単 | × | 親-複(β) | β |
子-単 | × | |||
子-複(β) | β |
- 親の複数選択だけでなく子図形を複数選択しても正しい値が返ります。
検証結果の考察
- 単独の場合は②コメントを含めてすべてエラーになります。
- 「①入力規則」が「セル・セル範囲」としての戻り値と考えると、②コメントを除き複数であればすべて正しい結果が返ります。
- ただし⑦図表と⑨グループ化では、親図形を1つ選択してその中の子図形を複数選択すると正しい結果が返ります。
- この場合はSelection.Countの値だけでは親図形の数なのか?子図形の数なのか?分からないので、親子を判別する必要があります。
なおグループ化・グラフ・図表(Smart Art)など子図形が存在する場合、単独の場合は子図形を選択できるのですが、複数選択する場合は子図形を選択していても強制的に親図形にフォーカスが移動するのがExcelの仕様です。
Selection(Index)が指し示めすのは選択した描画オブジェクトだけではない
複数選択されればSelectionプロパティだけで描画オブジェクトを処理できるか?というと残念ながら処理する事ができません。
最初の章で「Selectionは…特定の状況下ではIndex(添え字)を付けたItemメゾットを使用する事ができます。」とご説明しましたが、「特定の状況下」というのは「複数の描画オブジェクトが選択されている状況下」となります。
確かに「複数の描画オブジェクトが選択されている状況下」ではSelection(Index)を使用する事ができるのですが、実はSelection(Index)は選択された描画オブジェクトだけを指し示していません。
では何を指し示しているか?と言うと『ActiveSheetに存在する全ての描画オブジェクトから、「データの入力規則がリストの場合」とコメントを除いた残りすべての描画オブジェクト』を指しています。
つまりSelection(Index)は選択した描画オブジェクトを表さないというのがExcelの仕様です。
そのためにSelectionプロパティだけで描画オブジェクトを処理する事はできません。
Selection.ShapeRangeの場合、単独で処理できる描画オブジェクトはあるが…
Selectionプロパティだけでは描画オブジェクトを処理できないとすると、選択されている描画オブジェクトを扱うためにはどのようにしたら良いのでしょうか?
それはShapeRangeオブジェクトをSelectionプロパティに組み合わせて使用する事です。
これによって結果がかなり変わってきますので、前章と同様のやり方で動作検証をして行きます。
イミディエイトウィンドウでSelection.ShapeRange.Countを動作検証する
No | 選択 | 値 | 選択 | 値 |
---|---|---|---|---|
① | 単 | × | 複 | × |
- 描画オブジェクトではなくセル・セル範囲として処理と推察されます。
④ | 単 | 1 | 複(β) | β |
⑦ | 親-単 | 1 | 親-複(β) | β |
子-単 | × | |||
子-複 | × |
- 子図形が選択されると単独・複数に関わらずNG
No | 選択 | 値 | 選択 | 値 |
---|---|---|---|---|
② | 単 | 2 | 複 | - |
- なぜか単独にも関わらず結果は「2」です。
⑤ | 単 | 1 | 複(β) | β |
⑧ | 単 | 1 | 複(β) | β |
No | 選択 | 値 | 選択 | 値 |
---|---|---|---|---|
③ | 単 | 1 | 複(β) | β |
⑥ | 親-単 | × | 親-複(β) | β |
子-単 | × | |||
子-複 | - |
- 単独は親・子ともにエラーです。
⑨ | 親–単 | 1 | 親-複(β) | β |
子-単 | 1 | |||
子-複(β) | β |
- 親でも子でも選択された個数が返ります。
検証結果の考察
Selectionの時と同様に①入力規則と②コメントを除き、複数であればすべて正しい結果が返ります。
また単独選択の場合でも正しい結果が返るものがありますが、残念ながら全てではありません。
以下に考慮すべきポイントを列挙いたします。
- ShapeRangeオブジェクトはセル・セル範囲は対象ではありません。そのため描画オブジェクトが存在しない時や、選択されていない状態でShapeRangeオブジェクトを使用するとエラーになります。従って使用する場合は事前にSelectionの対象がセル・セル範囲では無い事を判定する必要があります。
- コメント(テキストボックスが選択状態)の単独の結果は「2」になりますが、単独選択でも結果が返る事が重要で、Countしても意味は無いので結果は気にしない事とします。
- これはまったくの推測ですが、単独選択したTypename(selection)の結果は「TextBox」になるのですが、図形的には直線要素も含まれるために「2」を返すのかもしれません。
- Selection.Shaperange(2)はエラーになるので描画オブジェクトとしては1個のはずです。
- グラフは単独選択ではエラーになります。子要素を選択した場合も同じエラーです。そのため単独で選択されている描画オブジェクトがグラフでは無い事を判定する必要があります。
- グラフの単独選択はSelection.ShapeRange(1)では処理できないので代わりにActivechartオブジェクトを使用します。
- 図表(Smart Art)は親図形は正しいのですが、子図形が選択されていると単独・複数ともにエラーになります。そのためまずは選択されている描画オブジェクトが図表(Smart Art)の子図形では無い事を判定する必要があります。
- 図表(Smart Art)の子図形が選択されている場合は残念ながらそれをそのまま処理する手立てが無さそうです。
- そのため対処法としては「図表(Smart Art)の親図形を選択し直してもらう」などのメッセージを表示して手動で選び直してもらう事になりそうです。
- グループ化した描画オブジェクトは単独・複数でも親・子でも正しいのですが、具体的な処理をする前に選択されている図形が親図形なのか?子図形なのか?を判定する必要がありそうです。
- 例えば「図形を配置する場所」なども親図形と子図形の場合では異なるはずです。
なおSelection.ShapeRange(Index)はSelection(Index)とは異なり、選択された描画オブジェクトだけを返します。
考察が長くなりましたが、これら5つの内容を詳細に見て行きます。
「Selectionがどんな選択状態か?」をエラーにならずに判定するには
描画オブジェクトの半数以上は単独であれ複数であれSelection.ShapRange(Index)で処理できるのですが、セル・セル範囲や一部の描画オブジェクトについては処理できない事が前章で解りました。
本章では前章の5つの検証結果を具体的にどのように実装するか?を4つのステップ分けて見て行きますが、各ステップに入る前にTypeName関数の戻り値について確認しておきます。
TypeName関数の戻り値
冒頭の章でApplication.SelectionのMicrosoft Officeのドキュメントをご紹介しましたが、この中に「TypeName関数を使用して、選択されているオブジェクトの種類を検出する」と書かれていました。
という事はTypeName関数を使えばSelectionが何を指しているか?に関わらずエラーにならずに結果を返してくれるはずです。
以上を踏まえて、これまで検証してきた描画オブジェクトに対してTypeName(Selection)の内容を確認して見ます。
【TypeName(Selection)の戻り値一覧】
描画オブジェクト | 単独 | 複数 | 子-単独 | 子-複数 |
---|---|---|---|---|
①入力規則 | Range | Range | 該当無 | 該当無 |
②コメント(テキストボックス選択) | TextBox | - | 〃 | 〃 |
③フォームコントロール | ※DropDown | DrawingObjects | 〃 | 〃 |
④テキストボックス | TextBox | DrawingObjects | 〃 | 〃 |
⑤オートシェープ | ※Rectangle/Line | DrawingObjects | 〃 | 〃 |
⑥グラフ | ChartArea | DrawingObjects | 各要素別 | - |
⑦図表(Smart Art) | GroupObject | DrawingObjects | Shape | ShapeRange |
⑧画像 | Picture | DrawingObjects | 該当無 | 該当無 |
⑨グループ化(⑤) | GroupObject | DrawingObjects | ※Rectangle | DrawingObjects |
③フォームコントロールと⑤オートシェープで単独の場合のすべての戻り値は下記のようになります。
※なおすべての描画オブジェクトのTypeName(Selection)の戻り値の検証につきましては、長くなるので別の記事でご紹介する予定です。
描画オブジェクト | 単独の場合のTypeName(Selection)のすべての戻り値 |
---|---|
③フォームコントロール | Button/CheckBox/DropDown/GroupBox/Label/ListBox/OptionButton/ ScrollBar/Spinner |
⑤オートシェープ | Arc/Line/Oval/Rectangle/Drawing ※ほとんどのオートシェープが「Rectangle」を返します。 |
「①入力規則」が「セル・セル範囲」としての戻り値と考えると、これらの結果から導き出せるのは下記の5点です。
- Selectionの対象がセル・セル範囲の時は「Range」が返る。
- 言い換えると「Range」でなければ描画オブジェクトが選択されている事になります。
- これによりSelectionの対象がセル・セル範囲では無い事を判定する事ができる。
- 「DrawingObjects」の時は描画オブジェクトが複数選択されている事になる。
- 言い換えると「DrawingObjects」の時はSelection.ShapeRange(Index)で問題なく処理できる事になる。
- ただしグループ化の時は親図形か子図形かを切り分ける必要がある。
- 「Shape」または「ShapeRange」の時は「⑦図表(Smart Art)」の子図形が単独または複数選択されている事になる。
- 単独選択で「GroupObject」が返る時は「⑦図表(Smart Art)」と「⑨グループ化(③・④・⑤・⑧)」を切り分ける必要がある。
- 単独選択で「TextBox」が返る時は「②コメント(テキストボックス選択)」と「④テキストボックス」を切り分ける必要がある。
TypeName(Selection)の戻り値は、⑦図表(Smart Art)の子図形単独選択の時は「Shape」、子図形複数選択の時は「ShapeRange」を返しますが、何か他に「Shape」や「ShapeRange」を返す事が無いのか?を調べていて気が付いた事象をメモしておきます。
- ActiveSheetで例えばオートシェープを1つ選択して、TypeName(Selection.ShapeRange)を調べると「ShapeRange」を返します。
- 同様にして、TypeName(Selection.ShapeRange(1))を調べると「Shape」を返します。
戻り値の意味合いとしては合っていますが、調べるまでも無い事だと思いますので、特段気にすることは無いはずです。
ステップ1.セル・セル範囲が選択されているか?を調べる
まず最初にセル・セル範囲が選択されているのか?を切り分けます。
これはTypeName(Selection)の戻り値が「Range」であるか?を調べることで判定できます。
ステップ2.複数選択が選択されているか?を調べる
「DrawingObjects」の場合には2つの課題がありますので順番にご説明いたします。
2-1.子図形が選択されているか?を調べる
本章でご説明する切り分けは複数選択の場合だけではなく、単独選択の場合にも必要になりますのでお含み置きください。
まずは最初にShapeRange.ChildプロパティについてのMicrosoft Officeのページを見てみます。
指定した 図形が子図形である場合、または図形範囲内のすべての図形が同じ親の子図形である場合は、msoTrue(-1) を返します。(1) 選択した図形が子図形ではない場合は、 msoFalse(0) を返します。(2) 選択した図形の一部だけが子図形である場合は、 msoTriStateMixed(-2) を返します。(3)
上記のように書かれていのですが、実際に動かしてみると(1)でも(3)でもmsoTrueが返りますのでここでは実際の動きを優先します。
なおこの引用で注意しなければならないのは上記ではなく、青い下線を引いた箇所です。
ChildプロパティはShapeRangeでもShapeRange(Index)でも子図形であるか?を判定してくれます。
ただし残念ながらすべての描画オブジェクトで正しく動くか?というとそうではありません。
セル・セル範囲を除いた②から⑨までの描画オブジェクトについてShapeRangeとShapeRange(Index)の動きを確認した結果は下記になります。
【一覧表の表示説明】
- X|0
- ShapeRange.ChildはエラーだがShapeRange(1).Childは「msoFalse(=0)」を返す。
エラーは「-2147024809(80070057)指定された値は境界を越えています。」
- ShapeRange.ChildはエラーだがShapeRange(1).Childは「msoFalse(=0)」を返す。
- 上記の箇所以外はShapeRange.Countと同じ動き方です。
【ChildプロパティのShapeRangeとShapeRange(Index)での戻り値】
描画オブジェクト | 単独 | 複数 | 子-単独 | 子-複数 | 補足 |
---|---|---|---|---|---|
②コメント(テキストボックス選択) | ×|0 | - | 該当無 | 該当無 | msoFalse(=0) |
③フォームコントロール | ×|0 | 0|0 | 〃 | 〃 | |
④テキストボックス | 0|0 | 0|0 | 〃 | 〃 | |
⑤オートシェープ | 0|0 | 0|0 | 〃 | 〃 | |
⑥グラフ | ×|× | 0|0 | ×|× | - | Xのエラーは438 |
⑦図表(Smart Art) | 0|0 | 0|0 | ×|× | ×|× | 〃 |
⑧画像 | 0|0 | 0|0 | 該当無 | 該当無 | |
⑨グループ化(⑤) | 0|0 | 0|0 | -1|-1 | -1|-1 | msoTrue(=-1) |
上記の結果から導き出せるのは下記の3点です。
- 単独選択の場合は、「selection.ShapeRange(1).Child」を使用する必要がある。
- ShapeRange.Childでは「②コメント(テキストボックス選択)」と「③フォームコントロール」の場合はエラーになる。
- ただし「selection.ShapeRange(1).Child」を使用する前にグラフが選択されているか?を切り分けて置く必要がある。
- 「DrawingObjects」の場合は、「selection.ShapeRange.Child」の戻り値を見る事で子図形であるか?を判定できる。
なお、子図形が複数選択されている場合は「どれか一つが子図形であれば全部が子図形」と言えるので上記をまとめて「selection.ShapeRange(1).Child」だけで判定する事も可能です。
ただし、ShapeとShapeRangeの違いについては前回の「VBA ShapesオブジェクトとItemメソッド・Rangeプロパティ」でもご説明しましたが注意が必要です。
2-2.どのような描画オブジェクトが選択されているか?調べる
複数の描画オブジェクトが選択されていれば、Selection.ShapeRange(Index)が機能するので、Selection.ShapeRange.Countの回数分ShapeRange(Index).Typeを調べて、選択されている描画オブジェクトが何であるか?を切り分ける事ができます。
なおShapeRange(Index).Typeの戻り値につきましては後段でご説明いたします。
ステップ3.Selectionの対象が「グラフの単独選択である」事を調べる
ステップ1で、Selectionの対象が「セル・セル範囲」ではなく、またテスップ2で複数選択でもない事は切り分けできました。
つぎのステップとしては、Selection.ShapeRange(Index)が機能しない「グラフの単独選択」を切り分ける必要があります。
これは親図形の単独選択であればTypeName(Selection)の戻り値が「ChartArea」である事で判断できますが、子図形の単独選択の場合は各要素別の戻り値になっているので一筋縄ではいきません。
そこで使用するのがApplication.ActiveChartプロパティです。
※Microsoft Officeのドキュメントのページは下記になります。
アクティブなグラフがないときは、Nothing を返します
つまり、ActiveChartプロパティはActiveSheet上にグラフが存在しなくてもエラーになりません。
また子要素が選択されている状態でも正しく機能しますので、親・子どちらが選択されていても判定する事ができます。
判定のコーディング事例は下記になります。
If Not (ActiveChart Is Nothing) Then
- 「ActiveChartがNothingでは無い」という事はグラフが選択されている事が判定できます。
ただし、ドキュメントには記載されていませんがActiveChartプロパティは描画オブジェクトが複数選択された状態ではグラフが選択されていてもNothingを返しますので注意が必要です。
そのためにステップ1で、Selectionの対象が複数選択でもない事を事前に切り分けて置かなければなりません。
選択されているグラフの子要素に応じた処理が必要になるアプリケーションの場合は、TypeName(Selection)の各要素別に戻り値を調べて、その値によって場合分けする必要がありますが、ただ単に選択されているのがグラフであるか?を調べるのであれば、先ほどのコーディング事例で十分です。
ステップ4.特定するために必要なその他の場合分けについて
まずは子図形が選択されているとSelection.ShapeRange(Index)が機能しない図表(Smart Art)から見て行きます。
4-1.Selectionの対象が「図表(Smart Art)の子図形である」事を調べる
ステップ1で調べたように表(Smart Art)のTypeName(Selection)の戻り値は下記のようになります。
描画オブジェクト | 単独 | 複数 | 子-単独 | 子-複数 |
---|---|---|---|---|
⑦図表(Smart Art) | GroupObject | DrawingObjects | Shape | ShapeRange |
従って、戻り値が「Shape」また「ShapeRange」の場合は図表(Smart Art)の子図形が選択されていると判定できます。
ただしこれらの場合は選択されている子図形に直接アプローチする手段が無いので、「エラーメッセージを表示して選択し直してもらう」とか「未処理描画オブジェクトとして出力する」などの対応策を講じる事になります。
なお「図表(Smart Art)」であっても、次の章の「GroupObject」が返る時はShapeRange.GroupItemsプロパティを使用して「図表(Smart Art)」の子図形を処理対象にする事ができます。
ただしGroupItemsプロパティの使い方にもいろいろと注意点があるので、これはこれで別記事にしてご説明したいと思います。
4-2.単独選択で「GroupObject」が返る時の図表(Smart Art)とグループ化の切り分け
先に図表(Smart Art)の子図形が切り分けられていれば、この場合はSelection.ShapeRange(Index)が機能するので、ShapeRange.Typeプロパティで両者を切り分けます。
Microsoft OfficeのドキュメントのMsoShapeType 列挙(Office)では両者はそれぞれ次の値になります。
描画オブジェクト | 列挙名 | 値 | 説明 |
---|---|---|---|
⑨グループ化(⑤) | msoGroup | 6 | Group |
⑦図表(Smart Art) | msoIgxGraphic | 24 | SmartArt グラフィック |
なお、ここまで切り分けが進んでいる状態であれば、TypeName(Selection)の戻り値が「GroupObject」である事は気にせずに残りの描画オブジェクトと合わせてShapeRange.Typeプロパティで判定した方がコーディングが簡単になります。
4-3.単独選択で「TextBox」が返る時のコメント(テキストボックス選択)とテキストボックスの切り分け
先に切り分けが終わっていれば、この場合はSelection.ShapeRange(Index)が機能するので、ShapeRange.Typeプロパティで両者を切り分ける事ができます。
なおMsoShapeType 列挙(Office)の値は下記のようになります
描画オブジェクト | 列挙名 | 値 | 説明 |
---|---|---|---|
②コメント(テキストボックス選択) | msoComment | 4 | コンポーネント |
④テキストボックス | msoTextBox | 17 | テキスト ボックス |
「GroupObject」と同様に「TextBox」は残りの描画オブジェクトと合わせてShapeRange.Typeプロパティで判定した方がコーディングが簡単になります。
4-4.残っている描画オブジェクトはShapeRange.Typeプロパティで切り分ける
これまでにまだ出てきていないMsoShapeType 列挙(Office)を下記にまとめて置きます。
描画オブジェクト | 列挙名 | 値 | 説明 |
---|---|---|---|
③フォームコントロール | msoFormControl | 8 | フォーム コントロール |
⑤オートシェープ | msoAutoShape msoCallout msoFreeform msoLine | 1 2 5 9 | AutoShape 吹き出し フリーフォーム 直線 |
⑥グラフ | msoChart | 3 | グラフ |
⑧画像 | msoPicture | 11 | リンク画像 |
13 | 画像 |
- 画像はリンクの有無でMsoShapeType 列挙の値が異なりますが、これまで検証してきた結果に違いはありません。
オートシェープに関しては4種類の戻り値がありますが、msoAutoShapeが大半を占めています。この辺りの詳細はTypeName(Selection)の戻り値の検証と合わせて、長くなるので別の記事でご紹介する予定です。
コーディング事例
前章で「Selectionがどんな選択状態か?」を調べるためのステップを述べて来ましたが、ここではその仕様に基づく簡単なコーディング事例をご紹介いたします。
構造としては1つのメインサブルーチンと1つのファンクションからなります。
メインサブルーチンは切り分けテスップを順番に実行し、ファンクションではTypeプロパティでの場合分けを実施してどのような描画オブジェクトが選択されているか?を判定します。
なおTypeプロパティでの場合分けをファンクションにしているのは、単独選択でも複数選択でもどちらかも呼べるようにする事でコーディング量を減らすためです。
- 場合分けした後のメッセージは文字列のままだと長くなるので定型文の中に記述した「@」をReplace関数で置き換える事で短縮しています。
- 結果はメッセージボックスに表示します。大量の描画オブジェクトを選択するとメッセージボックスには表示しきれませんのでお含み置きください。
Sub WhatSelection() Dim st_OyaKo As String Dim st_Msg As String Dim st_phrase As String Dim st_selection As String Dim i As Integer st_OyaKo = "親": st_Msg = "" st_phrase = "「図表」の子図形を@選択" & vbCrLf & "※選択し直してください" st_selection = TypeName(Selection) If st_selection = "Range" Then 'ステップ1 st_Msg = Selection.CountLarge & " セル選択" ElseIf st_selection = "DrawingObjects" Then 'ステップ2 If Selection.ShapeRange.Child Then st_OyaKo = "子" 'ステップ2-1 子図形複数選択 For i = 1 To Selection.ShapeRange.Count 'ステップ2-2 st_Msg = st_Msg & SetMsgByType(Selection.ShapeRange(i), st_OyaKo) & vbCrLf Next Else If ActiveChart Is Nothing = False Then 'ステップ3 With ActiveChart If st_selection = "ChartArea" Then '親図形選択 st_Msg = .chartType & vbCrLf & .Name & vbCrLf & "グラフ全体を単独選択" Else '子要素選択 st_Msg = .Name & vbCrLf & "グラフの「" & st_selection & "」要素を単独選択" End If End With ElseIf st_selection = "Shape" Then 'ステップ4-1単独 st_Msg = Replace(st_phrase, "@", "単独") ElseIf st_selection = "ShapeRange" Then 'ステップ4-1複数 st_Msg = Replace(st_phrase, "@", " " & Selection.Count & " 個") Else 'ステップ4-2~4-4 If Selection.ShapeRange(1).Child Then st_OyaKo = "子" 'ステップ2-1 子図形単独選択 st_Msg = SetMsgByType(Selection.ShapeRange(1), st_OyaKo) End If End If MsgBox st_Msg End Sub
Function SetMsgByType(sp As Shape, stOyaKo As String) As String Dim st_phrase As String st_phrase = "図形「@」を選択" If sp.Type = 1 Then SetMsgByType = stOyaKo & Replace(st_phrase, "@", "AutoShape") ElseIf sp.Type = 2 Then SetMsgByType = stOyaKo & Replace(st_phrase, "@", "吹き出し") ElseIf sp.Type = 3 Then SetMsgByType = stOyaKo & Replace(st_phrase, "@", "グラフ") ElseIf sp.Type = 4 Then SetMsgByType = stOyaKo & Replace(st_phrase, "@", "コンポーネント(コメント)") ElseIf sp.Type = 5 Then SetMsgByType = stOyaKo & Replace(st_phrase, "@", "フリーフォーム") ElseIf sp.Type = 6 Then SetMsgByType = "グループ化された" & stOyaKo & "図形を単独選択" ElseIf sp.Type = 8 Then SetMsgByType = stOyaKo & Replace(st_phrase, "@", "フォーム コントロール") ElseIf sp.Type = 9 Then SetMsgByType = stOyaKo & Replace(st_phrase, "@", "直線") ElseIf sp.Type = 11 Or sp.Type = 13 Then SetMsgByType = stOyaKo & Replace(st_phrase, "@", "画像") ElseIf sp.Type = 17 Then SetMsgByType = stOyaKo & Replace(st_phrase, "@", "テキスト ボックス") ElseIf sp.Type = 24 Then SetMsgByType = stOyaKo & Replace(st_phrase, "@", "SmartArt グラフィック") Else '想定外への備え SetMsgByType = stOyaKo & Replace(st_phrase, "@", "※" & sp.Type) End If End Function
OLEオブジェクトと3Dモデルとワードアート オブジェクトの結果
OLEオブジェクトと3Dモデルとワードアート オブジェクトのこれまでと同様な検証をした結果を以下にまとめます。なおOLEオブジェクトと3Dモデルはリンクの有無によりMsoShapeType 列挙(Office)の値は異なりますが検証結果に違いはありません。
①Selection.ShapeRange.Countの動作は単独・複数ともに正しい値を返します。
②【TypeName(Selection)の戻り値一覧】
描画オブジェクト | 単独 | 複数 | 子-単独 | 子-複数 |
---|---|---|---|---|
⑩OLEオブジェクト | OLEObject | DrawingObjects | 該当無 | 該当無 |
⑪3Dモデル | ※Rectangle | DrawingObjects | 〃 | 〃 |
⑫ワードアート オブジェクト | Rectangle | DrawingObjects | 〃 | 〃 |
③【ChildプロパティのShapeRangeとShapeRange(Index)での戻り値】
描画オブジェクト | 単独 | 複数 | 子-単独 | 子-複数 | 補足 |
---|---|---|---|---|---|
⑩OLEオブジェクト | ×|0 | 0|0 | 該当無 | 該当無 | |
⑪3Dモデル | 0|0 | 0|0 | 〃 | 〃 | |
⑫ワードアート オブジェクト | 0|0 | 0|0 | 〃 | 〃 |
④MsoShapeType 列挙(Office)の値
描画オブジェクト | 列挙名 | 値 | 説明 |
---|---|---|---|
⑩OLEオブジェクト | msoEmbeddedOLEObject | 7 | 埋め込み OLE オブジェクト |
msoLinkedOLEObject | 10 | リンク OLE オブジェクト | |
⑪3Dモデル | mso3DModel | 30 | 3D モデル |
msoLinked3DModel | 31 | リンクされた 3D モデル | |
⑫ワードアート オブジェクト | msoAutoShape | 1 | AutoShapeと同じ |
以上の結果からコーディング事例に反映するにはファンクションのIF文に下記を追加すれば良い事になりますが、ワードアート オブジェクトはTypeプロパティがAutoShapeと同じ「1」であるために別途切り分けが必要です。
※今回は長くなるのでワードアート オブジェクトとAutoShapeの切り分けについては省略いたします。
ElseIf sp.Type = 7 Or sp.Type = 10 Then SetMsgByType = stOyaKo & Replace(st_phrase, "@", "OLEオブジェクト") ElseIf sp.Type = 30 Or sp.Type = 31 Then SetMsgByType = stOyaKo & Replace(st_phrase, "@", "3Dモデル")
最後に
複数の描画オブジェクトを選択した時のTypeName(Selection)の戻り値は「DrawingObjects」になるのですが、ExcelではDrawingObjectsという表現には歴史があります。
下記のエクセルの神髄のページを見ていただくと分かるのですがShapesオブジェクトの昔の形がDrawingObjectsで、現在でもオブジェクトブラウザでは「非表示メンバー」になっていますが存在をしています。
Selectionプロパティを描画オブジェクトで使うためには、あれやこれや注意しなければならないポイントが多々あり、あまり深く考えずに使用するとアプリケーションをリリースした後に「何か良く分からないエラーが起こる」という問い合わせを受ける事になりそうです。
その場合、On Errorステートメントでエラーを回避する事ができるかもしれませんが、今回見てきたようにステップを踏んで場合分けをする事で、Selectionプロパティの処理についてはOn Errorステートメントを使わずにコーディングできるはずです。
ただそうは言っても「何でShapeRange.Countが描画オブジェクトによって微妙に動きが異なるのか?」とか「ShapeRange.Childが描画オブジェクトによってエラーになるのはなぜなんだ?」というやるせない気持ちになってしまうのは事実です。
恐らくは下位互換を図りながら30年以上もバージョンアップを重ねてきたExcelゆえの根深い問題なのかもしれませんが、VBAは正直どこかで全く新しいアプリケーションに作りかえる必要があるのかもしれません。
ただその場合は今まではまったく違った形になるとは思いますが…
次回は宿題として残してある「すべての描画オブジェクトのTypeName(Selection)の戻り値」についてご説明する予定です。
以上最後までご一読いただき誠にありがとうございました。