2021/08/03

値入力後のセル移動方向を切替える




1.背景

Excelワークシートのセルに値を入力した後、次のセルへ移動する方法としては、いくつか考えられます。
タブレット等でソフトキーボードを使っている場合は、ソフトキーボード上のキーを使わず、タッチペンや指でセルを直接指定する方が便利かもしれません。一方でハード的なキーボードで値を入力している場合は、マウスを使い慣れている方であれば、マウスクリックで次に入力するセルを指定することもある思います。
しかし「キーボードからマウスに持ち替える」のは結構煩わしく、「EnterキーやTabキー」「矢印キー」でセルを移動させる人の方が多いのではないかと思います。

Excelは「表計算ソフト」ですので、値を入力し易いような工夫が色々なところに盛り込まれています。
まず(セルへ値入力後の操作としての)EnterキーとTabキーの基本的な動きを図1-1で示します。
(矢印キーは、その方向にセルが動くだけですので、説明は割愛します。)
EnterキーとTabキーの動きの違い
図1-1

図1-1のように、Enterキーを押すと入力セル(選択セル)は「下に移動」し、Tabキーでは「右に移動」します。
(Shiftキーを押しながらだと、Enterキーでは「上に移動」し、Tabキーでは「左に移動」します。)
なお、Enterキーで「動く方向」及び「移動するか否か」は設定が可能です。図1-2のように「ファイル」→「オプション」→「詳細設定」→「編集オプション」の部分で変更が可能です。(初期は、移動有り+下に移動)
Enterキーでの動きの設定
図1-2

図1-1で「Enterキーは、下に動く」と説明しましたが、セル範囲を指定した場合の動きは少し変わります。
図1-3の左側のように「値を入力したい範囲を単一行選択」したのち入力開始すると、その選択範囲内では「Enterキーで右に移動」します。
なお図1-3の右側のように「複数行を選択」すると、選択範囲内を「まず下のセルへ移動」し、選択範囲の最下行に達したら「右列の、選択範囲の一番上のセルへ移動」しますので、選択する行数には注意が必要です。
範囲指定した際のEnterキーでの動き
図1-3

また、TabキーとEnterキーを組み合わせ、図1-4のように使う方法もあります。
つまり、右側への移動には「Tabキー」を使用し、最右列で「Enterキー」を使用することで「入力開始列の1つ下の行」に移動することができます。
TabとEnterキーの組み合わせ
図1-4

この動き方は、テキストエディタやワープロソフトの上での「TabとEnterの役割」と似ています。図1-5はMicrosoftのワープロソフトWordのもので、通常は表示されませんが「Tab(→印)」「Enter(印)」と図1-4のTabキーとEnterキーで移動する方向を比較すると、共通していることが分かると思います。
エディタ・ワープロ上でのTabとEnterの役割
図1-5

この図1-4の入力方法は、データ入力時には非常に便利なのですが、唯一欠点があります。それは、通常のキーボードでは「Tabキーは一番左端に1つ」しか無く、数値入力時に良く使われる「テンキー部」から「遠く離れている」ために、操作しづらいのです。(図1-6)
キーボード上のTabキーとEnterキーの位置
図1-6

そこで今回は、「Enterキーでの移動方向の切替」と「Tabキー機能を他のキーに置き換え」をダイアログ上で行えるものを紹介します。

ちなみにソフトキーボードの表示には、「Ctrl+Windowsキー+O(オー)」のショートカットが使用できます。
また、図1-3の右側図での「選択範囲内でのEnterキーでの移動順序」は「選択範囲(Selection)のItemインデックス順序」と似ていますが、図1-7のように「行列逆転」していますので、注意が必要です。
選択範囲内のインデックスの順序
図1-7

2.システム概要

本システムはExcelアドインに登録することをイメージして作っています。図2-1のようにアドインしたボタンをクリック①することで、操作ダイアログ②が表示されます。
なお一番下のサンプルファイルには、シート上にある起動用ボタンをクリックすることで、すぐ試用できます。
システム起動
図2-1

操作ダイアログ②内のユーザーが操作できる機能は、図2-2のように以下の2種です。
 ③Enterキーで移動する方向の選択(上下左右の4方向のどれか1方向)
 ④Tabキーの動作を「右矢印」「PageUp(PGUP)」「PageDown(PGDN)」のどれか1つで代用(非選択も可)
ダイアログの各機能
図2-2

ダイアログの一番右端には、システム起動前のExcelの「Enterキーの移動方向の既設定値⑤」を表示しています。
また「終了ボタン⑥」をクリックすることで、システム終了します。ダイアログ右上×印でも、同様に終了します。
システム終了すると、ユーザーがダイアログ上で設定したEnterキー移動方向、Tabキーの代替キーの設定は解除されます。

なおExcelの設定として「Enterキーを押しても移動しない(レ点が付いていない)」設定になっている時には、どの方向を選択してもEnterキーでの移動はしません。またExcelの設定としてのEnterキー移動方向を変更するには、図1-2のExcelオプション設定での変更が必要です。

なお、Tabキーを押せば図1-1の下側のように「右側へ移動」しますが、Shiftキーを押しながらTabキーを押すと「左側へ移動」します。この動作も代替キーに持たせていますので、例えば「右矢印」を選択した状態で「Shiftを押しながら右矢印キーを押す」と「左側へ移動」させるようにしています。この動作は「Shift+Tab」と同様、図1-4のようにEnterキーとの組合せることにより、ある列範囲内でデータを連続入力させることも可能です。

3.プログラムの流れ

1つ目の機能の「Enterキーで移動する方向の選択」は、選択したオプションボタンに従って「マクロ側から図1-2の変更を実行」しているだけです。ですので設定は「上・下・左・右」の4種内の1つを選択することになります。

2つ目の機能の「Tabキーの代用キー」のプログラムは、図3-1のような流れになっています。
Tabキー代替のプログラムの流れ
図3-1

今回のシステムでは、指定したキー(右矢印、PageUp、PageDown のどれか)にOnKeyメソッドを使って「Tabキーを実行するマクロ」を割り当てしています。これにより、指定したキーを押すと「Tabキー」を押したことにしています。
なお今回システムでは訳があって「SendKeysメソッド」は使用していません。その理由は「よりみち」で説明します。

設定段階では、ダイアログのチェックボックスをON(レ点を付ける)にすると、まずOnKeyメソッドの初期化をし「指定キーの機能を元の状態に戻す」処理をします。指定キーのどれかにレ点があり、それを再クリックして消した場合には、この元の状態に戻したところで終了します。

レ点が付いた状態(どこかのCheckBoxに初めてレ点を付けた 又は、どこかのCheckBoxにレ点が付いている状態から別のCheckBoxにレ点を付けた)の場合には、新たにレ点を付けたCheckBox以外のレ点を外した後(レ点がついたCheckBoxは、最大でも1つにするため)、OnKeyメソッドで「新たにレ点を付けた指定キー」に対して「Tabキーを実行するマクロ」を割り当てします。

今回システムで選択できるTabの代替キーは、「右矢印、PageUp、PageDown」の内の1つとしました。3種以外にも設定可能なキーはもちろんありますが、「どのキーでもOK」という訳にはいきません。
例えば、テンキーの「+」キーは「電卓の機能」から考えれば理想的です。しかし、セルを編集中に+キーを押しても「文字列としての『+』」が、編集文字列の1つになるだけで、OnKeyメソッドで「Tabキーを実行するマクロ」が起動されることはありません。(セル編集中で無ければ、OnKeyでマクロが呼び出されます)

このように、キーには「セル編集の文字列の1つになるキー」が存在し、図3-2のキーボード上で表すと①のオレンジ色のキーになります。
また、編集文字列にはならないが編集のツールの役目となる②の青いキーもありますが、これもセル編集中にはOnKeyでマクロを呼び出せません。
今回システムで「Tabキーの代わりに使える」のは「編集を終わらせられるキー」で、図3-2の③の緑色キーのみです。
キーボード上でTabの代わりに使用できそうなキー
図3-2

この緑色のキー③の中から、3つのキーを「Tabキーの代替キー」として使っています。Tabキーと感覚的に合うのは「右矢印」だけですが、(私としてはあまり使っていない)「PageUp」「PageDown」も加えてシステムを作りました。

なお、Windows APIなどを使えば、編集中のKeyPressイベントを取得できるみたいですが、結構大変そうです。
また図3-2の残りの白いキーは、何の種類のキーと言えば良いのか分かりませんが、とりあえず「編集を終了させることは出来ない」ので、対象から外しています。

4.標準モジュール(Module1)

4-1.宣言部

