using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using ChatworkBulkSender.Utils;
namespace ChatworkBulkSender.UserControls
{
/// <summary>
/// マスタ画面DGVコントロールの基底クラス。
/// 検索・更新・ソートの機能を提供する。
/// </summary>
/// <typeparam name="TDto"></typeparam>
/// <typeparam name="TDao"></typeparam>
/// <typeparam name="TForm"></typeparam>
[DesignerCategory("")] // デザイナーで表示しない
public abstract class AbstractBaseMasterDgvControl<TDto, TDao, TForm> : AbstractUserControl
where TDto : class
where TDao : class, new()
where TForm : Form
{
private IContainer components = null;
// Dgv
protected DataGridView _dgv = null;
// データリスト
protected List<TDto> _dataList = null;
// ソート可能なリスト
protected SortableBindingListUtil<TDto> _sortableList = null;
// 編集フォーム
protected TForm _individualEditForm = null;
// 検索条件
protected Dictionary<string, object> _searchCriteria = new Dictionary<string, object>();
// 編集ボタン列索引
protected abstract int EditButtonColumnIndex { get; }
// ソート可能な列数の最大索引
protected abstract int SortableColumnMaxIndex { get; }
// 初期ソートの列名
protected abstract string InitialSortColumnName { get; }
// 最終更新時刻
private DateTime? _lastLoadTime = null;
// 更新日時の元データを保持するディクショナリ (主キー -> 更新日時)
// メモリ効率化: 表示中のデータのみ保持、最大1000件まで
protected Dictionary<object, DateTime?> _originalUpdateTimes = new Dictionary<object, DateTime?>();
private const int MAX_CACHED_ITEMS = 1000;
// 初回ロード済みフラグ
private bool _isInitialLoadCompleted = false;
/// <summary>
/// 選択行の取得
/// </summary>
/// <returns></returns>
public TDto GetSelectedItem()
{
if (_dgv.CurrentRow != null)
{
return _dgv.CurrentRow.DataBoundItem as TDto;
}
// 選択行がない場合、nullを返す
return null;
}
/// <summary>
/// セルの選択をクリア
/// </summary>
public virtual void ClearSelection()
{
if (_dgv != null)
{
_dgv.ClearSelection();
_dgv.CurrentCell = null;
}
}
protected AbstractBaseMasterDgvControl()
{
// InitializeComponent();
// 派生クラスで呼び出し
}
/// <summary>
/// 継承先で実装。
/// 顧客データを取得する。
/// </summary>
/// <returns></returns>
protected abstract List<TDto> GetDataFromDao();
/// <summary>
/// 検索条件を取得し、検索を行う。
/// </summary>
/// <param name="criteria"></param>
public virtual void Search(Dictionary<string, object> criteria)
{
_searchCriteria = criteria ?? new Dictionary<string, object>();
// ソート可能なリストを再作成する
_sortableList = new SortableBindingListUtil<TDto>(GetDataFromDao());
_dgv.DataSource = _sortableList;
}
protected virtual void LoadData()
{
try
{
// データソースが設定されているか
if (_dgv.DataSource != null)
{
// 最新のデータリストを取得
_dataList = GetDataFromDao();
// 取得後、現在時刻に更新する
_lastLoadTime = DateTime.Now;
// 更新日時の元データを最新に更新
// エラー後のRefreshDataでも最新のデータで比較できるようにする
StoreOriginalUpdateTimes(_dataList);
if (!_isInitialLoadCompleted)
{
// 大量データ対策: MAX_CACHED_ITEMS件を超える場合は警告
if (_dataList.Count > MAX_CACHED_ITEMS * 2)
{
System.Diagnostics.Debug.WriteLine($"警告: データ件数が多い ({_dataList.Count}件)。パフォーマンスに影響する可能性があります。");
}
_isInitialLoadCompleted = true;
}
// ソート可能なリストを再作成する
_sortableList = new SortableBindingListUtil<TDto>(_dataList);
// 初期ソートを設定
if (!string.IsNullOrEmpty(InitialSortColumnName))
{
SetUpSortColumn(InitialSortColumnName);
}
_dgv.DataSource = _sortableList;
}
else
{
// 初回以降のロード時はソート状態を保持する
RefreshData();
}
}
catch(Exception ex)
{
MessageBoxUtil.ShowErr($"データの読み込み中にエラーが発生しました。\n\n{ex.Message}", "エラー");
}
}
/// <summary>
/// 最新のデータを取得した後、最終更新日時を現在時刻に設定する。
/// </summary>
protected virtual void RefreshData()
{
DataGridViewRefresherUtil.RefreshWithState<TDto>(
_dgv,
() => GetDataFromDao(),
(list) =>
{
_dataList = list;
_lastLoadTime = DateTime.Now;
// 更新日時の元データを最新に更新
// エラー後のRefreshDataでも最新のデータで比較できるようにする
StoreOriginalUpdateTimes(list);
if (!_isInitialLoadCompleted)
{
_isInitialLoadCompleted = true;
}
_sortableList = new SortableBindingListUtil<TDto>(list);
return _sortableList;
}
);
}
/// <summary>
/// 新規アイテムの更新日時のみを追加(既存アイテムの更新日時は変更しない)
/// </summary>
protected virtual void UpdateOriginalUpdateTimesForNewItems(List<TDto> list)
{
// 継承先で実装
}
/// <summary>
/// 各DTOの更新日時を保存する(継承先でオーバーライド)
/// メモリ効率化のため、表示中のデータのみ保存
/// </summary>
protected virtual void StoreOriginalUpdateTimes(List<TDto> list)
{
// 継承先で実装
// 注意: 大量データの場合は表示中のページのみ保存することを推奨
}
/// <summary>
/// 指定されたDTOの元の更新日時を取得する(継承先でオーバーライド)
/// </summary>
protected virtual DateTime? GetOriginalUpdateTime(TDto dto)
{
// 継承先で実装
return null;
}
protected virtual bool IsEditFormOpen()
{
return _individualEditForm != null &&
!_individualEditForm.IsDisposed &&
_individualEditForm.Visible;
}
protected abstract bool HasUpdateAfter(DateTime? lastLoadTime);
protected virtual void InitializeDgv()
{
if (_dgv == null) { return; }
// 列の自動生成を行わない
_dgv.AutoGenerateColumns = false;
// 行ヘッダ非表示
_dgv.RowHeadersVisible = false;
// ユーザーによる行の高さ変更を禁止
_dgv.AllowUserToResizeRows = false;
// 編集不可
_dgv.ReadOnly = true;
// 行単位で選択する形式とする
_dgv.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
// 複数選択不可
_dgv.MultiSelect = false;
_dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
_dgv.CellClick += Dgv_CellClick;
_dgv.ColumnHeaderMouseDoubleClick += Dgv_ColumnHeaderMouseDoubleClick;
}
protected virtual void Dgv_CellClick(object sender, DataGridViewCellEventArgs e)
{
// ヘッダー行の場合はスキップ
if (e.RowIndex < 0) { return; }
// 編集ボタン列かチェック(列名でも確認)
bool isEditColumn = e.ColumnIndex == EditButtonColumnIndex;
if (!isEditColumn && _dgv.Columns[e.ColumnIndex].Name == "BtnEdit")
{
isEditColumn = true;
}
if (!isEditColumn) { return; }
var selectedItem = _dgv.Rows[e.RowIndex].DataBoundItem as TDto;
if (selectedItem == null)
{
MessageBoxUtil.ShowErr("選択されたデータが取得できませんでした。","エラー");
return;
}
ShowEditForm(selectedItem);
}
/// <summary>
/// 編集フォームを表示する。
/// 継承先で実装。
/// </summary>
/// <param name="selectedItem"></param>
protected abstract void ShowEditForm(TDto selectedItem);
protected virtual void Dgv_ColumnHeaderMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.RowIndex == -1 && e.ColumnIndex >= 0 && e.ColumnIndex <= SortableColumnMaxIndex)
{
var propertyName = _dgv.Columns[e.ColumnIndex].DataPropertyName;
SetUpSortColumn(propertyName);
}
}
protected virtual void SetUpSortColumn(string propertyName)
{
if (!string.IsNullOrEmpty(propertyName))
{
var pd = TypeDescriptor.GetProperties(typeof(TDto))[propertyName];
if (pd != null && _sortableList != null)
{
_sortableList.ApplySort(pd);
}
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
components?.Dispose();
if (_individualEditForm != null && !_individualEditForm.IsDisposed)
{
_individualEditForm.Close();
_individualEditForm.Dispose();
}
}
base.Dispose(disposing);
}
}
}