WPF DataGrid를 가변 개수의 열에 바인딩하려면 어떻게해야합니까?


124

내 WPF 응용 프로그램은 매번 다른 수의 열을 가질 수있는 데이터 집합을 생성합니다. 출력에는 형식을 적용하는 데 사용할 각 열에 대한 설명이 포함됩니다. 출력의 단순화 된 버전은 다음과 같을 수 있습니다.

class Data
{
    IList<ColumnDescription> ColumnDescriptions { get; set; }
    string[][] Rows { get; set; }
}

이 클래스는 WPF DataGrid에서 DataContext로 설정되지만 실제로는 프로그래밍 방식으로 열을 만듭니다.

for (int i = 0; i < data.ColumnDescriptions.Count; i++)
{
    dataGrid.Columns.Add(new DataGridTextColumn
    {
        Header = data.ColumnDescriptions[i].Name,
        Binding = new Binding(string.Format("[{0}]", i))
    });
}

대신이 코드를 XAML 파일의 데이터 바인딩으로 바꿀 수있는 방법이 있습니까?

답변:


127

다음은 DataGrid의 열 바인딩에 대한 해결 방법입니다. Columns 속성이 ReadOnly이기 때문에 모든 사람이 알아 차린 것처럼 BindableColumns라는 연결된 속성을 만들었습니다.이 속성은 CollectionChanged 이벤트를 통해 컬렉션이 변경 될 때마다 DataGrid의 Columns를 업데이트합니다.

이 DataGridColumn 컬렉션이 있다면

public ObservableCollection<DataGridColumn> ColumnCollection
{
    get;
    private set;
}

그런 다음 BindableColumns를 다음과 같이 ColumnCollection에 바인딩 할 수 있습니다.

<DataGrid Name="dataGrid"
          local:DataGridColumnsBehavior.BindableColumns="{Binding ColumnCollection}"
          AutoGenerateColumns="False"
          ...>

연결된 속성 BindableColumns

