2008-11-26 15 views
112

Moja aplikacja WPF generuje zestawy danych, które za każdym razem mogą mieć inną liczbę kolumn. W wyjściu znajduje się opis każdej kolumny, która zostanie użyta do zastosowania formatowania. Uproszczona wersja wyjścia może być coś takiego:Jak powiązać element DataFrid WPF ze zmienną liczbą kolumn?

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

Klasa ta jest ustawiona jako DataContext na WPF DataGrid ale faktycznie tworzyć kolumn 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 istnieje jakiś sposób, aby zastąpić ten kod z powiązaniami danych w pliku XAML?

Odpowiedz

113

Oto obejście do wiązania Kolumny w DataGrid. Ponieważ właściwość Columns to ReadOnly, tak jak wszyscy zauważyli, stworzyłem właściwość Attached Property o nazwie BindableColumns, która aktualizuje kolumny w DataGrid za każdym razem, gdy kolekcja zmienia się poprzez zdarzenie CollectionChanged.

Jeśli mamy ten Kolekcja

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

Wtedy możemy wiążą BindableColumns DataGridColumn do tej ColumnCollection jak ten

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

Załączony Property BindableColumns

+3

To jest poprawna odpowiedź, Bravo! –

+0

Zgadzam się, najbardziej lubię to rozwiązanie. – Jaime

+1

ładne rozwiązanie dla wzorca MVVM – WPFKK

1

Możesz to zrobić za pomocą AutoGenerateColumns i DataTemplate. Nie jestem pewien, czy działałby bez dużego nakładu pracy, musiałbyś się z tym bawić. Szczerze mówiąc, jeśli masz już działające rozwiązanie, nie wprowadziłbym jeszcze zmian, chyba że jest jakiś ważny powód. Kontrolka DataGrid staje się bardzo dobra, ale nadal wymaga trochę pracy (i mam dużo nauki do zrobienia), aby móc wykonywać dynamiczne zadania, jak to łatwo.

+0

Moja powodem jest to, że przychodzi z ASP.NET Jestem nowy, co można zrobić z wiązania przyzwoite dane i jestem nie wiem, gdzie są granice. Będę grał z AutoGenerateColumns, dzięki. –

17

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

Bryan zasugerował, że można coś zrobić z AutoGenerateColumns, więc rzuciłem okiem. Wykorzystuje proste odbicie .Net do przeglądania właściwości obiektów w ItemSource 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 robi się daleko poza torem.

Ponieważ problem jest tak łatwo sovled w kodzie będę trzymać się prostej metody wydłużania nazywam gdy kontekst danych jest aktualizowana 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); 
+0

Co robi "indeks"? –

+1

Najwyżej ocenione i zaakceptowane rozwiązanie nie jest najlepsze! Dwa lata później odpowiedź brzmi: http://msmvps.com/blogs/deborahk/archive/2011/01/23/populating-a-datagrid-with-dynamic-columns-in-a-silverlight-application-using- mvvm.aspx – Mikhail

+4

Nie, nie byłoby. W każdym razie nie pod warunkiem, że rezultat tego rozwiązania jest zupełnie inny! – 321X

2

Można utworzyć usercontrol z definicji siatki i zdefiniuj elementy sterujące "dziecko" z różnymi definicjami kolumn w Xaml. Rodzic musi właściwość zależność dla kolumn oraz sposobu ładowania kolumny:

nadrzędny:


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, do którego należy zadzwonić "LoadGrid" ".
walczę z tym, ale mam rzeczy do pracy, wywołując po InitalizeComponent w moim okna konstruktora (childGrid jest x: nazwa window.xaml):

childGrid.deGrid.LoadGrid(); 

Related blog entry

9

Znalazłem blogowy artykuł Deborah Kurata z miłym trikiem, jak pokazać varię ble liczba kolumn w DataGrid:

Populating a DataGrid with Dynamic Columns in a Silverlight Application using MVVM

Zasadniczo, ona tworzy DataGridTemplateColumn i stawia ItemsControl wnętrza, który wyświetla wiele kolumn.

+1

Działa doskonale zarówno dla Silverlight, jak i WPF! – Mikhail

+1

To zdecydowanie nie taki sam wynik jak zaprogramowana wersja !! – 321X

+0

@ 321X: Czy mógłbyś wyjaśnić, jakie są obserwowane różnice (i określić, co masz na myśli przez * zaprogramowaną wersję *, ponieważ wszystkie rozwiązania tego są zaprogramowane), proszę? –

5

udało mi się zrobić to możliwe, aby dynamicznie dodać kolumnę używając tylko wiersza kodu:

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

Dotyczy do pytania, to nie jest rozwiązaniem XAML oparte (ponieważ jak wspomniano istnieje nie ma racjonalnego sposobu, aby to zrobić), ani nie jest rozwiązaniem, które działałoby bezpośrednio z DataGrid.Columns. To faktycznie działa z DataGrid związanym ItemsSource, który implementuje ITypedList i jako taki zapewnia niestandardowe metody pobierania właściwości PropertyDescriptor. W jednym miejscu kodu możesz zdefiniować "wiersze danych" i "kolumny danych" dla swojej siatki.

Jeśli chcesz mieć:

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

można 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 wiązanie z użyciem siatki MyItemsCollection byłyby wypełniane odpowiednich kolumnach. Te kolumny można modyfikować (nowe dodane lub istniejące usunięte) w czasie wykonywania dynamicznie, a siatka automatycznie odświeży kolekcję kolumn.

DynamicPropertyDescriptor wspomniany powyżej jest jedynie aktualizacją do zwykłego PropertyDescriptor i zapewnia silnie typowaną definicję kolumn z dodatkowymi opcjami. DynamicDataGridSource w przeciwnym razie działałby po prostu dobrze z podstawowym PropertyDescriptor.

3

Wykonano wersję zaakceptowanej odpowiedzi, która obsługuje rezygnację z subskrypcji.

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

Jest próbka drodze 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; } 
} 
Powiązane problemy