DOBON.NETプログラミング道掲示板

■35308 / 親記事)  継承元フォームで各フォームのボタン動作を検知したい
  
□投稿者/ saku 一般人(1回)-(2022/12/30(Fri) 08:33:39)
  • アイコン環境/言語:[C#] 
    分類:[.NET] 

    おはようございます。
    皆さまのお知恵を拝借できないでしょうか。

    基本となるフォーム(ファンクション、デザインの統一など)を作成して
    そのフォームを継承して各機能画面を作成しているのですが、

    継承元フォームの方で、キーボード入力を検知できるように(KeyPreview=True,KeyDownイベントなど)、
    マウスでボタンにフォーカスがあたる(またはクリック)した事を検知できるような方法って何か考えられないでしょうか?

    継承元フォームの方で処理を共通化することになり、各機能画面のボタンの検知が必要になったためです。

    何かしらヒントだけでも頂けると有難いです。

    よろしくお願いいたします。

マルチポストを報告
違反を報告
引用返信 削除キー/
■35309 / ResNo.1)  Re[1]: 継承元フォームで各フォームのボタン動作を検知したい
□投稿者/ 魔界の仮面弁士 大御所(1509回)-(2022/12/30(Fri) 10:19:06)
  • アイコンNo35308に返信(sakuさんの記事)
    > マウスでボタンにフォーカスがあたる(またはクリック)した事を検知
    マウス操作に限定した検知ということでしょうか。
    それともマウス以外の操作によるものも検出したいのでしょうか?

    たとえば Windows Touch 操作や、スタイラスによるタップも
    マウス操作によるクリックと同種に扱うという事で良いでしょうか。

    フォーカス移動にはマウス操作だけでなく、Tab / Shift+Tab 移動もあります。
    プログラムから Focus メソッドや SelectNextControl メソッド等によって
    フォーカスが移り変わることもあります。
    https://dobon.net/vb/dotnet/control/selectnextcontrol.html

    Click イベントの発火には、Alt + ニーモニックキー によるショートカット、
    フォーカス取得時の Space キーでの操作もあります。Form の
    AcceptButton / CancelButton によるショートカット操作もありますし、
    プログラムから PerformClick メソッドで呼ばれることもありますね。


    別にマウス操作に限る必要は無いのなら、継承フォーム側で OnControlAdded を
    オーバーライドして Button の追加を追跡しておき、そのボタンに対して
    GotFocus/Enter/MouseDown/Click イベントを追跡するのが良いと思います。

    その場合 Panel などの入れ子構造も再帰的にとらえる必要があるでしょう。
    状況によっては、ControlRemoved イベント/ OnControlRemoved メソッドでの
    解除処理も加えた方が良いかも知れません。


    ボタン操作と言えば、非アクティブなフォームのボタンがクリックされた場合
     ・フォームおよびそのボタンがアクティブになるだけ
     ・フォームとボタンがアクティブになり、Click イベントまで発動
    のどちらが好ましいか(Click Through の有無)という話があり、その切替を
    WndProc をオーバーライドすることで制御することがあります。
    https://learn.microsoft.com/en-us/archive/blogs/rickbrew/how-to-enable-click-through-for-net-2-0-toolstrip-and-menustrip

    「イベント」を処理する代わりに、こうした「Windows Message」を処理することで
    検出できるケースもあります。
違反を報告
引用返信 削除キー/
■35310 / ResNo.2)  Re[2]: 継承元フォームで各フォームのボタン動作を検知したい
□投稿者/ saku 一般人(2回)-(2022/12/30(Fri) 12:13:32)
  • アイコン詳しい説明、考慮すべき点に参考すべき情報、
    大変勉強になりました。
    今回はキー入力まわりの対応は完了しているので、マウスでのクリックに対する継承フォーム側での OnControlAdded、Windows Messageを念頭に調べてみます。

    もう感謝しかありません。
    ありがとうございました。