public class DataGridColumnsBehavior
{
    public static readonly DependencyProperty BindableColumnsProperty =
        DependencyProperty.RegisterAttached("BindableColumns",
                                            typeof(ObservableCollection<DataGridColumn>),
                                            typeof(DataGridColumnsBehavior),
                                            new UIPropertyMetadata(null, BindableColumnsPropertyChanged));
    private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        DataGrid dataGrid = source as DataGrid;
        ObservableCollection<DataGridColumn> columns = e.NewValue as ObservableCollection<DataGridColumn>;
        dataGrid.Columns.Clear();
        if (columns == null)
        {
            return;
        }
        foreach (DataGridColumn column in columns)
        {
            dataGrid.Columns.Add(column);
        }
        columns.CollectionChanged += (sender, e2) =>
        {
            NotifyCollectionChangedEventArgs ne = e2 as NotifyCollectionChangedEventArgs;
            if (ne.Action == NotifyCollectionChangedAction.Reset)
            {
                dataGrid.Columns.Clear();
                foreach (DataGridColumn column in ne.NewItems)
                {
                    dataGrid.Columns.Add(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (DataGridColumn column in ne.NewItems)
                {
                    dataGrid.Columns.Add(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Move)
            {
                dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex);
            }
            else if (ne.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (DataGridColumn column in ne.OldItems)
                {
                    dataGrid.Columns.Remove(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Replace)
            {
                dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn;
            }
        };
    }
    public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value)
    {
        element.SetValue(BindableColumnsProperty, value);
    }
    public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element)
    {
        return (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty);
    }
}

1
MVVM 패턴에 대한 좋은 솔루션
WPFKK 2013

2
완벽한 솔루션! 아마도 BindableColumnsPropertyChanged에서 몇 가지 다른 작업을 수행해야합니다. 1. 액세스하기 전에 dataGrid에 null이 있는지 확인하고 DataGrid에만 바인딩에 대한 좋은 설명과 함께 예외를 throw합니다. 2. 메모리 누수를 방지하기 위해 e.OldValue에서 null을 확인하고 CollectionChanged 이벤트를 구독 취소합니다. 당신의 설득을 위해서.
Mike Eshva 2013-10-23

3
CollectionChanged열 컬렉션 의 이벤트에 이벤트 처리기를 등록하지만 등록을 취소하지 않습니다. 이렇게 하면 처음에 DataGrid를 포함하는 컨트롤 템플릿 DataGrid이 교체 된 경우에도 뷰 모델이 존재하는 동안은 계속 유지됩니다 . DataGrid더 이상 필요하지 않을 때 해당 이벤트 처리기를 다시 등록 취소하는 보장 된 방법이 있습니까?
OR Mapper

1
@OR 매퍼 : 이론적으로 존재하지만 작동하지 않습니다. WeakEventManager <ObservableCollection <DataGridColumn>, NotifyCollectionChangedEventArgs> .AddHandler (columns, "CollectionChanged", (s, ne) => {switch ....});
너무

6
그것은 해결책이 아닙니다. 주된 이유는 ViewModel에서 UI 클래스를 사용하고 있기 때문입니다. 또한 일부 페이지 전환을 만들려고 할 때 작동하지 않습니다. 이러한 dataGrid.Columns.Add(column)데이터 그리드를 사용 하여 페이지로 다시 전환하면 DataGrid의 Columns 컬렉션에 헤더 'X'가있는 DataGridColumn이 이미 존재한다는 기대 가 있습니다. DataGrid는 열을 공유 할 수 없으며 중복 열 인스턴스를 포함 할 수 없습니다.
Ruslan F.

19

나는 연구를 계속했으며이를 수행하는 합리적인 방법을 찾지 못했습니다. DataGrid의 Columns 속성은 바인딩 할 수있는 것이 아니라 실제로 읽기 전용입니다.

Bryan은 AutoGenerateColumns로 무언가를 할 수 있다고 제안했기 때문에 제가 살펴 보았습니다. 간단한 .Net 리플렉션을 사용하여 ItemsSource의 개체 속성을 확인하고 각 개체에 대한 열을 생성합니다. 아마도 각 열에 대한 속성을 사용하여 즉석에서 유형을 생성 할 수 있지만 이것은 궤도를 벗어나고 있습니다.

이 문제는 코드에서 쉽게 해결할 수 있으므로 데이터 컨텍스트가 새 열로 업데이트 될 때마다 호출하는 간단한 확장 메서드를 사용합니다.

public static void GenerateColumns(this DataGrid dataGrid, IEnumerable<ColumnSchema> columns)
{
    dataGrid.Columns.Clear();

    int index = 0;
    foreach (var column in columns)
    {
        dataGrid.Columns.Add(new DataGridTextColumn
        {
            Header = column.Name,
            Binding = new Binding(string.Format("[{0}]", index++))
        });
    }
}

// E.g. myGrid.GenerateColumns(schema);

1
가장 많이 투표되고 승인 된 솔루션은 최고의 솔루션이 아닙니다! 2 년 후 대답은 다음과 같습니다. msmvps.com/blogs/deborahk/archive/2011/01/23/…
Mikhail

4
아니, 그렇지 않습니다. 솔루션의 결과가 완전히 다르기 때문에 어쨌든 제공된 링크가 아닙니다!
321X

2
Mealek의 솔루션은 훨씬 더 보편적이며 ControlTemplates와 같이 C # 코드를 직접 사용하는 것이 문제가되는 상황에서 유용합니다.
EFraim 2011

@Mikhail는 깨진 링크
LuckyLikey

3
여기 링크입니다 : blogs.msmvps.com/deborahk/...
미하일

9

Deborah Kurata의 블로그 기사에서 DataGrid에 다양한 수의 열을 표시하는 방법에 대한 멋진 트릭을 찾았습니다.

MVVM을 사용하여 Silverlight 응용 프로그램에서 동적 열로 DataGrid 채우기

기본적으로 그녀 는 여러 열을 표시 하는을 만들고 그 안에 DataGridTemplateColumn넣습니다 ItemsControl.


1
프로그래밍 된 버전과 단연 같은 결과가 아닙니다 !!
321X

1
@ 321X : 관찰 된 차이점이 무엇인지 자세히 설명해 주시겠습니까 (그리고 이에 대한 모든 솔루션이 프로그래밍 되었으므로 프로그래밍 된 버전 이 의미하는 바도 지정 하십시오)?
OR Mapper

그것은 "페이지를 찾을 수 없습니다"라는
Jeson Martajaya

2
여기 링크입니다 blogs.msmvps.com/deborahk/...은
미하일

이것은 놀랍습니다!
Ravid Goldenberg 2016-06-29

6

다음과 같은 코드 한 줄을 사용하여 열을 동적으로 추가 할 수 있도록 관리했습니다.

MyItemsCollection.AddPropertyDescriptor(
    new DynamicPropertyDescriptor<User, int>("Age", x => x.Age));

질문과 관련하여 이것은 XAML 기반 솔루션이 아니며 (언급 한대로 합리적인 방법이 없기 때문에) DataGrid.Columns와 직접 작동하는 솔루션도 아닙니다. 실제로 ITypedList를 구현하는 DataGrid 바인딩 된 ItemsSource와 함께 작동하며 PropertyDescriptor 검색을위한 사용자 지정 메서드를 제공합니다. 코드의 한 곳에서 그리드에 대한 "데이터 행"및 "데이터 열"을 정의 할 수 있습니다.

만약 당신이 :

IList<string> ColumnNames { get; set; }
//dict.key is column name, dict.value is value
Dictionary<string, string> Rows { get; set; }

예를 들어 사용할 수 있습니다.

var descriptors= new List<PropertyDescriptor>();
//retrieve column name from preprepared list or retrieve from one of the items in dictionary
foreach(var columnName in ColumnNames)
    descriptors.Add(new DynamicPropertyDescriptor<Dictionary, string>(ColumnName, x => x[columnName]))
MyItemsCollection = new DynamicDataGridSource(Rows, descriptors) 

MyItemsCollection에 대한 바인딩을 사용하는 그리드는 해당 열로 채워집니다. 이러한 열은 런타임에 동적으로 수정 (새로 추가 또는 기존 제거) 할 수 있으며 그리드는 열 컬렉션을 자동으로 새로 고칩니다.

위에서 언급 한 DynamicPropertyDescriptor는 일반 PropertyDescriptor 로의 업그레이드 일 뿐이며 몇 가지 추가 옵션과 함께 강력한 형식의 열 정의를 제공합니다. 그렇지 않으면 DynamicDataGridSource는 기본 PropertyDescriptor로 잘 작동합니다.


3

구독 취소를 처리하는 승인 된 답변의 버전을 만들었습니다.

public class DataGridColumnsBehavior
{
    public static readonly DependencyProperty BindableColumnsProperty =
        DependencyProperty.RegisterAttached("BindableColumns",
                                            typeof(ObservableCollection<DataGridColumn>),
                                            typeof(DataGridColumnsBehavior),
                                            new UIPropertyMetadata(null, BindableColumnsPropertyChanged));

    /// <summary>Collection to store collection change handlers - to be able to unsubscribe later.</summary>
    private static readonly Dictionary<DataGrid, NotifyCollectionChangedEventHandler> _handlers;

    static DataGridColumnsBehavior()
    {
        _handlers = new Dictionary<DataGrid, NotifyCollectionChangedEventHandler>();
    }

    private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        DataGrid dataGrid = source as DataGrid;

        ObservableCollection<DataGridColumn> oldColumns = e.OldValue as ObservableCollection<DataGridColumn>;
        if (oldColumns != null)
        {
            // Remove all columns.
            dataGrid.Columns.Clear();

            // Unsubscribe from old collection.
            NotifyCollectionChangedEventHandler h;
            if (_handlers.TryGetValue(dataGrid, out h))
            {
                oldColumns.CollectionChanged -= h;
                _handlers.Remove(dataGrid);
            }
        }

        ObservableCollection<DataGridColumn> newColumns = e.NewValue as ObservableCollection<DataGridColumn>;
        dataGrid.Columns.Clear();
        if (newColumns != null)
        {
            // Add columns from this source.
            foreach (DataGridColumn column in newColumns)
                dataGrid.Columns.Add(column);

            // Subscribe to future changes.
            NotifyCollectionChangedEventHandler h = (_, ne) => OnCollectionChanged(ne, dataGrid);
            _handlers[dataGrid] = h;
            newColumns.CollectionChanged += h;
        }
    }

    static void OnCollectionChanged(NotifyCollectionChangedEventArgs ne, DataGrid dataGrid)
    {
        switch (ne.Action)
        {
            case NotifyCollectionChangedAction.Reset:
                dataGrid.Columns.Clear();
                foreach (DataGridColumn column in ne.NewItems)
                    dataGrid.Columns.Add(column);
                break;
            case NotifyCollectionChangedAction.Add:
                foreach (DataGridColumn column in ne.NewItems)
                    dataGrid.Columns.Add(column);
                break;
            case NotifyCollectionChangedAction.Move:
                dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex);
                break;
            case NotifyCollectionChangedAction.Remove:
                foreach (DataGridColumn column in ne.OldItems)
                    dataGrid.Columns.Remove(column);
                break;
            case NotifyCollectionChangedAction.Replace:
                dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn;
                break;
        }
    }

