Reputation: 17049
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
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
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
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
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