解決み!
違反を報告
引用返信 削除キー/
■35312 / ResNo.3)  Re[2]: 継承元フォームで各フォームのボタン動作を検知したい
□投稿者/ 魔界の仮面弁士 大御所(1511回)-(2022/12/30(Fri) 15:10:57)
  • アイコン
    解決済みチェックはそのままにしておきます。
    
    
    ■No35309に追記(魔界の仮面弁士の記事)
    > 別にマウス操作に限る必要は無いのなら、継承フォーム側で OnControlAdded を
    > オーバーライドして Button の追加を追跡しておき、そのボタンに対して
    > GotFocus/Enter/MouseDown/Click イベントを追跡するのが良いと思います。
    
    Button の Click イベントと Enter イベントを継承元フォームで捉えて
    独自の ControlClick / ControlEnter イベントとして発生させてみました。
    
    イベント引数 e.Control から、対象の Button オブジェクトを得られます。
    
    private void Form1_ControlClick(object sender, ControlEventArgs e)
    {
        listBox1.Items.Insert(0, $"{DateTime.Now:HH:mm:ss.ffff}/Click: {e.Control.Text}");
    }
    private void Form1_ControlEnter(object sender, ControlEventArgs e)
    {
        listBox1.Items.Insert(0, $"{DateTime.Now:HH:mm:ss.ffff}/Enter: {e.Control.Text}");
    }
    
    
    ベースクラスの実装はこんな感じ。
    
    
    // ---> SakuFormBase.cs --->
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows.Forms;
    using System.Collections;
    
    [DefaultEvent(nameof(SakuFormBase.ControlClick))]
    public class SakuFormBase : Form
    {
        // 実際には InitializeComponent(); の呼び出し等も記述
        public SakuFormBase() { }
    
        #region 公開イベント
        // ControlClick イベントという名に反して、Button にしか反応させていなかったりする
        [Category("Action"), Description("フォーム上の Button がクリックされた時に発生します。")]
        public event ControlEventHandler ControlClick;
        [Category("Focus"), Description("フォーム上の Button がアクティブになった時に発生します。")]
        public event ControlEventHandler ControlEnter;
        /// <summary><see cref="ControlClick"/> イベントを発生させます。</summary>
        protected virtual void OnControlClick(ControlEventArgs e) => ControlClick?.Invoke(this, e);
        /// <summary><see cref="ControlEnter"/> イベントを発生させます。</summary>
        protected virtual void OnControlEnter(ControlEventArgs e) => ControlEnter?.Invoke(this, e);
        #endregion
    
        #region 内部処理
        protected override void OnControlAdded(ControlEventArgs e)
        {
            base.OnControlAdded(e);
            // 子コントロールが追加されたら、Click/Enter イベントを捉える
            AttachEvents(e.Control);
        }
        private void AttachEvents(Control control)
        {
            if (control is Button button)
            {
                // Button が追加された時はイベントを追跡
                if (subscribes.Add(control))
                {
                    var a = new ControlEventArgs(control);
                    button.Click += (sender, e) => OnControlClick(a);
                    button.Enter += (sender, e) => OnControlEnter(a);
                }
            }
            else
            {
                // Button で無ければ、さらにその配下のコントロールに対して再帰的に追跡
                if (subscribes.Add(control))
                {
                    control.ControlAdded += (sender, e) => AttachEvents(control);
                }
                foreach (var c in control.GetControls<Control>())
                {
                    AttachEvents(c);
                }
            }
        }
        // イベントを追跡済みかどうかを管理
        private HashSet<Control> subscribes = new HashSet<Control>();
        #endregion
    }
    internal static class ControlExtensions
    {
        /// <summary>
        /// 指定した型の子孫コントロールを列挙する
        /// </summary>
        public static IEnumerable<T> GetControls<T>(this Control control) where T : Control
        {
            var controls = control.Controls.OfType<Control>();
            return controls.OfType<T>().Concat(controls.SelectMany(c => GetControls<T>(c)));
        }
    }
    // <--- SakuFormBase.cs ---
    
    
    ControlAdded イベント(OnControlAdded メソッド)を用いているのは、
    Button の追加が実行時に動的に行われた場合に備えるためのものです。
    (今回、ControlRemoved イベントの追跡は省略しています)
    
    デザイン時の画面構成のままで、ボタン数の増減が無いような場合は、
    Load 完了後に全 Button を列挙して捉えるだけでも十分かと思います。

解決み!
違反を報告
引用返信 削除キー/



スレッド内ページ移動 / << 0 >>

このスレッドに書きこむ

Mode/  Pass/


- Child Tree -