Thursday, April 4, 2019

Make individual DataGridCells read only

DataGridCells don't support IsReadOnly. Grids and columns do, but cells only support IsEditable. You can read IsReadOnly, but the property is read only - ironic isn't it? But if you set a cell's IsEditable = False it grays out. This is pretty lame; I'm talking to you Microsoft.

Our designers want to make an entire row read-only and indicate the read only rows by changing their background color.

I looked at several ways to achieve equivalent results using template selectors and trapping key and mouse events, but the best way I found was using the IsTabStop and Focusable properties.

Start a new WPF project (I'm using Visual Studio 2019 now) and call it ReadOnlyCell.


The XAML involves creating a new style that expects a property on the data source - I've called it IsReadOnly. If this is true, the entire row needs to become read only with the appropriate background color.


<Window x:Class="ReadOnlyCell.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ReadOnlyCell"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style x:Key="BackgroundStyle" TargetType="DataGridCell">
            <Setter Property="Foreground" Value="Black"/>
            <Setter Property="Background" Value="AliceBlue"/>
            <Setter Property="IsTabStop" Value="True"/>
            <Setter Property="Focusable" Value="True"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsReadOnly}" Value="True">
                    <Setter Property="IsTabStop" Value="False"/>
                    <Setter Property="Focusable" Value="False"/>
                    <Setter Property="Background" Value="Aqua"/>
                    <Setter Property="BorderBrush" Value="Aqua"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <DataGrid ItemsSource="{Binding AccountsAndAmounts}" AutoGenerateColumns="false">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Account}" Header="Account" CellStyle="{StaticResource BackgroundStyle}"/>
                <DataGridTextColumn Binding="{Binding Amount}" Header="Amount" CellStyle="{StaticResource BackgroundStyle}"/>
                <DataGridTextColumn Binding="{Binding IsReadOnly}" Header="IsReadOnly" CellStyle="{StaticResource BackgroundStyle}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

The code to populate the data source is.


using System.Collections.ObjectModel;
using System.Windows;

namespace ReadOnlyCell
{
    public partial class MainWindow : Window
    {
        public class cAccountAndAmount
        {
            public string Account { get; set; }
            public string Amount { get; set; }
            public bool IsReadOnly { get; set; }
        }

        public ObservableCollection<cAccountAndAmount> AccountsAndAmounts { get; set; }
        public MainWindow()
        {
            AccountsAndAmounts = new ObservableCollection<cAccountAndAmount>()
            { new cAccountAndAmount() { Account = "1234", Amount = "0", IsReadOnly = true }
            ,
            new cAccountAndAmount() { Account = "abcd", Amount = "1", IsReadOnly = false }};
            InitializeComponent();
        }
    }
}

The result is a datagrid with one row read only, one row editable, and an empty row to add new ones. Note you can change IsReadOnly to True in the second row which will instantly make that row read only (and you can never change it back).



No comments:

Post a Comment