Denys Wessels
Denys Wessels

Reputation: 17049

Enable WPF button based on checkbox list selection MVVM

I have a checkbox list of sports which is bound to an observable collection using the MVVM pattern. The user can select any number of sports he\she enjoys and when the OK button is pressed the selected sports must be displayed in the message box(simplified the code for the sake of the question).

The OK button must be enabled only if one or more sports have been selected, at the moment this is not working.The enabling\disabling of the button is done using IsValid , is there any way to execute this method everytime one of the checkboxes is checked?

I cannot use <Button IsEnabled="{Binding ElementName=checkBox1, Path=IsChecked}" /> because there are multiple properties in the dev code which need to be checked for validity before the button can be used and because I'm using Prism, so this should be achieved using the IsValid method if at all possible.

XAML

<Window x:Class="WpfApplication13.MVVM.ComboboxWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="clr-namespace:Microsoft.Practices.Composite.Presentation.Commands;assembly=Microsoft.Practices.Composite.Presentation"
        xmlns:local="WpfApplication13.MVVM" Title="MainWindow" Height="170" Width="507">
    <Grid>
        <ListBox ItemsSource="{Binding Sports}" Name="lbModules" ScrollViewer.VerticalScrollBarVisibility="Visible" 
                 Height="72" Margin="3" VerticalAlignment="Top">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <CheckBox Content="{Binding Text}" IsChecked="{Binding IsChecked,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="chkModules" Margin="0,5,0,0" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Height="27" Width="70" Margin="3,80,3,3" VerticalAlignment="Top" 
                Content="OK" HorizontalAlignment="Left" 
                prism:Click.Command="{Binding Path=NewCommand}"></Button>
    </Grid>
</Window>

View Model

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using Microsoft.Practices.Composite.Presentation.Commands;

namespace WpfApplication13.MVVM
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public DelegateCommand<object> NewCommand { get; protected set; }
        public event PropertyChangedEventHandler PropertyChanged;

        private ObservableCollection<ListHelper> modules = new ObservableCollection<ListHelper>();
        public ObservableCollection<ListHelper> Sports
        {
            get { return modules; }
            set { modules = value; OnPropertyChanged("Sports"); }
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                NewCommand.RaiseCanExecuteChanged();
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public MainWindowViewModel()
        {
            ListHelper item1 = new ListHelper() { Text = "Footbal", IsChecked = false };
            ListHelper item2 = new ListHelper() { Text = "Boxing", IsChecked = false };
            ListHelper item3 = new ListHelper() { Text = "Basketball", IsChecked = false };

            Sports.Add(item1);
            Sports.Add(item2);
            Sports.Add(item3);

            NewCommand = new DelegateCommand<object>(NewTemplate, IsValid);
        }

        private bool IsValid(object parameter)
        {
            //TODO:This method must execute EVERYTIME any of the checkboxes are checked\unchecked.(currently not happening)
            //The return value then determines the enabled state of the button.
            return Sports.Any(e => e.IsChecked);
        }

        private void NewTemplate(object parameter)
        {
            //Display a list of selected sports
            string sports = String.Empty;
            Sports.Where(e => e.IsChecked).ToList().ForEach(c => sports += c.Text + " ");
            MessageBox.Show(sports);
        }
    }

    public class ListHelper
    {
        public String Text { get; set; }

        private bool isChecked = false;
        public Boolean IsChecked 
        {
            get { return isChecked; }
            //The setter is executed everytime the checkbox is checked
            set {isChecked = value;}
        }
    }
}

Upvotes: 0

Views: 4060

Answers (4)

Softlion
Softlion

Reputation: 12625

I have the best solution: call

CommandManager.InvalidateRequerySuggested();

from the setter of your checkbok IsChecked setter:

    public bool IsChecked 
    {
        get { return isChecked; }
        set 
        {
            isChecked = value;
            CommandManager.InvalidateRequerySuggested();
        }
    }

No need to keep a strong reference to the command. Available from .NET 4.0 at least.

Upvotes: 0

Denys Wessels
Denys Wessels

Reputation: 17049

Had to change the ListHelper class as shown below. The button validation is performed as expected now:

 public class ListHelper : INotifyPropertyChanged
    {
        private DelegateCommand<object> _command = null;
        private string _propertyName = String.Empty;

        public ListHelper(DelegateCommand<object> command,string propertyName)
        {
            _command = command;
            propertyName = _propertyName;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public String Text { get; set; }

        private bool isChecked = false;
        public Boolean IsChecked 
        {
            get { return isChecked; }
            //The setter is executed everytime the checkbox is checked
            set 
            {
                isChecked = value;
                OnPropertyChanged(_propertyName);
            }
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null && _command != null)
            {
                _command.RaiseCanExecuteChanged();
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

Upvotes: 0

Martin Liversage
Martin Liversage

Reputation: 106936

You have to signal a change in availability of the NewCommand (which is determined by IsValid) when a checkbox is checked/unchecked. You have to call this:

NewCommand.RaiseCanExecuteChanged();

whenever ListHelper.IsChecked is updated. You will have to come up with a good way of wiring this together. You have several options but the most straightforward is probably providing ListHelper with a reference to MainViewModel allowing it to call an appropriate method on the view-model.

Or if you want to avoid the strong coupling created you could implement INotifyPropertyChanged in ListHelper and let MainViewModel listen for changes to IsChecked.

Upvotes: 1

Thomas
Thomas

Reputation: 56

I think the reason for your problem is, that the PRISM DelegateCommand doesn't include requery support. A proper workaround for this problem is described on this site http://compositewpf.codeplex.com/discussions/44750?ProjectName=compositewpf

Upvotes: 0

Related Questions