Bir WPF DataGrid ürününü değişken sayıda sütuna nasıl bağlarım?


124

WPF uygulamam, her seferinde farklı sayıda sütuna sahip olabilen veri kümeleri oluşturur. Çıktıya, biçimlendirmeyi uygulamak için kullanılacak her sütunun bir açıklaması dahildir. Çıktının basitleştirilmiş bir versiyonu aşağıdaki gibi olabilir:

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

Bu sınıf, bir WPF DataGrid üzerinde DataContext olarak ayarlandı, ancak aslında sütunları programlı olarak oluşturuyorum:

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

Bunun yerine bu kodu XAML dosyasındaki veri bağlamalarıyla değiştirmenin bir yolu var mı?

Yanıtlar:


127

DataGrid'de Sütunları Bağlama için bir geçici çözüm burada verilmiştir. Columns özelliği ReadOnly olduğundan, herkesin fark ettiği gibi, CollectionChanged olayı aracılığıyla koleksiyon her değiştiğinde DataGrid'deki Sütunları güncelleyen BindableColumns adında bir Ekli Özellik oluşturdum.

Bu DataGridColumn Koleksiyonu'na sahipsek

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

Sonra BindableColumns'u ColumnCollection'a şu şekilde bağlayabiliriz

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

Ekli Özellik 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 kalıbı için güzel çözüm
WPFKK

2
Mükemmel bir çözüm! Muhtemelen BindableColumnsPropertyChanged'da birkaç şey daha yapmanız gerekir: 1. DataGrid'e erişmeden önce null için kontrol edin ve yalnızca DataGrid'e bağlanma hakkında iyi bir açıklama içeren bir istisna atın. 2. e.OldValue'da null olup olmadığını kontrol edin ve bellek sızıntılarını önlemek için CollectionChanged olayına olan aboneliğinizi iptal edin. Sadece ikna etmen için.
Mike Eshva

3
CollectionChangedSütun koleksiyonunun olayı ile bir olay işleyicisi kaydedersiniz , ancak asla kaydını silmezsiniz. Böylelikle , ilk etapta DataGridbulunan kontrol şablonu bu DataGridarada değiştirilse bile, görünüm modeli var olduğu sürece canlı tutulacaktır . DataGridArtık gerekli olmadığında bu olay işleyicisinin kaydını silmenin garantili bir yolu var mı ?
VEYA Eşleyici

1
@OR Mapper: Teorik olarak var ama çalışmıyor: WeakEventManager <ObservableCollection <DataGridColumn>, NotifyCollectionChangedEventArgs> .AddHandler (sütunlar, "CollectionChanged", (s, ne) => {anahtar ....});
çok

6
Çözüm değil. Ana neden, ViewModel'de UI sınıflarını kullanıyor olmanızdır. Ayrıca, bazı sayfa geçişleri oluşturmaya çalıştığınızda da çalışmayacaktır. Böyle bir datagrid ile sayfaya geri döndüğünüzde, dataGrid.Columns.Add(column)DataGridColumn satırında Header 'X' ile bir DataGrid'in Columns koleksiyonunda zaten var olan bir beklentiniz olacaktır . DataGrid'ler sütunları paylaşamaz ve yinelenen sütun örnekleri içeremez.
Ruslan F.

19

Araştırmama devam ettim ve bunu yapmanın makul bir yolunu bulamadım. DataGrid üzerindeki Columns özelliği, bağlanabileceğim bir şey değil, aslında salt okunur.

Bryan, AutoGenerateColumns ile bir şeyler yapılabileceğini önerdi, bu yüzden bir baktım. ItemsSource'taki nesnelerin özelliklerine bakmak için basit .Net yansımasını kullanır ve her biri için bir sütun oluşturur. Belki her sütun için bir özellik ile anında bir tür oluşturabilirim, ancak bu yolun dışına çıkıyor.

Bu problem kodda çok kolay çözüldüğünden, veri bağlamı yeni sütunlarla her güncellendiğinde çağırdığım basit bir uzantı yöntemine bağlı kalacağım:

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
En yüksek oyu alan ve kabul edilen çözüm en iyisi değildir! İki yıl sonra cevap şöyle olurdu: msmvps.com/blogs/deborahk/archive/2011/01/23/…
Mikhail

4
Hayır, olmaz. Sağlanan bağlantı zaten değil, çünkü bu çözümün sonucu tamamen farklı!
321X