標準モジュールの宣言部では、以下の宣言をしています。
 ・システム内でWindows APIを使用するための外部プロシージャへの参照宣言(Declareステートメント)
 ・Windows APIのkeybd_eventで引数として使用する定数の宣言
 ・システム起動前にユーザーが設定していた「Enterキーで移動する方向」を一時的に保存しておく変数宣言
  1. '========== ⇩(1) 宣言部 ============
  2. #If Win64 Then
  3.  Public Declare PtrSafe Function SetFocus Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
  4.  Public Declare PtrSafe Sub keybd_event Lib "user32" _
  5.   (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As LongPtr, ByVal dwExtraInfo As LongPtr)
  6. #Else
  7.  Public Declare Function SetFocus Lib "user32" (ByVal hwnd As Long) As Long
  8.  Public Declare Sub keybd_event Lib "user32" _
  9.   (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
  10. #End If
  11. Public Const VK_TAB As Byte = 9
  12. Const KEYEVENTF_EXTENDEDKEY As Long = 1
  13. Const KEYEVENTF_KEYUP As Long = 2
  14. Public Const keKEYDW As Long = 0
  15. Public Const keKEYUP As Long = KEYEVENTF_KEYUP
  16. Public UserSetting As XlDirection
図4-1

4-1-1.Windows API参照宣言(Excelバージョンでの分岐)

2~10行目では、Windows APIの「SetFocus」「keybd_event」への参照宣言をしています。
但し、Excelのバージョン(64ビット版Excel OR 32ビット版Excel)により参照宣言の内容が異なります。つまり64ビット版と32ビット版ではコードを変更しなければならなくなり、管理も大変になります。
そこで今回は、条件付きコンパイル「#If Win64 Then(2行目)」を使い、3~5行目では「64ビット版Excel用」、7~9行目では「32ビット版Excel用」のDeclare宣言をしています。
このような分岐を「#If...Then...#Else ディレクティブ」と呼ぶようです。

なお、使っているExcelのバージョンは、図4-2のように「ファイル」→「アカウント」→「Excelのバージョン情報」をクリックすることで確認できます。
Excelのバージョン情報を確認
図4-2

宣言方法の違いを、今回参照している「SetFocus」について比較してみます。
 32ビット版 Public Declare Function SetFocus Lib "user32" (ByVal hwnd As Long) As Long
 64ビット版 Public Declare PtrSafe Function SetFocus Lib "user32" (ByVal hwnd As LongPtr) As LongPtr

このように、64ビット版には「Function/Sub の前に PtrSafe を追加」「Long型はLongPtr型に変更」の2箇所を変更します。尚LongPtr型は、64ビット版ではLongLong型に自動的に変更され、32ビット版ではLong型に変更されるそうです。
(私のPCの環境が32ビット版のみなので未確認です。間違っていたらごめんなさい。)

4-1-2.Windows API参照宣言(SetFocus と keybd_event)

今回Declareで参照宣言している1つ目の「SetFocus」は「指定されたウィンドウにキーボードフォーカスを設定」するもので、今回システムでは「操作ダイアログで設定を切り替えた後、ワークシート側を一度クリックしなくても、すぐに入力することを可能」にするために、使っています。

2つ目の「keybd_event」は「キーストロークを合成」するもので、今回システムでは「Tabキー」操作をマクロ側から実行することに使用しています。keybd_eventの引数としては、図4-3の4つを渡します。
引数内容
bVkByte仮想キーコード(1~254の値)
bScanByteハードウェアスキャンコード。今回はゼロを指定
dwFlagsLongキーの動作(図4-6のフラグの組合せで指定)
dwExtraInfoLongキーストロークの追加情報(通常はゼロを指定)
図4-3

4-1-3.keybd_event引数(仮想キーコード)の定数宣言

keybd_eventの第一引数は「仮想キーコード」ですが、これについては様々なサイトで紹介されていますので、そちらも参照して下さい。図4-4では今回使用しているキーを中心に、仮想キーコードの一部のみを紹介します。
仮想キーコードの一部
定数名値(10進数)キー
VK_TAB9Tab
VK_RETURN13Enter
VK_SHIFT16Shift
VK_CONTROL17Ctrl
VK_PRIOR33PageUp
 
定数名値(10進数)キー
VK_NEXT34PageDown
VK_LEFT37
VK_UP38
VK_RIGHT39
VK_DOWN40
図4-4

今回は、Tabキーをマクロ側から実行させますので、12行目の「Public Const VK_TAB As Byte = 9」で定数「VK_TAB」に値9を代入しています。尚、図4-4の「定数名」は、Excelで定義されている定数ではありませんので、12行目の宣言が必要となります。

4-1-4.keybd_event引数(ハードウェアスキャンコード)

keybd_eventの第二引数は「ハードウェアスキャンコード」を指定することになっていますが、今回は「使用せず」ということで「ゼロ」を指定します。

しかし、キーボード種類によってはゼロ以外を指定しないと動作しない場合があるようです。図4-5に「TABキー」に於けるハードウェアスキャンコードを示しますので、もし「keybd_eventの第二引数がゼロでは動かない」場合、図4-5の値を指定してみて下さい。
なお図4-5の中の「メイクコード」はキーを押す場合、「ブレイクコード」はキーを離すに対応します。
スキャンコードセット対応キーボードメイクコードブレイクコード
Set 1XTキーボード、86キーボード0x0f0x8f
Set 2IBM PC/AT拡張キーボード,101キーボード,Enchandedキーボード0x0D0xF0,0x0D
Set 3PS/2キーボード0x0D0xF0,0x0D
図4-5

ここで紹介したスキャンコードは「主要キーレイアウトのキーとその Scancode まとめ」等を参考にしました。これ以外のサイトでも色々取り上げられています。
但し「0xF0,0x0D」のような値を、keybd_eventの引数としてそのまま設定して良いのかは、私の環境では試すことが出来ないので分かりません。ご了解下さい

4-1-5.keybd_event引数(キーの動作)の定数宣言

keybd_eventの第三引数は「キーを押す・離す」をフラグ値として指定します。指定する値は、図4-6のフラグ値(0,1,2)を組み合わせたものになります。組み合せとは、ビット単位のフラグの組み合わせですので、各フラグ値を「OR」でつないだものになります。数値の結果としては「足し算」と同じ事になります。
定数名フラグ値内容
(KEYEVENTF_KEYUPを指定せず)0キーを押す
KEYEVENTF_EXTENDEDKEY1スキャンコードにプリフィックスバイト0xE0(224)を追加
KEYEVENTF_KEYUP2キーを離す
図4-6

「フラグ値の組み合せ」は、図4-7の4種になります。合計値の0~1が「キーを押す」、2~3が「キーを離す」になります。
フラグ値キーを押すキーを離す
0
1
2
組合せ(合計)0123
図4-7

この説明だと何か曖昧で、どの値を「押す・離す」にすれば良いのかを迷ってしまいます。
 例1:押す=0・離す=2
 例2:押す=1・離す=3
他のサイトを見ても「例1方式」「例2方式」が混在します。
この違いは何かと考えてみると、フラグ値=1「スキャンコードにプリフィックスバイト0xE0(224)を追加」をキーの動作に含めるか否かの違いだと分かります。

では、この「スキャンコードにプリフィックスバイト0xE0(224)を追加」とは何でしょうか。
(これより先は、私が色々なサイトで調べExcelマクロを実行させて確認を取り、一応私が納得した範囲の内容です。)

スキャンコードとは「キーボードのキーを押したり離したりした時に、キーボードから送信されるコード」で、キーを押したときに発生するメークコードとキーを離したときに発生するブレークコードからなります。
キーボードの中には、図4-8のように左右に同じキーがある場合があります。
キーボードには同じキーが存在
図4-8

左右どちらのキーを操作したかを判別できるように、片方には「0xE0」を付けて区別をしています。(Shiftキーも左右2箇所あるのですが、どちらかに0xE0を付けるという説明はありませんでした。但しExcelでマクロを組んで確認してみると、0xE0有無で左右が判別できそうです)
この機能を使って、左右のキー(例えば、AltやCtrl)で別の機能が使えるアプリケーションも存在するようです。

では、左右どちらのキーに「0xE0」を付けているのでしょうか。スキャンコード一覧をみてみると、2つ存在するキーのみで無く1つのみのキーにも0xE0は付くようです。
色分けしてみると、図4-9のように「スキャンコードに0xE0が付いているキー」は、キーボードの右側に集中しています。どうも、これを「拡張キー」と呼んでいるようです。
0xE0が付いているキー(拡張キー)
図4-9

この図4-9と図4-8を重ねてみると、「同じキーの場合は右側のキーに0xE0を付ける」決まりのようで、キーコード表で確かめてみても確かに右キーのようです。

私の確かめ方としては、以下のような方法で試しました。
例えばキーコード表で、Ctrlキー(キーコード17)には、別のコードが割り当てられた「左Ctrlキー(キーコード162)」「右Ctrlキー(キーコード163)」が存在します。
キーコード17に対してkeybd_eventで押す・離すの動作をさせた時に、どのキーが動いているかを「Windows APIのGetKeyboardState」で取得し、表示させてみました。コードは図4-10のようなもので、Declare参照宣言は省略しています。
  • Sub Test1()
  •  Dim Keys(0 to 255) As Byte
  •  
  •  keybd_event 17, 0, 0, 0
  •  keybd_event 17, 0, 2, 0
  •  
  •  GetKeyboardState Keys(0)
  •  Debug.Print Keys(17) & "/" & Keys(162) & "/" & Keys(163)
  •  
  • End Sub
図4-10

このTest1プロシージャを実行することで、「Ctrlキーを押して離す」動作の代理をするというものです。
GetKeyboardStateで取得した仮想キーの状態は配列Keysに代入され、その各値の最下位ビットは「キーを押すたびにトグルで値が変わる」特性を持っています。表面的には「キーが押されている」のは「キーの値が0と1を繰り返す」ことで判断できます。
図4-10では、keybd_eventの第三引数には「フラグ値1(0xE0を追加)」を入れていない押し方(押す=0、離す=2)ですので、実行のたびに「Keys(17)」及び「Keys(162)」が押されるのが確認できます。

逆に、図4-11のようにkeybd_eventの第三引数に「フラグ値1(0xE0を追加)」を入れた押し方(押す=1、離す=3)をすると、実行のたびに「Keys(17)」及び「Keys(163)」が押されます。
  • Sub Test2()
  •  Dim Keys(0 to 255) As Byte
  •  
  •  keybd_event 17, 0, 1, 0
  •  keybd_event 17, 0, 3, 0
  •  
  •  GetKeyboardState Keys(0)
  •  Debug.Print Keys(17) & "/" & Keys(162) & "/" & Keys(163)
  •  
  • End Sub
図4-11

つまり
 ・「フラグ値1(0xE0を追加)」を含める(押す=1、離す=3)  →「キーが2つある場合は、右側のキーを操作」
 ・「フラグ値1(0xE0を追加)」を含めない(押す=0、離す=2) →「キーが2つある場合は、左側のキーを操作」
したことになるようです。
(なおEnterキーは、左右でキーコードが同じようなので確認できませんでした。)

ちなみに、第一引数に「左側のキー」のキーコードを設定しても同じ結果が得られます。一方、第一引数に「右側のキー」のキーコードを設定した場合には「フラグ値1(0xE0を追加)」を入れないと「左側のキー」が動いたことになるようです。
なお、1つしか無いキー(例えばTabキー)は、「フラグ値1(0xE0を追加)」を入れても入れなくても、動作に違いは見当たりません。また1つしか無いキーで、もともと0xE0が付いているキー(例えばDeleteキー)も「フラグ値1(0xE0を追加)」を入れても入れなくても動作は同じです。

結論としては、2つあるキーでは「フラグ値1(0xE0を追加)」を付けないと左キーが動き、「フラグ値1(0xE0を追加)」を付けると右キーが動く。1つしかないキーはどちらでも良い ことが分かりました。

但し、キーを押す・キーを離すのセットの中で「一方には0xE0を付ける・一方には0xE0を付けない」とすると、キーを離したあとでも「キーは押されっぱなし(押したのとは別のキーを離した)」となります。この影響はExcelだけではなく、Windowsの他のアプリにも影響し、思いも寄らない不具合が発生しますので注意が必要です。必ず「0xE0は押す・離すの両方に付ける」「0xE0は押す・離すの両方に付けない」のどちらかにすべきです。

標準モジュールのコードに話を戻し、図4-1の14行目「Const KEYEVENTF_EXTENDEDKEY As Long = 1」、15行目「Const KEYEVENTF_KEYUP As Long = 2」では、図4-6の定数を一旦準備します。
その定数を使い、今回システム用の定数を設定するのですが、今回は「1つしか無いTabキーを押すシステム」ですので「フラグ値1(0xE0を追加)」は付けず、17行目「Public Const keKEYDW As Long = 0」で「キーを押す(keKEYDW)」値の設定、18行目「Public Const keKEYUP As Long = KEYEVENTF_KEYUP」で「キーを離す(keKEYUP)」値の設定をします。

なお、keybd_eventの第4引数に付いては全く情報が無く、他サイトと同様にゼロ値としました。

寄り道
今回システムは、例えば「右矢印キー」を押すと「Tabキー」が働く というシステムですので、OnKeyで「右矢印キーを押したら、Aのマクロが動く」ように割り当てし、Aのマクロには「Sendkeys "{TAB}"」と1行書けばよさそうです。
確かに右方向に動いていくTABの特性を再現するには、それで充分でした。

一方「Shiftキーを押しながら」TABキーを押すと、御存知のように左方向へ動いていきます。これをシステムに盛り込むには、OnKeyで「Shift+右矢印キーを押したら、Bのマクロが動く」ように割り当てし、Bのマクロは「Sendkeys "+{TAB}"」で良さそうです。
これも、Shiftキーを毎回押し直してくれれば(毎回キーを離せば)、正しく動きました。

意図に反した動きをしたのは「Shiftキーを押しっぱなし」にしながら「右矢印キー」を何回も押すような動作でした。
1回目は正常に左方向へ動くのですが、2回目以降は右方向へ動いていくのです。
調べてみると、Shiftキーを押したまま「Sendkeys "+{TAB}"」を実行すると、「Shiftキーが押されていない状態になる」ようなのです。どうも「-1× -1=1」のようにShiftが打ち消されてしまう感じです。

逆に(なんの役にも立たないマクロですが)Shift無しの時に「Sendkeys "+{TAB}"」を実行し、Shift有りの時に「Sendkeys "{TAB}"」を実行するようにしてみると、Shiftキーを押しっぱなしでも「一方向へ動き続ける」のです。

それなら「Sendkeys "+{TAB}"」を実行した後に、Shiftキーを押したことにしてみよう、と考え「setKeyboardState」でShift状態をセットしてみましたが全くダメでした。このことから、ハードウェア上でShift状態がキャンセルされてる様子なので、SendKeysを使うのをあきらめ「keybd_event」に乗り換えました。

「keybd_event」を使ってみると良い点が見つかりました。SendKeysでは「Shift状態と右矢印キーをセット」で考えなくてはいけなかったのですが、keybd_eventでは「Shift状態と右矢印キーは別扱い」で良さそうです。
つまり、OnKeyで「右矢印キーを押したら、Cのマクロが動く」「Shift+右矢印キーを押したら、Cのマクロが動く」としておき、マクロCでは「keybd_eventを使って、Tabキーを押して離す」だけで良いのです。Shiftキーはユーザーが押しているか否かの信号をそのまま使う形です。

他サイトでは「SendKeysを使うとNumLockキーがオフになる」というような記事が多くありますが、今回keybd_eventを使うことで「NumLockキーを自動でオンにする」ような余計なプロシージャも不要になり、スッキリしたコードになったのは幸いでした。

なお図4-10、図4-11で「getKeyboardState」を使っていますが、これは図4-12のように「全てのキーの状態」を取得する関数です。キーの設定は「setKeyboardState」で行いますが、ここで設定できるのは「メッセージキュー内」のデータだけです。
キーボード関係のクラス内容
getKeyboardStateキーボードの各キーの状態を取得
setKeyboardStateキーボードの各キーの状態を設定
getKeyStateキーボードの1つのキーの状態を取得
(メッセージキュー内に入っているキーの状態)
getAsyncKeyStateキーボードの1つのキーの状態を取得
(キーボード上のキーの状態)
図4-12

もっと上流のデータは「getAsyncKeyState」で取得できますが、それに対応する「setAsyncKeyState」のようなものが無いため、前述したExcel側からShiftを設定するようなことは出来ませんでした。

4-1-6.Enterキー移動方向(初期値)を保存するための変数宣言

図4-1の20行目「Public UserSetting As XlDirection」は、変数「UserSetting」を「XlDirection」型で宣言しています。
今回システムでは、Enterキーでセルが移動する方向を変更することを可能にしています。作業シート上では、ユーザーの都合に合わせてEnterキー移動方向を設定するのは構わないのですが、ダイアログを閉じた時(システム終了時)に設定されていたEnterキー移動方向が「Excelの設定として残る」ことになってしまいます。

PCを複数人で共用している場合もありますので、今回は「システム起動前のEnterキー移動方向を取得」しておいて、システム終了時に「システム起動前の状態に戻す」ことにし、その保存用として変数「UserSetting」を設けました。

また、その保存する値は、MoveAfterReturnDirectionプロパティで取得した値そのもの(XlDirection型)にしています。なお、XlDirection型は図4-13の4方向の値になります。
XlDirection列挙型
定数内容
xlDown-4121下方向
xlToLeft-4159左方向
xlToRight-4161右方向
xlUp-4162上方向
図4-13

なお今回は使用しませんが、MoveAfterReturnプロパティの値をFalseに設定すると、図1-2で「レ点を消した」ことになります。この場合は、MoveAfterReturnDirectionに何が設定されていても、Enterキー後でも移動しなくなります。

よって、Excelの設定としてEnterキー後に動かないという設定にしている場合は、今回システムを起動しオプションボタンのどれを選んでも「Enterキーでは動かない」ことになってしまいます。
システムとして「MoveAfterReturnプロパティの値」も保存しておき、システム起動時にはEnterキーで動く(=True)ようにする方法も考えましたが、Excelで「Enterキー後にセルを移動しない」使い方がどうしても想像できないことと、Excelの基本設定の変更は必要最小限に留めたい(万一エラーでExcel設定が変わってしまうと、その後Excelを使う全ての人に迷惑がかかってしまうから)との思いから、無視することにしました。
もし移動しない設定をしている方が今回システムを使う場合には、保存用変数を追加し、起動時に「=True」にし、終了時に「元の設定に戻す」ようにして下さい。

4-2.システム起動マクロ

システム起動には、図4-14の「MoveDirection」プロシージャを呼び出します。Excelアドインに登録する際は、このマクロを登録して下さい。
  1. '========== ⇩(2) システム起動 ============
  2. Public Sub MoveDirection()
  3.  UserSetting = Application.MoveAfterReturnDirection
  4.  UserForm1.Show vbModeless
  5. End Sub
図4-14

24行目「UserSetting = Application.MoveAfterReturnDirection」では、現在のExcelの「Enterキーでの移動方向」を取得し、変数UserSettingに代入します。この値は、ダイアログ上に「移動方向のExcel設定値」として方向を表示し、システム終了時(ダイアログを閉じる時:図5-15)にExcel側に戻されます。

26行目「UserForm1.Show vbModeless」は、操作ダイアログであるUserForm1を起動します。起動方法はモードレス(ダイアログ起動中もシート作業が可)にするため、Showメソッドに引数「vbModeless」を指定します。なお定数vbModelessの代わりに、値「ゼロ」を指定しても同じです。
定数内容
vbModal1モーダル(既定)
vbModeless0モードレス
図4-15

4-3.OnKeyメソッドの登録マクロ(Tabの実行)

指定されたキー(今回は、右矢印キー・PageUpキー・PageDownキー のいずれか)が押された時に、OnKeyメソッドに従って呼び出されるマクロが図4-16です。
  1. '========== ⇩(3) OnKeyメソッドの登録マクロ(Tabの実行) ============
  2. Public Sub subTab()
  3.  keybd_event VK_TAB, 0, keKEYDW, 0
  4.  keybd_event VK_TAB, 0, keKEYUP, 0
  5. End Sub
図4-16

32行目「keybd_event VK_TAB, 0, keKEYDW, 0」でTabキーを押し、33行目「keybd_event VK_TAB, 0, keKEYUP, 0」でTabキーを離しています。
keybd_eventには引数を4つ設定(図4-3)する必要がありますが、ここの段階では設定する値を定数にしてあるため、定数を引数として設定します。
 ・第一引数:VK_TAB ・・・Tabキー
 ・第二引数:ゼロ  ・・・ハードウェアスキャンコード無し
 ・第三引数:keKEYDW ・・・キーを押す / keKEYUP ・・・キーを離す
 ・第四引数:ゼロ ・・・追加情報なし

なお「よりみち」にも記述しましたが、この32~33行目のコードは、内容的には「Application.SendKeys "{Tab}"」と同じですが、Shift+Tabの動作も正常に作動させるためにkeybd_eventを使用しています。

5.操作ダイアログ(UserForm1)

5ー1.フォーム上のコントロールの配置

フォーム上のコントロールの配置は、図5-1のようにしました。
フォーム上のコントロール配置
図5-1

まず「Enterキーで移動する方向」については「必ず、どれかを選ばなければいけない」と考えたのでオプションボタンを使用することにしました。時計の12時の位置にOptionButton1を「上方向」として配置し、そこから時計回りでOptionButton2「右方向」、OptionButton3「下方向」、OptionButton4「左方向」と配置しました。
オプションボタンの文字列は、そのままで問題ありません。マクロ側からオプションボタンの全体幅を調整することで、文字列を見えなくさせています。
また、オプションボタン中央に配置した4方向矢印のついた「Enter移動」という文字列は、Labelに改行を含ませながら4段で作っています。

「Tabキーの代替キー」については、「指定キーを選んだり、外したりする」ためチェックボックスを使用することにしました。但し「指定キーとして選択できるのは1つのみ」とする機能は、マクロで「チェックボックスのレ点は1つ以下にする」ようにしています。

右端のLabel1は、システム起動前のEnterキー移動方向を表示します。この表示は無くても問題はありません。
右下のCommandButton1は、システムを終了させるためのものです。この機能はダイアログ右上×印で代用してもOKです。

その他の表示用Label、チェックボックスの文字列は、コントロール配置時にCaptionプロパティに書き込んでいます。

5ー2.フォームモジュール

5ー2-1.フォーム起動時の設定

図4-14の26行目からUserForm1が呼び出され、フォーム起動前に最初に実行されるのが図5-2のInitializeイベントです。
  1. '========== ⇩(4) フォームモジュール内変数の宣言 ============
  2. Dim keyArray(1 To 3) As String
  3. '========== ⇩(5) フォーム起動時の設定 ============
  4. Private Sub UserForm_Initialize()
  5.  Dim i As Long    '←カウンタ変数(OptionButton、CheckBoxの数)
  6.  For i = 1 To 4
  7.   Me.Controls("OptionButton" & i).Width = 11.25
  8.  Next i
  9.  Select Case UserSetting
  10.   Case xlTop
  11.    Me.Label1.Caption = "↑"
  12.    Me.OptionButton1.Value = True
  13.   Case xlDown
  14.    Me.Label1.Caption = "↓"
  15.    Me.OptionButton3.Value = True
  16.   Case xlToRight
  17.    Me.Label1.Caption = "→"
  18.    Me.OptionButton2.Value = True
  19.   Case xlToLeft
  20.    Me.Label1.Caption = "←"
  21.    Me.OptionButton4.Value = True
  22.  End Select
  23.  keyArray(1) = "{Right}"
  24.  keyArray(2) = "{PGUP}"
  25.  keyArray(3) = "{PGDN}"
  26.  For i = 1 To UBound(keyArray, 1)
  27.   Me.Controls("CheckBox" & i).Value = False
  28.  Next i
  29.  Me.CommandButton1.Caption = "終了"
  30.  Me.Caption = "Enterキー移動方向 Tabキー代替"
  31. End Sub
図5-2

まずフォームモジュールの宣言部(先頭部)で、フォーム内で使用する変数の宣言を行います。
38行目「Dim keyArray(1 To 3) As String」は、Tabキーの代替キーを「OnKeyメソッドに指定する文字列」の形で格納します。
Tabキーの代替キーは、今回3つのCheckBoxで切り替えますので、扱い易いようにCheckBoxの番号に対応した「1~3」のインデックスを持った配列としています。

41~73行目のInitializeイベントプロシージャ内では、フォーム起動前のシステム準備をします。

まず44~46行目では、OptionButton(Enterキー移動方向選択)の形を整えています。
44行目「For i = 1 To 4」で、4つのOptionButtonに対し、45行目「Me.Controls("OptionButton" & i).Width = 11.25」でOptionButtonの幅を揃えています。

「なぜ幅だけを合わせる」かですが、フォームのレイアウトをする場合、多くの方は目見当でコントロールの位置合わせをすると思います。今回のオプションボタン(例えばOptionButton2)で考えてみると、中央の文字列(4方向に矢印のついた『Enter移動』)の矢印の先端にオプションボタンの丸ボタンを合わせるのではないでしょうか。
私でしたら図5-3のように、一旦オプションボタンの高さ方向を広げておいてから、下辺をマウスで持って高さを徐々に縮め、丸ボタンの位置が文字列の矢印の先になるように狙います。
オプションボタンの位置合わせ
図5-3

また、グリッドの設定を変更して合わせる方もいるかもしれません。
(グリッドの変更は図5-4のように、VBE(VBエディタ)の「ツール」→「オプション」→「全般」→「グリッドの設定」で設定が出来ます。但し、この設定はブックでは無くExcel自体の設定になります。微調整のために設定を今だけ変更するのであれば、あとで元に戻した方が良いかもしれません。) フォーム上のグリッドの設定
図5-4

横方向の位置合わせは、縦方向よりは楽だと思いますが、方法はほぼ同じです。
いずれにしても、せっかくオプションボタンの位置を合わせたのに、実行時にズレてしまったのでは面白くありません。

レイアウト時に合わせたオプションボタンは、LeftとTop、HightとWidthの位置・サイズのプロパティ値を持っています。オプションボタンは、コントロールの「先頭部分に丸ボタン」が付いていますので、「Left・Top・Hight」の値さえ変えなければ「レイアウトの状態を保てる」ことになります。

つまり「変更できるのはWidth値のみ」であり、且つ「Widthを縮める」ことで、オプションボタンについている「文字列(Captionプロパティ値)も見えなくなる」ことになります。
そこで今回は、45行目「Me.Controls("OptionButton" & i).Width = 11.25」とすることで、「丸ボタンの位置は保持し、文字列を見えなくする」ようにしています。

なお「11.25」という値は「丸ボタンのサイズを変えずに、文字列も見えなくなる」値を狙っています。私のPCでの値ですので、環境が異なり「丸ボタンのサイズが変わってしまう」「文字列が見えてしまう」ような場合には調整してみて下さい。

48~61行目は、図4-14の24行目で取得した「Enterキー移動方向(Excelの設定値)」= 変数UserSettingの値 に応じて、Label1の表示、及びオプションボタンの初期のON選定を行っています。
48行目「Select Case UserSetting」で変数UserSettingの値を調べ、その値によって各Case文の中の2行のコードを実行します。例えば、既定ではEnterキーでの移動方向は下方向(xlDown)ですので、52行目「Case xlDown」がヒットしたとすると、53行目「Me.Label1.Caption = "↓"」でダイアログ右端のLabel1に「下矢印(↓)」を書き込み、54行目「Me.OptionButton3.Value = True」で下側に配置したOptionButton3をONにします。
変数UserSettingは4方向の内、どれか一つの方向の値を持っていますのでOptionButton1~4の内どれか1つがONになります(1つがONになった時に、残りのOptionButtonはOFFになる)。つまり、レイアウト時にOptionButton1~4のどれかがONの状態で保存されていたとしても、Excelの設定値の方向だけがONになるので、フォームレイアウト時に「全てのOptionButtonをOFFにしておかないと・・・」などの気を遣う必要は無いのです。

63~65行目は、38行目で変数宣言したKeyArray配列に値を代入しています。
今回システムでは、ダイアログ上のCheckBoxの文字列は、上からCheckBox1 =「→(右)」、CheckBox2 =「PageUp」、CheckBox3 =「PageDown」としていますので、そのCheckBoxの番号と配列のインデックスを合わせるように、
63行目「keyArray(1) = "{Right}"」、64行目「keyArray(2) = "{PGUP}"」、65行目「keyArray(3) = "{PGDN}" 」
としています。設定した文字列は、OnKeyメソッドで「各キーに指定のマクロを割り当てる」ための文字列です。様々なサイトでキーと指定文字列については解説されていますので、詳細はそちらを参照下さい。

67~69行目は、Tabキー代替用のCheckBox(今回3つ)のレ点をクリアしています。
67行目「For i = 1 To UBound(keyArray, 1)」でカウンタ変数iを配列keyArrayの要素数だけ回しています。「UBound(keyArray, 1)」の部分は「3」と書いてしまっても良かったのですが、Tabキー代替キーを変更した場合に少しでも変更コードを少なくするためUBoundで最終インデックスを取り出して使っています。
68行目「Me.Controls("CheckBox" & i).Value = False」では、各CheckBoxのValue値をFalseにしてレ点を消しています。

71行目「Me.CommandButton1.Caption = "終了"」では、終了ボタンのボタン表面文字列に「終了」を書き込んでいます。また72行目「Me.Caption = "Enterキー移動方向 Tabキー代替"」では、ダイアログのタイトルに「システム名」らしきものを書き込んでいます。

5ー2-2.Enterキー移動方向(オプションボタン)の操作

Enterキー移動方向指定用のオプションボタンを操作した時のイベントプロシージャが、図5-5です。
  1. '========== ⇩(6) オプションボタン(1)操作時 ============
  2. Private Sub OptionButton1_Click()
  3.  Call DirectionChange(xlUp)
  4. End Sub
  5. '========== ⇩(7) オプションボタン(2)操作時 ============
  6. Private Sub OptionButton2_Click()
  7.  Call DirectionChange(xlToRight)
  8. End Sub
  9. '========== ⇩(8) オプションボタン(3)操作時 ============
  10. Private Sub OptionButton3_Click()
  11.  Call DirectionChange(xlDown)
  12. End Sub
  13. '========== ⇩(9) オプションボタン(4)操作時 ============
  14. Private Sub OptionButton4_Click()
  15.  Call DirectionChange(xlToLeft)
  16. End Sub
図5-5

オプションボタンのClickイベントは、そのオプションボタンが「OFF(〇)からON(●)になった時に発生」します。
例えばOptionButton1(上方向)が新たに選択(OFF→ON)された時には、「OptionButton1_Click(76~78行目)」が呼び出されます。そして77行目「Call DirectionChange(xlUp)」で、図5-6のDirectionChangeプロシージャが呼び出されます。

DirectionChangeプロシージャには引数を1つ渡しますが、その引数は「XlDirection型」で図4-13の内の1つになります。
例えばOptionButton1(上方向)の場合には「xlUp(値では -4162)」を渡すことになります。
DirectionChangeプロシージャ内では、その引数を使ってExcelの設定変更をしています。

なお、今回はオプションボタンが4つと少なかったため、WithEventsを使った一括制御は行いませんでした。
もしコントロールが多くなった時には「セルへの日付入力をカレンダー日付クリックで選定」「西暦・和暦対照表」などを参照し一括制御をすると、コード管理が楽になります。

図5-5の、77・81・85・89行目から呼び出されるDirectionChangeプロシージャが図5-6です。
引数として、変更する「Enterキーで移動する方向(XlDirection型)」を受け取ります。
  1. '========== ⇩(10) Excelの設定を変更 ============
  2. Private Sub DirectionChange(d As XlDirection)
  3.  Application.MoveAfterReturnDirection = d
  4. End Sub
図5-6

引数として受け取った「Enterキーで移動する方向(d)」を使って、94行目「Application.MoveAfterReturnDirection = d」でExcelの「Enterキーで移動する方向」の設定を変更しています。

5ー2-3.Tabキー代替キー選択(チェックボックス)の操作

Tabキー代替キー選択用のチェックボックスを操作した時のイベントプロシージャが、図5-7です。
  1. '========== ⇩(11) チェックボックス(1)操作時 ============
  2. Private Sub CheckBox1_Change()
  3.  Call TabkeyReset
  4.  If CheckBox1.Value = True Then
  5.   Call CheckBoxClear(1)
  6.   Call TabkeyChange(1)
  7.  End If
  8. End Sub
  9. '========== ⇩(12) チェックボックス(2)操作時 ============
  10. Private Sub CheckBox2_Change()
  11.  Call TabkeyReset
  12.  If CheckBox2.Value = True Then
  13.   Call CheckBoxClear(2)
  14.   Call TabkeyChange(2)
  15.  End If
  16. End Sub
  17. '========== ⇩(13) チェックボックス(3)操作時 ============
  18. Private Sub CheckBox3_Change()
  19.  Call TabkeyReset
  20.  If CheckBox3.Value = True Then
  21.   Call CheckBoxClear(3)
  22.   Call TabkeyChange(3)
  23.  End If
  24. End Sub
図5-7

チェックボックスのChangeイベントは、チェックボックスがOFF(レ点なし)→ON(レ点あり)、またはON(レ点あり)→OFF(レ点なし)と、Value値が変更された時に発生します。
例えばCheckBox1(今回は右矢印キー)をOFF→ON(レ点あり)に変更、またはON→OFF(レ点なし)に変更した時に、98~104行目が実行されます。
実行されると、まず99行目「Call TabkeyReset」で、TabkeyResetプロシージャ(図5-8)を呼び出し、OnKeyメソッドによる指定キー(今回3つのキー)の割り当てをクリアします。もしOnKeyによる割り当てが無い場合でも実行されますが、特に害はありません。

次にOFF→ON(レ点あり)に変更した時だけ、100行目「If CheckBox1.Value = True Then」で仕訳け、101~102行目を実行します。
101行目「Call CheckBoxClear(1)」で、CheckBoxClearプロシージャ(図5-9)を呼出します。CheckBoxClearには引数として「チェックボックスの番号」を渡します。101行目は「CheckBox1」のコードですので、その「1」が引数になります。
機能としては全3つのチェックボックスの内、引数で指定したチェックボックス(=自分)以外のチェックボックスをOFF(レ点を外す)にします。

また102行目「Call TabkeyChange(1)」では、TabkeyChangeプロシージャ(図5-10)を呼出します。引数には同じく「チェックボックスの番号」を渡します。機能としては、引数で指定したチェックボックス番号( = 指定キーの文字列を入れた配列keyArrayのインデックス番号)の「キーの文字列をOnKeyに割り当て」をします。

なお今回チェックボックスは3つと少ないため、オプションボタンと同様にWithEventsを使っての一括制御は行いませんでした。ご了承下さい。

図5-7の99・107・115行目、および図5-15の164行目から呼び出される「TabkeyReset」が図5-8です。
  1. '========== ⇩(14) OnKey割り当てのリセット ============
  2. Private Sub TabkeyReset()
  3.  Dim i As Long    '←カウンタ変数(CheckBoxの数)
  4.  For i = 1 To UBound(keyArray, 1)
  5.   Application.OnKey keyArray(i)
  6.   Application.OnKey "+" & keyArray(i)
  7.  Next i
  8.  keybd_event VK_TAB, 0, keKEYUP, 0
  9. End Sub
図5-8

126~129行目では、配列keyArrayに格納した「Tab代替キー」の全てについて、OnKey割り当てをクリアしています。
126行目「For i = 1 To UBound(keyArray, 1)」では、カウンタ変数iを「配列keyArrayの要素数」だけ回しています。今回システムでは「3」となりますが、指定キーを増減する改造時にもコード変更無く対応できます。
127行目「Application.OnKey keyArray(i)」では、OnKeyメソッドの第二引数を省略していますので、指定キーの割り当てがクリアされます。
128行目「Application.OnKey "+" & keyArray(i)」では、「Shiftキーを押しながら」の割り当てキーもクリアしています。

Shift無し、Shift有りの両方についてクリアしているのは、図5-10で「Shift無し(147行目)」「Shift有り(148行目)」の両方についてOnKey割り当てをしているためです。

131行目「keybd_event VK_TAB, 0, keKEYUP, 0」は「Tabキーを離す」動作を入れています。これはチェックボックス操作でTabキー代替を設定した時と言うよりも、システム終了時(図5-15)に「万一Tabキーが押しっぱなしになっている時に、キーを離す」ことで正常の状態に戻しているつもりです。マクロが正常に動いていれば不要なコードですが、万一を考えてコードを加えました。
なお「Tabキーを押していない状態」で「Tabキーを離す」指示をしても、問題は無さそうです。

図5-7の101・109・117行目から呼び出される「CheckBoxClear」が、図5-9です。
引数として、チェックボックスの番号を受け取り、「その番号以外のチェックボックスをOFF(Value値=False)」にするものです。なお、システム作成過程で「引数を指定しなかったら、全てのチェックボックスをOFF」にする機能も必要そうだ と考えて、引数は任意(Optional)設定としましたが、結果的には使用しませんでした。
  1. '========== ⇩(15) チェックボックスをOFFにする ============
  2. Private Sub CheckBoxClear(Optional k As Integer = 0)
  3.  Dim i As Long    '←カウンタ変数(CheckBoxの数)
  4.  For i = 1 To UBound(keyArray, 1)
  5.   If Not i = k Then
  6.    Me.Controls("CheckBox" & i).Value = False
  7.   End If
  8.  Next i
  9. End Sub
図5-9

138行目「For i = 1 To UBound(keyArray, 1)」では、カウンタ変数iを「配列keyArrayの要素数」だけ回しています。
139行目「If Not i = k Then」では、配列keyArrayのインデックスと引数kが同一で無い(Not)時に140行目を実行させます。つまり、このプロシージャを呼び出したチェックボックスの番号以外のチェックボックスに対して、チェックボックスをOFFにする処理を行うことになります。
140行目「Me.Controls("CheckBox" & i).Value = False」では、チェックボックスをOFFにしています。

例えばCheckBox1がOFF→ONになった時には、図5-7の101行目「Call CheckBoxClear(1)」から図5-9が呼び出される訳ですが、図5-9の139行目「If Not i = k Then」で仕訳けられ、CheckBox2とCheckBox3がOFFになり、140行目が実行されないCheckBox1だけが「レ点が残る」ことになります。
一方、CheckBox1がON→OFFになった時にも、同様に図5-9の139行目「If Not i = k Then」で仕訳けられ、CheckBox2とCheckBox3がOFFになります。CheckBox1については140行目が実行されませんが「ユーザーの手動によりレ点が消去」されており、「全てのチェックボックスのレ点が消える」ことになります。

図5-7の102・110・118行目から呼び出される「TabkeyChange」プロシージャが図5-10です。
引数として、チェックボックスの番号(=指定キー文字列を格納したkeyArray配列のインデックス)を受け取り、「OnKeyで指定キーをマクロに割り当てる」ものです。
  1. '========== ⇩(16) 指定キーの割り当て ============
  2. Private Sub TabkeyChange(k As Integer)
  3.  Application.OnKey keyArray(k), "subTab"
  4.  Application.OnKey "+" & keyArray(k), "subTab"
  5. End Sub
図5-10

147行目「Application.OnKey keyArray(k), "subTab"」は、OnKeyメソッドの第一引数に、TabkeyChangeプロシージャの引数(k)とKeyArray配列データから得た「指定キーの文字列」を指定し、第二引数には「指定キーが押された時に実行するマクロ"subTab"」を設定します。
148行目「Application.OnKey "+" & keyArray(k), "subTab"」は、147行目とほぼ同等ですが、「Shiftキーを押しながら("+"を文字列先頭に追加)」指定キーを押した状態を設定します。
これにより、例えば「OptionButton1(右矢印キー)」にレ点が付いていれば、「右矢印キーを押した時」、および「Shiftキーを押しながら右矢印キーを押した時」には、「マクロsubTabを実行」することになります。

Shift無しもShift有りも同じ「マクロsubTabの実行」で良いのは、subTab内で「keybd_eventを使って、他のキー操作をする」という今回システムの特徴からとも言えます。通常はShift無しとShift有りでは別マクロ起動になることが一般的と思います。

なお、OnKeyで設定したマクロを実行させるためには、マクロは「標準モジュール」に置く必要があります。そのため図4-16は標準モジュールに置いています。

5ー2-4.ワークシートへのFocus移動

フォーカスをExcelシートに移動するには、SetFocus関数を使い、その引数にExcel(=Application)のハンドルを指定します。
今回システムでは、操作ダイアログ上で「マウスが動いた」時に、そのフォーカス移動を実行させています。「マウスが動いた」はフォームのMouseMoveイベント(図5-11)で得られます。
  1. '========== ⇩(17) ダイアログ→ExcelシートにFocus移動 ============
  2. Private Sub UserForm_MouseMove _
  3.    (ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
  4.  SetFocus Application.hwnd
  5. End Sub
図5-11

今回フォーカス移動の実行のタイミングをMouseMoveにしたのですが、その他のイベントも図5-12のように何種類かが考えられます。
フォーカス移動のイベントを取るコントロール
図5-12

オプションボタン操作時には、そのClick等のイベントが必ず発生しますし、またチェックボックス操作時にはChange等のイベントが必ず発生します。しかし、その他にもフォームのMouseMoveイベントも発生しています。
一方でダイアログの上部タイトル部をマウスで掴んで移動した時には、オプションボタンやチェックボックスのイベントは発生しませんが、フォームのMouseMoveイベントは「マウスがフォーム上を通過」すればイベントが発生してくれます。

図5-12でダイアログ移動時のMouseMoveイベントが「△」になっているのは、図5-13の左側のように「ダイアログのタイトル部からシートへマウスが移動する際、フォーム上を通過しない」場合があるからです。
また、オプションボタンやチェックボックス操作時も「〇~△」となっているのは、図5-13の右側のように「フォーム上のコントロール(Labelも含めて)を橋渡りしてフォーム外に抜け出す」ことが出来れば、フォームのMouseMoveイベントが発生しない可能性があります。
ダイアログ移動後のMouseMoveイベントの発生有無
図5-13

図5-13の左側は避けることは出来ませんが、右側は「コントロールとコントロールの間に、少しでもフォームの生地を見せる」ことで防ぐことが可能です。その意味では今回システムで作ったフォームは、コントロール間を詰めすぎたレイアウトになってしまいました。

5ー2-5.システムの終了

終了ボタンをクリックした時に呼び出されるのが、図5-14です。ボタンをクリックすることで、フォームを閉じています。
  1. '========== ⇩(18) 終了ボタンの操作 ============
  2. Private Sub CommandButton1_Click()
  3.  Unload Me
  4. End Sub
図5-14

「終了ボタン」でフォームを閉じた時、及びダイアログ右上×印をクリックすることで「フォームが閉じた直後」に発生するイベントが、図5-15のTerminateイベントです。
  1. '========== ⇩(19) フォーム終了時の処理 ============
  2. Private Sub UserForm_Terminate()
  3.  Application.MoveAfterReturnDirection = UserSetting
  4.  Call TabkeyReset
  5. End Sub
図5-15

フォームを閉じる=システム終了時には、
 ・「Enterキーで移動する方向」を今回システム起動前の状態に戻す
 ・OnKeyによる指定キーの割り当てをクリア
する必要があります。

163行目「Application.MoveAfterReturnDirection = UserSetting」では、図4-14の24行目で取得した「今回システム起動前のEnterキー移動方向(変数UserSetting)」をExcel側に復元します。
164行目「Call TabkeyReset」では、図5-8のTabkeyResetプロシージャを実行し、「OnKeyによる指定キーの割り当てをクリア」すると共に、万一Tabキーが押しっぱなしになっていた時に正常に戻すため、「Tabキーを離す」動作を実行しています。

6.アドインとしてExcelにマクロを登録

このマクロをExcelのアドインに登録することで、どのブックでも「Enterキー移動方向の変更、Tabキーの代替キーシステム」を使用することが出来ます。アドイン方法については「年賀状リスト等の宛名検索と追記 アドイン登録」を参照下さい。
またアドイン登録した際の実行マクロは、図4-14の「MoveDirectonプロシージャ」にして下さい。

7.最後に

キーの入替えは、OnKeyメソッドで割り当てをした後、SendKeysで代わりのキーを実行させれば良い と簡単に考えていました。途中までは思惑通りだったのですが、Shiftキーを押しながらの動作を盛り込ませようとした段階で行き詰まり、色々調べている内にkeybd_eventに辿り着きました。

Excel VBAは古いプログラム言語が基本なので「限界はすぐに訪れます」し、それほど「効率的でも無い」と言われています。しかし諦めずに解決策を考え続ければ、何か手があるものです。古いけれども、「表計算」というユーザーにとって扱いやすいワークシートとVBAとの協業で得られる効果は、非常に大きいと私は思います。


値入力後のセル移動方向を切替える(it-062.xlsm)

セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。