我试图找到一个在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属性设置为蓝色,并将检查更改为蓝色。 我找到了一些有希望的链接: Mutually exclusive checkable menu items? How to bind RadioButtons to an enum?
但它们似乎都没有处理所有问题(互斥复选框;复选框,而不是单选按钮),而且许多问题涉及很多代码。我在Visual Studio 2012上,所以也许现在有更好的方法或我忽略的东西?
我必须认为绑定到枚举的菜单中的互斥复选框的想法最常见。 感谢。
答案 0 :(得分:3)
感谢Rachel的评论,我提出以下答案。我希望它可以帮助那些需要这样做的人。我四处搜索,并没有看到明确写下的例子。也许这太麻烦了太烦了:)我发现把所有东西拉到一起并且工作有些痛苦,所以我在这里写下来。再次感谢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类的信息答案 1 :(得分:0)
请参阅我对"Mutually exclusive checkable menu items?的回答,了解使用RoutedUICommands,枚举和DataTriggers的方法。这几乎就是你最初要求的。
答案 2 :(得分:0)
一个受this博客文章启发的替代答案:
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>