VBAにおける、D&D・マウスホイール操作について

VBAの弱点、ドラッグ&ドロップとマウスのホイール操作について

VBAでは、ドラッグ&ドロップ(以下、D&D)とマウスのホイール操作が使用できません

D&D操作について

外部ファイルをD&D操作で直接VBAに読み込ませるには、2通りの方法がありますが、いずれも不安要素が大きいです

まず、よくあるのがリストビューコントロールを使用する形です
このリストビューコントロールは、ユーザーフォームの追加コントロールです
「その他のコントロール」から追加して使用することが出来ます
そのコントロールには、D&Dを検知するイベントがあり、そこからデータを操作することが出来るようになります

ただ、このコントロールは追加する操作が必要なのが難点です
そして最大の難点が、新しいExcelではコントロール自体が存在していないことです
これから主流になっていくであろう、64bitのExcelにはありません

永遠に使い続けられる処理を作成するのは幻想ではありますが、可能性ではなく実際に無くなっているコントロールを使い続けるのは少し不安です

なので、このコントロールでの解説は当サイトでは行っていません
別のWebサイトであれば、使用コード・イベントが解説されていますので、検索すればすぐに見つかりますので、そちらにお任せします

もう1つの方法が、WinAPIを使用する方法です

これはSleep関数などのように単体のAPIでサクッと使用できる、というような処理にはならないのでハードルが高いです
APIは便利ですし、それでしか実行できないことは任せますが、ハードルの高いAPIはbit違いによるメンテナンスも考慮すると、こちらも不安が残ります

そこで、本質的な話です

そもそもD&Dを使用したい場面について
これは単純に指定ファイルをシートやフォームに挿入したり処理を実行する際に、「ダイアログを開いて選択して挿入ボタンをクリックする」という操作を省くために使用したいのだと思います

確かに、D&D操作になれていると出来ないと不便に感じてしまいます
しかし、その操作の実現には不安要素が多いのが現実です

そして、この操作の最終目的は何かというと、D&D操作によって指定ファイルを操作することです、決してD&D操作が目的ではありません
なので、ユーザーからの依頼でどうしてもそれが無いと嫌だという状況以外においてはダイアログボックスでの運用をおすすめします
ダイアログボックスについては以下の記事から確認してください

マウスホイール操作について

ユーザーフォームのリストコントロールなどで上下移動したい場合などにマウスのホイール操作は使用できません

これはVBAでは、このボタン自体が認識されていないためです
ホイールを回す操作だけでなく、押し込む操作も使用できません

これをVBAで実装するにはWinAPIが必要になります
これも上記とほぼ同様なのですが、非常にハードルが上がります

実際、自分は別のWebサイトにあるコードを見てもあまり理解できません
そもそもホイールの概念自体がVBAに存在していないので、そこにその操作を放り込むというのは考えからも非常に困難なことが容易に想像できます

そこで、また本質の話です

このホイール操作で実現したいのは、ホイール操作ではない、という点です
要は、リストなどをマウス操作で簡単に上下移動したいのが目的です

それならば、頑張ってAPIを使用するよりも仕様内で考えてみます

SpinButtonコントロールのMouseMoveイベントを利用します
このコントロールを移動させたいコントロールの隣に配置し、その矢印上にマウスを移動させると、リストなどの選択を上下に移動させるような処理を作成します

これなら、マウスのクリックやドラッグ操作も必要なく簡単に操作できます

結論として

あらゆる言語のあらゆる知識を動員すれば、出来ないことなどなんにもありません

ただ、それには費用対効果をしっかり考える必要があります
効果に対して、費用が掛かりすぎる場合はデメリットの方が大きいので別の方法を考えるか、仕様としてあきらめるかです

勉強のためなら、どんどん突き詰めていくと良いと思いますが、仕事で使用するものであればメンテナンスのことは考えておきたいです

できないことを頑張るより、できることを頑張るほうが精神的にもいいです
そのほうがVBAも楽しめます

結論は、「仕様なのでできません、せやからこの方法でええやん♪」こう言うことにしましょう

Terminateイベント

TerminateイベントはUserFormがメモリ上から解放されると発生するイベントです

