如何使 WPF DataGridCell 只读?

21

我知道你可以将整个DataGrid或整列设置为只读(IsReadOnly = true)。然而,在单元格级别,此属性是只读的。但我确实需要这种细粒度控制。以前DataGrid是公共领域的源代码,有一篇关于通过更改源代码向行添加IsReadOnly的博客,但现在我没有DataGrid的源代码。有什么解决方法吗?

使单元格被禁用(IsEnabled=false)几乎符合我的需求。但问题是,你甚至不能点击被禁用的单元格来选择行(我采用了完整的行选择模式)。

编辑: 由于没有人回答这个问题,所以我想这并不是一个简单的修复。这里有一个可能的解决方法:使单元格不可编辑。唯一的问题是,点击单元格不会选择该行。我刚注意到,当点击禁用的单元格时,DataGrid的MouseDown或MouseUp事件仍然会触发。在这个事件处理程序中,如果我能找出它单击的行,我就可以以编程方式选择行。然而,我无法从中找到底层行。有人可以给我一些提示吗?

11个回答

14

在搜索和实验后,使用IsTabStop = False和Focusable = False对我来说效果最好。

<DataGridTextColumn Header="My Column" Binding="{Binding Path=MyColumnValue}">
    <DataGridTextColumn.CellStyle>
        <Style TargetType="DataGridCell">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=ReadOnly}" Value="True">
                    <Setter Property="IsTabStop" Value="False"></Setter>
                    <Setter Property="Focusable" Value="False"></Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </DataGridTextColumn.CellStyle>
</DataGridTextColumn>

哦...这是我找到的最干净的解决方案! - Enhakiel
很棒的解决方案! - Võ Đình Toàn
2
这种情况可能会导致问题:有一个带有两列的表,第一列可编辑,第二列不可编辑。当焦点在第一列的某一行时,单击第二列的另一行,您会发现焦点没有改变。新选择的行是蓝色的,但如果用户按键,则以前选择的单元格将被编辑。 - Sorush
这是一个非常优雅和简单的解决方案。干得好! - Patrick

13

DataGridCell.IsReadOnly上有一个属性,您可能认为可以将其绑定到,例如使用以下XAML:

<!-- Won't work -->
<DataGrid Name="myDataGrid" ItemsSource="{Binding MyItems}">
    <DataGrid.Resources>
        <Style TargetType="DataGridCell">
            <Setter Property="IsReadOnly" Value="{Binding MyIsReadOnly}" />
        </Style>
    </DataGrid.Resources>
    <!-- Column definitions... -->
</DataGrid>

很不幸,这样做行不通,因为该属性不可写。
接下来,您可以尝试拦截和停止鼠标事件,但这并不能阻止用户使用F2键进入编辑模式。

我解决这个问题的方法是在DataGrid上监听PreviewExecutedEvent事件,然后有条件地将其标记为已处理。
例如,通过将类似于此代码添加到我的窗口或UserControl(或其他更合适的位置)的构造函数中:

myDataGrid.AddHandler(CommandManager.PreviewExecutedEvent,
    (ExecutedRoutedEventHandler)((sender, args) =>
{
    if (args.Command == DataGrid.BeginEditCommand)
    {
        DataGrid dataGrid = (DataGrid) sender;
        DependencyObject focusScope = FocusManager.GetFocusScope(dataGrid);
        FrameworkElement focusedElement = (FrameworkElement) FocusManager.GetFocusedElement(focusScope);
        MyRowItemModel model = (MyRowItemModel) focusedElement.DataContext;
        if (model.MyIsReadOnly)
        {
            args.Handled = true;
        }
    }
}));

通过这种方式,单元格仍然可以聚焦和选择。
但是,除非您的模型项目允许给定行进入编辑模式,否则用户将无法进入编辑模式。
并且,使用DataGridTemplateColumn不会遭受性能成本或复杂性的影响。

