ReadOnly Rows and Cells in a DataGrid

A common requirement for a DataGrid control is to have cells, or entire rows and/or columns that are read-only, or in other words non-editable. My requirements for this are as follows:

  1. ReadOnly can be applied to the entire grid, a column, a row, or an individual cell.
  2. The cell(s) must not allow the cell value to be modified.
  3. The cell(s) must be highlighted in some manner (e.g. background colour) to indicate that they are different to the editable cells.
  4. ReadOnly columns require only single direction data-binding (i.e. Mode=OneWay to read-only properties).

GridEditing - ReadOnly Rows

The Microsoft WPF DataGrid meets these requirements via:

  • DataGrid.IsReadOnly property
<toolkit:DataGrid IsReadOnly="True"
  • DataColumn.IsReadOnly property
<toolkit:DataGridTextColumn Binding="{Binding FullName,Mode=OneWay}" IsReadOnly="True"/>

We can use a simple style targeting all DataGridCells to change the background colour.

<Style TargetType="{x:Type toolkit:DataGridCell}">
    <Style.Triggers>
        <Trigger Property="IsReadOnly" Value="True">
            <Setter Property="Background" Value="LightGray"/>
        </Trigger>
    </Style.Triggers>
</Style>

So the only thing that’s really missing here is the ability to mark an entire row as read-only. In my experience this is a common requirement – we have a list of records displayed in the grid some of which are locked/completed/secured, whilst others can be edited.

One solution is to override the OnBeginningEdit method of the DataGrid. The following example assumes that I have an attached property ControlSupport.IsReadOnly.

protected override void OnBeginningEdit( DataGridBeginningEditEventArgs e )
{
    base.OnBeginningEdit( e );

    bool isReadOnlyRow = ControlSupport.GetIsReadOnly( e.Row );
    if ( isReadOnlyRow )
        e.Cancel = true;
}

However, since I’ve got used to “tweaking” some of the DataGrid code I decided to instead to simply add an IsReadOnly property to the DataGridRow class.

public bool IsReadOnly
{
    get { return (bool) GetValue( IsReadOnlyProperty ); }
    set { SetValue( IsReadOnlyProperty, value ); }
}

// Using a DependencyProperty as the backing store for IsReadOnly.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsReadOnlyProperty =
        DependencyProperty.Register( "IsReadOnly", typeof( bool ), typeof( DataGridRow ), 
new FrameworkPropertyMetadata( false, OnNotifyRowAndCellsPropertyChanged ) );
private static void OnNotifyRowAndCellsPropertyChanged( DependencyObject d, 
DependencyPropertyChangedEventArgs e ) { ( d as DataGridRow ).NotifyPropertyChanged( d, e, NotificationTarget.Rows | NotificationTarget.Cells ); }

Then I just needed to make sure that the read-only property DataGridCell.IsReadOnly would correctly return the right value when its row was marked as read-only.

private static object OnCoerceIsReadOnly(DependencyObject d, object baseValue)
{
    var cell = d as DataGridCell;
    var column = cell.Column;
    var row = cell.RowOwner;
    var dataGrid = cell.DataGridOwner;
    return DataGridHelper.GetCoercedTransferPropertyValue(
        cell, 
        baseValue, 
        IsReadOnlyProperty,
        row,
        DataGridRow.IsReadOnlyProperty,
        column,   
        DataGridColumn.IsReadOnlyProperty,
        dataGrid, 
        DataGrid.IsReadOnlyProperty);
}

There’s a fairly complex series of notification propagation calls going on within the DataGrid classes. For this solution to work it requires that the DataGridRow.IsReadOnly property changing flows down and causes the read-only DataGridCell.IsReadOnly property to be re-evaulated (via the coerce method above). I had to add another GetCoervedTransferPropertyValue method that took another pair of object/property parameters, and also tweaked the DataGridCell.NotifyPropertyChanged as follows:

else if (e.Property == DataGrid.IsReadOnlyProperty || 
e.Property == DataGridColumn.IsReadOnlyProperty ||
e.Property == DataGridRow.IsReadOnlyProperty ||
e.Property == IsReadOnlyProperty) { DataGridHelper.TransferProperty(this, IsReadOnlyProperty); }

Now in my XAML I can do the following (assuming I have an IsReadOnly property on my business objects):

<Style TargetType="{x:Type toolkit:DataGridRow}">
     <Setter Property="IsReadOnly" Value="{Binding IsReadOnly}"/>
