DataGrid에 System.Data.DataTable Binding하면서 Cell마다 Style을 줄 수 있을까요?

안녕하세요.

저는 회사에서 동적 스키마구조를 가진 데이터를 다루는 프로그램을 개발하고 있습니다.

ChatGPT에게 문의해본 결과, 동적 스키마를 갖고 있는 데이터를 다루는 .NET에서 다루는 방법은 DataTable, JObject, ExpendoObject, Dictionary 이렇게 4가지가 있다고 합니다.

그 중에서 DataTable이 가장 무겁기는 하나, Memory에 Load된 이후에 데이터를 다룰 때는 가장 편리하기 때문에 DataTable을 사용하기로 했습니다.


DataGrid.ItemsSource 에 DataTable을 Binding하고 AutoGenerateColumn = True를 하면 Column도 자동생성해주면서, 데이터도 모두 보일 것입니다.

단순하게 이것을 원하는 것은 아닙니다.

Cell마다 값을 판단하여 Style을 다르게 가져가고 싶습니다.

현재하고 있는 프로젝트는 제가 최선을 다해서 MVVM을 지키는 프로젝트입니다.

그러면 바인딩 되는 Cell Value를 판단하는 코드는 View가 아닌 ViewModel에 존재해야합니다.

ItemsSource가 되는 DataTable도 ViewModel에 있습니다.

Converter를 사용할 경우, Converter는 WPF에 해당하는 FCL이므로, Converter Class를 정의할 때 Binding되는 ViewModel의 Class를 참조해야합니다.

Class를 참조한다는 것은, Namespace를 참조해야한다는 뜻이고, Namespace를 참조한다는 것은 다른 프로젝트에 있다면 프로젝트도 참조를 걸어야 한다는 뜻입니다.

class를 참조해야 거기에 있는 소스코드를 converter에서 이용할 수 있으니까요.

순수한 WPF의 기능이 제가 만든 Boilerplate의 Model을 참조하는 형태가 됩니다.

저는 이 부분이 MVVM을 완벽하게 지키지 못하는 부분이라고 생각하는 쪽이기 때문에 Converter를 이용하면 매우 쉽겠지만 그렇게 하지 않고 다른 방법을 찾는 중에 있습니다.


그래서 ChatGPT가 추천해준 방법은, DataGrid.ItemsSource에 DataTable을 Binding 하지말고, Dictionary형태로 변환해서 바인딩하라고 합니다.

Dictionary의 Key값은 ColumnName이 되고, Value값은 실제 값과 Style 값이 들어있는 Class가 될 것입니다.

그리고 Dictionary자체가 하나의 Row가 될테니, DataTable은 2차원 배열형태이므로, Dictionary를 한번더 감싸주어야 합니다.

아래와 같을 것입니다.

public partial class CellItem : ObservableObject
{
    [ObservableProperty]
    public string cellValue;

    [ObservableProperty]
    public string background;
}
public partial class RowItem : ObservableObject
{
    [ObservableProperty]
    public Dictionary<string, CellItem> row;
}
public partial class ViewModel : ObservableObject
{
    public ViewModel
    {
        Source = new();
    }

    [ObservableProperty]
    ObservableCollection<RowItem> source;
}

따라서 XAML에서는

<DataGrid ItemsSource={Binding Source}
          AutoGenerateColumn=True
          .../>

위와 같이 될 것입니다.

그러나, 이렇게 하면 AutoGenerateColumn 기능으로 Column을 만들어주는 규칙을 위반하는 것이기 때문에 저의 의도대로 코드가 동작하지 않습니다.

위와 같은 코드는 DataGrid가 Column을 만들면 Row라는 Column을 1개 만들어 줄 것입니다.
아니면 오류가 나거나요. 아직 해보지는 않았습니다.

이 때 Column을 Generate해주는 이벤트를 MVVM을 헤치지 않으면서 View쪽에서 정의할 수 있을까요?
Dictionary의 key값을 참고해서 Column을 Generate하고, 그 Generate된 Column들은 위의 CellItem.Background Property를 참고해서 안에 ‘#FF123456’ 같은 hex값이 들어있다면 Converter를 이용해서 SoildColorBrush로 바꿔주고 싶습니다. (View단에서 CellItem을 참조하지 않고 이게 되는지도 모르겠습니다.)

이게 가능한것지 확신들지 않습니다…

혹시 경험이 있으신 분이 계시다면 고견 부탁드리겠습니다.

읽어주셔서 감사합니다.

3 Likes

비슷한 목적으로 작업했을 때, Behavior 를 통해 VM에 인터페이스 걸고 인터페이스로 behavior에 의존속성 추가하고 바인딩을 걸어 특정 이벤트에서 셀 값에 따른 스타일 제어를 인터페이스를 통해 판단하고 제어되도록 했었습니다. 이 방법 어떠실까요.

3 Likes

스타일에 대한 코드인데 ViewModel에 있어야 하나요? 저는 View에 있어야 한다고 생각합니다.

2 Likes

아 마침 생각했던 방법 중에 하나였습니다.

글을 정리해서 게시하고 2가지 방법이 생각났는데 하나가 말씀하신 AttachedProperty를 이용하는 것이고, 나머지 하나는 DataTable의 Cell에 그냥 String값이 아니라 class를 집어넣는건 어떨지 고려해봤습니다.

하나씩 해보면서 일단 되는 방식으로 해보려고 하겠습니다.

결과는 여기 다시 남겨보겠습니다.

1 Like

저도 스타일에 대한 코드는 당연히 View에 있어야한다고 생각합니다.

하지만 현재 구조가 그렇게 안되는 상황이고, 제가 말씀드린 Style은 현재 Background 정도인데 string으로 #FF123456 처럼 주고 converter를 통해 변환하려고 합니다.

아니면 혹시 DataGrid와 바인딩되는 ItemsSource로 DataTable을 사용하면서 각 Cell마다 다른 스타일을 가져가게 설정할 수 있을까요? 각 Column이 아니라 각 Cell마다 다른 Style 입니다.

1 Like

그렇담 오히려 datatable을 한번 더 감싸는 방법은 어떨까요?
visualization을 유도해서 binding 할 수 있는 방법으로요.
보통은, style에 관한건 view에 가야 맞지만 binding이 필요한 부분이 있을 것 같습니다.

DataTable의 모든 기능을 사용하지 말고
AsEnumerable 등을 활용해서 DataRow의 컬렉션만 가져온다거나 하는 보완책도 있을 것 같습니다.

1 Like

DataTable이 2차원 배열형태이기 때문에 Collection 형태로 변경할 경우 Collection 안에 Collection이 들어간 Nested Collection 형태가 됩니다.

이 상태에서 AutoGenerateColumn 적용이 어떻게 되는지 몰라서 Custom하게 적용하려는 방법도 찾고 있었는데 원하는 게 잘 안나오네요…ㅠㅠ

1 Like

흠… CellStyleSelector 같은 것은 DataGrid에 없는 것 같고 또 CellStyle 스타일에 DataTrigger를 걸어 받는 값이 DataRowView여서 안되겠네요. 지금은 딱히 아이디어는 없습니다.

1 Like

일단, TestCode는 성공했습니다.

실제로 적용이 가능할지 지금부터 테스트 해보겠습니다…

3 Likes

의도한대로 잘 동작합니다.

DataGrid.AutoGeneratingColumn Event를 사용해보면서, FrameworkElementFactory를 통해 Code-Behind로 WPF 컨트롤을 생성하고 제어할 수 있다는 좋은 경험을 하고 가네요.

2 Likes

잘 동작합니다.

2 Likes