How to make WPF DataGridCell ReadOnly?
Asked Answered
D

11

21

I understand you can make the whole DataGrid or a whole column readyonly (IsReadOnly = true). However, at cell level this property is ready only. But I do need this level of granularity. There is blog about adding IsReadOnly to a row by changing the source code in old days when DataGrid was public domain, but now I don't have source code for DataGrid. What's workaround?

Making cell disabled (IsEnabled=false) almost meets my need. But the problem is that you can't even click the disabled cell to select the row (I have full row selection mode).

EDIT: Since nobody has responded to this question, so I guess it's not an easy fix. Here is a possible workaround: Make the cell uneditable. The only problem is that clicking the cell doesn't select the row. I just noticed that MouseDown or MouseUp event of the DataGrid is still fired when the disabled cell is clicked. In this event handler, if I could figure out the row it clicked, I could select the row programmatically. However, I couldn't figure out how to find the underlying row from DataGrid.InputHitTest. Can somebody please give me some tip?

Devote answered 1/10, 2010 at 22:1 Comment(0)
S
12

I've encountered the same problem, the cell should be read-only in some rows but not in the others. Here is a workaround solution:

The idea is to dynamically switch the CellEditingTemplate between two templates, one is the same as the one in the CellTemplate, the other is for editing. This makes the edit mode acts exactly the same as the non-editing cell although it is in edit mode.

The following is some sample code for doing this, notice that this approach requires DataGridTemplateColumn:

First, define two templates for read-only and editing cells:

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

Then define a data template with additional ContentPresenter layer and use Trigger to switch the ContentTemplate of the ContentPresenter, so the above two templates can be switched dynamically by the IsEditable binding:

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

HTH

Subsidy answered 19/8, 2011 at 2:21 Comment(3)
Recle, this was actually what I have come up as well. The only thing I miss is that, in general, I'd like to show read-only cells in a different color. So, I'm accepting yours. I hope Microsoft could introduce this cell level property in the next release.Devote
@Subsidy what is your source data type for grid binding? In case that you r using datatable to fill DataView type as DataGrid ItemSource and then you would like to access object on Value level in DataTable, which has IsEditable and MyCellValue properties, how your solution will look like? ThanksSmegma
@Subsidy In my case I have the collection of grid sources and grids are populated dynamically based on the source (in ScrollViewer - within DataTemplate property). Thanks for adviceSmegma
D
14

After much searching and experimentation using IsTabStop = False and Focusable = False works best for me.

<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>
Drogheda answered 9/9, 2015 at 7:57 Comment(4)
oh… this is the cleanest solution I'v found!Ineludible
Great solution!Minelayer
A scenario which this creates problem is: there is a table with two columns, the first one is editable the second one is not. While focus is on a row of first column, click on another row of second column, you will see the focus doesn't change. The new selected row is blue, but if user press keys the previously selected cell would be edited.Comose
What a fantastically elegant and dead simple solution. Well done, that man!Grimace
S
13

There is a property on DataGridCell.IsReadOnly that you might think you can bind to,
e.g. using XAML like this:

<!-- 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>

Unfortunantly this won't work because this property is not writable.
Next you might attempt to intercept and stop mouse events, but this won't prevent the user from entering edit mode using the F2 key.

The way I sloved this was by listening for the PreviewExecutedEvent on the DataGrid and then conditionally flagging it as handled.
E.g. by adding code similar to this to the constructor of my Window or UserControl (or another more suitable place):

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

By doing it like this the cells are still focusable and selectable.
But the user will not be able to enter edit mode unless your model items allow it for the given row.
And you will not suffer the performance costs or complexities by using the DataGridTemplateColumn.

Salvatoresalvay answered 2/10, 2011 at 0:24 Comment(4)
Nice solution. You can also make specific column-row combinations readonly with this code by looking at dataGrid.CurrentCell.Column.Summand
I ended up following this except I used the BeginningEdit event event on DataGrid which gives me the Row and Column in the args so I didn't have to figure it out.Redford
I really wish I could give you +100 for this. I've worked through all other suggestions I found but they all came with side-effects. This was EXACTLY what I needed!!!! Kudos!!!!Chutney
This solution was perfect because it allows you to effectively disable the rows but still preserve keyboard driving via the arrow keysViquelia
S
12

I've encountered the same problem, the cell should be read-only in some rows but not in the others. Here is a workaround solution:

The idea is to dynamically switch the CellEditingTemplate between two templates, one is the same as the one in the CellTemplate, the other is for editing. This makes the edit mode acts exactly the same as the non-editing cell although it is in edit mode.

