Problem: W WPF DataGrid chcemy tworzyć dynamicznie kolumny tj. zarówno kolumny jak i dane. Dodatkowo chcemy mieć możliwość bindowania do różnych typów danych.
Uczestnicząc w obecnym projekcie napotkałem na opisany powyżej problem.
Rozwiązanie problemu rozpocząłem od przeszukania internetu - a nuż ktoś już rozwiązał podobny problem.
A więc reasumując będziemy chcieli osiągnąć taki wygląd:
Nasz grid ma się składać z następujących elementów:
- Standardowo nagłówek z nazwami kolumn - zwykły tekst
- 1 kolumna zawiera dane tekstowe (np. źródła)
- Elementy to checkbox + textbox
Tak więc jak widać tworzenie danych na pewno musi być dynamiczne - jak i tworzenie całej struktury DataGrid-a.
Na początek od razu zaznaczam że nie biorę tutaj pod uwagę pracy ze wzorcem MVVM - implementację w MVVM zostawiam jako deser dla Was :)
Problemem jest to iż wiersze w DataGrid są typu object. Najczęściej wykorzystujemy bindowanie do gotowej kolekcji obiektów które wcześniej przygotujemy. Tworzymy w tym celu klasę, następnie wypełniamy kolekcję obiektami stworzonej klasy. Ostatecznie bindujemy kolekcję do właściwości ItemsSource DataGrid-a.
Nasz przypadek mówi o tym, iż nie mamy podanej ilości kolumn ani ich typów.
Zostaje więc pytanie do czego zbindować, żeby mieć możliwość dodawania kolumn w trakcie działania aplikacji? Do tego zadania nadaje się świetnie DataTable.
Tworzymy więc klasę która będzie służyła jako kolumny naszego grida, tworzone dynamicznie:
Code:
using System.Windows.Media;
namespace WpfApplication13
{
public class CellWithValue
{
public double Value { get; set; }
public SolidColorBrush Color { get; set; }
}
}
Klasa resetująca binding:
Code:
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApplication13
{
public class MyDataGridTemplateColumn : DataGridTemplateColumn
{
public string ColumnName
{
get;
set;
}
protected override System.Windows.FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var cp = (ContentPresenter)base.GenerateElement(cell, dataItem);
BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding(this.ColumnName));
return cp;
}
}
}
Binding resetujemy z powodu tego iż domyślnie mielibyśmy zbindowane obiekty typu DataRowView.
Kolejnym etapem jest stworzenie w XAMLu odpowiedniego szablonu danych:
Code:
<Window x:Class="WpfApplication13.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="Wzor" DataType="DataGridCell">
<StackPanel Orientation="Horizontal" Background="{Binding Color}">
<CheckBox VerticalAlignment="Center" />
<TextBox Width="50" Text="{Binding Value}" Opacity="0.8" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<DataGrid Name="dgData" AutoGeneratingColumn="dgData_AutoGeneratingColumn" />
</Grid>
</Window>
W kodzie (code behind) definiujemy binding:
Code:
using System.Data;
using System.Windows;
using System.Windows.Media;
namespace WpfApplication13
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var dataTable = new DataTable();
dataTable.Columns.Add("Source");
dataTable.Columns.Add(new DataColumn { ColumnName = "CellWithValue", DataType = typeof(CellWithValue) });
var dr = dataTable.NewRow();
dr[0] = "Źródło";
dr[1] = new CellWithValue {Color = Brushes.Red, Value = 5.26};
dataTable.Rows.Add(dr);
dgData.ItemsSource = dataTable.DefaultView;
}
private void dgData_AutoGeneratingColumn(object sender, System.Windows.ControlsDataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyType == typeof(CellWithValue))
{
var col = new MyDataGridTemplateColumn();
col.ColumnName = e.PropertyName;
col.CellTemplate = (DataTemplate) FindResource("Wzor");
e.Column = col;
e.Column.Header = e.PropertyName;
}
}
}
}
Mamy tutaj do czynienia z następującymi elementami:
- Tworzymy obiekt DataTable który następnie bindujemy do DataGrid poprzez właściwość DefaultView
- W zdarzeniu AutoGeneratingColumn nadajemy odpowiedni styl naszej kolumnie.
Sposób prosty i przyjemny. Co prawda nie jest to MVVM ale spełnia swoją rolę w przypadku dynamicznego tworzenia DataGrid-a.
Efekt końcowy:
Dzięki, bardzo mi pomógł ten artykuł.
OdpowiedzUsuń