フォームがエンターキーでクローズしない

タグの編集
投稿者 hori  (社会人) 投稿日時 2023/12/12 19:40:04
win10 VS2019 のVBで書いています。

フォーム2つにそれぞれボタンを1つずつ貼り付け下記のように書きました。

=========================================

Public Class Form1

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        Dim f As New Form2

        If f.ShowDialog(Me) = DialogResult.OK Then

            MsgBox("ok")

        Else

            MsgBox("no")

        End If

    End Sub

End Class

==========================================

Public Class Form2

    Public Overloads Function ShowDialog(ByVal Owner As IWin32Window) As DialogResult

        Return Me.ShowDialog()

    End Function


    Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        Button1.Select()

    End Sub


    Private Sub Button1_MouseUp(sender As Object, e As MouseEventArgs) Handles Button1.MouseUp

        Me.DialogResult = DialogResult.OK

        Me.Close()

    End Sub

    Private Sub Button1_PreviewKeyDown(sender As Object, e As PreviewKeyDownEventArgs) Handles Button1.PreviewKeyDown

        MsgBox(e.KeyCode.ToString())

        If e.KeyCode = Keys.Return Or e.KeyCode = Keys.Tab Or e.KeyCode = Keys.Enter Then

            MsgBox("pkd")

            Me.DialogResult = DialogResult.OK

            Me.Close()

        End If

    End Sub

End Class

============================================

このプログラムで Form2 に行った後、
[ボタンクリック]か[タブキーを押す]と Form2 を終われますが
[エンターキー]では、2つあるのにどちらを押しても終われません。
ちなみに[エンターキー]では、KeyCode = Return と示され
メッセージボックスの[pkd]も表示されます。
なのに、Form2 はクローズされず Form2 のボタンにフォーカスされた状態に戻ります。

10年ほど前に、るきおさんにご教授頂いた方法を使えば
テンキー横の[エンターキー]で終われるようにできますが
そもそも、このプログラムで[エンターキー]が機能しない理由を知りたいです。

判る方居られましたら、ご教示お願いいたします。


投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/12/12 23:21:45
ShowDialog(IWin32Window) のオーバーロードをオーバーライドしている意図は何でしょうか?
IWin32Window を握りつぶしているうえに、MyBase.ShowDialog() ではなく Me.ShowDialog() を
呼び出しているのに、引数無しのオーバーロードはオーバーライドしていないので、目的が謎です。

それはさておき、Form1 側の実装ですが、他のフォームを ShowDialog で呼び出す場合は、
呼び出し側で、使用後に Dispose を呼ばなければなりません。(もしくは Using ブロックで囲むようにします)
Using f As New Form2()
    If f.ShowDialog(Me) = DialogResult.OK Then
        MsgBox("OK")
    Else
        MsgBox("Cancel")
    End If
End Using

ShowDialog で表示した時は、Close メソッドや ×ボタンで閉じても、実際には非表示になるだけでメモリに残るので、
使用後に Dispose しなければならない仕様です。

https://dobon.net/vb/bbs/log3-42/25503.html

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.form.showdialog?view=netframework-4.0#:~:text=%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%81%AF%E9%96%89%E3%81%98%E3%82%8B%E3%81%AE%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%8F%E9%9D%9E%E8%A1%A8%E7%A4%BA%E3%81%AB%E3%81%AA%E3%82%8B%E3%81%9F%E3%82%81

もしお Form1 側で Dispose を行うのが面倒なのであれば、Form2 側に Shared Function なメソッドを用意して、
その中で Form2 自身を New & Dispose すると良いでしょう。


さて本題。

>[エンターキー]では、2つあるのにどちらを押しても終われません。
Form2 のデザイン画面で、そのボタンの DialogResult プロパティは何になっていますか?
None のままであれば、OK や Cancel に設定して、再度試してみてください。

ダイアログとして利用されるフォームでは通常、そのフォーム上のボタンの DialogResult プロパティに
OK/Cancel/Yes/No といった値を設定しておくのが一般的です。(ついでに Form の AcceptButton/CancelButton も設定すると便利)
これだけで、Form2 側は何もコーディングせずとも、その Form2 が ShowDialog メソッドで呼ばれれば、
Form2 自身が Close されますし、ShowDialog メソッドの戻り値も自動設定されます。

