WPF Datagrid with some read-only rows

20,767

Solution 1

I had the same problem. Using information provided in jsmith's answer and on Nigel Spencer's blog, I've come up with a solution that doesn't require changing WPF DataGrid source code, subclassing or adding code to view's codebehind. As you can see, my solution is very MVVM Friendly.

It uses Expression Blend Attached Behavior mechanism so you'll need to install Expression Blend SDK and add reference to Microsoft.Expression.Interactions.dll, but this behavior could be easily converted to native attached behavior if you don't like that.

Usage:

<DataGrid 
    xmlns:Behaviors="clr-namespace:My.Common.Behaviors"
...
>
    <i:Interaction.Behaviors>
         <Behaviors:DataGridRowReadOnlyBehavior/>
    </i:Interaction.Behaviors>
    <DataGrid.Resources>
        <Style TargetType="{x:Type DataGridRow}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsReadOnly}" Value="True"/>
                    <Setter Property="Behaviors:ReadOnlyService.IsReadOnly" Value="True"/>
                    <Setter Property="Foreground" Value="LightGray"/>
                    <Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
                    <Setter Property="ToolTip" Value="Disabled in ViewModel"/>
                </DataTrigger>

            </Style.Triggers>
        </Style>
      </DataGrid.Resources>
...
</DataGrid>

ReadOnlyService.cs

using System.Windows;

namespace My.Common.Behaviors
{
    internal class ReadOnlyService : DependencyObject
    {
        #region IsReadOnly

        /// <summary>
        /// IsReadOnly Attached Dependency Property
        /// </summary>
        private static readonly DependencyProperty BehaviorProperty =
            DependencyProperty.RegisterAttached("IsReadOnly", typeof(bool), typeof(ReadOnlyService),
                new FrameworkPropertyMetadata(false));

        /// <summary>
        /// Gets the IsReadOnly property.
        /// </summary>
        public static bool GetIsReadOnly(DependencyObject d)
        {
            return (bool)d.GetValue(BehaviorProperty);
        }

        /// <summary>
        /// Sets the IsReadOnly property.
        /// </summary>
        public static void SetIsReadOnly(DependencyObject d, bool value)
        {
            d.SetValue(BehaviorProperty, value);
        }

        #endregion IsReadOnly
    }
}

DataGridRowReadOnlyBehavior.cs

using System;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace My.Common.Behaviors
{
    /// <summary>
    /// Custom behavior that allows for DataGrid Rows to be ReadOnly on per-row basis
    /// </summary>
    internal class DataGridRowReadOnlyBehavior : Behavior<DataGrid>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            if (this.AssociatedObject == null)
                throw new InvalidOperationException("AssociatedObject must not be null");

            AssociatedObject.BeginningEdit += AssociatedObject_BeginningEdit;
        }

        private void AssociatedObject_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
        {
            var isReadOnlyRow = ReadOnlyService.GetIsReadOnly(e.Row);
            if (isReadOnlyRow)
                e.Cancel = true;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.BeginningEdit -= AssociatedObject_BeginningEdit;
        }
    }
}

Solution 2

I found a couple of simple solutions to this problem. The best in my opinion was hooking up to the BeginningEdit event of the DataGrid. This is similar to what Nigel Spencer did in his post, but you don't have to override it from DataGrid. This solution is great since it doesn't allow the user to edit any of the cells in that row, but it does allow them to select the row.

In Code Behind:

private void MyList_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
  if (((MyCustomObject)e.Row.Item).IsReadOnly)  //IsReadOnly is a property set in the MyCustomObject which is bound to each row
  {
    e.Cancel = true;
  }
}

In XAML:

<DataGrid ItemsSource="{Binding MyObservableCollection}"
          BeginningEdit="MyList_BeginningEdit">
  <DataGrid.Columns>
    <DataGridTextColumn Binding="{Binding Name}"
                        Header="Name"/>
    <DataGridTextColumn Binding="{Binding Age}"
                        Header="Age"/>
  </DataGrid.Columns>
</DataGrid>

Different Solution... This does not allow the user to select the row at all, but does not require additional code in the code behind.

<DataGrid ItemsSource="{Binding MyObservableCollection}">
  <DataGrid.Resources>
    <Style TargetType="{x:Type DataGridRow}">
      <Style.Triggers>
        <DataTrigger Binding="{Binding IsReadOnly}"
                     Value="True" >
        <Setter Property="IsEnabled"
                Value="False" />   <!-- You can also set "IsHitTestVisble" = False but please note that this won't prevent the user from changing the values using the keyboard arrows -->
        </DataTrigger>

      </Style.Triggers>
    </Style>
  </DataGrid.Resources>

  <DataGrid.Columns>
    <DataGridTextColumn Binding="{Binding Name}"
                        Header="Name"/>
    <DataGridTextColumn Binding="{Binding Age}"
                        Header="Age"/>
  </DataGrid.Columns>
</DataGrid>

Solution 3

I think the easiest way to do it is to add an IsReadOnly property to the DataGridRow class. There is a detailed article by Nigel Spencer on how to do this here.

Share:
20,767
joerage
Author by

joerage

Software Developer working on the InRelease software since its inception four years ago. InRelease has been purchased by Microsoft and is now called Release Management.

Updated on February 06, 2020

Comments

  • joerage
    joerage over 4 years

    I have the need to show some of my WPF Datagrid rows as read only or not depending on a property on my bound model.

    How can this be done?

  • joerage
    joerage over 14 years
    Thanks, I had see this post. I was hoping for something easier. I don't like the idea of modifying the source code (for maintability issues when new versions come out).
  • jsmith
    jsmith over 14 years
    Yea, It's Unfortunate they have yet to add an IsReadOnly property to their DataGridRow, there is an IsEnabled feature you can play with however.
  • Mohsen
    Mohsen almost 12 years
    Thank you.The right answer is here. it worked for me like a charm.
  • Berryl
    Berryl over 11 years
    You need the IsReadOnly property somewhere to make this work so I added an interface and casted e.Row.Item to it, making the ReadOnlyService unnecessary, IMO. Big +1 on this though. Cheers
  • Vijay Chavda
    Vijay Chavda almost 7 years
    The XAML only approach is much better and cleaner :)