WPF Datagrid binding custom column headers
I am trying to figure out how to bind a WPF DataGrid's column header and main data to a data source using an MVVM pattern. The result I'm looking for would look like this:
(source: vallelunga.com)I've successfully styled the headers here, but I'm unsure how to bind the values in the headers. Specifically, the IsChecked property of the check-box, the selected index of the combo box and the value of the text box.
I was previously using a simple DataTable to populate the main grid data, but I'm going to need something more complex to hold both the grid data and the values for each column. Or perhaps I can store them as separate entities entirely.
So, does anyone have any idea of how I might pull off this binding? One limitation is 开发者_StackOverflowthat the columns must be auto-generated since I have no idea what they will be until runtime. The application simply loads the data form an Excel spreadsheet and there may be any number of columns present.
Thanks, Brian
Here's what I ended up doing to use this with the MVVM pattern:
I have two sets of data for binding on my view model: one for the actual grid data and one for the column headers. Currently these are exposed as two properties:
// INotifyPropertyChanged support not shown for brevity
public DataTable GridData { get; set; }
public BindingList<ImportColumnInfo> ColumnData { get; set; }
The trick to working with two differing sets of data is in the grid. I have subclassed the DataGrid and given the grid an additional data source called ColumnSource, as a dependency property. This is what is bound to the ColumnData on my view model. I then set the header of each auto-generated column to the appropriately indexed data in the ColumnSource data source. The code is as follows:
public class ImporterDataGrid : DataGrid
{
protected override void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e)
{
base.OnAutoGeneratingColumn(e);
int columnIndex = this.Columns.Count;
var column = new ImporterDataGridColumn();
column.Header = ColumnSource[columnIndex];
column.Binding = new Binding(e.PropertyName) { Mode = BindingMode.OneWay };
e.Column = column;
}
public IList ColumnSource
{
get { return (IList)GetValue(ColumnSourceProperty); }
set { SetValue(ColumnSourceProperty, value); }
}
public static readonly DependencyProperty ColumnSourceProperty = DependencyProperty.Register("ColumnSource", typeof(IList), typeof(ImporterDataGrid), new FrameworkPropertyMetadata(null));
}
I can now perform normal data binding in the templated header of my columns, which will all bind against the data in the ColumnData property of my view model.
UPDATE: I was asked to show the XAML for my grid. It's really basic, but here it is:
<Controls:ImporterDataGrid
AutoGenerateColumns="True" x:Name="previewDataGrid"
VerticalScrollBarVisibility="Visible"
HorizontalScrollBarVisibility="Visible"
IsReadOnly="True"
SelectionMode="Extended"
HeadersVisibility="Column"
ItemsSource="{Binding PreviewData}"
ColumnSource="{Binding PreviewColumnData}"
Style="{StaticResource ImporterDataGridStyle}"
Background="White" CanUserReorderColumns="False" CanUserResizeRows="False"
CanUserSortColumns="False" AlternatingRowBackground="#FFFAFAFA" AllowDrop="True" />
And here is the ImporterColumnHeaderStyle:
<Style x:Key="ImporterDataGridColumnHeaderStyle" TargetType="{x:Type toolkit:DataGridColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type toolkit:DataGridColumnHeader}">
<Grid>
<toolkit:DataGridHeaderBorder Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" IsClickable="{TemplateBinding CanUserSort}" IsHovered="False" IsPressed="False" SortDirection="{TemplateBinding SortDirection}">
<Grid>
<CheckBox Height="16" Margin="6,6,16,0" Name="importCheckBox" IsChecked="{Binding Path=Import}" VerticalAlignment="Top">Import Column</CheckBox>
<StackPanel IsEnabled="{Binding Path=Import}">
<ComboBox Height="24" Margin="6,29,6,0" Name="columnTypeComboBox" VerticalAlignment="Top" SelectedValue="{Binding ColumnType}" ItemsSource="{Binding Source={local:EnumList {x:Type Models:ImportColumnType}}}">
</ComboBox>
<TextBox Height="23" Margin="6,6,6,33" Name="customHeadingTextBox" VerticalAlignment="Bottom" Text="{Binding Path=CustomColumnName}" IsEnabled="{Binding ColumnType, Converter={StaticResource ColumnTypeToBooleanConverter}}" />
</StackPanel>
<TextBlock Height="20" Margin="6,0,6,7" Name="originalHeadingTextBlock" Text="{Binding Path=OriginalColumnName}" VerticalAlignment="Bottom" Foreground="Gray" />
</Grid>
</toolkit:DataGridHeaderBorder>
<Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left">
<Thumb.Style>
<Style TargetType="{x:Type Thumb}">
<Setter Property="Width" Value="8"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Cursor" Value="SizeWE"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
<Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right">
<Thumb.Style>
<Style TargetType="{x:Type Thumb}">
<Setter Property="Width" Value="8"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Cursor" Value="SizeWE"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I am definitely a WPF / MVVM / databinding noob, but have been working hard on this stuff lately. I don't know what you have wired up so far, but first you'll want to set the DataContext for your View. Since you're using MVVM, I assume you have a ViewModel, so that should be the DataContext for your View.
i.e. if you have your View create / own your ViewModel, it could look something like this:
MyViewModel vm = new MyViewModel();
this.DataContext = vm;
You can easily databind your CheckBox, ComboBox, and TextBox to properties in your ViewModel. I have found the easiest way is to make your ViewModel inherit from a base viewmodel class, like the one that Josh Smith wrote. This will give you a method to call internally when you want the ViewModel to notify the GUI of any changes in values.
Assuming you have properties like ImportColumn, LastName, and LastNameText (all C# properties, not fields that call OnPropertyChanged accordingly), then your XAML would look something like this:
<CheckBox IsChecked="{Binding ImportColumn}" />
<ComboBox SelectedItem="{Binding LastName}" />
<TextBox Text="{Binding LastName Text, Mode=TwoWay}" />
I hope this helps you out. If not, please comment and I'll try to do as best as I can to try other things out.
We do something similar in our app.
What i have done is derived my own column type (DataGridSearchableBooleanColumn), then i replace the DataGridColumnHeader template, i put two content presenters in there. the first i bind to the content (the same as the default template) the second i bind to the column. I use a data template for the column (i have a few of them for different search types (text, combo, boolean). then i add the extra properties to the column so i can bind to them. See if this code makes sense.
<!--Style for the datagrid column headers, contains a text box for searching-->
<Style
x:Key="columnHeaderStyle"
TargetType="dg:DataGridColumnHeader">
<Setter
Property="Foreground"
Value="#FF000000" />
<Setter
Property="HorizontalContentAlignment"
Value="Left" />
<Setter
Property="VerticalContentAlignment"
Value="Center" />
<Setter
Property="IsTabStop"
Value="False" />
<Setter
Property="Padding"
Value="1,2,1,2" />
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="dg:DataGridColumnHeader">
<Grid
x:Name="Root">
<dg:DataGridHeaderBorder
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
IsClickable="{TemplateBinding CanUserSort}"
IsHovered="{TemplateBinding IsMouseOver}"
IsPressed="{TemplateBinding IsPressed}"
SeparatorBrush="{TemplateBinding SeparatorBrush}"
SeparatorVisibility="{TemplateBinding SeparatorVisibility}"
SortDirection="{TemplateBinding SortDirection}">
<Grid
HorizontalAlignment="Stretch"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Grid.Resources>
<DataTemplate
DataType="{x:Type local:DataGridSearchableBooleanColumn}">
<CheckBox
Margin="0,5,0,0"
IsThreeState="True"
IsChecked="{Binding Path=IsChecked}" />
</DataTemplate>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition
Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition
Height="19" />
<RowDefinition
Height="Auto" />
</Grid.RowDefinitions>
<ContentPresenter
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay, Path=Content}" />
<Path
x:Name="SortIcon"
Fill="#FF444444"
Stretch="Uniform"
HorizontalAlignment="Left"
Margin="4,0,0,0"
VerticalAlignment="Center"
Width="8"
Opacity="0"
RenderTransformOrigin=".5,.5"
Grid.Column="1"
Data="F1 M -5.215,6.099L 5.215,6.099L 0,0L -5.215,6.099 Z ">
<Path.RenderTransform>
<ScaleTransform
ScaleX=".9"
ScaleY=".9" />
</Path.RenderTransform>
</Path>
<ContentPresenter
x:Name="columnHeaderContentPresenter"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Column}"
Grid.Row="1"
Grid.ColumnSpan="2"
Margin="0,0,0,0" />
</Grid>
</dg:DataGridHeaderBorder>
<Thumb
x:Name="PART_LeftHeaderGripper"
HorizontalAlignment="Left">
<Thumb.Style>
<Style
TargetType="{x:Type Thumb}">
<Setter
Property="Width"
Value="8" />
<Setter
Property="Background"
Value="Transparent" />
<Setter
Property="Cursor"
Value="SizeWE" />
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="{x:Type Thumb}">
<Border
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
<Thumb
x:Name="PART_RightHeaderGripper"
HorizontalAlignment="Right">
<Thumb.Style>
<Style
TargetType="{x:Type Thumb}">
<Setter
Property="Width"
Value="8" />
<Setter
Property="Background"
Value="Transparent" />
<Setter
Property="Cursor"
Value="SizeWE" />
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="{x:Type Thumb}">
<Border
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
精彩评论