「Terminate」イベントはUserFormがメモリ上から解放されると発生します

QueryCloseイベントで終了が許可された場合、その後に発生しますのでフォームの終了処理に向いています

ただ、注意点としてフォームがメモリ上から解放されているため、UserFormの情報が取得できません
サイト記事にもある、前回と同じ場所にフォームを表示する処理で位置を取得する際にはQueryCloseイベントを使用しております
このイベントでは、その位置情報を取得が出来ないためです

なので、このイベントはフォームの情報取得が必要ない場合に使用できます

上記の通りフォームの情報が終了処理に必要な場合はQueryCloseイベントを使用してください
それ以外のブックを終了させるなどの処理なら、このイベントを利用する形で十分だとおもいます

また、リファレンスにもありますが、このイベントはUserFormがメモリ上に読み込まれた後、解放されて初めて実行されます

別記事にEndステートメントで強制的にフォームの起動をキャンセルさせる処理がありますが、その処理の場合はこのイベントはフォーム終了するタイミングであっても発生しません

あと、このイベントは解放されてしまっているので、QueryCloseイベントのように終了をキャンセルするようなことはできません

Endステートメントの有効利用

Endステートメントは処理を強制終了させます。数少ない有効利用場面の解説

'全ての処理を強制終了
End

処理を強制的に終了させるには「End」ステートメントを使用します

このステートメントの使用上の注意点があります、以下の記事で解説しています

基本的にこのコードは強制終了という側面から、あまり多用すべきコードではありません

終了する場合はしっかりプロシージャの最後まで実行するべきです

ですが、それも状況次第で有効に利用できる場合があります

フォームの起動を強制終了

ユーザーフォームを起動させるには、ShowメソッドとLoadステートメントがあります

どちらも、必ずInitializeイベントを発生させます
なのでフォームの初期化処理はこのイベントに作成します

起動条件を設定したフォームの場合は注意する点があります

例えば、入力補助のフォームであれば
作業するシート以外では、フォームは表示される必要はありませんし、場合によってはバグが発生する可能性があります
そもそも使用しないシートでフォームが表示されるのも邪魔です

そんな場合には、フォームの起動時に条件分岐を行い条件に一致しない場合にはフォームを起動しないようにします

そして、通常は問題にならないこの状況が問題になるのが、初期化処理に時間が多少かかる場合です

条件分岐によりフォームを表示させないのに、待ち時間をユーザーに発生させてしまいます
シートを切り替えるたびに、数秒待たされるような状況になったら非常に迷惑です

解決法として、あるのは2通りです
読み込みコード実行前に条件分岐を行うか、Initializeイベントの処理最上部に条件分岐を行うかのどちらかです

If ActiveSheet.Name = "対象" Then
UserForm1.Show
Else: End If

読み込みコード実行前に行うのが通常の方法です
If分岐で条件に一致しなければ、読み込みコードは実行しないようにします

ただ、このコードでも問題はあります
基本的にこういった処理の場合は、シートイベントのActivateイベントで表示処理、Deactivateイベントでフォームを終了させる動きにします

こうしておかないと、表示は条件分岐で対象シートで行えても、シートを切り替えた時にフォームが表示されたままになるためです

そこで問題の発生です

対象シートが複数あった場合、全てのシートに条件分岐を作成する必要があります
少しの修正があっても大変になってしまいます

そこで、フォームのInitializeイベントで一括して条件分岐を行います

If Not ActiveSheet.Name = "対象1" Or ActiveSheet.Name = "対象2" Then
End
Else: End If

Initializeイベントの最上部にこのコードを挿入します
アクティブシートの名前が指定シートでなければ、Endステートメントにより強制終了します
指定シートであれば処理が継続して、多少時間のかかる初期化処理が実行されます

Initializeイベント中に、Unloadステートメントを実行するとエラーが発生しますので、このEndステートメントが効果を発揮します

当然ですが、呼び出し元処理(ShowメソッドやLoadステートメント)も強制終了しますので読み込み処理には他の処理は入れないようにしましょう

使用場面は限られて少ないですが、あると便利な場面もありました

なお、気づいた方もいるかもしれませんが、終了処理に対応してません
これにはイベント最強のWithEventsキーワードを使用します

Initializeイベント

Initializeイベントはメモリ上に読み込みが完了した時点で発生するイベントです。起動時の注意点など

「Initialize」イベントは、UserFormがメモリ上に読み込まれたら発生するイベントです
表示される前に発生するイベントで、引数はありません

フォームの初期化処理で使用します

Loadステートメントを実行すると、このイベントが発生します
Showメソッドを実行した場合は、このイベントが発生後にActivateイベントが発生します

また、メモリ上に読み込みが終了しているので、UserFormの操作及び各コントロール全ての操作が可能な状態になります

表示前の処理になるので、このイベントに作成した処理が時間のかかるものであった場合は上記の各読み込みコード実行前に起動の明示をしていないとユーザーにフリーズさせているような誤解を与える可能性があります

そういった場合は、あえてActivateイベントに処理を入れてフォームを表示させておいてもいいかもしれません

フォームの起動条件の作成について

このイベント中にフォームを終了させるとエラーが発生します
フォームの起動条件の確認には、読み込みプロシージャかActivateイベントで行い終了させるようにしてください

ただ、この処理では少し問題がある場合があります
その状況と解決方法は以下の記事で解説しています

Zoomプロパティ

Zoomプロパティは、各コントロールの表示倍率の設定です。使用時の注意点も

'100%表示にする
UserForm1.Zoom = 100

「Zoom」プロパティは、フォーム内の表示倍率の設定です
初期値は100になっています
これは%表示と考えてもらっていいです

設定できる範囲が「10~400」の間に決まっています
元々の10分の1から4倍の大きさにまで、拡大縮小が出来るようになっています

Zoomが100の時の表示
初期値100の表示状態
Zoomが50の時の表示
50に設定した表示状態
Zoomが200の時の表示
200に設定した表示状態

以上の画像の様に、同じフォームの同じサイズのコントロールでも大きな違いが生まれます
なにより、見やすさを向上させるためのプロパティと思ってください

表示の乱れについて

このプロパティの難点が文字列がコントロールの表示領域からはみ出してしまうことがあることです

Zoomの数値によって文字列がはみ出している
labelの文字列がはみ出している状態

画像を確認してください、これは元は上記の画像と同じものです
赤枠内のlabelコントロールの表示文字列の最後の「n」がはみ出して消えてしまっています
これはコントロールのサイズの単位と文字列のサイズの単位が違うため、Zoomプロパティの数値を1ずつ動かしていくと必ずどこかで発生します
文字サイズが影響するため、文字の種類によっても差があります

なので、自動調整で文字列がはみ出す場合はいかんともしがたいです

このプロパティを主に使用するのは、フォームのサイズ調整に合わせて、各コントロールの表示も整える場合に使用します
フォームの自動サイズ調整については以下の記事で解説しています

解説にもありますが、いかんせん表示が乱れることがあるのが困っちゃう
困っちゃうのであんまり普段は使わんですねぇ

Tagプロパティ

Tagプロパティは追加情報を設定するプロパティです。設定するコード

'フォームの追加情報の設定
UserForm1.Tag = "追加情報"

「Tag」プロパティは追加情報を設定できます

これ自体は、設定してもなんの意味もないプロパティです
なんの意味もないということは、他に影響を与えないと言い換えれます

これはそのプロパティ値で、処理を行う為に使用します

タグという名前の通り、なんらかの判定に使用します
例えば、このプロパティに「グループA」という文字列を入力しておいて、そのグループごとに処理を実行するようなことを行えます

また、処理中の一時的なデータの保持にも使用できます
加工前の文字列をプロパティに代入しておき、加工後の文字列を出力後、Tagプロパティから加工前の文字列を取得する、というようなことも出来ます

使用方法は多岐にわたりますので、いろいろな使用方法を考えてみてください

IsReadyプロパティ

IsReadyプロパティは、ドライブの使用可能状態を返します。その状態を取得するコード

'Eドライブの使用可能状態を取得
Debug.Print bhFSO.GetDrive("E").IsReady

「IsReady」プロパティは、ドライブが使用可能な状態にあるかを調べるプロパティです

使用可能であれば「True」、使用不可であれば「False」が指定されます

これはUSBメモリーなどの着脱を行うドライブに対して、データのやり取りが可能な状態かを調べるのに使用します
そのため、これはドライブオブジェクトのプロパティになりますので、まずGetDriveメソッドを使用してドライブを取得します
その後、そのドライブオブジェクトのIsReadyプロパティを調べます

例コードでは1行で実行していますが、他にもドライブに対して処理を行うのであれば、GetDriveメソッドでオブジェクト変数に代入させるほうが良いです

ただ、使用状態を知りたいだけならこのコードで十分です

使用可能な状態とは、ドライブが接続されてデータの通信が可能になった状態です

ドライブを取り外す場合に、基本的には「取り外し」という操作を行います
この操作を行った段階ではまだ、物理的には取り外していません
なので、DriveExistsメソッドではTrueが返される状態です
ですがデータのやり取りは出来ない状態なので、IsReadyプロパティはFalseになります

物理的に取り外すと、DriveExistsメソッドでもFalseが返されるようになります

ドライブが存在しないときは、このプロパティを取得しようとするとエラーが発生します
つまり、DriveExistsメソッドとIsReadyプロパティの2つともがTrueを返したときにそのドライブとのデータのやり取りが可能になる、ということです

FileExists・FolderExists・DriveExistsメソッド

FileExists・FolderExists・DriveExistsメソッドは、ドライブ・フォルダ・ファイルの存在確認をするメソッドです

'ファイルの存在確認
Debug.Print bhFSO.FileExists("ファイルの絶対パス")
'フォルダの存在確認
Debug.Print bhFSO.FolderExists("フォルダの絶対パス")
'ドライブの存在確認
Debug.Print bhFSO.DriveExists("C")

FileSystemObjectを使用して、存在確認を行うメソッドです
「FileExists」メソッドはファイル
「FolderExists」メソッドはフォルダ
「DriveExists」メソッドはドライブ
それぞれの存在確認に、各メソッドを使用します

メソッドは全て、存在すれば「True」、存在しなければ「False」を返します

FileExistsメソッド

Debug.Print bhFSO.FileExists("ファイルの絶対パス")

FileExistsメソッドは、ファイルの存在確認を行うメソッドです

引数には拡張子までを含めた絶対パスを指定します
これは単体のファイルの存在確認のため、ワイルドカードは使用できません

指定の名前のファイルが存在していれば、処理を行うような場合に使用します
存在確認の中でも一番使用頻度が高いメソッドと思います

FolderExistsメソッド

Debug.Print bhFSO.FolderExists("フォルダの絶対パス")

FolderExistsメソッドは、フォルダの存在確認を行うメソッドです

引数にはフォルダ名を含む絶対パスを指定します

またパス指定であれば、ドライブの存在確認も行えます
「C:」のように「:」を付けて指定します
専用のメソッドがあるので、意味はありませんが一応可能です

フォルダは上書きされないので、あまり存在確認は必要ありません

DriveExistsメソッド

Debug.Print bhFSO.DriveExists("C")

DriveExistsメソッドは、ドライブの存在確認を行うメソッドです

引数にはドライブ名を指定します、小文字でも実行可能ですがなるべくドライブ名と同じ大文字で指定するほうがいいです

また、パスでも指定は可能です
「C:」というように指定しても確認を行えますが、メソッド的に意味はありませんので、ドライブ名で指定します

基本的には着脱されるドライブに対して使用します
USBメモリーなどのものです、これをPCで使用されているかを調べて存在すればそのドライブにデータを移動させたりする処理に使用します

なお、ドライブは存在が確認できても使用できる状態とは限りません
使用できる状態を調べるにはIsReadyプロパティを使用します

StartUpPositionプロパティ

StartUpPositionプロパティでフォームの初期表示位置を設定できます

「StartUpPosition」プロパティは、UserFormが表示される初期位置の設定です
基本的には初期表示位置なのでデザインウィンドウで設定します

「0 – 手動」は、初期位置を設定しません。この場合LeftプロパティとTopプロパティの設定値により表示位置が調整されますそれらのプロパティを使用して任意の位置に表示するにはこの設定にしておく必要があります

「1 – オーナーフォームの中央」は、Excelのウィンドウの中央に表示されます。Excelの位置によって表示位置が変更されます。マルチディスプレイで表示する場合に便利な設定です

「2 – 画面の中央」は、そのままの意味で画面の中央に表示します

「3 – Windows の既定値」は、画面の左上にぴったりくっついた場所に表示されます。Leftプロパティなどで位置調整を行っていない「手動」と同じ場所に表示されますので、あまり使いません
また、Leftプロパティなどを設定していても左上の表示位置は変わりません

フォームの初期表示位置設定の位置図
フォームの初期表示位置図

この画像が、各設定で表示される場所です
ピンクがフォームで、設定により場所が変わります

緑色がExcelのウィンドウです、UserFormのオーナーは基本Excelなのでその中央になるので、Excelの位置によって変化します

グレーが画面全体のウィンドウです、その中央に画面の中央に設定したフォームです
初期設定では、この位置設定になっています

コードで変更する場合

Private Sub UserForm_Initialize()

'フォームの初期表示を手動で表示する
Me.StartUpPosition = 0

End Sub

上記のコードを使用すれば、表示位置を変更して表示させられます
ですが上記にもあるように、初期表示位置なのであまり動的に変更する場面が少ないと思いますのでここで解説します

このプロパティは、初期表示位置なので表示されてから設定を変更しても意味はありませんので、表示前に設定を変更する必要があります

なので、メモリ上に読み込まれて表示前に発生するInitializeイベントに記載します

またリファレンスにもあるような、メモリ上に読み込みをしてプロパティを変更してから表示させる方法もあります

ただ、このコードの注意点として手動とWindowsの既定値の設定が切り替わりません
自分の環境のExcel2010でしか試していませんが、デザイン画面で手動にして、コードでWindowsの既定値の設定にしても手動のままです
逆も同様でした、それ以外の設定には切り替わります

まあ、使用場面の少なさからあまり影響は無いと思いますが・・・

画面の表示位置について

画面の表示位置とは、案外重要な要素です

入力補助などのフォームで、フォームの起動を繰り返すような場合に毎回表示位置をタイトルバーをドラッグして移動させるのは面倒です
特にマルチディスプレイを使用している場合に、毎回メインからサブへ移動させるというような動きは非常に面倒です

なので、最終的に完成したときにはこのプロパティから表示位置を調整するようにします

なお、表示位置を保存して前回と同じ場所に表示する処理は以下の記事で解説しています

レジストリのデータを削除

レジストリに保存したデータを削除するコード(DeleteSettingステートメント)

'レジストリの値の削除
DeleteSetting "ファイル名", "モジュール名", "データ名"
'レジストリのsection以下削除
DeleteSetting "ファイル名", "モジュール名"
'レジストリのappname以下削除
DeleteSetting "ファイル名"

レジストリに保存したデータを削除するには「DeleteSetting」ステートメントを使用します

ステートメントの書式

引数(太字は必須引数)
DeleteSetting appname, section, key

引数の指定によって削除される階層が変化します
引数と階層は以下の記事で解説しています

VBAのリファレンスでは、2つ目の引数「section」は必須となっていますが、実際には省略しても実行されます

削除する範囲と必要性

指定した箇所以下が全て削除されます

DeleteSetting "ファイル名", "モジュール名"

つまり、この様にsectionまでの指定を行うとsectionが削除されます
なので値のキーは全て削除されます

DeleteSetting "ファイル名"

appnameのみ指定した場合は、全てのデータを削除します

以下の全データとは言っても、VBAで作成したデータなので、知らないデータや別アプリのデータが削除されるわけではありませんので安心してください

また、このキーより上位のアイテムは本当に必要が無ければ削除したほうが良いと思います

これはエクスプローラーのフォルダ階層と同じ構造です
つまり、フォルダ内にあるファイルを全て削除しても空フォルダがそこに残ったままの状態になっています
空なので、影響はありませんが無駄ファイルであることは間違いありません
キーの値が全て必要ないのであれば、appnameのみで指定して全て削除してしまっていいと思います