WPF(MVVM)菜单中的互斥(可绑定)复选框

WPF(MVVM)菜单中的互斥(可绑定)复选框,wpf,mvvm,binding,menu,checkbox,Wpf,Mvvm,Binding,Menu,Checkbox,我试图找到一个在WPF MVVM应用程序的菜单中使用复选框的示例,该应用程序可以绑定到基础ViewModel类中的枚举。我举了一个简单的例子: public class MyViewModel { public MyViewModel() // constructor { MyChosenColor = Colors.Red; // Pick red by default... } public enum Colors { Red,

我试图找到一个在WPF MVVM应用程序的菜单中使用复选框的示例,该应用程序可以绑定到基础ViewModel类中的枚举。我举了一个简单的例子:

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;}
}
我希望有一些XAML(如果需要的话,还有少量的代码绑定、转换器等),允许用户选择菜单项“颜色”,并看到红色、绿色、蓝色和红色的复选框(在开始时)。选中蓝色会将MyChosenColor属性设置为蓝色,并将复选框更改为蓝色。 我发现了一些有希望的链接:

但它们似乎都不能处理所有的问题(互斥的复选框;复选框,而不是单选按钮),而且许多都涉及很多代码。我在Visual Studio 2012上,所以现在可能有更好的方法,或者我忽略了什么

我不得不认为在绑定到枚举的菜单中使用互斥复选框是一个常见的想法。
谢谢。

感谢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
}
namespace Demo.ViewModel
{
公共类MainViewModel:ViewModelBase
{
公共主视图模型()
{
_myCollection=新的ObservableCollection();
foreach(Enum.GetValues中的NumberOfPlayersEnum值(typeof(NumberOfPlayersEnum)))
{
NumberOfPlayerClass myClass=新的NumberOfPlayerClass();
myClass.Player=value;
myClass.IsChecked=value==NumberOfPlayersEnum.Two?true:false;//默认使用2个播放器
myClass.Title=Enum.GetName(typeof(NumberOfPlayersEnum),value);
_myCollection.Add(myClass);
}
}
私人ICommand_myCommand;
公共ICommand MyCommand
{
得到
{
如果(_myCommand==null)
{
_myCommand=newrelayCommand(新操作(解析复选框));
}
返回_myCommand;
}
}
ObservableCollection_myCollection=新的ObservableCollection();
公共可观测集合MyCollection
{
得到
{
返回_myCollection;
}
}
公共枚举NumberOfPlayersEnum
{
1=1,
二等于二,
三等于三,
}
公共类NumberOfPlayerClass:ViewModelBase
{
公众号PlayerClass()
{
IsChecked=false;
}
public NumberOfPlayersEnum播放器{get;set;}
private bool_isChecked=假;
公共场所被检查
{得到
{返回}已检查;
}
设置
{
_isChecked=值;
财产变更(“已检查”);
}
}
公共字符串标题{get;set;}
}
私有void解析复选框(对象复选框编号)
{
NumberOfPlayersEnum myEnum=(NumberOfPlayersEnum)复选框编号;
ObservableCollection集合=MyCollection;
NumberOfPlayerClass theClass=collection.First(t=>t.Player==myEnum);
//好的,他们想勾选这个,让他们去勾选其他的
foreach(集合中的NumberOfPlayerClass iter)
{
iter.IsChecked=false;
}
class.IsChecked=true;
}
}
/// 
///一种命令,其唯一目的是
///将其功能传递给其他用户
///通过调用委托创建对象
///CanExecute的默认返回值
///方法为“true”。
/// 
公共类中继命令:ICommand
{
#区域字段
只读操作_执行;
只读谓词_canExecute;
#endregion//字段
#区域构造函数
/// 
///创建始终可以执行的新命令。
/// 
///执行逻辑。
公共中继命令(操作执行)
:此(执行,空)
{
}
/// 
///创建一个新命令。
/// 
///执行逻辑。
///执行状态逻辑。
公共RelayCommand(操作执行,谓词canExecute)
{
if(execute==null)
抛出新的ArgumentNullException(“执行”);
_执行=执行;
_canExecute=canExecute;
}
#endregion//构造函数
#区域ICommand成员
[调试步骤至]
公共布尔CanExecute(对象参数)
{
返回_canExecute==null?true:_canExecute(参数);
}
公共事件事件处理程序CanExecuteChanged
{
添加{CommandManager.RequerySuggested+=value;}
删除{CommandManager.RequerySuggested-=value;}
}
public void Execute(对象参数)
{
_执行(参数);
}
#endregion//ICommand成员
}
}
/// 
///应用程序中所有ViewModel类的基类。
///它支持属性更改通知
///并具有DisplayName属性。这个类是抽象的。
/// 
公共抽象类ViewModelBase:INotifyPropertyChanged,IDisposable
{
#区域构造函数
受保护的ViewModelBase()
{
}
#endregion//构造函数
#区域显示名
/// 
///返回此对象的用户友好名称。
///子类可以将此属性设置为新值,
///或覆盖它以按需确定值。
/// 
公共虚拟字符串DisplayName{get;protected set;}
#endregion//DisplayName
#区域调试助手
/// 
///如果此对象没有
///公共道具
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;
        }
    }
}
<ListBox local:CheckBoxGroup.IsEnabled="True">
    <CheckBox Content="Dibble"/>
    <CheckBox Content="Dobble"/>
    <CheckBox Content="Dabble"/>
    <CheckBox Content="Dubble"/>
</ListBox>