본문 바로가기
Development/.NET

커스텀 체크박스 열 추가 (제네릭 타입)

by 박수무당벌레 2024. 11. 28.

지난 포스팅에서 그냥 체크박스 열을 추가해버리면 된다 라는 식의 해결책을 언급했었는데, 실제로 간단히 구현해 보았습니다.

(실제 행 선택 로직은 추후 업데이트 예정)

관련글 : https://waydd.tistory.com/3

 

 

1. 제네릭 bool 처리

 

데이터베이스에서 GridView로 데이터를 바인딩할 때는 bool 타입이 없는 경우가 많습니다.
그래서 아래 코에서는 true/false 값을 제네릭으로 처리할 수 있도록 구성했습니다.

또한, GridViewColumnHelper를 사용하면 체크박스뿐 아니라 다른 RepositoryItem도 간단히 생성할 수 있습니다.

 

 

2. CustomUnboundColumnData 이벤트

 

체크박스 열을 UnboundColumn 으로 구성하면 데이터 표시 및 변경 시점에 따라 다음과 같이 이벤트를 처리할 수 있습니다.

  1. e.IsGetData == true
    • GridView가 데이터를 표시 해야 할 때 호출됩니다.
    • 열이 화면에 렌더링 되거나 RefreshRow가 호출될 때 발생합니다.
  2. e.IsSetData == true
    • 사용자가 Unbound 열의 데이터를 수정 할 경우 호출됩니다.
    • 이때 변경된 데이터를 직접 데이터소스에 반영 해야 합니다.
      → 기본적으로 UI와 데이터 간 자동 동기화가 없으므로, 별도의 로직 구현이 필요합니다.
※ UnboundColumn 사용시 주의사항
- FieldName : 열의 필드 이름을 반드시 지정
- UnboundType : 명시적으로 설정 필요
- FieldName은 기본 데이터소스의 필드와 절대 중복되면 안됨
- 참고 : https://supportcenter.devexpress.com/ticket/details/q552716/the-customunboundcolumndata-gridview-event-is-not-firing

 


3. CellValueChanging 이벤트

  • 편집 종료 직전, 편집기가 닫히기 전에 발생
  • 이 시점에서 데이터소스를 직접 변경하는 방식으로 동작시킬 수 있습니다.

 

위 구조로 구현한 코드는 아래와 같으며, 체크박스 UI는 자유롭게 다루면서도 실제 행 선택 로직은 이후에 연결할 수 있는 확장성을 확보할 수 있습니다.

 

 

코드 구현 )

class GridViewColumnHelper {
    protected readonly GridView _gridView;

    public GridViewColumnHelper(GridView gridView) {
        _gridView = gridView ?? throw new ArgumentNullException(nameof(gridView));
    }

    public void AddRepositoryColumn(string fieldName, string caption, RepositoryItem repositoryItem, int visibleIndex) {
        if (string.IsNullOrEmpty(fieldName))
            throw new ArgumentException("Field name cannot be null or empty.", nameof(fieldName));
        if (repositoryItem == null)
            throw new ArgumentNullException(nameof(repositoryItem));

        var gridColumn = _gridView.Columns.AddField(fieldName);
        gridColumn.Caption = caption;
        gridColumn.VisibleIndex = visibleIndex;
        gridColumn.ColumnEdit = repositoryItem;
        gridColumn.Visible = true;
    }

    public T CreateRepositoryItem<T>() where T : RepositoryItem, new() {
        return new T();
    }
}
class CheckBoxColumnHelper<T> : GridViewColumnHelper {
    private readonly Dictionary<int, T> _checkboxStates = new Dictionary<int, T>();
    private readonly Func<bool, T> _valueTrueConverter;
    private readonly Func<bool, T> _valueFalseConverter;
    private readonly Func<T, bool> _stateToBoolConverter;
    private string _checkBoxFieldName;

    public CheckBoxColumnHelper(GridView gridView, Func<bool, T> valueTrueConverter, Func<bool, T> valueFalseConverter, Func<T, bool> stateToBoolConverter)
        : base(gridView) {
        _valueTrueConverter = valueTrueConverter ?? throw new ArgumentNullException(nameof(valueTrueConverter));
        _valueFalseConverter = valueFalseConverter ?? throw new ArgumentNullException(nameof(valueFalseConverter));
        _stateToBoolConverter = stateToBoolConverter ?? throw new ArgumentNullException(nameof(stateToBoolConverter));

        _gridView.CustomUnboundColumnData += GridView_CustomUnboundColumnData;
        _gridView.CellValueChanging += GridView_CellValueChanging;

        // MultiSelection 모드 활성화
        _gridView.OptionsSelection.MultiSelect = true;
        _gridView.OptionsSelection.MultiSelectMode = GridMultiSelectMode.RowSelect;
    }

    public void AddCheckBoxColumn(string fieldName, string caption, int visibleIndex) {
        _checkBoxFieldName = fieldName;

        RepositoryItemCheckEdit checkEdit = CreateRepositoryItem<RepositoryItemCheckEdit>();
        checkEdit.ValueChecked = _valueTrueConverter(true);
        checkEdit.ValueUnchecked = _valueFalseConverter(false);
        checkEdit.AllowGrayed = false;

        AddRepositoryColumn(fieldName, caption, checkEdit, visibleIndex);
        _gridView.Columns[fieldName].UnboundType = DevExpress.Data.UnboundColumnType.Object; // 제네릭 타입을 사용하므로 Object
        _gridView.Columns[fieldName].UnboundDataType = typeof(T);
    }

    private void GridView_CustomUnboundColumnData(object sender, DevExpress.XtraGrid.Views.Base.CustomColumnDataEventArgs e) {
        if (e.Column.FieldName == _checkBoxFieldName) {
            if (e.IsGetData) {
                e.Value = _checkboxStates.ContainsKey(e.ListSourceRowIndex)
                    ? _checkboxStates[e.ListSourceRowIndex]
                    : _valueFalseConverter(false); // 기본값
            } else if (e.IsSetData) {
                _checkboxStates[e.ListSourceRowIndex] = (T)e.Value;
            }
        }
    }

    private void GridView_CellValueChanging(object sender, DevExpress.XtraGrid.Views.Base.CellValueChangedEventArgs e) {
        if (e.Column.FieldName == _checkBoxFieldName) {
            _gridView.SetRowCellValue(e.RowHandle, _checkBoxFieldName, (T)e.Value);
            _gridView.RefreshRow(e.RowHandle);
        }
    }
}

 

사용 예시)

var checkBoxHelper = new CheckBoxColumnHelper<int>(
    gridView1,
    valueTrueConverter: isChecked => 1,
    valueFalseConverter: isChecked => 0,
    stateToBoolConverter: state => state == 1
    );

checkBoxHelper.AddCheckBoxColumn("Selection", "Int Column", 0);

 

참일때 값, 거짓일때의 값, 참일때의 수식(함수) 를 매개변수로 받습니다.