こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

Vba UserForm SetFocus

ExcelのVbaでUserForm上に英単語のタイピング練習のソフトを作ろうとしていますが、うまくいきません。コントロールのイベントについての理解が不足しているのが分かりました。
ユーザフォーム上の複数のテキストボックス間のフォーカスの移動について教えて下さい。
UserForm1にテキストボックスを3つ、コマンドボタンを1つ配置しました。それぞれTextBox1、TextBox2、TextBox3、CommandButton1とします。

Private Sub CommandButton1_Click()
TextBox3.SetFocus ・・・(1)
End Sub

Private Sub TextBox3_Enter()
MsgBox "In TextBox3"
TextBox2.SetFocus・・・(2)
End Sub

各TextBoxのTabStopプロパティは「True」、「TabIndex」は番号順になっています。
コマンドボタンのTabStopプロパティは「False」にしています。

フォームを表示し、コマンドボタンをクリックするとエラーが発生します。
(2)の実行の後(?)、(1)がエラー表示されます。
各イベントの発生のタイミング、連鎖、終了等の理解が不足していて、その理由がよく分かりません。どなたか教えていただけないでしょうか。よろしくお願いします。

投稿日時 - 2017-05-13 17:56:17

QNo.9328845

困ってます

質問者が選んだベストアンサー

No.1 です。ふと、10年程前に回答した時の質問者さんの言葉を思い出し、
補足説明を加えることにしました。説明というか前提の確認になります。
もし、該当しない話だと思われたら最後まで読む必要はありません。
 Private Sub TextBox3_Enter()
イベントの名前に"Enter"という単語が使われていることtで、
"ENTER_KEY"を連想し、転じて、このイベントは、
テキストボックスへの入力確定時に発生するものだと
思い違いをしていた方がいらっしゃいました。
もし、そう思っていたとすると、No.1に書いてあることは、
伝わる筈もありませんので、後から思えば、必要な確認を
出来ていなかったかも知れません。
テキストボックス_Enterイベントは、
テキストボックスがフォーカスを取得したタイミングを捉えます。
反対にフォーカスを他のコントロールに移譲したタイミングでは、
 Private Sub TextBox3_Exit() イベントが発生し、
(意味合いとして、Enterは"入口"、Exitは"出口")
"ENTER_KEY"をおしたタイミングというのは、
 Private Sub TextBox2_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
  If KeyCode = vbKeyReturn Then
   ' "ENTER_KEY" が押された場合の処理
のような捉え方を、
入力が確定したタイミングという意味に最も近いのは、
 Private Sub TextBox2_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
 Private Sub TextBox2_AfterUpdate()
あたりなのですが、これはこれで色々分岐しないといけなくて面倒なので、
_Exitを"確定"のタイミングだということにして設計することが多いです。

改めて、
「_Enterイベントはフォーカスを取得したタイミング」
ですので、
 TextBox3.SetFocus ・・・(1)
を実行すると、この1行の処理が完結するまで僅かな時間に
割り込むようにして、先に、
 Private Sub TextBox3_Enter()
が、呼び出され、このプロシージャの処理がすべて済むのを待って、
(1)の処理が完結することになります。
つまり、ご質問のケースでは、
 TextBox3.SetFocus ・・・(1)
を実行中(まだ処理が終っていない時に)に
 TextBox2.SetFocus・・・(2)
を実行してしますので、
(1)の処理が完結した時にはTextBox2にフォーカスが
いくことになるので、不正終了になる、
という説明をNo.1の冒頭に掲げています。

> 各TextBoxのTabStopプロパティは「True」、「TabIndex」は番号順になっています。
という前提に合わせて、
フォーカスを取得する(移動する)タイミングや
その他のイベントについて説明します。
TextBox1の編集が済んで、TabIndexに従って、
TextBox2にフォーカスが移った処から、
 フォーカスを取得したので、
   _Enter イベント
 編集中には、
   _KeyUp イベント _KeyDown イベント に続いて
   _KeyPress イベント(貼付け、消去は除外)
   _Change イベント の順。
 TAB_KEY,ENTAER_KEY,↑_KEY,↓_KEY を押して編集を終了したら、
   _KeyUp イベント _KeyDown イベント に続いて
   _BeforeUpdate イベント _AfterUpdate イベント
  続いてフォーカスを失うので、
   TextBox2_Exit イベント
  TabIndexに従って
   TextBox3_Enter イベント に繋がります。
 キー操作による確定を省略して、他のコントロールを直接クリックした場合は、
   _BeforeUpdate イベント _AfterUpdate イベント
  続いてフォーカスを失うので、
   TextBox2_Exit イベント
  クリックしたコントロール側で、
   _Enter イベント に繋がります。
