Jak powiązać DataGrid WPF ze zmienną liczbą kolumn?

Moja aplikacja WPF generuje zestawy danych, które za każdym razem mogą mieć inną liczbę kolumn. Wyjście zawiera opis każdej kolumny, która zostanie użyta do zastosowania formatowania. Uproszczona wersja wyjścia może być podobna do:

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

Ta klasa jest ustawiona jako DataContext na WPF DataGrid, ale tak naprawdę tworzę kolumny programowo:

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))
    });
}

Czy Jest jakiś sposób na zastąpienie tego kodu powiązaniami danych w pliku XAML?

Author: fedab, 2008-11-26

8 answers

Oto obejście dla wiążących kolumn w DataGrid. Ponieważ właściwość Columns jest tylko do odczytu, jak wszyscy zauważyli, zrobiłem dołączoną właściwość o nazwie BindableColumns, która aktualizuje kolumny w DataGrid za każdym razem, gdy kolekcja zmienia się poprzez Zdarzenie CollectionChanged.

Jeśli mamy ten zbiór DataGridColumn

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

Następnie możemy powiązać BindableColumns z ColumnCollection w następujący sposób

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

Załączona Nieruchomość 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);
    }
}
 121
Author: Fredrik Hedblad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-07 18:01:44

Kontynuowałem swoje badania i nie znalazłem żadnego rozsądnego sposobu, aby to zrobić. Właściwość Columns na DataGrid nie jest czymś, z czym mogę się wiązać, w rzeczywistości jest tylko do odczytu.

Bryan zasugerował, że można zrobić coś z AutoGenerateColumns, więc rzuciłem okiem. Używa prostego odbicia. Net, aby spojrzeć na właściwości obiektów w ItemsSource i generuje kolumnę dla każdego z nich. Być może mógłbym wygenerować typ w locie z właściwością dla każdej kolumny, ale to jest coraz daleko poza torem.

Ponieważ ten problem jest tak łatwo rozwinięty w kodzie, zostanę przy prostej metodzie rozszerzenia, którą wywołuję, gdy kontekst danych zostanie zaktualizowany o nowe kolumny:

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);
 18
Author: Generic Error,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-19 18:25:29

Znalazłem artykuł na blogu Deborah Kurata z miłą sztuczką, jak pokazać zmienną liczbę kolumn w DataGrid:

Wypełnianie DataGrid dynamicznymi kolumnami w aplikacji Silverlight przy użyciu MVVM

Zasadniczo tworzy DataGridTemplateColumn i wkłada ItemsControl do środka, który wyświetla wiele kolumn.

 9
Author: Lukas Cenovsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-03 13:38:21

Udało mi się umożliwić dynamiczne dodawanie kolumny za pomocą tylko linii kodu w taki sposób:

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

Jeśli chodzi o pytanie, nie jest to rozwiązanie oparte na XAML (ponieważ jak wspomniano, nie ma na to rozsądnego sposobu), ani nie jest to rozwiązanie, które działałoby bezpośrednio z DataGrid.Kolumny. W rzeczywistości działa z DataGrid ItemsSource, które implementuje ITypedList i jako takie dostarcza niestandardowe metody pobierania PropertyDescriptor. W jednym miejscu w kodzie możesz zdefiniuj "wiersze danych" i "kolumny danych" dla swojej siatki.

Gdybyś miał:

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

Możesz użyć na przykład:

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) 

A twoja siatka za pomocą powiązania z MyItemsCollection zostanie wypełniona odpowiednimi kolumnami. Kolumny te mogą być modyfikowane (nowe dodane lub istniejące usunięte) w czasie wykonywania dynamicznie i grid automatycznie odświeży swoją kolekcję kolumn.

DynamicPropertyDescriptor wspomniany powyżej jest tylko uaktualnieniem do zwykłego PropertyDescriptor i zapewnia definiowanie kolumn z pewnymi dodatkowymi opcjami. DynamicDataGridSource w przeciwnym razie działałoby dobrze Zdarzenie z basic PropertyDescriptor.

 5
Author: doblak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-10 21:01:36

Stworzyła wersję zaakceptowanej odpowiedzi, która obsługuje wypisywanie.

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);
    }
}
 3
Author: Mikhail Orlov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-09 13:43:09

Możesz utworzyć kontrolkę usercontrol z definicją siatki i zdefiniować kontrolki 'potomne' z różnymi definicjami kolumn w xaml. Rodzic potrzebuje właściwości zależności dla kolumn i metody do ładowania kolumn:

Rodzic:


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);
  }
}

Dziecko 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>

I wreszcie, najtrudniejszą częścią jest znalezienie miejsca, w którym można nazwać "LoadGrid".
Mam problem z tym, ale mam coś do pracy przez wywołanie po InitalizeComponent w moim konstruktorze okien (childGrid to X:name in okno.xaml): {]}

childGrid.deGrid.LoadGrid();

Related blog entry

 2
Author: Andy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-22 19:57:20

Możesz to zrobić z AutoGenerateColumns i DataTemplate. Nie jestem pewien, czy to zadziała bez dużo pracy, trzeba byłoby się nim bawić. Szczerze mówiąc, jeśli masz już działające rozwiązanie, nie dokonałbym jeszcze zmiany, chyba że jest duży powód. Kontrola DataGrid staje się bardzo dobra, ale nadal wymaga trochę pracy (i mam dużo nauki do zrobienia), aby móc wykonywać dynamiczne zadania, takie jak to łatwo.

 1
Author: Bryan Anderson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-26 15:37:35

Jest próbka sposobu, w jaki robię programowo:

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; }
}
 0
Author: David Soler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-21 10:24:03