「追加したはずの右クリックメニューが表示されない事件」の真相 ③
先日来、2年越しの事件が解決しそうだ。
infoment.hatenablog.com
今日も、先日の続きから。
右クリックメニューを追加する際、他のマクロで追加された別メニューの有無に関わらず、今までは毎回バツンとリセットしていた。
Sub Test() Dim LoopIndex As Variant For Each LoopIndex In Array(38, 41, 61, 74, 75) With Application.CommandBars(LoopIndex) .Reset .Controls.Add.Caption = "テスト" End With Next End Sub
こうしておかないと、追加するたびにドンドンと増えてしまう。
しかしそれでは、余りに乱暴が過ぎると思っていた。
そこで今回、設定済み右クリックメニューの情報取得と、ピンポイント削除に挑戦してみた。まず、このように設定しておく。
次いでこの中から、「テスト3」だけを消してみよう。
Sub Test2() Dim LoopIndex As Variant Dim CommandBar As CommandBar Dim i As Long For Each LoopIndex In Array(38, 41, 61, 74, 75) With Application.CommandBars(LoopIndex) ' 右クリックメニューを消すたびに段違いが発生する。 ' 対策として、後ろから削除する。 For i = .Controls.Count To 1 Step -1 If .Controls.Item(i).Caption = "テスト3" Then .Controls.Item(i).Delete End If Next End With Next End Sub
結果、テスト3だけを消すことが出来た。
これを応用すれば、追加時は事前に追加済みか否かを確認できるし、終了時も目的のメニューだけを消すことが出来そうだ。
次回に続きます(あと1回か、多くても2回)
参考まで。
「追加したはずの右クリックメニューが表示されない事件」の真相 ②
先日来、2年越しの事件が解決しそうだ。
infoment.hatenablog.com
今日は、先日の続きから。
個人的に、追加した右クリックメニューを実行したくなるのは、以下のパターンのとき。
- テーブル上のセル又は範囲
- ピボットテーブル上のセル又は範囲
- 上記以外のセル又は範囲
そこで、先日無理矢理追加した右クリックメニューから、各ビューの上記それぞれについて何が登録されているかを確認してみた。
結果は、下表のとおり。
PivotTableは全ビュー共通なのに、RangeとTable(=ListObject)は、改ページプレビューだけ違う。何だか、一貫性が無いように見えて気持ち悪いが(仕様だから)仕方ない。
ところで2年前は、CommandBars("Cell") のように名前で指定した。
今回は、38や61などの番号で登録できないか試してみよう。
Sub Test() With Application.CommandBars.Item(38).Controls.Add .Caption = "テスト" End With End Sub
↓ できた。
それでは、これをループさせてみよう。
Sub Test() Dim LoopIndex As Variant For Each LoopIndex In Array(38, 41, 61, 74, 75) With Application.CommandBars(LoopIndex).Controls.Add .Caption = "テスト" End With Next End Sub
結果、ビューに関係なくテーブルでもピボットテーブルでも、右クリックメニューを表示させることが出来た。
次回に続きます。
参考まで。
「追加したはずの右クリックメニューが表示されない事件」の真相 ①
約2年前、このような記事を作成した。
infoment.hatenablog.com
右クリックメニューに追加したはずのコマンドが表示されない。
でもそれは、改ページプレビューで見たのが原因。そんな感じで
締めくくられている。
しかし実際はそうではないことを、先日職場で教えてもらった。
当時のメニュー追加マクロは、下記のとおり。
Sub AddNewMenu(new_caption As String, new_action As String) Dim NewMenu As CommandBarButton Set NewMenu = Application.CommandBars("Cell").Controls.Add() With NewMenu .Caption = new_caption .OnAction = new_action .BeginGroup = False End With End Sub
Sub AddMenuTest() Application.CommandBars("Cell").Reset Call AddNewMenu("追加メニュー", "追加メニュー") End Sub
今ならわかる。CommandBarsとはつまり、CommandBarがたくさん集まったもの。
従って、CommandBarsコレクションのうち、「Cell」に対してのみメニューを追加していた訳だ。そこで試しに、かなり乱暴ではあるがこんなコードを書いてみた。
Sub Test() Dim CommandBar As CommandBar On Error Resume Next For Each CommandBar In Application.CommandBars CommandBar.Reset With CommandBar.Controls.Add .Caption = CommandBar.Index & ":" & CommandBar.Name End With Next End Sub
すると、Cellが複数あることが分かる。
- 標準ビューの場合
- ページレイアウトビューの場合
- 改ページプレビューの場合
38番と41番の「Cell」は、どちらも同じ名前なので、名前で指定した結果、38番(=標準およびページレイアウトビュー)のみに右クリックメニューが追加されていたらしい。
何か、だんだんわかってきた。
次回に続きます。
参考まで。
最初の作りが甘いと、後々まで祟るというお話
1年前作成した ↓ こちら。
infoment.hatenablog.com
改築・増築を繰り返した結果、考え方の不一致などが多数生じていた。
そこで今回、数日かけて全て再構築してみた。
再構築したクラスモジュール全文は、上記リンク先に反映済みだ。
更に今回は、配列に新たなレコード行を追加できるよう、機能拡張してみた。
例えば、 ↓ の表について。
この表に、
- No. 4
- 品名 ぶどう
- 単価 250
の一行を追加し、新たなシートを作成して「編集後」という名前をつけ、
そのシートに編集後の配列を張り付けたうえでテーブル化し、さらに
書式を整えてみよう。
↓ サンプルがこちら。
Sub ArrayTest() Dim arr As Variant arr = Array(4, "ぶどう", 250) With New VBAProject.ArrayEdit ' 編集前配列。 .source_array = Range("A1:C4") ' 編集前配列に、上記arrを最終レコードとして追加。 .RowAdd arr, , cpInsert ' 上記編集後配列を、新たに作成した「編集後」シートに ' テーブルとして貼り付け。 .PasteArray sheet_name:="編集後", _ paste_type:=ptTable, _ column_autofit:=True, _ select_array:=case_edited End With End Sub
結果、無事にテーブルを作成できた。
今回の再構築で、一番苦労したところ。それは、再構築の前後で互換性を完全に
保つこと。最初の作りが甘いと、後々まで祟ります(自戒の句)。
参考まで。
ひっくり返して、もう一回ひっくり返す
世の中には、頭のいい人が居るもんだ。というお話。
例えばこのような、5行3列の二次元配列があるとする。
配列のサイズを変更(Redim)する場合、最大次元のみ変更可能だ。
従って、上記の例に於いては、このようになる。
二次元配列に於いては、これをテーブルに見立て、レコードを追加するイメージで一次元のサイズを変更したくなる場面が多々ある。しかし、変更できない。そこで昔はいつも、サイズ変更後の配列を別に準備し、そちらに引っ越すという、非常に面倒な手段をとっていた。
ところがその後、このような方法があることを知った。
まず、Transpose関数で行と列を入れ替える。
次いで、最大次元のサイズを一つ増やす。
最後に、もう一度行と列を入れ替える。
これで、一次元のサイズが変更できてしまった。
コードにするなら、こんな感じだろうか。
Sub ArrayTest() ' 5行3列の範囲を、配列として変数arrに格納。 Dim arr As Variant arr = Range("A1:C5") ' arrの行列を反転させた配列を、TempArrayに格納。 Dim TempArray As Variant TempArray = WorksheetFunction.Transpose(arr) ' TempArrayの二次元サイズを5から6に変更。 ReDim Preserve TempArray(1 To 3, 1 To 6) ' TempArrayの行列を反転させた配列を、arrに格納。 arr = WorksheetFunction.Transpose(TempArray) End Sub
結果、希望通りのサイズ変更が出来た。
初めてこれを目にしたとき、その鮮やかさに感動した。
まさに、コロンブスの卵とはこのこと。種明かしされれば何ということもないが、思いつくのは至難の業。
当たり前の話ですが世の中には、到底かなわないほど頭のいい人がたくさんいる
ものだと、改めて思った次第です。
参考まで。
配列の重複削除 ④ 重複の無い一次元配列を作成するクラスモジュール
先日来、二次元配列の任意の一列から、重複の無い一次元配列作成を試みている。
infoment.hatenablog.com
今日は最終回。以前作成したクラスモジュールに、先日作成したユーザー定義関数を含めてみる。
今回行ったのは、以下の三つ。
- 以前作成したクラスモジュール「Seaquence」に、今回のユーザー定義関数を
含めてみる。 - 配列の次元数取得は同クラスモジュール内に実装済みのため、今回の関数から除外する。
- 「空白は除去する」など、細かい調整を行う。
それでは今回行った編集の内、関係する部分だけを紹介する。
クラスモジュール(Seaquence)
' 配列の次元数取得 Private Function GetArrayDimension(arr As Variant) As Long If IsArray(arr) = False Then GetArrayDimension = -1 Exit Function End If ' 配列の次元数を取得。 Dim i As Long Dim TempNumber As Long On Error Resume Next Do While Err.Number = 0 i = i + 1 TempNumber = UBound(arr, i) Loop GetArrayDimension = i - 1 End Function
' 配列、範囲、テーブルから重複の無い一次元配列を作成。 Function RemovalDuplicateArray(ByVal source As Variant, _ Optional ByVal target_column_index As Variant = 1) As Variant ' 配列確認。 Dim source_array As Variant ' 配列に格納できないものがセットされる場合を想定し、一時的にエラーを無視。 On Error Resume Next ' sourceを範囲で指定された場合。 If TypeName(source) = "Range" Then ' 列番号がアルファベットで指定された場合、数値に変換する。 If IsNumeric(target_column_index) = False Then target_column_index = StrConv(target_column_index, vbNarrow + vbUpperCase) If target_column_index Like "*[A-Z]*" Then target_column_index = Cells(1, target_column_index).Column End If source_array = source.Value End If ' sourceがテーブルの場合。 ElseIf TypeName(source) = "ListObject" Then source_array = source.DataBodyRange.Value ' 列がラベル名で指定されている場合、列番号に置き換える。 If IsNumeric(target_column_index) = False Then target_column_index = source.ListColumns(target_column_index).index End If ' sourceが配列の場合。 ElseIf IsArray(source) Then source_array = source ' sourceが上記以外の場合(例えば文字列などの場合)。 Else source_array = Array(source) End If ' 上記でエラーが発生していた場合の処理。 If Err.Number <> 0 Then GoTo er: Else On Error GoTo 0 End If ' 配列の次元数を確認。 Dim DimensionNumber As Long DimensionNumber = GetArrayDimension(source_array) ' 作業用配列。 Dim TempArray As Variant Select Case DimensionNumber ' 一次元配列の場合。 Case 1 TempArray = source_array ' 二次元配列の場合、目的列を抜き出し。 Case 2 TempArray = WorksheetFunction.index(source_array, 0, target_column_index) ' 三次元以上は対応しない。 Case Else GoTo er: End Select ' 重複除去用の辞書(連想配列) Dim Dict As Object Set Dict = CreateObject("Scripting.Dictionary") Dim a As Variant For Each a In TempArray ' 配列内の各値を、辞書に格納する。 ' 重複除去が目的のため、itemは不問。今回は「1」とした。 ' ※空欄はリストに含めない。 If a <> vbNullString Then Dict(a) = 1 End If Next ' 辞書のkeyが、重複除去後の配列として取り出せる。 RemovalDuplicateArray = Dict.Keys Exit Function er: RemovalDuplicateArray = Array() On Error GoTo 0 End Function
標準モジュール
テスト用マクロがこちら。
題材は前回と同様、なんちゃって個人情報(テーブル化済み)だ。
Sub ArrayTest() Dim arr As Variant With New Seaquence arr = .RemovalDuplicateArray(source:=ActiveSheet.ListObjects(1), _ target_column_index:="都道府県") MsgBox Join(arr, vbNewLine) End With End Sub
結果
実行して、意図した結果を得ることが出来た。
これでまた、(少なくとも個人的には)業務の効率が上がった。良きかな。
なお、↓ こちらは既に更新済みです。
infoment.hatenablog.com
参考まで。
配列の重複削除 ③ 範囲やテーブルの任意の一列からも、重複の無い一次元配列を作成してみる
昨日は、二次元配列の任意の一列から、重複の無い一次元配列を作成してみた。
infoment.hatenablog.com
しかし、このままでは未だ足りない。もう少し機能拡張してみよう。
ということで、昨日のものを少し弄ってみた。
- 配列だけでなく、範囲(Range)またはテーブル(ListObject)も受け取る。
- 範囲の場合は、「L列」のようにアルファベットでも指定できるようにする。
ただし、範囲がA列から始まっていない場合は要注意。 - テーブルの場合は、ラベル名でも指定できるようにする。
コードが、だんだん長くなってきた。
いずれ分割も検討するとして、現状はこんな感じ。
Function RemovalDuplicateArray(ByVal source As Variant, _ Optional ByVal target_column_index As Variant = 1) As Variant ' 配列確認。 Dim source_array As Variant ' 配列に格納できないものがセットされる場合を想定し、一時的にエラーを無視。 On Error Resume Next ' sourceを範囲で指定された場合。 If TypeName(source) = "Range" Then ' 列番号がアルファベットで指定された場合、数値に変換する。 If IsNumeric(target_column_index) = False Then target_column_index = StrConv(target_column_index, vbNarrow + vbUpperCase) If target_column_index Like "*[A-Z]*" Then target_column_index = Cells(1, target_column_index).Column End If source_array = source.Value End If ' sourceがテーブルの場合。 ElseIf TypeName(source) = "ListObject" Then source_array = source.DataBodyRange.Value ' 列がラベル名で指定されている場合、列番号に置き換える。 If IsNumeric(target_column_index) = False Then target_column_index = source.ListColumns(target_column_index).Index End If ' sourceが上記以外であって、且つ、配列ではない場合(例えば文字列などの場合)。 ElseIf IsArray(source) = False Then source_array = Array(source) End If ' 上記でエラーが発生していた場合の処理。 If Err.Number <> 0 Then GoTo er: ' 配列の次元数を確認。 Dim i As Long For i = 1 To 3 Debug.Print UBound(source_array, i) If Err.Number <> 0 Then Exit For Next On Error GoTo 0 Dim DimensionNumber As Long DimensionNumber = i - 1 ' 作業用配列。 Dim TempArray As Variant Select Case DimensionNumber ' 一次元配列の場合。 Case 1 TempArray = source_array ' 二次元配列の場合、目的列を抜き出し。 Case 2 TempArray = WorksheetFunction.Index(source_array, 0, target_column_index) ' 三次元以上は対応しない。 Case Else GoTo er: End Select ' 重複除去用の辞書(連想配列) Dim Dict As Object Set Dict = CreateObject("Scripting.Dictionary") Dim a As Variant For Each a In TempArray ' 配列内の各値を、辞書に格納する。 ' 重複除去が目的のため、itemは不問。今回は「1」とした。 Dict(a) = 1 Next ' 辞書のkeyが、重複除去後の配列として取り出せる。 RemovalDuplicateArray = Dict.keys Exit Function er: RemovalDuplicateArray = Array() On Error GoTo 0 End Function
それではこちら、なんちゃって個人情報を元に作成したテーブルで試してみよう。
都道府県の列から、重複の無い一次元配列を作成する。
Sub ArrayTest() Dim arr As Variant arr = RemovalDuplicateArray(ActiveSheet.ListObjects(1), "都道府県") MsgBox Join(arr, vbNewLine) End Sub
結果がこちら。
一区切りつくまで、あと少し。次回に続きます。
参考まで。