سؤال

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>

نصائح أخرى

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; }
}

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>
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top