    public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value)
    {
        element.SetValue(BindableColumnsProperty, value);
    }

    public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element)
    {
        return (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty);
    }
}

2

그리드 정의로 사용자 컨트롤을 만들고 xaml에서 다양한 열 정의로 '자식'컨트롤을 정의 할 수 있습니다. 부모에는 열에 대한 종속성 속성과 열을로드하기위한 메서드가 필요합니다.

부모의:


public ObservableCollection<DataGridColumn> gridColumns
{
  get
  {
    return (ObservableCollection<DataGridColumn>)GetValue(ColumnsProperty);
  }
  set
  {
    SetValue(ColumnsProperty, value);
  }
}
public static readonly DependencyProperty ColumnsProperty =
  DependencyProperty.Register("gridColumns",
  typeof(ObservableCollection<DataGridColumn>),
  typeof(parentControl),
  new PropertyMetadata(new ObservableCollection<DataGridColumn>()));

public void LoadGrid()
{
  if (gridColumns.Count > 0)
    myGrid.Columns.Clear();

  foreach (DataGridColumn c in gridColumns)
  {
    myGrid.Columns.Add(c);
  }
}

자식 Xaml :


<local:parentControl x:Name="deGrid">           
  <local:parentControl.gridColumns>
    <toolkit:DataGridTextColumn Width="Auto" Header="1" Binding="{Binding Path=.}" />
    <toolkit:DataGridTextColumn Width="Auto" Header="2" Binding="{Binding Path=.}" />
  </local:parentControl.gridColumns>  
