Utils/DataChangeDetector.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace ChatworkBulkSender.Utils
{
    /// <summary>
    /// データ変更検知ヘルパークラス
    /// </summary>
    public class DataChangeDetector : IDisposable
    {
        private readonly Dictionary<Control, string> _initialValues = new Dictionary<Control, string>();
        private readonly HashSet<Control> _monitoredControls = new HashSet<Control>();
        private bool _hasChanges = false;
        private bool _isLoading = true;

        /// <summary>
        /// データ変更があるかどうか
        /// </summary>
        public bool HasChanges => _hasChanges && !_isLoading;

        /// <summary>
        /// 変更検知を開始
        /// </summary>
        /// <param name="container">監視対象のコンテナコントロール</param>
        public void StartMonitoring(Control container)
        {
            // 既存の監視を解除
            StopMonitoring();

            // コンテナ内のすべての入力コントロールを再帰的に取得
            var controls = GetAllInputControls(container);

            foreach (var control in controls)
            {
                // 初期値を保存
                string initialValue = GetControlValue(control);
                _initialValues[control] = initialValue;
                _monitoredControls.Add(control);

                // 変更イベントをフック
                AttachChangeEvent(control);
            }

            _isLoading = false;
            _hasChanges = false;
        }

        /// <summary>
        /// 変更検知を停止
        /// </summary>
        public void StopMonitoring()
        {
            foreach (var control in _monitoredControls)
            {
                DetachChangeEvent(control);
            }
            _monitoredControls.Clear();
            _initialValues.Clear();
            _hasChanges = false;
        }

        /// <summary>
        /// 変更状態をリセット
        /// </summary>
        public void ResetChanges()
        {
            _hasChanges = false;
            
            // 現在の値を初期値として再設定
            foreach (var control in _monitoredControls)
            {
                _initialValues[control] = GetControlValue(control);
            }
        }

        /// <summary>
        /// リソースの解放
        /// </summary>
        public void Dispose()
        {
            StopMonitoring();
        }

        /// <summary>
        /// コンテナ内のすべての入力コントロールを取得
        /// </summary>
        private List<Control> GetAllInputControls(Control container)
        {
            var controls = new List<Control>();

            foreach (Control control in container.Controls)
            {
                if (control is TextBox || control is CheckBox || control is ComboBox || control is RadioButton)
                {
                    controls.Add(control);
                }

                // 子コントロールも再帰的に検索
                if (control.HasChildren)
                {
                    controls.AddRange(GetAllInputControls(control));
                }
            }

            return controls;
        }

        /// <summary>
        /// コントロールの現在値を取得
        /// </summary>
        private string GetControlValue(Control control)
        {
            if (control is TextBox textBox)
                return textBox.Text;
            else if (control is CheckBox checkBox)
                return checkBox.Checked.ToString();
            else if (control is ComboBox comboBox)
                return comboBox.SelectedIndex.ToString();
            else if (control is RadioButton radioButton)
                return radioButton.Checked.ToString();
            else
                return string.Empty;
        }

        /// <summary>
        /// 変更イベントをアタッチ
        /// </summary>
        private void AttachChangeEvent(Control control)
        {
            if (control is TextBox textBox)
                textBox.TextChanged += Control_ValueChanged;
            else if (control is CheckBox checkBox)
                checkBox.CheckedChanged += Control_ValueChanged;
            else if (control is ComboBox comboBox)
                comboBox.SelectedIndexChanged += Control_ValueChanged;
            else if (control is RadioButton radioButton)
                radioButton.CheckedChanged += Control_ValueChanged;
        }

        /// <summary>
        /// 変更イベントをデタッチ
        /// </summary>
        private void DetachChangeEvent(Control control)
        {
            if (control is TextBox textBox)
                textBox.TextChanged -= Control_ValueChanged;
            else if (control is CheckBox checkBox)
                checkBox.CheckedChanged -= Control_ValueChanged;
            else if (control is ComboBox comboBox)
                comboBox.SelectedIndexChanged -= Control_ValueChanged;
            else if (control is RadioButton radioButton)
                radioButton.CheckedChanged -= Control_ValueChanged;
        }

        /// <summary>
        /// コントロールの値が変更されたときのイベントハンドラ
        /// </summary>
        private void Control_ValueChanged(object sender, EventArgs e)
        {
            if (_isLoading) return;

            var control = sender as Control;
            if (control == null || !_monitoredControls.Contains(control)) return;

            string currentValue = GetControlValue(control);
            string initialValue = _initialValues.ContainsKey(control) ? _initialValues[control] : string.Empty;

            // 初期値と現在値を比較
            if (currentValue != initialValue)
            {
                _hasChanges = true;
            }
            else
            {
                // すべてのコントロールの値が初期値と同じかチェック
                _hasChanges = _monitoredControls.Any(c => 
                {
                    string currVal = GetControlValue(c);
                    string initVal = _initialValues.ContainsKey(c) ? _initialValues[c] : string.Empty;
                    return currVal != initVal;
                });
            }
        }
    }
}