it-swarm.com.de

Wie binde ich ein WPF-DataGrid an eine variable Anzahl von Spalten?

Meine WPF-Anwendung generiert Datensätze, die jedes Mal eine andere Anzahl von Spalten haben. In der Ausgabe ist eine Beschreibung jeder Spalte enthalten, die zum Anwenden der Formatierung verwendet wird. Eine vereinfachte Version der Ausgabe könnte etwa so aussehen:

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

Diese Klasse wird als DataContext für ein WPF-DataGrid festgelegt. Ich erstelle die Spalten jedoch programmgesteuert:

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

Gibt es eine Möglichkeit, diesen Code stattdessen durch Datenbindungen in der XAML-Datei zu ersetzen?

118
Generic Error

Hier finden Sie eine Problemumgehung für das Binden von Spalten im DataGrid. Da die Columns-Eigenschaft ReadOnly ist, habe ich, wie jeder bemerkt hat, eine angehängte Eigenschaft namens BindableColumns erstellt, die die Columns im DataGrid jedes Mal aktualisiert, wenn sich die Auflistung durch das CollectionChanged-Ereignis ändert. 

Wenn wir diese Collection von DataGridColumn haben

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

Dann können wir BindableColumns auf diese Weise an die ColumnCollection binden

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

Die angefügte Eigenschaft 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
Fredrik Hedblad

Ich habe meine Forschung fortgesetzt und keinen vernünftigen Weg gefunden, dies zu tun. Die Columns-Eigenschaft im DataGrid kann ich nicht binden, sondern nur lesen.

Bryan schlug vor, mit AutoGenerateColumns etwas zu tun, also habe ich einen Blick darauf geworfen. Es verwendet einfache .Net-Reflektionen, um die Eigenschaften der Objekte in ItemsSource zu betrachten, und generiert für jedes eine Spalte. Vielleicht könnte ich einen Typ mit einer Eigenschaft für jede Spalte im laufenden Betrieb generieren.

Da dieses Problem im Code so einfach zu lösen ist, bleibe ich bei einer einfachen Erweiterungsmethode, die ich immer dann anrufe, wenn der Datenkontext mit neuen Spalten aktualisiert wird:

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
Generic Error

Ich habe einen Blogartikel von Deborah Kurata mit einem Trick gefunden, wie man die variable Anzahl von Spalten in einem DataGrid anzeigt:

Ein DataGrid mit dynamischen Spalten in einer Silverlight-Anwendung mit MVVM füllen

Im Grunde erstellt sie eine DataGridTemplateColumn und fügt ItemsControl ein, die mehrere Spalten anzeigt.

9
Lukas Cenovsky

Es ist mir gelungen, eine Spalte mithilfe einer Codezeile wie folgt dynamisch hinzuzufügen:

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

In Bezug auf die Frage handelt es sich nicht um eine XAML-basierte Lösung (da, wie erwähnt, keine sinnvolle Möglichkeit besteht), und es ist auch keine Lösung, die direkt mit DataGrid.Columns arbeiten würde. Es arbeitet tatsächlich mit der DataGrid-gebundenen ItemsSource, die ITypedList implementiert und stellt somit benutzerdefinierte Methoden für den PropertyDescriptor-Abruf bereit. An einer Stelle im Code können Sie "Datenzeilen" und "Datenspalten" für Ihr Raster definieren.

Wenn Sie hätten:

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

sie könnten zum Beispiel verwenden:

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) 

ihr Raster, das eine Bindung zu MyItemsCollection verwendet, wird mit entsprechenden Spalten aufgefüllt. Diese Spalten können zur Laufzeit dynamisch geändert (neu hinzugefügt oder entfernt werden), und das Gitter wird die Spaltensammlung automatisch aktualisieren.

Der oben erwähnte DynamicPropertyDescriptor ist nur ein Upgrade des regulären PropertyDescriptor und bietet eine stark typisierte Spaltendefinition mit einigen zusätzlichen Optionen. DynamicDataGridSource würde ansonsten mit einem einfachen PropertyDescriptor-Ereignis funktionieren.

5
doblak

Eine Version der akzeptierten Antwort erstellt, die die Abmeldung übernimmt.

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
Mikhail Orlov

Sie können ein Benutzersteuerelement mit der Rasterdefinition erstellen und untergeordnete Steuerelemente mit unterschiedlichen Spaltendefinitionen in xaml definieren. Das übergeordnete Element benötigt eine Abhängigkeitseigenschaft für Spalten und eine Methode zum Laden der Spalten:

Elternteil:


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

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

Und schließlich ist es schwierig, herauszufinden, wo 'LoadGrid' aufgerufen wird.
Ich habe Probleme damit, aber ich habe etwas getan, indem ich in meinem Fenster-Konstruktor nach InitalizeComponent aufrief (childGrid ist x: name in window.xaml):

childGrid.deGrid.LoadGrid();

Zugehöriger Blogeintrag

2
Andy

Möglicherweise können Sie dies mit AutoGenerateColumns und einem DataTemplate durchführen. Ich bin nicht sicher, wenn es ohne viel Arbeit klappen würde, müsste man damit rumspielen. Ehrlich gesagt, wenn Sie bereits eine funktionierende Lösung haben, würde ich die Änderung erst jetzt vornehmen, wenn es einen großen Grund gibt. Das DataGrid-Steuerelement wird sehr gut, aber es bedarf noch einiger Arbeit (und ich muss noch viel lernen), um dynamische Aufgaben wie diese problemlos ausführen zu können.

1
Bryan Anderson

Es gibt ein Beispiel für meine programmatische Vorgehensweise:

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
David Soler