I have a DataGridTemplateColumn that is editable. I only want the user to be able to edit the content of a cell in this column if the business object satisfies some criterion. Suppose my business object implements INotifyPropertyChanged and has three properties: Name, Department, and Sales. Name and Department are strings, and Sales is a double.
I want the user to be able to edit the Sales value only if Department equals "Retail". Here's a datagrid I might use to do this:
<DataGrid ItemsSource="{Binding Path=MyTypeCollection}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name, Mode=OneTime}" IsReadOnly="True" />
<DataGridTextColumn Header="Department" Binding="{Binding Path=Department, Mode=OneTime}" IsReadOnly="True" />
<DataGridTemplateColumn Header="Sales">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Sales, Mode=TwoWay}" Visibility="{Binding Path={StaticResource ResourceKey=IsRetail}}" />
<TextBlock Text="{Binding Path=Sales, Mode=OneWay}" Visibility="{Binding Path={StaticResource ResourceKey=IsNotRetail}}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Path=Sales, Mode=TwoWay}" Visibility="{Binding Path={StaticResource ResourceKey=IsRetail}}" />
<TextBlock Text="{Binding Path=Sales, Mode=OneWay}" Visibility="{Binding Path={StaticResource ResourceKey=IsNotRetail}}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I'm using a stack panel with two Text elements. I use a binding in the Visibility of the Text elements to toggle Text elements. If I have a row with a Department value that is not "Retail", I display a TextBlock in the Sales column whether the cell is in display mode or edit mode.
This seems like a clumsy solution to me. Is there some way I can prevent these types of cells from entering edit mode entirely? I only want to allow edit mode in the case that the Department is "Retail". Is this possible?
Edit: Adding code.
@Rachel. Thank you for your help. I want to paste in all of my datagrid XAML code to make sure I have everything right.
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=Data}">
<DataGrid.Resources>
<DataTemplate x:Key="TextBoxTemplate">
<TextBox Text="{Binding Path=Sales}" />
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Department" Binding="{Binding Path=Department, Mode=OneTime}" />
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name, Mode=OneTime}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ContentControl x:Name="salesControl">
<TextBlock Text="{Binding Sales}" />
</ContentControl>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Department}" Value="Retail">
<Setter TargetName="salesControl" Property="ContentTemplate" Value="{StaticResource TextBoxTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Sales}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I've almost got this where I want it. When I click on a Sales
value in a Retail
row, a TextBox appears but it doesn't have a value in it (see here). I'm not sure why it doesn't have a value because the TextBox in the TextBoxTemplate specifies a binding. Do you know why this is?
Edit: I noticed one other problem with this solution, I can't actually edit the value in the Sales column. If I try, the value reverts to the original pre-edit value.
I would use a DataTrigger
which toggles the value of a property like TextBox.IsReadOnly
based on if the Department is equal to "Retail" or not
<Style ...>
<!-- Set Default -->
<Setter Property="IsReadOnly" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding Department}" Value="Retail">
<Setter Property="IsReadOnly" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
If you don't need any other editing in your DataGrid, it would be easiest to set IsReadOnly="True"
on your DataGrid
to disable editing entirely, and set this style on the TextBox
in your DataGridTemplateColumn
. This would get rid of a lot of your extra XAML code, like IsReadOnly="True"
<DataGrid ItemsSource="{Binding Path=MyTypeCollection}"
AutoGenerateColumns="False"
IsReadOnly="True">
<!-- This could also go in Window.Resources, UserControl.Resources, etc -->
<DataGrid.Resources>
<Style x:Key="SalesTextBoxStyle" TargetType="{x:Type TextBox}">
<!-- Set Default -->
<Setter Property="IsReadOnly" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding Department}" Value="Retail">
<Setter Property="IsReadOnly" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/>
<DataGridTextColumn Header="Department" Binding="{Binding Path=Department}" />
<DataGridTemplateColumn Header="Sales">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Sales}"
Style="{StaticResource SalesTextBoxStyle}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
If you do need the default DataGrid editing functionality, you could still use the same thing, but you'd only need a single TextBox
/TextBlock
in your DataTemplate
instead of the StackPanel
and multiple objects.
And if you really want it to display an actual TextBlock
instead of a TextBox
when the user doesn't have the ability to edit, you can use a ContentControl
and toggle it's ContentTemplate
property with a DataTrigger
<DataGrid.Resources>
<DataTemplate x:Key="TextBoxTemplate">
<TextBox Text="{Binding Path=.}" />
</DataTemplate>
</DataGrid.Resources>
...
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl x:Name="salesControl" Content="{Binding Sales}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Department}" Value="Retail">
<Setter TargetName="salesControl"
Property="ContentTemplate"
Value="{StaticResource TextBoxTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>