UserControls/SendContentControl.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Globalization;
using System.Windows.Forms;
using ChatworkBulkSender.Utils;
using ChatworkBulkSender.Daos;
using ChatworkBulkSender.Dtos;

namespace ChatworkBulkSender.UserControls
{
    /// <summary>
    /// 水平 or 垂直 にレイアウトを変更する
    /// </summary>
    public enum LayoutMode
    {
        Horizontal,
        Vertical
    }

    public partial class SendContentControl : AbstractUserControl
    {
        // 送信対象一覧
        private List<CustomerMasterDto> _senderList;

        // 初期の表示設定は水平
        private LayoutMode _layoutMode = LayoutMode.Horizontal;
        
        // バリデーション用
        private ErrorProvider errorProvider;

        public LayoutMode LayoutMode
        {
            get
            {
                return _layoutMode;
            }

            set
            {
                _layoutMode = value;
                ApplyLayout();
            }
        }

        public SendContentControl()
        {
            InitializeComponent();
            if (IsInDesignMode()) return;
            Init();
            InitializeValidation();
            LoadCustomerList();
            ToggleManualSettingPanel();
        }

        private void InitializeValidation()
        {
            // ErrorProviderの初期化
            errorProvider = new ErrorProvider();
            errorProvider.BlinkStyle = ErrorBlinkStyle.NeverBlink;

            // MaxLength設定
            txtContents.MaxLength = Constants.PATTERN_TEMPLATE_MAX_LENGTH;

            // イベントハンドラーの登録
            txtContents.TextChanged += TxtContents_TextChanged;
        }

        private void TxtContents_TextChanged(object sender, EventArgs e)
        {
            ValidateContents();
        }

        public bool ValidateContents()
        {
            if (string.IsNullOrWhiteSpace(txtContents.Text))
            {
                errorProvider.SetError(txtContents, "定型文を入力してください。");
                return false;
            }
            else if (txtContents.TextLength > Constants.PATTERN_TEMPLATE_MAX_LENGTH)
            {
                errorProvider.SetError(txtContents, 
                    ValidationHelperUtil.GetLengthErrorMessage("定型文", txtContents.TextLength, Constants.PATTERN_TEMPLATE_MAX_LENGTH));
                return false;
            }
            else
            {
                errorProvider.SetError(txtContents, "");
                return true;
            }
        }

        public bool ValidateAll()
        {
            return ValidateContents();
        }

        /// <summary>
        /// 「定期送信」「不定期送信」に応じて表示レイアウトを調整する
        /// </summary>
        /// <param name="sendType"></param>
        public void ChangeMode(Constants.SEND_TYPE sendType)
        {
            if(sendType== Constants.SEND_TYPE.ADHOC_SENDING)
            {
                rdoAutomaticSelection.Visible = false;
                rdoManualSelection.Visible = false;
                pnlManualSetting.Location = new Point(this.Width - 688, 24);
                pnlManualSetting.Size = new Size(685, txtContents.Height);
                // 送信対象は手動設定とする
                rdoManualSelection.Checked = true;
            }
        }

        public Constants.DESTINAION_SELECT_TYPE GetTypeOfDestinationSelect()
        {
            if (rdoAutomaticSelection.Checked) return Constants.DESTINAION_SELECT_TYPE.AUTO;
            if (rdoManualSelection.Checked) return Constants.DESTINAION_SELECT_TYPE.MANUAL;
            return Constants.DESTINAION_SELECT_TYPE.NOT_SELECTED;
        }

        /// <summary>
        /// 現在「送信対象」グリッドでチェックの付いている顧客だけを返す
        /// </summary>
        public List<CustomerMasterDto> GetDestinationList()
        {
            // 自動選択の場合
            if (rdoAutomaticSelection.Checked)
            {
                // 送信対象一覧をそのまま返す
                // ※PDF有無による自動選択はこの関数呼び出しもとで行う
                return _senderList
                   .OrderBy(c => c.SortOrder)
                    .Select(c => new CustomerMasterDto(c))  // 新規インスタンスとして返す
                   .ToList();
            }
            // 手動選択の場合
            if (rdoManualSelection.Checked)
            {
                // 送信対象一覧のうちチェックがついたデータのみ返す
                return _senderList
                    .Where(c => c.IsSelected)
                    .OrderBy(c => c.SortOrder)
                    // 各要素を新規インスタンスにコピーしてからリスト化
                    .Select(c => new CustomerMasterDto(c))  // 新規インスタンスとして返す
                    .ToList();
            }
            return null;
        }

        public string GetContents()
        {
            return txtContents.Text;
        }

        /// <summary>
        /// 送信内容をセットする
        /// </summary>
        /// <param name="sendContents"></param>
        public void SetSendContents(string sendContents)
        {
            txtContents.Text = sendContents;
        }
        /// <summary>
        /// 送信対象をセットする
        /// </summary>
        /// <param name="header"></param>
        /// <param name="details"></param>
        public void SetSendTargetOnlyFailed(List<SendHistoryDetailDto>  details)
        {
            // 自動選択・手動選択(今回は個別にチェックを付けるため、手動選択固定とする)
            rdoManualSelection.Checked = true;
            ToggleManualSettingPanel();
            // 送信対象選択
            var failedNumbers = new HashSet<int>(
             details
                 .Where(d => d.Success == Constants.SEND_RESULT.FAILURE)
                 .Select(d => d.ManagementNumber));
            foreach (var cust in _senderList)
            {
                cust.IsSelected = failedNumbers.Contains(cust.ManagementNumber);
            }
            dgvDestinationList.Refresh();
        }


        /// <summary>
        /// レイアウトを変更する
        /// </summary>
        ///
        private void ApplyLayout()
        {
            if (_layoutMode == LayoutMode.Vertical)
            {
                this.SuspendLayout();

                try
                {
                    // Anchorを設定
                    label2.Anchor = AnchorStyles.Top | AnchorStyles.Left;
                    rdoAutomaticSelection.Anchor = AnchorStyles.Top | AnchorStyles.Left;
                    rdoManualSelection.Anchor = AnchorStyles.Top | AnchorStyles.Left;
                    pnlManualSetting.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom;

                    // 送信内容の高さを固定
                    txtContents.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
                    txtContents.Size = new Size(this.Width - 20, 200);

                    // 送信対象を下に配置
                    int baseY = txtContents.Bottom + 10;

                    label2.Location = new Point(0, baseY);
                    label3.Anchor = AnchorStyles.Top | AnchorStyles.Left;
                    label6.Anchor = AnchorStyles.Top | AnchorStyles.Left;

                    chkSelectAll.Anchor = AnchorStyles.Top | AnchorStyles.Right;

                    rdoAutomaticSelection.Location = new Point(25, baseY + 30);
                    rdoManualSelection.Location = new Point(25, baseY + 56);
                    pnlManualSetting.Location = new Point(8, baseY + 82);
                    pnlManualSetting.Size = new Size(this.Width - 16, this.Height - baseY - 92);

                    dgvDestinationList.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;

                    // データ行の横幅をDGVの横幅に合わせた後、列の比率を調整する
                    foreach (DataGridViewColumn column in dgvDestinationList.Columns)
                    {
                        // 顧客名の列のみ横幅を広く設定する
                        if (column.DataPropertyName == nameof(CustomerMasterDto.CustomerName))
                        {
                            column.Width = 400;
                        }
                        else
                        {
                            column.Width = 90;
                        }
                    }
                }
                finally
                {
                    this.ResumeLayout();
                }
            }
        }

        private void Init()
        {
            // グリッド初期化
            // 行ヘッダ非表示
            dgvDestinationList.RowHeadersVisible = false;
            // ユーザーによる行の高さ変更を禁止
            dgvDestinationList.AllowUserToResizeRows = false;
        }
        /// <summary>
        /// パターン情報をセットする
        /// </summary>
        /// <param name="patternDto"></param>
        public void SetPatternData(PatternMasterDto patternDto)
        {
            // 定型文を設定
            txtContents.Text = patternDto.TemplateText;

            // 送信対象モードを設定
            var mode = (Constants.DESTINAION_SELECT_TYPE)patternDto.Target;
            rdoAutomaticSelection.Checked = mode == Constants.DESTINAION_SELECT_TYPE.AUTO;
            rdoManualSelection.Checked = mode == Constants.DESTINAION_SELECT_TYPE.MANUAL;

            // 自動モードの場合、送信対象選択グリッドのチェックをクリアする。
            if (rdoAutomaticSelection.Checked)
            {
                if (_senderList != null)
                {
                    foreach (var cust in _senderList)
                    {
                        cust.IsSelected = false;
                    }
                    dgvDestinationList.Refresh();
                }
            }
            // 手動モードの場合、送信対象選択グリッドで該当データをチェックした状態とする。
            if (rdoManualSelection.Checked)
            {
                if (_senderList != null)
                {
                    // パターンに紐づく管理番号の集合を作成
                    var selectedSet = new HashSet<int>(
                        patternDto.Customers.Select(c => c.ManagementNumber));

                    // すべての行に対して IsSelected を設定
                    foreach (var cust in _senderList)
                    {
                        cust.IsSelected = selectedSet.Contains(cust.ManagementNumber);
                    }

                    // グリッドを再描画
                    dgvDestinationList.Refresh();
                }
            }
        }

