I am trying to dynamically generate a DataGrid from a DataTable while at the same time display two button columns for "Edit" and "Delete" functionality. I can manually create a button column in XAML, and I can dynamically generate columns from a DataTable, but I can't seem to do both. If I were just coding this in the code-behind I think it would be a much simpler problem to solve because I would have direct access to the control. However, I am building this View using MVVM and I can't think of a way to manipulate the View dynamically to this level of detail.
Here's a little bit of my code (please note that there may be some Copy/Paste FAILS that are obvious to someone with more WPF/MVVM experience than I have):
XAML:
<DataGrid x:Name="grdMapValues"
AutoGenerateColumns="True"
ItemsSource="{Binding GridSourceDataTable}">
<DataGrid.Columns>
<DataGridTemplateColumn IsReadOnly="True">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button HorizontalAlignment="Left"
Command="{Binding Path=DataContext.Commands.AddMapValue,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}">
<Image Width="14" Height="14" Source="/Controls;component/Resources/Images/new.gif" ToolTip="New" />
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
Command="{Binding Path=DataContext.Commands.EditMapValue,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}">
<Image Width="14" Height="14" Source="/Controls;component/Resources/Images/edit.gif" />
</Button>
<Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
Command="{Binding Path=DataContext.Commands.DeleteMapValue,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}">
<Image Width="14" Height="14" Source="/Controls;component/Resources/Images/delete.gif" />
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
ViewModel:
private DataTable _gridSource;
public DataTable GridSourceDataTable
{
get
{
return _gridSource;
}
set
{
_gridSource = value;
OnPropertyChanged("GridSourceDataTable");
}
}
private void GenerateDataTable()
{
switch (caseInt)
{
case 1:
if (_isDate)
{
_gridSource.Columns.Add("Date", typeof(DateTime));
}
else
{
_gridSource.Columns.Add("Number", typeof(string));
}
_gridSource.Columns.Add("Value", typeof(decimal));
_gridSource.Columns.Add("SourceString", typeof(string));
GridIsVisible = true;
break;
//Other similar case blocks removed
default:
GridIsVisible = false;
break;
}
OnPropertyChanged("GridSourceDataTable");
}
So I am guessing that the solution, if there is one, will involve either some hybrid of XAML and the DataTable property OR it will involve a more sophisticated object or property than my simple DataTable.
I've been working on this one for several hours, so any help that can be offered will be greatly appreciated.
You can use attached behaviors to achieve what you are trying. You will be manipulating and adding additional columns to the grid from an attached behavior - that means you are still following MVVM pattern.
This is the attached behavior I tried:
public class DataGridColumnBehavior : Behavior<DataGrid>
{
public AdditionalColumnsList AdditionalColumns { get; set; }
protected override void OnAttached()
{
base.OnAttached();
AddAdditionalColumns();
}
void AddAdditionalColumns()
{
if(AdditionalColumns == null || AdditionalColumns.Count == 0) return;
foreach (var additionalColumn in AdditionalColumns)
{
AssociatedObject.Columns.Add(new DataGridTemplateColumn
{
HeaderTemplate = additionalColumn.HeaderTemplate,
CellTemplate = additionalColumn.CellTemplate
});
}
}
}
I have used couple of simple classes to represent additional column and additional column list.
public class AdditionalColumn
{
public DataTemplate HeaderTemplate { get; set; }
public DataTemplate CellTemplate { get; set; }
}
public class AdditionalColumnsList : List<AdditionalColumn>
{
}
And, this is how you attach a behavior to DataGrid:
<DataGrid Margin="5" ItemsSource="{Binding GridSource}">
<i:Interaction.Behaviors>
<local:DataGridColumnBehavior>
<local:DataGridColumnBehavior.AdditionalColumns>
<local:AdditionalColumnsList>
<local:AdditionalColumn>
<local:AdditionalColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="Button Header" />
</DataTemplate>
</local:AdditionalColumn.HeaderTemplate>
<local:AdditionalColumn.CellTemplate>
<DataTemplate>
<Button Content="Button" Command="{Binding ButtonCommand}" />
</DataTemplate>
</local:AdditionalColumn.CellTemplate>
</local:AdditionalColumn>
</local:AdditionalColumnsList>
</local:DataGridColumnBehavior.AdditionalColumns>
</local:DataGridColumnBehavior>
</i:Interaction.Behaviors>
</DataGrid>
And the resulting grid:
Read this CodeProject article on attached behaviors.
Please note, I have used Blend Behavior here. This blog post discusses differences between attached behaviors and blend behaviors.