C#

C# コントロールのイベントを一時的に無効化する方法

イベントを一時的に無効にする

こんにちは、リバティエンジニア[?]のFUNAです。 現役エンジニアとしてアプリケーション開発やWeb制作、SEOやブログ運営をしています。

今回は、

何かの処理をしている間だけコントロールのイベントを無効にするためにはどうすればよいか。

ということを見ていきましょう。

なぜ一時的に無効にする必要があるのか?

せっかくコントロールにイベントを追加したのに、どうしてわざわざ無効にする必要があるのですか?

IT博士
IT博士

理由はそのプログラムによって色んなパターンがあるのですが、いくつか紹介しますね!

  • 「アプリケーション画面の起動が遅い」
    この問題ですが、原因の一つとして画面のコントロールに値を設定する際、配置しているコントロールの不要なイベントが一斉に走るせいでパフォーマンスが悪化していることでした。

  • 「コントロールのValueを変えた際に期待した値と違う結果になる」
    こちらは、例えばTextBoxコントロールの値を書き換えたときに不要なイベントが走り、期待した値とは違う値に書き換えられてしまう

なるほど。
プログラムの規模が大きくなればなるほど気を付けなければいけないことですね。

IT博士
IT博士

そうなんです。
こんな時に「ある処理中はイベントを一時無効にする」という処置がとられるんです。

では実際にどのように実装するか見ていきましょう。

ソースコード

今回は、「ある処理中はイベントを一時無効にする」という一連の処理をクラスにまとめました。

クラス構成は、「DisableFormEvent.cs」というクラスに、Publicなメソッドはたった一つにして、シンプルにわかりやすくしました!

では、DisableFormEvent.csクラスのソースを見ていきます。

// ===========================================================================
// DisableFormEvent.cs
// コントロールイベント一時無効クラス.
// ===========================================================================
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

namespace ExtendedUtility
{
    public static class DisableFormEvent
    {
        /// <summary>
        /// 指定したコントロールのイベントを一時的に無効化し、処理を実行します
        /// </summary>
        /// <param name="control">対象コントロールの入ったList</param>
        /// <param name="action">実行したいイベント</param>
        public static void DoSomethingWithoutEvents(List<Control> control, Action action)
        {
            if (control == null)
                throw new ArgumentNullException();
            if (action == null) 
                throw new ArgumentNullException();
            foreach (var ctrl in control)
            {
                var eventHandlerInfo = RemoveAllEvents(ctrl);
                try
                {
                    action();
                }
                finally
                {
                    RestoreEvents(eventHandlerInfo);
                }
            }
        }
        private static List<EventHandlerInfo> RemoveAllEvents(Control root)
        {
            var ret = new List<EventHandlerInfo>();
            GetAllControls(root).ForEach((x) =>
                ret.AddRange(RemoveEvents(x)));

            return ret;
        }
        private static List<Control> GetAllControls(Control root)
        {
            var ret = new List<Control>() { root };
            ret.AddRange(GetInnerControls(root));

            return ret;
        }
        private static List<Control> GetInnerControls(Control root)
        {
            var ret = new List<Control>();
            foreach (Control control in root.Controls) 
            {
                ret.Add(control); 
                ret.AddRange(GetInnerControls(control));
            }
            return ret;
        }
        private static EventHandlerList GetEventHandlerList(Control control)
        {
            const string EVENTS = "EVENTS";
            const BindingFlags FLAG = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase;

            return (EventHandlerList)control.GetType().GetProperty(EVENTS, FLAG).GetValue(control, null);
        }
        private static List<object> GetEvents(Control control)
        {
            return GetEvents(control, control.GetType());
        }
        private static List<object> GetEvents(Control control, Type type)
        {
            const string EVENT = "EVENT";
            const BindingFlags FLAG = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
            var ret = type.GetFields(FLAG).Where((x) =>
                x.Name.ToUpper().StartsWith(EVENT)).Select((x) => 
                    x.GetValue(control)).ToList();
            if (!type.Equals(typeof(Control)))
                ret.AddRange(GetEvents(control, type.BaseType));
                return ret;
        }
        private static List<EventHandlerInfo> RemoveEvents(Control control)
        {
            var ret = new List<EventHandlerInfo>();
            var list = GetEventHandlerList(control);
            foreach (var x in GetEvents(control))
            {
                ret.Add(new EventHandlerInfo(x, list, list[x]));
                list.RemoveHandler(x, list[x]);
            }
            return ret;
        }
        private static void RestoreEvents(List<EventHandlerInfo> eventInfoList)
        {
            if (eventInfoList == null)
                return; 
            eventInfoList.ForEach((x) =>
                x.EventHandlerList.AddHandler(x.Key, x.EventHandler));
        }
        private sealed class EventHandlerInfo
        {
            public EventHandlerInfo(object key, EventHandlerList eventHandlerList, Delegate eventHandler)
            {
                this.Key = key;
                this.EventHandlerList = eventHandlerList;
                this.EventHandler = eventHandler;
            }
            public object Key{get;private set;}
            public EventHandlerList EventHandlerList{get;private set;}
            public Delegate EventHandler{get;private set;}
        }
    }
}

クラス全体なので、結構長くなってしまいました。

肝心のPublicな関数は、「DoSomethingWithoutEvents(List<Control> control, Action action)」です。

使い方としては、第一引数にイベントを停止したいコントロールの入ったListを指定し、
第二引数には、イベントを無効化している間に実行したい処理を指定します。

DisableFormEventクラスの使い方

では、実際に呼び出すときの使い方を見ていきます。

// イベントを無効化したいコントロールをListに追加
List<Control> controlList =newList<Control>{textBox1, button1};

// 関数呼び出し
WindowsFormExtended.DoSomethingWithoutEvents(
    controlList,
    () => textBox1.Text = "イベントを無効化している間に値を代入する。");

今回はラムダ式(7行目)を使ってイベントを無効化している間にする処理を記述しました。
ラムダ式については、また詳しく記事に書きますね!

[C#] ラムダ式はこれさえ見ればOK!簡単徹底解説

使い方はわかったのですが、もし配置しているコントロール全部を一時無効にしたい場合は、面倒くさいですね。。。

IT博士
IT博士

安心してください。
Listに入れるコントロールに"this"を入れるとフォームに配置されたすべてのコントロールを制御できますよ!

// すべてのコントロールのイベントを無効化
List<Control> controlList =newList<Control>{this};

// 関数呼び出し
WindowsFormExtended.DoSomethingWithoutEvents(
    controlList,
    () => textBox1.Text = "イベントを無効化している間に値を代入する。");

このように"this"を指定すると、フォームに配置されたすべてのコントロールをのイベントを一時無効化できます。

まとめ

今回は何かの処理をしている間だけ、コントロールのイベントを無効化させる方法についてみていきました。

記載したソースコードのクラスをこのままコピペして使えるので、便利だと思います。
ですが、ちゃんと一通り1度は目を通しておいてくださいね!


Copyright© FUNA BLOG , 2020 All Rights Reserved.