        private void rdoAutomaticSelection_CheckedChanged(object sender, EventArgs e)
        {
            ToggleManualSettingPanel();
        }

        private void rdoManualSelection_CheckedChanged(object sender, EventArgs e)
        {
            ToggleManualSettingPanel();
        }

        /// <summary>
        /// 「手動選択」で使用するパラメーターのEnabledを切り替える
        /// </summary>
        private void ToggleManualSettingPanel()
        {
            pnlManualSetting.Enabled = rdoManualSelection.Checked;
            dgvDestinationList.Enabled = rdoManualSelection.Checked;
        }

        /// <summary>
        /// DBより顧客情報を取得してグリッドに表示する。
        /// </summary>
        private void LoadCustomerList()
        {
            // データ取得
            var dao = new CustomerMasterDao();
            _senderList = dao.GetAll();

            // 表示する列を定義
            dgvDestinationList.AutoGenerateColumns = false;
            dgvDestinationList.Columns.Clear();
            dgvDestinationList.Columns.Add(new DataGridViewTextBoxColumn
            {
                DataPropertyName = nameof(CustomerMasterDto.ManagementNumber),
                HeaderText = "管理番号",
                Width = 90,
                ReadOnly = true
            });
            dgvDestinationList.Columns.Add(new DataGridViewTextBoxColumn
            {
                DataPropertyName = nameof(CustomerMasterDto.CustomerName),
                HeaderText = "顧客名",
                Width = 400,
                ReadOnly = true
            });
            dgvDestinationList.Columns.Add(new DataGridViewCheckBoxColumn
            {
                DataPropertyName = nameof(CustomerMasterDto.IsSelected),
                HeaderText = "送信対象",
                Width = 90
            });

            // 表示(初期表示は全件)
            dgvDestinationList.DataSource = _senderList;
        }

        /// <summary>
        /// 条件に応じてフィルターした顧客情報をグリッドに表示。
        ///  管理番号:前方一致
        ///  顧客名:部分一致
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnFilter_Click(object sender, EventArgs e)
        {
            string managementNumberFilter = txtFilterManagementNumber.Text.Trim();
            string customerNameFilter = txtFilterCustomerName.Text.Trim();

            // カレントカルチャーの CompareInfo を取得
            var cmp = CultureInfo.CurrentCulture.CompareInfo;

            var filtered = _senderList.Where(c =>
                // 管理番号は前方一致 → IndexOf==0
                (string.IsNullOrEmpty(managementNumberFilter)
                    || cmp.IndexOf(
                           c.ManagementNumber.ToString(),   // 対象文字列
                           managementNumberFilter,          // 検索文字列
                           CompareOptions.IgnoreWidth       // 半角全角を無視
                       ) == 0)
                &&
                // 顧客名は部分一致 → IndexOf>=0
                (string.IsNullOrEmpty(customerNameFilter)
                    || cmp.IndexOf(
                           c.CustomerName,
                           customerNameFilter,
                           CompareOptions.IgnoreWidth      // 半角全角を無視
                         | CompareOptions.IgnoreCase      // 英字の大小も無視
                       ) >= 0)
            ).ToList();

            dgvDestinationList.DataSource = filtered;
        }

        private void btnRemoveFilter_Click(object sender, EventArgs e)
        {
            // テキストボックスをクリア
            txtFilterManagementNumber.Clear();
            txtFilterCustomerName.Clear();

            // 全件を再バインド
            dgvDestinationList.DataSource = _senderList;
        }

        /// <summary>
        /// 現在グリッドに表示されている内容に対してのみ、チェック状態を一括操作する。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void chkSelectAll_CheckedChanged(object sender, EventArgs e)
        {
            bool isChecked = chkSelectAll.Checked;

            if (dgvDestinationList.DataSource is List<CustomerMasterDto> list)
            {
                foreach (var dto in list)
                    dto.IsSelected = isChecked;

                dgvDestinationList.Refresh();
            }
        }

        /// <summary>
        /// 管理番号テキストボックス Enter押下でフィルター処理を実行
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void txtFilterManagementNumber_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                btnFilter.PerformClick();
            }
        }

        /// <summary>
        /// 顧客名テキストボックス Enter押下でフィルター処理を実行
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void txtFilterCustomerName_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                btnFilter.PerformClick();
            }
        }
    }
}