2
votes

I am creating a wpf app and struggling with this problem for sometime now. I have a datagrid with DataGridTemplateColumn, which contains a checkbox and textblock.

  <DataGrid
        Name="ChargeDataGrid"
        Grid.Row="1"
        AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTemplateColumn Width="*">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <CheckBox x:Name="CheckBox1"/>
                            <TextBlock Text="{Binding}" />
                        </StackPanel>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.HeaderTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <CheckBox />
                            <TextBlock Text="Title" />
                        </StackPanel>
                    </DataTemplate>
                </DataGridTemplateColumn.HeaderTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
        <system:String>123</system:String>
        <system:String>124</system:String>
        <system:String>125</system:String>
        <system:String>126</system:String>
        <system:String>127</system:String>
    </DataGrid>

What i need to achieve is when row is clicked checkbox in this row must be in checked state too. I tried to use style triggers:

<Style TargetType="{x:Type DataGridCell}">
    <Style.Triggers>
        <Trigger Property="IsSelected" Value="True">
            <Setter Property="CheckBox1.IsChecked" Value="True" />
            <Setter Property="Background" Value="Blue" />
        </Trigger>
    </Style.Triggers>
</Style>

but it didn't seem possible to change checkbox state like this. I know how to do it in code-behind or mvvm style, but in this case i am wondering is it possible to do using xaml only?

Any help would be appreciated.

2

2 Answers

1
votes

I am afraid but with plain standard XAML you can't do it. As I see you have two options:

  1. You can use some extension libraries which will expand functionality of bindings. Some functionality can be found in mvvm frameworks like MugenMvvmToolkit
  2. Second option is to use some converter for this purpose.

My solution for the second variant is a kind of hack and to my mind more elegant way would be with code behind. Converter:

public class MultiValueConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {  
        if (values.Length != 3) throw new ArgumentException("Should be 3 params");
        if (!(values[2] is FrameworkElement element)) return values[1];

        if (!(bool)values[0])
        {
            element.Tag = "Value need to be changed.";
            return values[1];
        }

        if (element.Tag.Equals("Value changed.")) return values[1];
        var res = !(bool)(values[1] ?? true);
        element.Tag = "Value changed.";
        return res;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Converter inversing bool variable and saves previous state in Tag property of control. This is still not code behind and pretty reusable solution. You can use such converter in any other view where you need such behaviour

In XAML I've changed only checkbox control definition:

<CheckBox x:Name="RowCheckBox" IsHitTestVisible="False">
    <CheckBox.IsChecked>
        <MultiBinding Converter="{StaticResource MultiValueConverter}" Mode="OneWay">
            <Binding Path="IsSelected" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}" />
            <Binding Path="IsChecked" RelativeSource="{RelativeSource Self}" />
            <Binding Mode="OneWay" RelativeSource="{RelativeSource Self}" />
        </MultiBinding>
    </CheckBox.IsChecked>
</CheckBox>
1
votes

I think it can bed done rather simple like this:

<CheckBox x:Name="CheckBox1" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=DataGridRow}, Path=IsSelected}"/>

EDIT:

If the desired result is to only change the IsChecked state when (re)selecting the row it can be done with a attached property on a DependencyObject (for instance the containing window) like this:

1) Define the checkbox as this:

<CheckBox x:Name="CheckBox1" IsEnabled="true" local:MainWindow.CheckboxChecked="{Binding RelativeSource={RelativeSource AncestorType=DataGridRow}, Path=IsSelected,Mode=OneWay}">

2) Define the attached property as this:

public static bool GetCheckboxChecked(DependencyObject obj)
{
  return (bool)obj.GetValue(CheckboxCheckedProperty);
}

public static void SetCheckboxChecked(DependencyObject obj, bool value)
{
  obj.SetValue(CheckboxCheckedProperty, value);
}

public static readonly DependencyProperty CheckboxCheckedProperty =
    DependencyProperty.RegisterAttached("CheckboxChecked", typeof(bool), typeof(MainWindow), new PropertyMetadata(false, CheckboxChecked_Changed));
private static void CheckboxChecked_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  CheckBox chk = d as CheckBox;
  if (chk != null && chk.Tag == null)
  {
    bool chkValue = chk.IsChecked.GetValueOrDefault();
    bool oldValue = (bool)e.OldValue;
    bool newValue = (bool)e.NewValue;
    chk.Tag = true; // Just to prevent an infinite loop
    chk.IsChecked = !chkValue && !newValue || chkValue && !oldValue && newValue ? false : true;
    chk.Tag = null;
  }
}