感谢 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>
这是 ViewModel 代码:
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
}
您可以在http://msdn.microsoft.com/en-us/magazine/dd419663.aspx和http://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/获取有关 RelayCommand 和 ViewModelBase 类的信息