もちろん、hori さんが行ったように、Me.DialogResult を直接書き換えるという手もあるのですが、
処理しているのは PreviewKeyDown という「事前処理」のイベントなので、折角設定してみたところで、
ボタンの DialogResult プロパティの値で上書きされてしまうわけです。
投稿者 hori  (社会人) 投稿日時 2023/12/13 01:10:21
ご回答ありがとうございます。当方浅学のため詳しくは解りませんが
[タブキー]で Form2 は終われますし、試しに

If e.KeyCode = Keys.Return Or e.KeyCode = Keys.Tab Or e.KeyCode = Keys.Enter Then

の部分を

If e.KeyCode = Keys.H  Or e.KeyCode = Keys.F12 Then

にすると[H]キーでも[F12]キーでも終われます。

なので、Form2 や ボタンの設定の問題ではないような気がします。
設定の問題ならどのキーでも終われないはずだと思えるのですが・・・・


それと、もともとはいろいろ引数のあるプログラムで起こった(気付いた)問題でして
投稿のためミニマム化するにあたり関係のありそうなところは残したつもりです。


ともかく[Tab]や[F12]で出来る事が、何故[Enter]で出来ないのか。不思議です。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/12/13 11:02:59
> 当方浅学のため詳しくは解りませんが
詳しくは分からなくても良いので、先に述べた通り、
>> そのボタンの DialogResult プロパティは何になっていますか?
>> None のままであれば、OK や Cancel に設定して、再度試してみてください。
を試してみてください。

それで結果が変わるのであれば、その仕組みについて再度説明しなおしますし、
変わらないのであれば、また別の原因を探っていきたいと思います。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/12/13 13:48:27
> なので、Form2 や ボタンの設定の問題ではないような気がします。
> 設定の問題ならどのキーでも終われないはずだと思えるのですが・・・・
モーダルダイアログにおいて、DialogResult プロパティは重要ですよ。

現状の実装は、本来使うべき Click イベントを使わずに
MouseUp や PreviewKeyDown だけで処理するという
かなり不自然な実装になっている点にも問題があります。
オーバーライドの不自然さも含めて、一度しっかりと見直された方が良いでしょう。
(不自然に見えるのは、説明のために処理を簡略化したせいでもあるようですが)


> ともかく[Tab]や[F12]で出来る事が
> 何故[Enter]で出来ないのか。不思議です。
ボタンにフォーカスがある状態で Enter や Space を押した場合、
標準動作としてクリックと同義になることはご存じかと思います。
しかし Tab や F12 はそうではありません。端的に言えばその差です。

Button1_PreviewKeyDown 冒頭の「MsgBox(e.KeyCode.ToString())」を削除して
Button1_Click にて「MsgBox(Me.DialogResult.ToString())」と記述してから
Space や Enter を押してみてください。 "None" と表示されますよね?

これは、モーダルフォームのボタンがクリックされた時点で、
 Me.DialogResult = Button1.DialogResult
に相当する処理が行われてから、Button1_Click が実行されるためです。

これが、先の回答で
>> Form2 のデザイン画面で、そのボタンの DialogResult プロパティは何になっていますか?
と確認した理由です。デザイン時の初期値は None だからです。


まず前提知識として、モーダルダイアログについて理解しておいていただきたいことが 3 点あります。

(1) ShowDialog が呼び出された時点で、そのフォームの DialogResult は None にリセットされる仕様です。

(2) その後、そのフォームの DialogResult が None 以外に設定された場合、
  次のアイドル時にそのフォームは(Close を呼んだり×を押したりせずとも)閉じられます。
  そして閉じられると、その時の Me.DialogResult が、ShowDialog メソッドの戻り値になる仕様です。

(3) そのダイアログは実際には非表示になっているだけであるため、ShowDialog 後は
 明示的に Dispose を呼び出す責任が生じます。これは、ダイアログが閉じられた後も、
 そのダイアログが利用され続ける可能性があるため、そういう設計になっています。
 (OpenFileDialog でも、ShowDialog 後に FileName プロパティを参照したりしますよね)

ここまではよろしいでしょうか?
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/12/13 14:26:06
> ここまではよろしいでしょうか?
理解できている前提で、続けていきます。


> (2) その後、そのフォームの DialogResult が None 以外に設定された場合、
>   次のアイドル時にそのフォームは(Close を呼んだり×を押したりせずとも)閉じられます。

ShowDialog で表示された Form の DialogResult プロパティの値が変更される主な要因としては、
 🔹Me.DialogResult を直接書き換えた
 🔹Button1.DialogResult が 非 None なボタンのクリック操作が行われた
の 2 パターンです。これに加えて、下記のようなパターンでも変化します。

