lucas.mdo
lucas.mdo

Reputation: 439

Binding IsReadOnly of a DataGridTextColumn to a DataGridTemplateColumn checkbox IsChecked

Basically, I have a DataGrid with several columns, and I want to enable (changing the IsReadOnly property) a DataGridTextColumn based on a CheckBox IsChecked, located in another DataGridTemplateColumn of the same DataGrid.

Here is (the important part of) the code:

<DataGrid Name="lstTags" Grid.Row="0" ItemsSource="{Binding Path = LinesCollection}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" SelectionMode="Single" LostFocus="lstTags_LostFocus" SelectionChanged="lstTags_SelectionChanged">
    <DataGrid.Columns>
        <DataGridTemplateColumn x:Name="colAutoScale" Header="Auto Scale">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <CheckBox x:Name="ckbAutoScale" HorizontalAlignment="Center" IsChecked="{Binding AutoScale, UpdateSourceTrigger=PropertyChanged}"/>
                 </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTextColumn Header="Scale" Binding="{Binding Path=Scale}" IsReadOnly="{Binding ElementName ckbAutoScale, Path=IsChecked}" Width="60" />
    </DataGrid.Columns>
</DataGrid>

It is worth mentioning that I also want to invert the value of the IsChecked property, that is

I would probably achieve this with a simple Converter, but I need that first part working tho.

EDIT:

Answering a good question, my goal is to disable the adjacent cell (same row), not the whole column.

Upvotes: 3

Views: 6996

Answers (2)

Kylo Ren
Kylo Ren

Reputation: 8813

Use below binding for your Scale Column:

 <DataGridTextColumn Header="Scale" Binding="{Binding Path=Scale}" Width="60" >
      <DataGridTextColumn.CellStyle>
           <Style TargetType="DataGridCell">
                <Setter Property="IsEnabled" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}},Path=Children[0].Content.Content.AutoScale}" />
           </Style>
      </DataGridTextColumn.CellStyle>
</DataGridTextColumn>

OR simply

<DataGridTextColumn Header="Scale" Binding="{Binding Path=Scale}" Width="60" >
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="IsEnabled" Value="{Binding Path=AutoScale}" />
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>

Output:

ReadOnly

PS: Above Solution 1 is specific to your code, cause Auto Scale column is at 0 Index that's why I used Children[0] in Binding. Please change if there is any contextual need.

Upvotes: 3

devuxer
devuxer

Reputation: 42354

This type of problem is really the reason the Model-View-ViewModel (MVVM) pattern exists.

With MVVM, you bind to view models that have the exact properties needed to support the view. This allows the model to be more concerned with what data needs to be persisted.

So, for your problem, you would need to create a LineViewModel, which would look something like this:

public class LineViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private bool _isAutoScale;
    private double _scale;

    public bool IsAutoScale
    {
        get { return _isAutoScale; }
        set
        {
            if (value == _isAutoScale) return;
            _isAutoScale = value;
            OnPropertyChange("IsAutoScale");
            OnPropertyChange("IsReadOnly");
        }
    }

    public double Scale
    {
        get { return _scale; }
        set
        {
            if (value == _scale) return;
            _scale = value;
            OnPropertyChange("Scale");
        }
    }

    public bool IsReadOnly => !IsAutoScale;

    private void OnPropertyChange(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Meanwhile, you would also want to create a parent view model called MainWindowViewModel (or something that makes sense for your situation). Here is a very crude version:

public class MainWindowViewModel : INotifyPropertyChanged
{
    private List<LineViewModel> _lineViewModels;
    public event PropertyChangedEventHandler PropertyChanged;

    public List<LineViewModel> LineViewModels
    {
        get { return _lineViewModels; }
        set
        {
            if (value == _lineViewModels) return;
            _lineViewModels = value;
            OnPropertyChange("LineViewModels");
        }
    }

    public MainWindowViewModel()
    {
        LineViewModels = new[]
        {
            new { AutoScale = false, Scale = 0.2 },
            new { AutoScale = true, Scale = 0.3 },
            new { AutoScale = false, Scale = 0.4 },
        }
            .Select(
                x => new LineViewModel
                {
                    IsAutoScale = x.AutoScale,
                    Scale = x.Scale
                })
            .ToList();
    }

    private void OnPropertyChange(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Finally, you would update your XAML file to look something like this:

<Window x:Class="Sandbox.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:sandbox="clr-namespace:Sandbox"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="350"
        Width="525">
    <Window.DataContext>
        <sandbox:MainWindowViewModel />
    </Window.DataContext>
    <DataGrid ItemsSource="{Binding LineViewModels}"
              HorizontalAlignment="Stretch"
              VerticalAlignment="Stretch"
              AutoGenerateColumns="False"
              CanUserAddRows="False"
              CanUserDeleteRows="False"
              SelectionMode="Single">
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Auto Scale">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <CheckBox HorizontalAlignment="Center"
                                  IsChecked="{Binding IsAutoScale}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <DataGridTemplateColumn Header="Auto Scale">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Scale}"
                                 IsReadOnly="{Binding IsReadOnly}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Window>

So, basically, the view logic for MainWindow is determined by MainWindowViewModel and the view logic for each row of the DataGrid is controlled by a LineViewModel.

Note that a lot of the boilerplate for implementing INotifyPropertyChanged can be simplified using libraries/NuGet packages like MVVM Light Toolkit and PropertyChanged.Fody.

Upvotes: 3

Related Questions