概ねこんな感じの流れ(順番)でイベントは発生しますので、
意図したタイミングを捉えられているか、確認するのもよいかも、です。

ここに書いたことは既知のこと、ということでしたら失礼しました。
もし、ご存じなかったのだとしたら、これらの前提を踏まえて、
もう一度、No.1の説明を読んでいただければ、少しは理解し易くなっているのではと。

それと、
> Private Sub TextBox3_Enter()
> TextBox2.SetFocus・・・(2)
> End Sub
これでは、TextBox3がフォーカスを取得した瞬間に、
TextBox2にフォーカスが移る仕組みですから、
TextBox3をユーザーが編集することが出来ない、
ということになります。
編集を禁止する目的であるならば、
 TextBox3.Enabled = False
を設定するとか、
LabelコントロールのデザインをTextBoxに合わせて代用するとか、
等の方法を採る方が簡単なので良く使われます。

時間に追われての乱筆、すみません。
私事、今から期間未定で、オフラインになりますので、
後に何かお訊ねあってもお応えできそうにないので、重ねてすみません。

投稿日時 - 2017-05-15 01:03:48

お礼

realbeatinさん、再度のご回答ありがとうございます。
No.1の説明を読まずに、お礼を書かせていただきます。
>>フォーカスを取得する(移動する)タイミングや
>>その他のイベントについて説明します。
>>TextBox1の編集が済んで、TabIndexに従って、
・・・
>>   _Enter イベント に繋がります。
>>概ねこんな感じの流れ(順番)でイベントは発生しますので、
詳しい説明ありがとうございます。
アドバイスを参考に、うまくいかなかった点を整理できましたので、
あらためて、OKWAVEに質問を上げたいと思っています。
重ね重ねのアドバイス本当にありがとうございます。

投稿日時 - 2017-05-19 15:23:16

このQ&Aは役に立ちましたか?

1人が「このQ&Aが役に立った」と投票しています

回答(3)

ANo.2

残念ですがフォーカス関連はExcelのバージョンによって
微妙に動作が変わりやすい所です。

以下では、
http://ameblo.jp/qoll/entry-11083672592.html
Private Sub UserForm_Click()
CommandButton.SetFocus
TextBox.SetFocus
End Sub
という感じで対応できたようです。
(かなりキワドイやりかたです)

Win32APIのSetTimerを使い、
100mSEC後などにタイマーのプロシージャで
フォーカスをセットする方法も考えられます。
SetTimerのサンプルです。
http://note.phyllo.net/?eid=1106267
この例ですと、TimerProc内のタイマー処理のところで、
TextBox.SetFocusしてみることです。
※100mSECという値はあくまでも例です。調整が必要かもしれないです。

フォーカスの制御はExcel vbaとしてだけでなく
基本的に面倒なことになりがちです。
動作を理解するために、フォーカスに関するイベントに、
一時的にデバッグ出力するコードを入れて、
フォーカスの順番などを把握すると、
制御するコツがつかめる場合があります。
デバッグ出力の例。
http://excelvba.pc-users.net/fol8/8_1.html
そして、フォーム、ボタン、テキストボックスなどの、
フォーカスしたりロストしたりするイベントと、
ボタンのクリックイベントのタイミングを調べます。

設計の見直しをするという方法もありますし、
コントロールに対しフォーカスを受け付けない設定をし、
結果的に目的のコントロールをフォーカスさせる
というやり方も考えられます。(うまくいくかは分かりません)

参考にならなかったらごめんなさい。

投稿日時 - 2017-05-13 19:27:35

お礼

skp026さん、早速の回答ありがとうございます。「OKAWAVEでは質問する前に関連しそうなQ&Aを調べ、目を通しておく」というマナーを忘れていました。申し訳ございません。
関連Q&Aに目を通す前に返事を書かせてもらいます。
紹介していただいたWebページ「http://ameblo・・・」の
Private Sub UserForm_Click()
CommandButton.SetFocus
TextBox.SetFocus
End Sub
は使う自信はありません。
同じWebページでの紹介「この辺の記事」を見させてもらいました。
私の環境はOS:Windows7、10 Excel:2010、2013、2016
なので今のところ関係ないようなので「イベント発生確認テスト」は試していません。
必要な状況になれば参考したいと思います。
Webページ「http://note・・・」の内容は私にはハードルが高いですが、作っているタイピングのソフトでも制限時間をタイマーで設定しているので今後、参考にさせてもらうかもしれません。
デバッグ出力については以前から利用しています。
いろいろとアドバイスをいただきありがとうございました。