</Style>
 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments

  • Wednesday, June 17, 2009 10:32 AM Oliver wrote:
    Where do I write the code suggested do could you perhaps email mail me a solution of the full cs file?

    This was exactly what I wanted to do.
    Reply to this
    1. Wednesday, August 05, 2009 5:01 PM Bob wrote:
      I'm like Oliver, this is exactly what I would like to do too. Could you please send let me know how to get the source code for this sample?
      Many thanks
      Reply to this
      1. Thursday, August 06, 2009 9:31 AM Nigel Spencer wrote:
        Here is a sample solution that demonstrates customising the WPF Toolkit DataGrid - including allowing ReadOnly rows. The solution contains two projects - my sample application and a modified version of the WPFToolkit. It's based on the March 2009 version of the WPF Toolkit - there aren't that many changes - you should be able to use BeyondCompare/WifDiff etc. to work out the changes I made.

        Note that to make these changes I modified the original source so you will need to recompile the updated WPF Toolkit. I'm using source control so its easy to track the changes I make and re-apply the "patches" to new versions when Microsoft release them (like the just released June WPF Toolkit). Having said that it would have been nice to achieve the desired fixes by subclassing or using attached properties, events but I think that would have become very "messy" given the number of changes.

        From memory some of the other changes were - removing the ridiculous "Show Calendar" text that displays in the DatePicker, preserving CharacterCasing on grid TextBox... hmm... I can't remember the rest. Still you should see all the changes by comparing my source against the March 2009 source.
        Reply to this
  • Saturday, August 15, 2009 1:24 AM Hemant wrote:
    want this code.
    Reply to this
  • Thursday, April 22, 2010 2:00 AM Kurko wrote:
    Hi Nigel,
    thank you for sharing and nice piece of source code. Great!
    Reply to this
  • Wednesday, June 30, 2010 6:16 PM Elby wrote:
    Should the solution work for when I want to hypothetically make the Date of Birth cell readonly for those who's last name is Smith.
    Reply to this
    1. Thursday, July 01, 2010 7:53 PM Nigel Spencer wrote:
      Yes - you simply expose a property on your row's ViewModel that reflects that rule, e.g. public bool CanModifyDateOfBirth. Then data-bind the new cell IsReadOnly property to that ViewModel property. If the property can change dynamically - e..g when they change the last name, then make sure the CanModifyDateOfBirth setter fires a change notification.
      Reply to this
      1. Tuesday, August 10, 2010 7:46 AM Rasmus wrote:
        Yes but how is it possible to make only the Date of Birth cell readonly and leave other cells editable?
        Reply to this
        1. Saturday, August 14, 2010 8:52 PM Nigel Spencer wrote:
          That's pretty much the purpose of this post. You could probably hack the specific editing template for that cell - but I choose instead to create an IsReadOnly property at the individual cell level. To do this I had to modify the DataGrid source code itself. Refer comments above.
          Reply to this
  • Tuesday, October 05, 2010 10:22 PM miliu wrote:
    Hi Nigel, thanks for your great article. I need IsReadOnly property on each individual DataGridCell level. In your article, you changed the original DataGrid code directly, this is okay because you use WPF toolkit. Now, I'm using .NET4 DataGrid, and can't change the source directly. What can I do?
    Reply to this
    1. Thursday, October 21, 2010 7:04 AM Paul wrote:
      I have the same problem. I was thinking about some attached property "IsEnabled" solution, but unfortunately I can't figure it out how to make it works. Any suggestions will be appreciated.
      Reply to this
    2. Thursday, October 21, 2010 8:56 AM Paul wrote:
      Ok, I think I figure it out: DataGrid class has event "PreparingCellForEdit" and if we subscribe this event and check that current editing cell has attached property “IsReadOnly” set to true we can cancel further edit by calling “CancelEdit()” method on our DataGrid (the reference to that grid we have from casting “object sender” from EventHandler and that EventHandler we are adding to our grid by for example another attached property e.g. “AllowReadOnlyCells”. This works, but I don’t know if this is optimal solution.
      Reply to this
      1. Friday, April 29, 2011 10:26 AM Priya wrote:
        Can you pls post yr code in PreparingCellForEdit event on how you did it. I am trying to do similar thing.
        Thanks
        Reply to this
  • Friday, March 25, 2011 10:09 AM Gabriel wrote:
    I guess there aren't any follow-ups to this in .NET 4. I found that id I make this class:
    public class DataGridNew : DataGrid
    {
    protected override void OnBeginningEdit(DataGridBeginningEditEventArgs e)
    {
    base.OnBeginningEdit(e);
    bool isReadOnlyRow = ControlSupport <--- Here is where it all goes wrong.

    "ControlSupport" does not exist in the current context.

    I have been trying to find "ControlSupport" info everywhere to no avail. I'm using C# not VB, what would it be in C#?
    Regards,
    The Bug
    Reply to this
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.