2
不错的解决方案。您还可以通过查看dataGrid.CurrentCell.Column来使用此代码使特定的列-行组合只读。 - John Fisher
3
我跟着这样做了,但我使用 DataGrid 上的 BeginningEdit 事件,它会在参数中给出 RowColumn,所以我不需要自己计算。 - sohum
2
我真的希望能为这个给你+100。我尝试了所有其他建议,但它们都带来了副作用。这正是我所需要的!!!赞!!! - MegaMark
这个解决方案非常完美,因为它允许您有效地禁用行,但仍然可以通过箭头键进行键盘驱动。 - theartwebreathe

12

我遇到了同样的问题,即在某些行中单元格应该是只读的,但在其他行中不应该是只读的。这里提供了一个解决办法:

思路是在两个模板之间动态切换CellEditingTemplate。其中一个模板与CellTemplate中的模板相同,另一个模板用于编辑。这使得编辑模式的行为完全与非编辑模式的单元格相同,尽管它处于编辑模式。

以下是一些执行此操作的示例代码,请注意,此方法需要使用DataGridTemplateColumn

首先,定义两个模板,用于只读和编辑单元格:

<DataGrid>
  <DataGrid.Resources>
    <!-- the non-editing cell -->
    <DataTemplate x:Key="ReadonlyCellTemplate">
      <TextBlock Text="{Binding MyCellValue}" />
    </DataTemplate>

    <!-- the editing cell -->
    <DataTemplate x:Key="EditableCellTemplate">
      <TextBox Text="{Binding MyCellValue}" />
    </DataTemplate>
  </DataGrid.Resources>
</DataGrid>

然后定义一个数据模板,其中包括一个额外的ContentPresenter层,并使用Trigger来切换ContentPresenterContentTemplate,这样上述两个模板就可以通过IsEditable绑定动态切换:

<DataGridTemplateColumn CellTemplate="{StaticResource ReadonlyCellTemplate}">
  <DataGridTemplateColumn.CellEditingTemplate>
    <DataTemplate>
      <!-- the additional layer of content presenter -->
      <ContentPresenter x:Name="Presenter" Content="{Binding}" ContentTemplate="{StaticResource ReadonlyCellTemplate}" />
      <DataTemplate.Triggers>
        <!-- dynamically switch the content template by IsEditable binding -->
        <DataTrigger Binding="{Binding IsEditable}" Value="True">
          <Setter TargetName="Presenter" Property="ContentTemplate" Value="{StaticResource EditableCellTemplate}" />
        </DataTrigger>
      </DataTemplate.Triggers>
    </DataTemplate>
  </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

希望这有所帮助。


Recle,这实际上也是我想到的。 我唯一遗漏的是,通常情况下,我希望以不同的颜色显示只读单元格。 因此,我接受你的建议。 我希望微软公司能在下一个版本中引入这个单元格级别的属性。 - newman
@Recle,您在网格绑定方面使用的源数据类型是什么?如果您使用DataTable来填充DataView类型作为DataGrid ItemSource,然后想要访问DataTable中具有IsEditable和MyCellValue属性的对象的值级别,您的解决方案将是什么样子的?谢谢。 - st35ly
@Recle 在我的情况下,我有网格源的集合,并且根据源动态填充网格(在 ScrollViewer 中 - 在 DataTemplate 属性内)。感谢建议。 - st35ly

4

我已经通过在单元格中设置底层对象(例如CheckBox)来解决了这个问题 - IsHitTestVisible = false; Focusable = false;

var cb = this.dataGrid.Columns[1].GetCellContent(row) as CheckBox;
cb.IsHitTestVisible = false;
cb.Focusable = false;

"row"是一个DataGridRowIsHitTestVisible=false表示你不能通过鼠标点击/选择/操作底层对象,但仍然可以选择DataGridCellFocusable=false表示你不能使用键盘选择/操作底层对象。这给人以只读单元格的假象,但你仍然可以选择该单元格,如果DataGrid设置为SelectionMode=FullRow,则点击“只读”单元格将选择整行。


这似乎是个好主意。然而,根据我的测试,你的建议只适用于DataGridCheckBoxColumn,而不适用于DataGridTextColumn和DataGridComboBoxColumn。有什么想法如何解决它? - newman
谢谢,兄弟。这对我也有效。复选框就是我的情况。 - Fedor Pinega
谢谢,兄弟。这个方法对我也有效。复选框就是我的问题所在。 - undefined