★現在値が None の状態で下記が行われると Cancel になり、ダイアログは閉じられます。
 🔹ユーザーが右上の閉じるボタン「×」を押す
 🔹タスクマネージャーから、そのウィンドウが閉じられる
 🔹そのダイアログの Close メソッドを呼び出す
 🔹そのダイアログの Hide メソッドを呼び出す
 🔹そのダイアログの Visible プロパティが False に変更される

★フォームが閉じられる前に下記の動作が行われると、DialogResult が非 None から None に戻り、閉じる動作がキャンセルされます。
 🔹Closing イベント/ FormClosing イベントで e.Cancel が True に変更された
 🔹OnClosing メソッド/ OnFormClosing メソッドで e.Cancel が True に変更された
 🔹フォームの DialogResult プロパティに None が設定された
 🔸「次のアイドル状態に入る前」に、DialogResult プロパティが None な Button の Click が呼び出された

そして最後の 🔸 マークの事象が、今回の原因に当たるというわけです。
説明箇所で不明な点があれば答えますので、
>> 当方浅学のため詳しくは解りませんが
と投げ出さないで欲しいかな…。


> If e.KeyCode = Keys.Return Or e.KeyCode = Keys.Tab Or e.KeyCode = Keys.Enter Then
あとこれ、正しくは Or ではなく OrElse ですね。
投稿者 hori  (社会人) 投稿日時 2023/12/13 15:20:51
ご回答ありがとうございます。

昨夜、そのボタンの DialogResult プロパティの変更を試行いたしました時には
元々の None 以外の何を選択してもエラーになってデバッグそのものが進行できませんでした。
今思えば、そこで再起動してみる事に思い至ればよかったのでしょうけれど
考え至らず、他の方法をあれこれやってみて諦めていた次第です。

で、今、再トライしてみたところ稼働しました。それで分かった事は

① エンターキーが返す値は、そのボタンの DialogResult プロパティの値。
   ただし、None の時は何も返さない。
② そのほかのキーが返す値は、Me.DialogResult = DialogResult.○✕ で書いた○✕の値。
   ○✕が None の時は、Cancel を返す。

であるらしいと云う事です。

で、結局、Me.DialogResult = DialogResult.OK の前に
 Button1.DialogResult = DialogResult.OK を入れれば
フォームでボタンをいじるより手っ取り早いようです。

今回の教訓としては、『エンターキーだけが挙動不審な場合は
エンターキーだけが特別扱いされていないかどうか調べてみる』と云うところかと思いました。

魔界の仮面弁士さまに於かれましては、いつもながらの的確なご助言、ありがとうございました。



投稿者 hori  (社会人) 投稿日時 2023/12/13 16:22:07
返信を書きかけフォームを開いたままで居りましたので追加の回答を読んでいませんでした。

懇切丁寧なご説明で僕の頭でも理解できたと思います。たぶん。ありがとうございます。


>ボタンにフォーカスがある状態で Enter や Space を押した場合、
>標準動作としてクリックと同義になることはご存じかと思います。
>しかし Tab や F12 はそうではありません。端的に言えばその差です。

恥ずかしながら、上記を認識していませんでした。
結局、ボタンにフォーカスがある状態で Enter や Space を押した場合、

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        Me.DialogResult = Button1.DialogResult

    End Sub

の記述が無くても、これと同義の事が実行されると理解すべきなのですね。

逆にこれをキャンセルする方法があれば知りたいです。
まぁ、Button1.DialogResult = DialogResult.OK でいいですか。


あと、フォームが非表示なだけでメモリーに保持されているのなら
僕はフォームを呼び出すとき同一のプロシージャじゃない限り変数にいつも同じ[f]を使って

Dim f As New Form1
Dim f As New Form2

とやっているんですが、これを

Dim f1 As New Form1
Dim f2 As New Form2

と毎回変数を変えておけば、いつでも  f2.TextBox1.text  などの値が取れると云う事でしょうか?。
別に何に使うと云う当てはありませんが、そう云う事なのかなと思っただけです。
まぁ、こういうのは人に聞かなくてもやってみればいいんでしょうけれど・・・・
ともかく、ありがとうございました。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/12/13 17:25:36
> 昨夜、そのボタンの DialogResult プロパティの変更を試行いたしました時には
> 元々の None 以外の何を選択してもエラーになってデバッグそのものが進行できませんでした。

恐らくは、デバッグ実行中に(実行を停止させずに)デザイン画面を変更したのではないでしょうか?
その場合「プロパティの値が無効です。」のエラーになりますので。
※エラーの詳細を見ると、「ビルド中またはデバッグ中に、ファイル {何某} をデザイナーで変更することはできません。」と表示される状況です。