The following is some sample code for doing this, notice that this approach requires DataGridTemplateColumn:

First, define two templates for read-only and editing cells:

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

Then define a data template with additional ContentPresenter layer and use Trigger to switch the ContentTemplate of the ContentPresenter, so the above two templates can be switched dynamically by the IsEditable binding:

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

HTH

Subsidy answered 19/8, 2011 at 2:21 Comment(3)
Recle, this was actually what I have come up as well. The only thing I miss is that, in general, I'd like to show read-only cells in a different color. So, I'm accepting yours. I hope Microsoft could introduce this cell level property in the next release.Devote
@Subsidy what is your source data type for grid binding? In case that you r using datatable to fill DataView type as DataGrid ItemSource and then you would like to access object on Value level in DataTable, which has IsEditable and MyCellValue properties, how your solution will look like? ThanksSmegma
@Subsidy In my case I have the collection of grid sources and grids are populated dynamically based on the source (in ScrollViewer - within DataTemplate property). Thanks for adviceSmegma
A
4

I've solved this problem in my application by setting the underlying object in the cell (eg. CheckBox) - IsHitTestVisible = false; Focusable = false;

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

"row" is a DataGridRow. IsHitTestVisible=false means that you can't click/select/manipulate the underlying object via mouse, but you can still select the DataGridCell. Focusable=false means that you can't select/manipulate the underlying object with the keyboard. This gives the illusion of a ReadOnly cell, but you can still select the cell and I'm sure if the DataGrid is set up to SelectionMode=FullRow then clicking the "read only" cell will select the entire row.

Anodyne answered 17/11, 2010 at 21:32 Comment(2)
This seems to be a good idea. However, based on my testing, your suggestion works only for DataGridCheckBoxColumn, but not for DataGridTextColumn and DataGridComboBoxColumn. Any idea how to fix it?Devote
Thanks man, this worked for me also. Checkbox was my case.Allowable
D
1

My solution is to use binding to the DataGridTemplateColumn with converter.

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

and the converter:

class isReadOnlyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        try
        {
            return !(bool)value;
        }
        catch (Exception)
        {
            return false;
        }
    }
Ducks answered 10/11, 2014 at 18:47 Comment(0)
E
1

This is a bit late but, I was looking into this as well, these solutions work well but I needed something a little different, I did the following and it works exactly like I wanted and what the question is looking for.

I essentially I wanted to be able to enter edit mode for the cell and have all that other templates and command logic the same while not being able to edit the cell.

The solution for all this is to set the TextBox.IsReadOnly property to true in the DataGridCell Style and to handle the initial keydown event

<Style TargetType="DataGridCell">
    <Setter Property="TextBox.IsReadOnly" Value="True"/>
    <EventSetter Event="PreviewKeyDown" Handler="cell_PreviewKeyDown"/>
</Style>

and the following code behind to stop the initial edit

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

Hopefully this is helpful.

Ezana answered 24/3, 2015 at 17:6 Comment(1)
Great solution! This solved my requirement to be able to enter edit mode in order to select and copy text without being able to change it. I only used the <Setter Property="TextBox.IsReadOnly" Value="True"/> part though, and seems to do the trick.Cordovan
M
1

Based on @sohum comment, here you can use simplified version of the response marked as answer.

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

You can use it later as Attached Property Behaviour.

Militant answered 10/8, 2018 at 11:45 Comment(0)
R
1

For me, the most simple solution was to style the TextBox inside the EditingElementStyle.

<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>
Rocketeer answered 10/12, 2021 at 18:0 Comment(0)
J
0

One way of getting selectable, read-only text cells for DataGrid is to use template and style like this:

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

And for CS backend:

private void DataGrid_TextBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        (sender as TextBox).SelectAll();
    }
Jabe answered 21/6, 2013 at 10:47 Comment(0)
O
0

In my case I was using DataGridTextColumn. I set the IsEnabled property on ContentPresenter in Style as follows and it works fine

     <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>
Od answered 22/9, 2018 at 14:1 Comment(0)
G
0

You can do this with a simpler data template.

<DataGrid.Resources>
    <DataTemplate x:Key="MyTemplate" DataType="MyRowDataType">
        <TextBox Text="{Binding Value}" IsReadOnly="{Binding IsReadOnly}" />
    </DataTemplate>
</DataGrid.Resources>

...

<DataGridTemplateColumn CellTemplate="{StaticResource MyTemplate}" />
Glottalized answered 13/9, 2019 at 19:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.