I am trying to find an example of using checkboxes in a menu in a WPF MVVM application that can bind to an enum in the underlying ViewModel Class. I have as a simple example:
public class MyViewModel
{
public MyViewModel() // constructor
{
MyChosenColor = Colors.Red; // Pick red by default...
}
public enum Colors
{
Red,
Green,
Blue, // this is just an example. Could be many more values...
}
public Colors MyChosenColor {get; set;}
}
I would like to have some XAML (and if necessary a minimal amount of code bind, converters, etc.) that allows the user to pick a menu item "Colors" and see Red, Green, Blue with Red checked (at beginning). Checking Blue would set the MyChosenColor property to Blue and change the check to Blue.
I have found some promising links:
Mutually exclusive checkable menu items?
How to bind RadioButtons to an enum?
but none of them seem to deal with all of the issues (mutually exclusive checkboxes; checkboxes, not radio buttons) and many involve much code behind. I am on Visual Studio 2012 so perhaps there are better methods by now or something I have overlooked?
I have to think the idea of mutual exclusive checkboxes in a menu bound to an enum most be a common idea.
Thanks.
Thanks to the comments from Rachel, I propose the answer below. I hope it helps someone that needs to do this. I searched around, and did not see an example explicitly written down. Perhaps it's too simple to bother :) I found getting everything pulled together and working somewhat painful, so I'm writing it down here. Thanks again Rachel!
<Window x:Class="Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Demo"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
</Window.Resources>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Number Of Players" ItemsSource="{Binding Path=MyCollection}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Title}" />
<Setter Property="IsCheckable" Value="True" />
<Setter Property="IsChecked" Value="{Binding IsChecked,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Setter Property="Command" Value="{Binding DataContext.MyCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MenuItem}}}" />
<Setter Property="CommandParameter" Value="{Binding Player}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
<Grid>
</Grid>
</DockPanel>
and here is the ViewModel Code:
namespace Demo.ViewModel
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
_myCollection = new ObservableCollection<NumberOfPlayersClass>();
foreach (NumberOfPlayersEnum value in Enum.GetValues(typeof(NumberOfPlayersEnum)))
{
NumberOfPlayersClass myClass = new NumberOfPlayersClass();
myClass.Player = value;
myClass.IsChecked = value == NumberOfPlayersEnum.Two ? true : false; // default to using 2 players
myClass.Title = Enum.GetName(typeof(NumberOfPlayersEnum), value);
_myCollection.Add(myClass);
}
}
private ICommand _myCommand;
public ICommand MyCommand
{
get
{
if (_myCommand == null)
{
_myCommand = new RelayCommand(new Action<object>(ResolveCheckBoxes));
}
return _myCommand;
}
}
ObservableCollection<NumberOfPlayersClass> _myCollection = new ObservableCollection<NumberOfPlayersClass>();
public ObservableCollection<NumberOfPlayersClass> MyCollection
{
get
{
return _myCollection;
}
}
public enum NumberOfPlayersEnum
{
One = 1,
Two =2,
Three =3,
}
public class NumberOfPlayersClass : ViewModelBase
{
public NumberOfPlayersClass()
{
IsChecked = false;
}
public NumberOfPlayersEnum Player { get; set; }
private bool _isChecked = false;
public bool IsChecked
{ get
{ return _isChecked;
}
set
{
_isChecked = value;
OnPropertyChanged("IsChecked");
}
}
public string Title { get; set; }
}
private void ResolveCheckBoxes(object checkBoxNumber)
{
NumberOfPlayersEnum myEnum = (NumberOfPlayersEnum)checkBoxNumber;
ObservableCollection<NumberOfPlayersClass> collection = MyCollection;
NumberOfPlayersClass theClass = collection.First<NumberOfPlayersClass>(t => t.Player == myEnum);
// ok, they want to check this one, let them and uncheck all else
foreach (NumberOfPlayersClass iter in collection)
{
iter.IsChecked = false;
}
theClass.IsChecked = true;
}
}
/// <summary>
/// A command whose sole purpose is to
/// relay its functionality to other
/// objects by invoking delegates. The
/// default return value for the CanExecute
/// method is 'true'.
/// </summary>
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
}
/// <summary>
/// Base class for all ViewModel classes in the application.
/// It provides support for property change notifications
/// and has a DisplayName property. This class is abstract.
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
#region Constructor
protected ViewModelBase()
{
}
#endregion // Constructor
#region DisplayName
/// <summary>
/// Returns the user-friendly name of this object.
/// Child classes can set this property to a new value,
/// or override it to determine the value on-demand.
/// </summary>
public virtual string DisplayName { get; protected set; }
#endregion // DisplayName
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region IDisposable Members
/// <summary>
/// Invoked when this object is being removed from the application
/// and will be subject to garbage collection.
/// </summary>
public void Dispose()
{
this.OnDispose();
}
/// <summary>
/// Child classes can override this method to perform
/// clean-up logic, such as removing event handlers.
/// </summary>
protected virtual void OnDispose()
{
}
#if DEBUG
/// <summary>
/// Useful for ensuring that ViewModel objects are properly garbage collected.
/// </summary>
~ViewModelBase()
{
string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode());
System.Diagnostics.Debug.WriteLine(msg);
}
#endif
#endregion // IDisposable Members
}
You can get information on the classes RelayCommand and ViewModelBase at http://msdn.microsoft.com/en-us/magazine/dd419663.aspx and http://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/
See my answer to "Mutually exclusive checkable menu items? for an approach that uses RoutedUICommands, an enum, and DataTriggers. It's pretty much what you originally asked for.
An alternative answer, inspired by this blog post:
class CheckBoxGroup
{
public static bool GetIsEnabled(DependencyObject obj) => (bool)obj.GetValue(IsEnabledProperty);
public static void SetIsEnabled(DependencyObject obj, string value) =>
obj.SetValue(IsEnabledProperty, value);
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(CheckBoxGroup),
new PropertyMetadata(false, Callback));
private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var container = d as UIElement;
container.AddHandler(ToggleButton.CheckedEvent,
new RoutedEventHandler(GroupedButton_Checked));
}
private static void GroupedButton_Checked(object sender, RoutedEventArgs e)
{
var container = sender as DependencyObject;
var source = e.OriginalSource as ToggleButton;
foreach(var child in LogicalTreeHelper.GetChildren(container).OfType<ToggleButton>())
{
if(child != source) child.IsChecked = false;
}
}
}
Usage:
<ListBox local:CheckBoxGroup.IsEnabled="True">
<CheckBox Content="Dibble"/>
<CheckBox Content="Dobble"/>
<CheckBox Content="Dabble"/>
<CheckBox Content="Dubble"/>
</ListBox>