もしも固定的に OK を返すだけで良いのであれば、今回の場合は
Form2 の Button1 の DialogResult を OK にするだけで充分です。

キーボードでの操作を含めたいなら、そのボタンの PerformClick メソッドを呼ぶことで
ボタンのクリック操作をエミュレートできます。例えばこのように書きます。
MouseUp イベントも不要ですし Close メソッドも要りません。

Public Class Form2
    Private Sub Button1_PreviewKeyDown(sender As Button, e As PreviewKeyDownEventArgs) Handles Button1.PreviewKeyDown
        Static KeyList As Keys() = {Keys.Enter, Keys.Space, Keys.H, Keys.Tab, Keys.Space, Keys.F12}
        If KeyList.Contains(e.KeyCode) Then
            sender.PerformClick()
        End If
    End Sub
End Class


常に OK を返すのではなく、条件によって戻り値を変更したい場合には、
Button1 の DialogResult は何でも良い(None でも良い)ので、
Button1 の Click イベント内で、条件に応じて Me.DialogResult を切り替えるようにします。
この場合は PreviewKeyDown の実装は変えずに、単に PerformClick するだけです。



> ① エンターキーが返す値は、そのボタンの DialogResult プロパティの値。
>  ただし、None の時は何も返さない。

Button1 をクリックすれば、Button1.DialogResult が Me.DialogResult に伝播するということです。
実行時の Button1.DialogResult が None なら、Me.DialogResult は None のままで閉じられません。


今回は Enter キーが問題になりましたが、Escape キーでも特殊な動作が発生する可能性があります。
具体的には、Form2 の【CancelButton プロパティ】に Button1 を割り当てていたようなケースです。
CancelButton を割りあてておくと、Esc キーを押したときに、そのボタンのクリック動作が発生します。

【CancelButton プロパティ】に Button1 を割り当てた時点で、
Button1 の DialogResult は Cancel に自動設定されます。
(後から Cancel 以外に変更することもできますが、デザイナ上で None に設定した場合、実行時には Cancel になるのでご注意ください)



さて、Button1 の DialogResult が戻り値として伝播するのは、マウスでクリックしたときだけではありません。
今回問題になった、いわゆる「ダイアログキー」による操作でも伝播します。

ボタンがアクティブな状態での Enter キーや
AcceptButton が設定されているときの Enter キーや
CancelButton が設定されているときの Esc キーのことです。

その結果、Button1_PreviewKeyDown で「If e.KeyCode = Keys.何某 Then」判定にて
Me.DialogResult を書き換えても、既定の Enter キーの動作(CencelButton なら Esc キーも)は継続して処理され、
実行時の Button1.DialogResult が、Me.DialogResult に上書きされることになります。

もしも PreviewKeyDown の処理後に、この既定の動作をスキップさせたい場合には、
ダイアログキーの処理を無効化するために、ProcessDialogKey メソッドをオーバーライドします。

Private SkipDialogKey As Boolean = False
Private Sub Button1_PreviewKeyDown(sender As Object, e As PreviewKeyDownEventArgs) Handles Button1.PreviewKeyDown
    If e.KeyCode = Keys.何某 Then
        SkipDialogKey = True
        Me.DialogResult = 任意
    End If
End Sub
Protected Overrides Function ProcessDialogKey(keyData As Keys) As Boolean
    If SkipDialogKey Then
        Return False
    Else
        Return MyBase.ProcessDialogKey(keyData)
    End If
End Function


なお、Tab キーや矢印キーもダイアログキーの一種です。にもかかわらず「e.KeyCode = Keys.Tab」の時に
問題が起きなかったのは、Tab キーは Button のクリック動作を引き起こすものではないためです。



> ② そのほかのキーが返す値は、Me.DialogResult = DialogResult.○✕ で書いた○✕の値。
> ○✕が None の時は、Cancel を返す。

DialogResult が未設定のまま閉じられた場合は、Cancel とみなす設計になっています。
これは先の回答でいうところの
>> ★現在値が None の状態で下記が行われると Cancel になり、ダイアログは閉じられます。
にあたります。

たとえば YesNo タイプや AbortRetryIgnore タイプのメッセージボックスの場合、
右上の ×ボタンが意図的に使えなくなっていますが、これも同様の理由によるものです。
(ボタンを選ばなかった時の戻り値がキャンセルになってしまうことを防ぐため)

キャンセルをボタンを持つメッセージボックスであれば、右上の×ボタンが使えます。