</local:parentControl>

마지막으로 까다로운 부분은 'LoadGrid'를 호출 할 위치를 찾는 것입니다.
나는 이것으로 어려움을 겪고 있지만 InitalizeComponent내 창 생성자 (childGrid는 window.xaml의 x : name입니다)에서 호출하여 작업 할 수 있습니다 .

childGrid.deGrid.LoadGrid();

관련 블로그 항목


1

AutoGenerateColumns 및 DataTemplate을 사용하여이 작업을 수행 할 수 있습니다. 많은 작업없이 작동한다면 긍정적이지 않습니다. 솔직히 이미 작동하는 솔루션이 있다면 큰 이유가 없으면 아직 변경하지 않을 것입니다. DataGrid 컨트롤은 매우 좋아지고 있지만 이와 같은 동적 작업을 쉽게 수행하려면 여전히 약간의 작업이 필요합니다.


내 이유는 ASP.Net에서 왔기 때문에 적절한 데이터 바인딩으로 수행 할 수있는 작업이 처음이고 한계가 어디에 있는지 잘 모르겠습니다. 감사합니다. AutoGenerateColumns를 사용해 보겠습니다.
일반 오류

0

프로그래밍 방식으로 수행하는 방법에 대한 샘플이 있습니다.

public partial class UserControlWithComboBoxColumnDataGrid : UserControl
{
    private Dictionary<int, string> _Dictionary;
    private ObservableCollection<MyItem> _MyItems;
    public UserControlWithComboBoxColumnDataGrid() {
      _Dictionary = new Dictionary<int, string>();
      _Dictionary.Add(1,"A");
      _Dictionary.Add(2,"B");
      _MyItems = new ObservableCollection<MyItem>();
      dataGridMyItems.AutoGeneratingColumn += DataGridMyItems_AutoGeneratingColumn;
      dataGridMyItems.ItemsSource = _MyItems;

    }
private void DataGridMyItems_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            var desc = e.PropertyDescriptor as PropertyDescriptor;
            var att = desc.Attributes[typeof(ColumnNameAttribute)] as ColumnNameAttribute;
            if (att != null)
            {
                if (att.Name == "My Combobox Item") {
                    var comboBoxColumn =  new DataGridComboBoxColumn {
                        DisplayMemberPath = "Value",
                        SelectedValuePath = "Key",
                        ItemsSource = _ApprovalTypes,
                        SelectedValueBinding =  new Binding( "Bazinga"),   
                    };
                    e.Column = comboBoxColumn;
                }

            }
        }

}
public class MyItem {
    public string Name{get;set;}
    [ColumnName("My Combobox Item")]
    public int Bazinga {get;set;}
}

  public class ColumnNameAttribute : Attribute
    {
        public string Name { get; set; }
        public ColumnNameAttribute(string name) { Name = name; }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.