Как привязать WPF DataGrid к переменному числу столбцов?
Мои приложения WPF создает наборы данных, которые могут иметь разное количество столбцов каждый раз. В выводе содержится описание каждого столбца, который будет использоваться для применения форматирования. Упрощенная версия вывода может быть чем-то вроде:
class Data
{
IList<ColumnDescription> ColumnDescriptions { get; set; }
string[][] Rows { get; set; }
}
этот класс установлен как DataContext на WPF DataGrid, но я на самом деле создаю столбцы программно:
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 файл вместо этого?
8 ответов:
вот обходной путь для привязки столбцов в DataGrid. Поскольку свойство Columns доступно только для чтения, как все заметили, я сделал прикрепленное свойство bindablecolumns, которое обновляет столбцы в DataGrid каждый раз, когда коллекция изменяется через событие CollectionChanged.
Если у нас есть эта коллекция 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); } }
я продолжил свои исследования и не нашел никакого разумного способа сделать это. Свойство Columns в DataGrid-это не то, что я могу привязать, на самом деле это только для чтения.
Брайан предположил, что что-то может быть сделано с 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);
Я нашел статью в блоге Деборы Курата с хорошим трюком, как показать переменное количество столбцов в DataGrid:
заполнение DataGrid динамическими столбцами в приложении Silverlight с помощью MVVM
в основном, она создает
DataGridTemplateColumnи ставитItemsControlвнутри которого отображается несколько столбцов.
мне удалось сделать возможным динамическое добавление столбца, используя только строку кода, как это:
MyItemsCollection.AddPropertyDescriptor( new DynamicPropertyDescriptor<User, int>("Age", x => x.Age));Что касается вопроса, это не решение на основе XAML (поскольку, как уже упоминалось, нет разумного способа сделать это), ни это решение, которое будет работать непосредственно с DataGrid.Столбцы. Это на самом деле работает с привязкой, что ItemsSource DataGrid элемент управления, который реализует ITypedList, и как таковой предоставляет специальные методы для извлечения дескриптор свойства. В одном месте в код вы можете определить "строки данных" и "столбцы данных" для вашей сетки.
Если бы:
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 будет заполнена соответствующими столбцами. Эти столбцы могут быть изменены (добавлены новые или удалены существующие) во время выполнения динамически, и grid автоматически обновит свою коллекцию столбцов.
DynamicPropertyDescriptor упомянутый выше-это просто обновление до обычного PropertyDescriptor и предоставляет строго типизированное определение столбцов с некоторыми дополнительными опциями. DynamicDataGridSource в противном случае будет работать просто прекрасное событие с базовым PropertyDescriptor.
сделал версию принятого ответа, который обрабатывает отписку.
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); } }
вы можете создать элемент управления usercontrol с определением сетки и определить "дочерние" элементы управления с различными определениями столбцов в 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 - это x: name в окне.xaml):childGrid.deGrid.LoadGrid();
вы могли бы сделать это с AutoGenerateColumns и DataTemplate. Я не уверен, что это будет работать без большой работы, вам придется играть с ним. Честно говоря, если у вас уже есть рабочее решение, я бы не стал вносить изменения, пока нет большой причины. Элемент управления DataGrid становится очень хорошим, но он все еще нуждается в некоторой работе (и у меня осталось много обучения), чтобы иметь возможность легко выполнять динамические задачи.
есть пример того, как я делаю программно:
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; } }
Comments