1
这有点晚了,但我也在研究这个问题,这些解决方案很好,但我需要一些不同的东西,我做了以下操作,它完全按照我的要求工作,也符合问题的要求。
基本上,我想能够进入单元格的编辑模式,并且所有其他模板和命令逻辑都保持不变,同时不能编辑单元格。
解决方案是在DataGridCell样式中将TextBox.IsReadOnly属性设置为true,并处理初始keydown事件。
<Style TargetType="DataGridCell">
    <Setter Property="TextBox.IsReadOnly" Value="True"/>
    <EventSetter Event="PreviewKeyDown" Handler="cell_PreviewKeyDown"/>
</Style>

以下是停止初始编辑的后台代码

protected void cell_PreviewKeyDown(object sender, KeyEventArgs e)
{
    DataGridCell cell = sender as DataGridCell;
    if (cell.IsEditing == false && 
        ((Keyboard.Modifiers & ModifierKeys.Control) != ModifierKeys.Control)) //So that Ctrl+C keeps working
    {
        cell.IsEditing = true;
        e.Handled = true;
    }
}

希望这对你有所帮助。


非常好的解决方案!这解决了我的需求,使我能够进入编辑模式以选择和复制文本,而不能更改它。我只使用了<Setter Property="TextBox.IsReadOnly" Value="True"/>部分,看起来很有效。 - Petter

1

根据 @sohum 的评论,这里可以使用简化版本的响应,该响应已被标记为答案。

dataGrid.BeginningEdit += DataGrid_BeginningEdit;

(...)

private static void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
    //Actual content of the DataGridCell
    FrameworkElement content = e.Column.GetCellContent(e.Row);
    MyObject myObject = (MyObject)content.DataContext;

    if (!myObject.CanEdit)
    {
        e.Cancel = true;
    }
}

您可以将其后作为附加属性行为使用。

1
我的解决方案是使用绑定到DataGridTemplateColumn并使用转换器来实现。
<UserControl.Resources>
    <c:isReadOnlyConverter x:Key="isRead"/>
</UserControl.Resources>

   <DataGridTemplateColumn x:Name="exampleTemplate" Header="example:" Width="120" IsReadOnly="True">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                            <CheckBox x:Name="exampleCheckBox" VerticalAlignment="Center" IsEnabled="{Binding ElementName=exmpleTemplate, Path=IsReadOnly, Converter={StaticResource isRead}}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>

和转换器:
class isReadOnlyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        try
        {
            return !(bool)value;
        }
        catch (Exception)
        {
            return false;
        }
    }

0
对我来说,最简单的解决方案是在EditingElementStyle中样式化TextBox
<DataGridTextColumn Binding="{Binding MyValueProperty}">
    <DataGridTextColumn.EditingElementStyle>
        <Style TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding MyReadonlyProperty}" Value="True">
                    <Setter Property="IsReadOnly" Value="True"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>

0

在DataGrid中获取可选择的只读文本单元格的一种方法是使用以下模板和样式:

<DataGrid>
<DataGrid.CellStyle>
    <Style TargetType="{x:Type DataGridCell}">                                        
        <Setter Property="BorderThickness" Value="0" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type DataGridCell}">
                    <Border Padding="0" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
                         <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        <TextBox BorderThickness="0" MouseDoubleClick="DataGrid_TextBox_MouseDoubleClick" IsReadOnly="True" Padding="5" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Text}"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</DataGrid.CellStyle>

对于计算机科学的后端:

private void DataGrid_TextBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        (sender as TextBox).SelectAll();
    }

0
在我的情况下,我正在使用 DataGridTextColumn。 我在 Style 中设置了 ContentPresenter 上的 IsEnabled 属性,它正常工作。
     <DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
        <DataGrid.Resources>
            <Style TargetType="{x:Type DataGridCell}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type DataGridCell}">
                            <Grid Background="{TemplateBinding Background}" >
                                <ContentPresenter IsEnabled="{Binding Path=IsEditable}"/>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </DataGrid.Resources>
        <DataGridTextColumn Header="A" 
                            Binding="{Binding Path=A}"/>
    </DataGrid>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接