2

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.

user2023861
  • 8,030
  • 9
  • 57
  • 86

3 Answers3

3

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>
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • I'm getting some errors with your third code block. On the line ``, I get an error on the `Style` attribute that says "'Style' was not found on type 'DataGridTemplateColumn'". If I comment out that line, I get an error on the line `` that says "Property 'CellTemplate' was not found on type 'DataGridCell'". Did I copy your code incorrectly? – user2023861 Mar 21 '13 at 17:48
  • 1
    @user2023861 Sorry, I wrote that without a compiler to double-check the syntax. See my updated answer for a working code sample – Rachel Mar 21 '13 at 18:10
  • Thanks for looking at this. I almost have what I want. I have one small problem: the TextBox that appears during edit-mode is not initially loaded with the `Sales` value. I wanted to post all of my datagrid xaml code, so I put in an edit to my original post. Is something wrong with my binding? How can I have the TextBox display the `Sales` value? – user2023861 Mar 21 '13 at 19:16
  • 1
    @user2023861 Change the binding in your `TextBoxTemplate` from `{Binding Path=Sales}` to just `{Binding }`. The DataContext of the ContentControl should already be the `Sales` property, so the current syntax is trying to access `Sales.Sales`, which doesn't exist – Rachel Mar 21 '13 at 19:39
  • I set the Binding to `{Binding }` like you said, but that didn't seem to do it. I put a simple converter in the binding so that I could see what is getting bound, and it turns out if I set the binding path to `Path=.`, the Converter value is of type `TextBlock`. I would have guessed that it was of type `RowType` (which is my business object). I could use the `Text` property of the TextBox, but when the user submits an edit, I have no way of setting the `Sales` value in the appropriate RowType. – user2023861 Mar 21 '13 at 19:58
  • 1
    @user2023861 Ahh I forgot you need to specify a `Path` for a TwoWay binding, so you are correct, you need to bind `TextBox.Text` to `{Binding Path=.}` instead of `{Binding }` :) The `DataContext` for your `DataGridRow` and `DataGridCell` should be your data object, and the `DataContext` for your `TextBox` should be the `Sales` property – Rachel Mar 21 '13 at 20:07
  • I got it to work! Thanks! I posted my code as another answer. Don't worry, I gave you the check mark :) – user2023861 Mar 21 '13 at 20:37
1

You could also subscribe to the BeginningEdit event of the DataGrid and then add a simple check in the code-behind.

In the XAML:

<DataGrid BeginningEdit="DataGrid_BeginningEdit" />

Example code:

private void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
    RowViewModel VM = (RowViewModel)((DataGrid)sender).SelectedItem;

    if (!VM.IsRetail) { e.Cancel = true; }
}
Martin Lottering
  • 1,624
  • 19
  • 31
1

I got it to work using this code. I don't totally understand it, but it works as I want it to work. Thanks Rachel!

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=Data}">

    <DataGrid.Resources>
        <DataTemplate x:Key="TextBoxTemplate">
            <TextBox Text="{Binding Path=Text, StringFormat=c0}" />
        </DataTemplate>
    </DataGrid.Resources>

    <DataGrid.Columns>
        <DataGridTextColumn Header="Department" Binding="{Binding Path=Department, Mode=OneTime}" />
        <DataGridTextColumn Header="Name" Binding="{Binding Path=Name, Mode=OneTime}" />

        <DataGridTemplateColumn Header="Sales">
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <ContentControl x:Name="salesControl" DataContext="{Binding Path=.}">
                        <TextBlock Text="{Binding Path=Sales, Mode=TwoWay, StringFormat=c0}" />
                    </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, StringFormat=c0}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>

    </DataGrid.Columns>
</DataGrid>
user2023861
  • 8,030
  • 9
  • 57
  • 86