简体   繁体   中英

Checkbox Command Not Firing When Initially Unchecked

I have a column of checkboxes in a datagrid.

<DataGridTemplateColumn CanUserResize="False" Header="" Width="auto">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <CheckBox Style="{StaticResource CheckBoxSelectTypeStyle}" IsChecked="{Binding Path=Selected}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

I have a command bound to unchecking and checking.

<Style x:Key="CheckBoxSelectTypeStyle" TargetType="{x:Type CheckBox}">
    <Setter Property="HorizontalAlignment" Value="Center" />
    <Setter Property="VerticalAlignment" Value="Center" />
    <Style.Triggers>
        <Trigger Property="IsChecked" Value="True">
            <Setter Property="Command" Value="{Binding DataContext.CheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
            <Setter Property="CommandParameter" Value="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
        </Trigger>
        <Trigger Property="IsChecked" Value="False">
            <Setter Property="Command" Value="{Binding DataContext.UncheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
            <Setter Property="CommandParameter" Value="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
        </Trigger>
    </Style.Triggers>
</Style>

My problem is that if the checkboxes start as unchecked and you check one, then no commands are fired (This is the problem). If you then uncheck the same checkbox, the uncheck command will fire (As expected). If you then check the same checkbox again, the check command will fire (As expected). Everything will work fine for that checkbox at that point, but the others still have the same problem.

If a checkbox starts as checked it will work fine. My question is how do I make the command fire when the checkbox starts as unchecked. I can't find any reason for it not to work.

Response to suggestion:

I tried to add a trigger for Null , but it has the exact same problem, nothing changed.

<Style.Triggers>
    <Trigger Property="IsChecked" Value="True">
        <Setter Property="Command" Value="{Binding DataContext.CheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
        <Setter Property="CommandParameter" Value="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
    </Trigger>
    <Trigger Property="IsChecked" Value="False">
        <Setter Property="Command" Value="{Binding DataContext.UncheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
        <Setter Property="CommandParameter" Value="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
    </Trigger>
    <Trigger Property="IsChecked" Value="{x:Null}">
        <Setter Property="Command" Value="{Binding DataContext.UncheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
        <Setter Property="CommandParameter" Value="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
    </Trigger>
</Style.Triggers>

ViewModel Code

public ICommand CheckedCommand { get; set; }
public ICommand UncheckedCommand { get; set; }

CheckedCommand = new RelayCommand<IList>(Checked);
UncheckedCommand = new RelayCommand<IList>(Unchecked);

private void Checked(IList selectedItems)
{
    ChangedChecked(selectedItems, true);
}

private void Unchecked(IList selectedItems)
{
    ChangedChecked(selectedItems, false);
}

private void ChangedChecked(IList selectedItems, bool selected)
{
    if (selectedItems.HasValue())
        foreach (var item in selectedItems)
            if(item is SelectedTypeModel) (item as SelectedTypeModel).Selected = selected;
}

RelayCommand implements ICommand. As I mentioned before, the Checked method is not called when the checkbox starts as unchecked, but it is called when it is checked, unchecked, and checked again or if it starts checked and is unchecked.

What I'm Trying To Do

I have a DataGrid with a column of CheckBoxs. I want to be able to highlight multiple rows, then check/uncheck a CheckBox in one of the selected rows and have all the other rows update to be the same. I also have a keyword filter for the datagrid, so the DataGrid is binding to a ListCollectionView.

Output Information

I enabled debug information for data binding and get this message:

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Selected; DataItem=null; target element is 'CheckBox' (Name=''); target property is 'IsChecked' (type 'Nullable`1')

I'm still not sure how to use this information to correct the problem though.

SOLUTION

I did not fix my original problem and I still don't know why it doesn't work correctly, but I have used another method with the same desired result. Here is the changed code:

<Style x:Key="CheckBoxSelectTypeStyle" TargetType="{x:Type CheckBox}">
    <Setter Property="Command" Value="{Binding DataContext.CheckedChangedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
    <Setter Property="CommandParameter">
        <Setter.Value>
            <MultiBinding Converter="{StaticResource SelectedItemsCheckedMultiValueConverter}">
                <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}" Path="SelectedItems"/>
                <Binding RelativeSource="{RelativeSource Self}" Path="IsChecked"/>
            </MultiBinding>
        </Setter.Value>
    </Setter>
</Style>

public ICommand CheckedChangedCommand { get; set; }

CheckedChangedCommand = new RelayCommand<Tuple<IList, bool>>(CheckedChanged);

Solution I think you can achieve your goals without any need in triggers (I've tested this solution in small sample app): so your style would looks like this:

<Style x:Key="CheckBoxSelectTypeStyle" TargetType="{x:Type CheckBox}">
    <Setter Property="Command" Value="{Binding DataContext.CheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
    <Setter Property="CommandParameter" Value="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
  ...

There will be only one command in your view model. And as parameter that command will receive list of selected items. So only one thing is missing - whether user is about to check or uncheck checkbox. At the moment I can think of one possible way of passing this info along with SelectedItems - write MultiBinding with custom converter which will put selectedItems and current value of IsChecked in something like Tuple<..., bool>

        <Setter Property="CommandParameter">
            <Setter.Value>
                <MultiBinding Converter="{x:Static PackTupleConverter.Instance}">
                    <Binding RelativeSource="{RelativeSource AncestorType=DataGrid}" Path="SelectedItems"/>
                    <Binding RelativeSource="{x:Static RelativeSource.Self}" Path="IsChecked"/>
                </MultiBinding>
            </Setter.Value>
        </Setter>

end

I was able to repro your problem by simply putting same checkbox inside ListBox. Once that done first time I click on checkbox - command don't get called

here is sample window xaml:

<Window.Resources>
    <Style x:Key="CheckBoxSelectTypeStyle" TargetType="{x:Type CheckBox}">
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="VerticalAlignment" Value="Center" />
        <Style.Triggers>
            <Trigger Property="IsChecked" Value="True">
                <Setter Property="Command" Value="{Binding DataContext.Test, RelativeSource={RelativeSource AncestorType=Window}}" />
            </Trigger>
            <Trigger Property="IsChecked" Value="False">
                <Setter Property="Command" Value="{Binding DataContext.Test, RelativeSource={RelativeSource AncestorType=Window}}" />
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<Grid>
    <ListBox>
        <ListBox.Items>
            <CheckBox Style="{StaticResource CheckBoxSelectTypeStyle}" />
        </ListBox.Items>
    </ListBox>

</Grid>

and sode behaind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
        Test = new DelegateCommand(TestCommand);
    }

    public ICommand Test { get; set; }

    private void TestCommand()
    {
    }
}

Here is what I've discovered in output regarding binding:

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=DataContext.Test; DataItem=null; target element is 'CheckBox' (Name=''); target property is 'Command' (type 'ICommand')

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM