Hosea146
Hosea146

Reputation: 7722

Executing a command on Checkbox.Checked or Unchecked

I have a checkbox control on a window. I'd like to execute a command that will call a method in the associated view model. I'll also need the value of the checkbox as well. I cannot seem to find a way to associate a command with a checkbox. Has anyone done this?

Upvotes: 57

Views: 90751

Answers (7)

Lunar
Lunar

Reputation: 31

Here's stuff I tried according to Microsoft docs and other SO questions:

None of these did what I wanted which was, similar to you, to call a method/command based on the Checkbox property changing, because these were either messy and convoluted or broke MVVM principles because they were limited to the View and not the View Model.

Similar to this SO answer to a separate question, and this SO answer, I ended up using the EventToCommandBehavior behaviour from the .NET MAUI Community Toolkit. I don't know about how this may or may not work with WPF, sorry.

Here's my simplified code for this example.

MainPage.xaml:

<ContentPage
        x:Class="MyProject.Views.MainPage"
        xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
        xmlns:viewmodel="clr-namespace:MyProject.ViewModels"
        x:DataType="viewmodel:MainPageViewModel">
 
        <CheckBox x:Name="WholeNameCheckbox"
              IsChecked="{Binding IsExactMatch}"
              HorizontalOptions="Center">
              <CheckBox.Behaviors>
                    <toolkit:EventToCommandBehavior
                               EventName="CheckedChanged"
                               Command="{Binding ExactMatchCheckedCommand}"
                               CommandParameter="{Binding IsChecked, Source={x:Reference WholeNameCheckbox}}"/>
             </CheckBox.Behaviors>
        </CheckBox>
</ContentPage>

MainPage.xaml.cs:

namespace MyProject.Views;

public partial class MainPage : ContentPage
{
    public MainPage(MainPageViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

MainPageViewModel.cs:

using MyProject.Services;
using MvvmHelpers;   // For [RelayCommand]

namespace MyProject.ViewModels;

public partial class MainPageViewModel : BaseViewModel 
{
  // ObservableProperty gives the variable a public property and
  // all the NotifyChanges interfaces and gives the properties
  // OnPropertyChanged() methods.
  [ObservableProperty]
  bool _returnsPartialMatch;

  [ObservableProperty]
  bool _returnsPartialMatchWithSpace;

  [ObservableProperty]
  public bool _isExactMatch;

  [RelayCommand]
  async Task ExactMatchCheckedAsync(bool isChecked_p)
  {
       // Offload processing to thread pool.
       await Task.Run(() =>
       {
           // Do stuff in your view model using your properties or whatever
       }
  }
}

Works as expected - when the Exact Match checkbox is checked, IsExactMatch property is True, and either way, when the checked state is changed it calls the asynchronous method in the view model and passes its IsChecked property value to the method. Since this command is called after the IsExactMatch property has been changed, there's no weirdness with timing on that value changing or needing to validate it.

Upvotes: 2

Rohit Vats
Rohit Vats

Reputation: 81333

This will work what you requires -

<CheckBox CommandParameter="{Binding}"
          Command="{Binding DataContext.YourCommand,
          RelativeSource={RelativeSource FindAncestor,
                           AncestorType={x:Type UserControl}}}"
          Content="{Binding Path=Name}">

Upvotes: 15

kub1x
kub1x

Reputation: 3582

  • Add System.Windows.Interactivity to your project references.
  • Add xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" to your XAML namespaces.
<CheckBox IsChecked="{Binding SomeBoolProperty, Mode=OneWay}" Content="Check Meee!">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Checked">
            <i:InvokeCommandAction Command="{Binding MyOnCheckedCommand}"/>
        </i:EventTrigger>
        <i:EventTrigger EventName="Unchecked">
            <i:InvokeCommandAction Command="{Binding MyOnUncheckedCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</CheckBox>

I implement INotifyPropertyChanged on my ViewModel as follows:

public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

The SomeBoolProperty of my ViewModel then looks like this:

private bool _SomeBoolProperty = false;
public bool SomeBoolProperty { 
    get => _SomeBoolProperty;
    set { 
        _SomeBoolProperty = value; 
        OnPropertyChanged(nameof(SomeBoolProperty)); 
    } 
}

I use RelayCommand as my command implementation from here https://stackoverflow.com/a/22286816/336753.

The commands on my ViewModel then look like this:

public ICommand MyOnCheckedCommand { get; } = new RelayCommand(o => {
    // Do something here.
    SomeBoolProperty = true;
});
public ICommand MyOnUncheckedCommand { get; } = new RelayCommand(o => {
    // Do something else here.
    SomeBoolProperty = false;
});

I got to this question trying to find a way to reuse two commands I already had on my ViewModel. One called when checked and one when unchecked. I use them on some buttons too so did not want to add an extra parametrized command. People were asking here about ViewModel implementation so adding this answer to complete the one from Igor_S. Hope it helps.

Upvotes: 5

Arseny
Arseny

Reputation: 7361

<CheckBox Content="CheckBox"
          Command="{Binding YourCommand}"
          CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}" />

Upvotes: 97

Eric Ouellet
Eric Ouellet

Reputation: 11763

I'm late... I used Rohit Vats answer and came up with this code.

The example is a working code extract and it is only here to help to understand every aspects. It is a pushpin that could be either active or inactive and it use a DelegateCommand. You could probably also use a RelayCommand or any other similar class to do the same job.

Command:

using System.Windows.Input;

namespace HQ.Wpf.Util.Command
{
    public class StandardCommand
    {
        public static RoutedUICommand PinPropertyGrid = new RoutedUICommand("Pin property grid", "PinPropertyGrid", typeof(StandardCommand));

Xaml:

                            <CheckBox HorizontalAlignment="Right" 
                                      VerticalAlignment="Top"
                                      Margin="2,0,3,0" 
                                      Command="{Binding CommandPinPropertyGrid}"
                                      CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}">
                                <CheckBox.Template>
                                    <ControlTemplate TargetType="{x:Type CheckBox}">
                                        <Grid>
                                            <Image x:Name="ImagePushpin" Width="16" Height="16" Source="pack://application:,,,/WpfUtil;component/Images/PushpinUnpinned16x16.png" />
                                        </Grid>
                                        <ControlTemplate.Triggers>
                                            <Trigger Property="IsChecked" Value="True">
                                                <Setter TargetName="ImagePushpin" Property="Source" Value="pack://application:,,,/WpfUtil;component/Images/PushpinPinned16x16.png" />
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </CheckBox.Template>
                            </CheckBox>

Model:

public MainWindowViewModel()
{
    CommandPinPropertyGrid = new DelegateCommand<bool>(PinPropertyGrid);

...

// ******************************************************************
public DelegateCommand<bool> CommandPinPropertyGrid { get; private set; }

public void PinPropertyGrid(bool pinned)
{
    this.IsPropertyGridPinned = pinned;
}

DelegateCommand:

using System;
using System.Windows.Input;

namespace HQ.Wpf.Util.Command
{

    /// <summary>
    /// Represents a command that forwards the <c>Execute</c> and <c>CanExecute</c> calls to specified delegates.
    /// </summary>
    public class DelegateCommand<T> : ICommand
    {

        private readonly Action<T> _executeCallback;
        private readonly Predicate<T> _canExecuteCallback;

        /////////////////////////////////////////////////////////////////////////////////////////////////////
        // OBJECT
        /////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateCommand<T>"/> class.
        /// </summary>
        /// <param name="executeCallback">The execute callback delegate.</param>
        public DelegateCommand(Action<T> executeCallback)
            : this(executeCallback, null)
        {
            // No-op
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateCommand<T>"/> class.
        /// </summary>
        /// <param name="executeCallback">The execute callback delegate.</param>
        /// <param name="canExecuteCallback">The can execute callback delegate.</param>
        public DelegateCommand(Action<T> executeCallback, Predicate<T> canExecuteCallback)
        {
            if (executeCallback == null)
                throw new ArgumentNullException("executeCallback");

            this._executeCallback = executeCallback;
            this._canExecuteCallback = canExecuteCallback;
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////
        // INTERFACE IMPLEMENTATION
        /////////////////////////////////////////////////////////////////////////////////////////////////////

        #region ICommand Members

        /// <summary>
        /// Defines the method that determines whether the command can execute in its current state.
        /// </summary>
        /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to <see langword="null"/>.</param>
        /// <returns>
        /// <c>true</c> if this command can be executed; otherwise, <c>false</c>.
        /// </returns>
        public bool CanExecute(object parameter)
        {
            return (this._canExecuteCallback == null) ? true : this._canExecuteCallback((T)parameter);
        }

        /// <summary>
        /// Occurs when changes occur that affect whether or not the command should execute.
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (this._canExecuteCallback != null)
                    CommandManager.RequerySuggested += value;
            }
            remove
            {
                if (this._canExecuteCallback != null)
                    CommandManager.RequerySuggested -= value;
            }
        }

        /// <summary>
        /// Defines the method to be called when the command is invoked.
        /// </summary>
        /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to <see langword="null"/>.</param>
        public void Execute(object parameter)
        {
            this._executeCallback((T)parameter);
        }

        #endregion // ICommand Members

    }
}

Upvotes: 0

Dennis Heldt
Dennis Heldt

Reputation: 71

When you need only the Status of the CheckBox (Checked or Unchecked), then you don't need a Parameter. You can detect the Status of the Checkbox when you use this code:

CheckBox box = e.OriginalSource as CheckBox;

if(box.IsChecked.Value)
    DoThis();
else
    DoAnotherMethod();

"e" is the ExecutedRoutedEventArgs-Parameter in the Command. You Need box.IsChecked.Value, because box.IsChecked is from Type bool?.

Upvotes: -2

Igor S
Igor S

Reputation: 638

If you use MVVM, you can use event triggers like this:

<CheckBox IsChecked="{Binding ServiceOrderItemTask.IsCompleted, Mode=TwoWay}" Content="{Binding ServiceOption.Name}">

    <i:Interaction.Triggers>
          <i:EventTrigger EventName="Checked">
                 <i:InvokeCommandAction Command="{Binding DataContext.IsCompletedCheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type t:RadGridView}}}" CommandParameter="{Binding}"/>
           </i:EventTrigger>

           <i:EventTrigger EventName="Unchecked">
                 <i:InvokeCommandAction Command="{Binding DataContext.IsCompletedUncheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type t:RadGridView}}}" CommandParameter="{Binding}"/>
           </i:EventTrigger>
    </i:Interaction.Triggers>

Upvotes: 32

Related Questions