なお、OK しかないメッセージボックスの場合は単一選択なので、
×ボタンを押しても Ok 扱いになる設計になっています。ですからダイアログを自作する場合は、
そのダイアログが×ボタンやタスクマネージャーで閉じられたときに、
何を返すように設計すべきかを検討しておきましょう。
投稿者 (削除されました)  () 投稿日時 2023/12/13 17:25:49
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/12/13 19:14:40
> 結局、ボタンにフォーカスがある状態で Enter や Space を押した場合、
ボタンにフォーカスが無い状態であっても、
Form の AcceptButton / CancelButton が設定されていた場合は
Enter や Esc で反応しますね。

さらにいえば、Button1.Text = "&Button1" などのようにニーモニックキーが設定されていた場合、
Alt+B のショートカット操作で、Button1 の Click イベントが発生します。


> 逆にこれをキャンセルする方法があれば知りたいです。
基本的にはデザイン時の Button の DialogResult 動作に任せるのが簡単ですが、
後から変更したい場合は、事前処理(PreviewKeyDown など)のタイミングではなく、
Click イベントなどで Me.DialogResult を調整するという手があります。

Button1 ではなく、「右上の "×" ボタンで閉じられた場合」の既定値を示したいなら、
FormClosing イベントなどで Me.DialogResult を調整するのも良いでしょう。


Me.Close にしても Me.DialogResult にしても、それを呼び出したからといって、
直ちにフォームが閉じられるわけではありません。実際に閉じられるのは、
設定した時の処理が End Sub に到達したのち、次のアイドル状態に遷移した後です。

そのため、PreviewKeyDown のタイミングで書き換えても、優先度の高い既存処理によって
Me.DialogResult の書き換えが発生してしまったのが今回の状況ですね。

標準で行われる本来の処理が邪魔になる場合は、
 案1) 本来の処理を抑制して、独自の処理に置き換える
 案2) 本来の処理をいったん行わせてしまい、後から追加の処理で補正する
などとします。

前者は、WndProc メソッドや Process何某 メソッドを上書きしたり、
CancelEventArgs や HandledEventArgs を継承した引数を持つイベントで処理するなど。
後者は  Click イベントや FormClosing で処理するなど。


> まぁ、Button1.DialogResult = DialogResult.OK でいいですか。
現行のコードからの修正量も少ないでしょうし、選択肢の一つになりえるでしょうね。
Me.DialogResult から Button1.DialogResult に変更した意図が後で分かりにくくならないよう、
自分なりの説明コメントを入れておいた方が良いかもしれません。

ただ、PreviewKeyDown は独自処理に置き換えることが想定されているイベントではないため、
個人的にはこのタイミングでの操作は避けるようにしています。
(今回の処理は大丈夫かもしれませんが、一応念のため)

たとえば PreviewKeyDown の段階では、判定結果をフィールド変数に保持するにとどめておき、
Click イベントや FormClosing など)で、Me.DialogResult に反映させるというのも手かと思います。
その場合は変更箇所が恐らく増えてしまうので、今回そこまで変更する必要があるかというと微妙なところですが…
単純化する前の元のソースを見ていないので、どう改修するのが良いのかは何とも言えません。



> Dim f As New Form1
> Dim f As New Form2
有効期間の短いローカル変数の話であれば、変数名の違いは大局に影響しないので、
分かりやすいか否かで決めてしまえば良いでしょう。同じでも違っていても構いません。

一方、有効期間の長い変数や後で使いまわされるインスタンスの場合は別です。
その場合、変数名が f や f1 と f2 と名付けられるべきではないでしょう。
たとえば「メール検索ダイアログ」なのか「新規注文登録画面」なのか
「パスワード入力画面」なのか「ハイスコア一覧画面」なのか
どのようなインスタンスであるのかが分かるような名前を付けるべきです。


> いつでも  f2.TextBox1.text  などの値が取れると云う事でしょうか?。
できます。が、その f2 をどこで宣言しているのか、そしてそのインスタンスの
生成や破棄をどこで行っているのかを、きちんと把握しておくことが肝要です。

その f2 変数が宣言されている箇所が、Module のフィールド変数なのか、
Form1 のフィールド変数なのか、Click イベントなどのローカル変数なのか、
あるいはメソッド引数で f2 As Form とか f2 As Form2 とか f2 As Object なのかなど。

いずれにせよ、そのインスタンスを複数の箇所で使いまわすのなら、通常はフィールド変数で管理します。
使いまわさずに一か所でしか使わないのであれば、ローカル変数、それもできれば局所変数にするのが良いでしょう(Using ブロックなど)。


そして同じインスタンスを使いまわす場合、New は何度も行う必要がありません。
閉じた後で ShowDialog すれば、先ほどの画面がそのまま再表示されるからです。
(New しなおす場合は、以前のインスタンスを Dispose することを忘れずに!)

ただし再表示できるのは、ShowDialog メソッドによる「モーダル ダイアログ」の場合です。
Show メソッドを使って「モードレス ダイアログ」として呼び出す場合はこの限りではありません。
Show の場合は Close 時点で破棄(Dispose)されるため、再利用には向きません。もしもモードレス時に
画面を使いまわすのであれば、Close ではなく Hide メソッドで隠す方法が使えます。(または Visible プロパティ)


で…使いまわすかどうかとは別の話として、「f2.TextBox1.Text 」という書き方はできれば避けましょう。
オブジェクト指向プログラミングの「カプセル化」の視点からみると、
f2.TextBox1 を直接操作するのは、本来は望ましくありません。

VB の場合、各コントロールがデザイン時に
 GenerateMember = True
 Modifiers = Friend
になっているために、外部からコントロールを直接読み書きできてしまいますけれどね…。
(C# の場合、Modifiers の既定値が private なので、自フォーム以外からは操作できない)

本来であれば、コントロールを読み書きするのは「そのフォーム自身」に任せるのが望ましいです。
そうしないと、そのダイアログの画面構成が変化したときに、ダイアログだけでなく、
それを呼び出す側のコードも併せて変更しなければならなくなってしまいますから。

たとえば「CheckBox1 がチェックされていた場合は、TextBox1 ではなく TextBox2 を使う」といった
追加改修が入ってきた場合、呼び出し元すべてにそのための If 文を追加して回るのは手間ですよね?

外部から操作させたいのであれば、ダイアログ側に Public なメソッドなりプロパティなりを追加して、
その中で Me.TextBox1 を読み書きするように設計します。そうすれば、呼び出し側の処理を変えることなく
ダイアログの実装を追加修正することができ、設計変更に強いコードになります。

もしも複数のフォームで統一的な操作にしたいのであれば、いわゆるポリモーフィズムの概念に従い、
操作用の Interface を自作して、それを各フォームに Implements するのが良いでしょう。
(継承フォームを使ってポリモーフィズムを実現する方法もありますが、設計難易度が高いのでおすすめしません)
投稿者 hori  (社会人) 投稿日時 2023/12/15 11:59:16
昨夕から出かけてまして、いま読ませていただきました。
多くの事を教えていただき感謝いたします。ありがとうございます。

ついでにと言っては失礼なのですが
Form に TextBox を1つ貼り


    Private Sub TextBox1_KeyDown(sender As Object, e As KeyEventArgs) Handles TextBox1.KeyDown

        MsgBox("tKD")

    End Sub

    Private Sub TextBox1_KeyPress(sender As Object, e As KeyPressEventArgs) Handles TextBox1.KeyPress

        MsgBox("tKP")

    End Sub

    Private Sub TextBox1_KeyUp(sender As Object, e As KeyEventArgs) Handles TextBox1.KeyUp

        MsgBox("tKU")

    End Sub

    Private Sub TextBox1_PreviewKeyDown(sender As Object, e As PreviewKeyDownEventArgs) Handles TextBox1.PreviewKeyDown

        MsgBox("tPKD")

    End Sub

とやって、TextBox でキー入力します。

それで出てきた MsgBox の[OKボタン]をマウスでクリックしていくと "tKU" は出てきませんが
エンターキーを押していくと最後に "tKU" が繰り返されてマウスでクリックしないと終われません。

これは、TextBox1.KeyUp が、直前に表示された MsgBox 内のキー操作に反応していると云う事でしょうか?。

ちなみに、先のフォームにラベルを張って


    Dim s As String = ""

    Private Sub TextBox1_KeyDown(sender As Object, e As KeyEventArgs) Handles TextBox1.KeyDown

        s &= "1"

        Label1.Text = s

    End Sub

    Private Sub TextBox1_KeyPress(sender As Object, e As KeyPressEventArgs) Handles TextBox1.KeyPress

        s &= "2"

        Label1.Text = s

    End Sub

    Private Sub TextBox1_KeyUp(sender As Object, e As KeyEventArgs) Handles TextBox1.KeyUp

        s &= "3"

        Label1.Text = s

    End Sub

    Private Sub TextBox1_PreviewKeyDown(sender As Object, e As PreviewKeyDownEventArgs) Handles TextBox1.PreviewKeyDown

        s &= "4"

        Label1.Text = s

    End Sub

とやると、ラベルには "4123" と表示されるので、KeyUp も動いているようなのですが
だったら何故マウスクリックで行った場合に、"tKU" が出てこないのでしょう?。

そう云う仕様なのだと云う事なのでしょうが、
どういう場合に使う事を想定して設計されたのか僕の頭では読めません。
こんな場合に使えるよと云うのがあったらご伝授いただけませんか?

今のところ、KeyUp は使わなくても済んでいるのでスルーしていただいて結構です。
けれど、なるほどと思えたらちょっと嬉しいです。


投稿者 (削除されました)  () 投稿日時 2023/12/15 22:50:00
(削除されました)
投稿者 (削除されました)  () 投稿日時 2023/12/15 22:58:25
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/12/16 08:01:39
疑問符「?」や感嘆符「!」の後には句点「。」を付けません。
https://www.jagat.or.jp/archives/12121
https://www.asobou.co.jp/blog/bussiness/punctuation#i-2


> これは、TextBox1.KeyUp が、直前に表示された MsgBox 内のキー操作に反応していると云う事でしょうか?。

というか【抑論】として、キーボード系イベント/フォーカス遷移系イベント/マウスUpDown系イベントで
メッセージボックスを使うようなデバッグ方法は避けるべきです。

メッセージボックスの表示によってフォーカスの移動が伴うことになりますし、
OK ボタンの操作のためにキーボードやマウス操作が必要になるため、
本来受け取るはずだったイベントの処理が変わってしまうことになるからです。

キーやマウスの Down と Up の間のタイミングなどで、本来とは異なる割り込み処理を入れてしまうと、
今回のように、事前に想定しにくい問題に悩まされることになりかねません。

もしも一時停止的な意味合いは無く、イベントなどの処理順を追跡したいという目的だけであれば、
メッセージボックスを使う代わりに
 Debug.WriteLine(何某)
 Trace.WriteLine(何某)
 ListBox1.Items.Insert(0, 何某)
 TextBox1.AppendText(何某 & vbCrLf)
などを用いることをお奨めします。これならば、本来の処理が阻害されずに済むでしょう。


> それで出てきた MsgBox の[OKボタン]をマウスでクリックしていくと "tKU" は出てきませんが
それはタイミングの問題ですね。

TextBox 上で最初に押したキーを、離さずに押し続けてみてください。
その状態で OK ボタンをクリックしていきましょう。

メッセージが出なくなったところで、TextBox 上でキーを離してみれば、"tKU" が表示されるはずです。
そしてメッセージが出ている最中にキーを離せば、"tKU" は表示されません。

あるいは TextBox に戻った後、TextBox 以外のコントロールにフォーカスを移してから
キーを離せば、やはり "tKU" は表示されないはずです。


> エンターキーを押していくと最後に "tKU" が繰り返されてマウスでクリックしないと終われません。
メッセージが閉じた時には、Enter(あるいがEsc)が押された状態のまま、フォーカスが TextBox に戻ります。
その状態で Enter キーが離されることで、"tKU" が表示された…という状況ですね。
そして "tKU" を閉じるために Enter を押すことになるので、この動作が繰り返される、と。

Enter を「押す」のではなく、「押しっぱなしにする」と、動きが分かりやすいと思いますよ。

そして Enter や Escape ではなく、Space キーで OK を操作すると違う結果になります。

何故なら[Space]キーで OK ボタンを押す場合、メッセージが閉じられるのはキーを離したタイミングだからです。
それに対して[Enter]キーや[Esc]キーの場合は、メッセージが閉じられるのはキーを押したタイミングです。


TextBox 上で [Shift]を押しっぱなしにして "tPKD" を出し、[Shift] を離さずに [Space] を押して閉じます。
続けて "tKD" が出るので、それも[Shift] を離さずに [Space] を押して閉じてみましょう。
Space による操作では、TextBox に戻ったときには キーを離した後なので、
先の Enter の場合と違って、連続してメッセージが出てしまうようなことはなく、
TextBox に戻った後で [Shift] を離したときに、"tKU" が表示されることになります。
投稿者 hori  (社会人) 投稿日時 2023/12/16 13:15:49
>疑問符「?」や感嘆符「!」の後には句点「。」を付けません。

ここは、大先生であっても一言謂わせてください。
元来、日本語に疑問符や感嘆符はありません。
後から来た外来文字に合わせて古来の日本語のルールを捻じ曲げるのは
明治維新、おそらく諭吉による『脱亜入欧』の悪弊と思われます。
日本人の一人として、ルール自体が間違っていると信じております。

まぁ、蚤の一念のような話はさておき、
丁寧なご説明により理屈はだいたい解ったように思います。ありがとうございます。

Debug.WriteLine がある事は知っていたのですがどこに表示されるのか判らなかったので
メッセージボックスを使っていました。(別に不自由とも思いませんでしたので)
イミディエイトウィンドウと云うところに表示されるのですね。
慣れたやり方を変えるのは苦手なんですが、使い方を勉強してみます。

それと、KeyUp 内でメッセージボックスを使うのは危険だと思っておきます。

いろいろご教示いただきありがとうございました。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/12/18 15:43:41
> イミディエイトウィンドウと云うところに表示されるのですね。
> 慣れたやり方を変えるのは苦手なんですが、使い方を勉強してみます。
[ツール]-[オプション] 画面を開いて、左ツリーから [デバッグ] - [全般] を選んでみると、
 ☑ 出力ウィンドウの文字をすべてイミディエイト ウィンドウにリダイレクトする
にチェックが付いている状態だと思います。
チェックを外した場合は [出力ウィンドウ] に表示されますね。


そして Debug.WriteLine は、「Release ビルドでコンパイルすると呼ばれなくなる」特性があります。
なので、Debug/Release を切り替えて検証するような場合にも使いやすいかもしれません。
もしもRelase ビルドでもログを残したい場合は、代わりに Trace.WriteLine を用いるのが良いでしょう。

これらの出力先は、ファイルやイベントログ、コンソール出力などに変更することもできます。
また、出力内の用の重要度(エラー、警告、情報)に応じて、 .config ファイルから on/off することも可能です。
https://dobon.net/vb/dotnet/programing/traceswitches.html
https://dobon.net/vb/dotnet/programing/tracelisteners.html


> 元来、日本語に疑問符や感嘆符はありません。
分類としては「約物」の一種ですね。
その 2 つに「‼」や「⁉」や「⁈」や「⁇」を加えた 6 種が、“区切り約物”。

> 明治維新、おそらく諭吉による『脱亜入欧』の悪弊と思われます。
旗頭にされてはいたものの、脫亞入歐そのものは諭吉の意図するところではなかったそうなので、
悪弊というと語弊があるかもしれませんが😅、その流れでいうと、そもそも句読点ですら、
明治三十九年の句読法案(句読点法案)を通すまでは、標準的な記法が無かったんですよね。
限定的に使われることはあったものの、日本語の文章としては記述位置や使い方が定められていなかった。

その後文部省などが統一化に対して動いていたものの、かの芥川龍之介は、
そうした流れがやまと言葉を乱す流れでものであると憤慨されていましたね。
彼は1925年の「改造」誌(大正十四年三月號)において、『文部省の仮名遣改定案について』への寄稿で
「僕等は句読点の原則すら確立せざる言語上の暗黒時代に生まれたるものなり。」という言葉をのこしています。
https://www.aozora.gr.jp/cards/000879/files/1133_6771.html#:~:text=%E3%80%82-,%E5%83%95%E7%AD%89,%E3%80%82

> 日本人の一人として、ルール自体が間違っていると信じております。
感嘆符や疑問符の後ろに句点を置くことは誤りであるという方向で明確に標準化されてますので、
現代日本語語文として記述する場合には、標準から外れた書き方を積極的に使うべきではないと思いますよ。

W3C "JLreq" (Japanese Layout Task Force: Requirements for Japanese Text Layout)
https://www.w3.org/TR/jlreq/#cl-04:~:text=No%20full%20stops,%E4%BB%98%E3%81%91%E3%81%AA%E3%81%84%EF%BC%8E
「『No full stops (cl-06) should be appended after dividing punctuation marks (cl-04) at the end of a sentence.』
  文末に区切り約物(cl-04)が付いた場合は,句点類(cl-06)は付けない.」


> 後から来た外来文字に合わせて古来の日本語のルールを捻じ曲げるのは
そう思うのであれば、感嘆符や疑問符を使わない言葉選びを心がけるのが良いでしょうし。
年代にもよるでしょうが、国語の作文でも、疑問符や感嘆符を避けるよう指導されるそうです。

なお疑問符や感嘆符は、以前は中学校の学習指導要領の範囲だったのですが、
現在では中学学習範囲からは削除され、小学校の学習指導要領に新設されているそうな。