2
Görünüşe göre Mealek'in çözümü çok daha evrensel ve C # kodunun doğrudan kullanımının sorunlu olduğu durumlarda kullanışlıdır, örneğin Kontrol Şablonları.
EFraim

@Mikhail bağlantısı bozuk
LuckyLikey


9

Deborah Kurata'nın bir DataGrid'de değişken sayıda sütun göstermenin güzel bir hile ile yazdığı bir blog makalesi buldum:

MVVM kullanarak bir Silverlight Uygulamasında DataGrid'i Dinamik Sütunlarla Doldurma

Temel olarak, bir oluşturur DataGridTemplateColumnve ItemsControlbirden çok sütunu görüntüleyen içine koyar .


1
Programlanmış sürümle aynı sonuç değil !!
321X

1
@ 321X: Lütfen gözlemlenen farklılıkların ne olduğunu açıklar mısınız (ve buna yönelik tüm çözümler programlandığından programlanmış sürümle ne demek istediğinizi de belirtin ), lütfen?
VEYA Eşleyici

"Sayfa bulunamadı"
yazıyor


Bu inanılmazdan başka bir şey değil !!
Ravid Goldenberg

6

Bunun gibi bir kod satırı kullanarak dinamik olarak bir sütun eklemeyi mümkün kılmayı başardım:

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

Soruyla ilgili olarak, bu XAML tabanlı bir çözüm değildir (belirtildiği gibi bunu yapmanın makul bir yolu olmadığından), ne de doğrudan DataGrid.Columns ile çalışacak bir çözümdür. Aslında, ITypedList'i uygulayan ve bu nedenle PropertyDescriptor alımı için özel yöntemler sağlayan DataGrid bağlı ItemsSource ile çalışır. Kodda tek bir yerde ızgaranız için "veri satırları" ve "veri sütunları" tanımlayabilirsiniz.

Eğer sahip olsaydın:

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

örneğin kullanabilirsiniz:

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) 

ve MyItemsCollection'a bağlama kullanan ızgaranız karşılık gelen sütunlarla doldurulur. Bu sütunlar, çalışma zamanında dinamik olarak değiştirilebilir (yeni eklenen veya mevcut kaldırılan) ve ızgara, sütun koleksiyonunu otomatik olarak yeniler.

Yukarıda bahsedilen DynamicPropertyDescriptor, normal PropertyDescriptor'a yapılan bir yükseltmedir ve bazı ek seçeneklerle birlikte güçlü bir şekilde yazılmış sütun tanımı sağlar. DynamicDataGridSource, aksi takdirde temel PropertyDescriptor ile gayet iyi çalışır.


3

Kabul edilen cevabın abonelikten çıkmayı ele alan bir versiyonunu yaptı.

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

Izgara tanımıyla bir kullanıcı denetimi oluşturabilir ve xaml'de çeşitli sütun tanımlarıyla 'çocuk' denetimleri tanımlayabilirsiniz. Üst öğe, sütunlar için bir bağımlılık özelliğine ve sütunları yüklemek için bir yönteme ihtiyaç duyar:

Veli:


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

Çocuk 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>

Ve son olarak, zor kısım, 'LoadGrid' olarak adlandırılacak yeri bulmaktır.
Bununla mücadele ediyorum ama InitalizeComponentpencere yapıcımda sonra arayarak çalışacak şeyler var (childGrid, x: window.xaml'deki ad):

childGrid.deGrid.LoadGrid();

İlgili blog girişi


1

Bunu AutoGenerateColumns ve bir DataTemplate ile yapabilirsiniz. Çok çalışma olmadan işe yarayacaksa, onunla oynamak zorunda kalacağından emin değilim. Açıkçası, halihazırda çalışan bir çözümünüz varsa, büyük bir neden olmadıkça henüz değişiklik yapmam. DataGrid kontrolü çok iyi hale geliyor ancak bunun gibi dinamik görevleri kolayca yapabilmek için hala biraz çalışmaya ihtiyacı var (ve yapacak çok şeyim var).


Sebebim, ASP.Net'ten gelmek, düzgün veri bağlama ile neler yapılabileceği konusunda yeniyim ve sınırlarının nerede olduğundan emin değilim. AutoGenerateColumns ile bir oyun oynayacağım, teşekkürler.
Genel Hata

0

Programlı olarak yaptığım yöntemin bir örneği var:

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; }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.