投稿日時 - 2017-05-15 11:45:32

ANo.1

こんにちは。
ネスト構造を整理して考えたら矛盾に気が付くのでは。

Sub CommandButton1_Click()
 TextBox3.SetFocus
 ┃上記処理実行中
 ┣ Sub TextBox3_Enter
 ┣ ┣ MsgBox "In TextBox3"
 ┣ ┣ TextBox2.SetFocus
 ┣ End Sub
 ┗上記処理確定
End Sub

↑これは処理そのものの階層構造(ネストレベル)を示すとともに、
 処理を時系列順に並べたものです。

TextBox3.SetFocus の処理確定のタイミングで、
フォーカスがTextBox2にあるという結果に対して、
TextBox3.SetFocus 処理は失敗したことになります。
メソッドが失敗すれば、それはエラーになりますし、
.SetFocusメソッドは戻り値を持たない
(FunctionではなくてSub)タイプのメソッドですので、
エラーは即ち実行停止につながります。
Private Sub CommandButton1_Click()
  On Error Resume Next
  TextBox3.SetFocus ' ・・・(1)
  On Error GoTo 0
End Sub
のようにすれば実行時エラーでも停止しないようにはできますが、
元々のTextBox3.SetFocus→TextBox2.SetFocus
という不自然な連続フォーカス移動自体、
ネストすることが間違いです。
何故、そういったことをしたいのか説明して貰えれば、
他に方法を提案することも可能です。
どうしてもその連続フォーカス移動を実現させる必要がある、
ということでしたら、
メソッドのネスト
【メソッドの実行が終る前に他のメソッドを実行させ結果に影響を与えること】
を解いて、
非同期でそれぞれの処理を順番に実行→終了させるような手段として、
メソッドをイベント内に書くのはやめて、
別途Sub(サブルーチン)として標準モジュ-ルに記述したものを、
Application.OnTimeメソッドで呼び出すやり方が
一般的な対処です。

' ' // 標準モジュール
Private Sub SetFocusTxb3()
  UserForm1.TextBox3.SetFocus
  Debug.Print "SF3"
End Sub
Private Sub SetFocusTxb2()
  UserForm1.TextBox2.SetFocus
  Debug.Print "SF2"
End Sub
' ' // UserForm1モジュール
Private Sub CommandButton1_Click()
  Debug.Print 1
  Application.OnTime Now, "SetFocusTxb3"
  Debug.Print 2
End Sub
Private Sub TextBox3_Enter()
  MsgBox "In TextBox3"
  Debug.Print 3
  Application.OnTime Now, "SetFocusTxb2"
  Debug.Print 4
End Sub

Debug.Printはトレース(デバッグ)用の記載ですが、
これによって、
すべて指示した順番に処理が完了していること
それぞれのプロシージャを抜けてから次のプロシージャに進んでいること
つまり、ネストが解かれていることが確認できると思います。
ただ、このやり方が本件の解決策として正しいかは疑問です。
今一度、何をやりたいのかよく整理するようにして、
処理フローの最適化を図ってみた方がいいのかも、です。

尚、気になさっている、TabStop,TabIndexに関しては、
今回の問題に影響を与えるものではありません。
ただ、それ以外の面で、CommanButtonについては、
TabStopが必要か、TakeFocusOnClickが必要か、
よく考えて決めた方がいいと思います。

以上、ご参考まで。

投稿日時 - 2017-05-13 19:20:27

お礼

realbeatinさん、早速の回答ありがとうございます。以前何度かお世話になり、その節は大変勉強させていただきました。「OKAWAVEでは質問する前に関連しそうなQ&Aを調べ、目を通しておく」というマナーを忘れていました。申し訳ございません。関連Q&Aに目を通す前に返事を書かせてもらいます。
ネスト構造を示していただき、よく分かりました。
>元々のTextBox3.SetFocus→TextBox2.SetFocus
>という不自然な連続フォーカス移動自体、
>ネストすることが間違いです。
自分の理解のための質問用に作りました。ごめんなさい。
>【メソッドの実行が終る前に他のメソッドを実行させ結果に影響を与えること】
>・・・
>Application.OnTimeメソッドで呼び出すやり方が一般的な対処です。
全く知りませんでした。勉強します。
現在作成中の英単語タイピングソフトでイベントメソッドの連鎖が本当に必要か検討したいと思います。必要なら、イベントメッソドの中で別のイベントメッソドを呼び出すときは終了と連鎖に注意しながら利用したいと思います。
解決できない場合、再度質問したいと考えています。その際にはよろしくお願いします。
本当にありがとうございました。

投稿日時 - 2017-05-15 14:47:43

あなたにオススメの質問