【问题标题】:How can I handle multiple CheckBoxes in the MVVM pattern?如何在 MVVM 模式中处理多个 CheckBox?
【发布时间】:2011-11-25 07:29:34
【问题描述】:

WPF 中的绑定复选框是常见问题,但我仍然没有找到易于初学者理解的示例代码。我在 WPF 中有复选框列表来选择最喜欢的运动名称。在我的情况下,复选框的数量是静态的。谁能告诉我如何针对这个问题实现 ViewModel?

FavoriteSportsView.xaml:

  <StackPanel Height="50" HorizontalAlignment="Left" VerticalAlignment="Top" 
  Width="150">

  <CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"  
  Command="{Binding Path=SportsResponseCommand}" 
  CommandParameter="Football" 
  Content="Football" 
  Margin="5" />

  <CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" 
  Command="{Binding Path=SportsResponseCommand}" 
  CommandParameter="Hockey" 
  Content="Hockey" 
  Margin="5" />

  <CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" 
  Command="{Binding Path=SportsResponseCommand}" 
  CommandParameter="Golf" 
  Content="Golf" 
  Margin="5" />
  </StackPanel>

FavoriteSportsViewModel.cs

  public class FavoriteSportsViewModel.cs {

    //Since I am using the same IsChecked in all check box options, I found all check 
    //boxes gets either checked or unchecked when I just check or uncheck one option.
    //How do i resolve this issue? I don't think i need seprate IsChecked for each 
    //check box option.

    private bool _isChecked;
    public bool IsChecked{
      get {
           return _isChecked;
       }

      set { if (value != _isChecked) 
             _isChecked = value;
            this.OnPropertyChanged("IsChecked");
       }
    }


    //How do i detect parameter in this method?
    private ICommand _sportsResponseCommand;
    public ICommand SportsResponseCommand
    {
        get
        {
            if (_sportsResponseCommand== null)
                _sportsResponseCommand= new
                    RelayCommand(a => DoCollectSelectedGames(), p => true);
            return _sportsResponseCommand;
        }
        set
        {
            _sportsResponseCommand= value;

        }

    }

    private void DoCollectSelectedGames(){ 
      //Here i push all selected games in an array
    }


    public abstract class ViewModelBase : INotifyPropertyChanged
    {
       public event PropertyChangedEventHandler PropertyChanged;
       public void OnPropertyChanged(string propertyName)
       {
         if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
       }
    }

  }

我不确定如何在上面的 ViewModel 中执行以下操作: 1. 如何实现单一方法来处理我的所有选项? 2.如何检测每个复选框以查看是否选中 3. 如何使用 CommandParameter? 4. 如何正确实现 SportsResponseCommand

【问题讨论】:

  • 您是否可以选择使用复选框列表?

标签: c# wpf xaml mvvm checkbox


【解决方案1】:

如果您只想在 IsChecked 更改时更新 ViewModel 中的属性,请将 IsChecked 的 Binding 替换为 ViewModel 中的布尔属性,该属性在其“设置”上引发 NotifyPropertyChanged

现在,如果您想在每次 IsChecked 更改 3 个复选框之一时执行一项操作:

首先,将您的 CommandParameter 替换为 "{Binding RelativeSource={RelativeSource Mode=Self}}"

在您的 ViewModel(应该实现 INotifyPropertyChanged)中,创建一个 ICommand (SportsResponseCommand),它在参数中接受一个 CheckBox。

在命令的方法中,检查 CheckBox 的内容,并检查“IsChecked”属性,然后对它们进行处理。

如果您还有其他问题,请告诉我。

【讨论】:

    【解决方案2】:

    您的视图模型应如下所示:

    public class MyViewModel : INotifyPropertyChanged
    {
        //INotifyPropertyChanged implementation
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    
        //bindable property
        private bool _football;
        public bool Football
        {
            get { return _football; }
            set
            {
                if (value != _football)
                {
                    _football = value;
                    this.OnPropertyChanged("Football");
                }
            }
        }
    
        //... and the same for Golf and Hockey
    }
    

    然后通过设置DataContext 属性将视图模型与视图关联(这很可能在WindowUserControl 代码后面,尽管有很多方法可以实现这一点)。

    最后,更新您的绑定,使它们看起来像:

    <CheckBox IsChecked="{Binding Football, Mode=TwoWay}"  
     Content="Football" 
     Margin="5" />
    
    <CheckBox IsChecked="{Binding Golf, Mode=TwoWay}"   
     Content="Football" 
     Margin="5" />
    

    作为最后的评论,你不应该真的需要绑定 Command 属性 - 你可以编写任何你需要在视图模型的属性设置器中运行的代码。

    【讨论】:

    • 击败我,但您应该从 CheckBox XAML 中删除 Command/CommandParameter
    【解决方案3】:

    您可以使用此分配视图模型

     //for the view
    
    partial class MainView:Window
     {
             InitializeComponent();
             this.DataContext=new MainViewModel();
    
     }
    
    //ViewModel Code
    
    public class MainViewModel: INotifyPropertyChanged
    {
      //INotifyPropertyChanged implementation
      public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    
    //bindable property
    private bool _football;
    public bool Football
    {
        get { return _football; }
        set
        {
            if (value != _football)
            {
                _football = value;
                this.OnPropertyChanged("Football");
            }
        }
    }
    
    //... and the same for Golf and Hockey
    }`
    

    然后你可以在 XAML 中实现 Binding

    <CheckBox IsChecked="{Binding Football, Mode=TwoWay}"
    Command="{Binding Path=SportsResponseCommand}" CommandParameter="Football" Content="Football" Margin="5" />

    <CheckBox IsChecked="{Binding Golf, Mode=TwoWay}"
    Command="{Binding Path=SportsResponseCommand}" CommandParameter="Football" Content="Football" Margin="5" />

    【讨论】:

      【解决方案4】:

      我强烈推荐你阅读这篇http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
      我在下面描述了一个解决方案,我尝试不修改您的 XAML 代码,但这不是唯一的方法(或最佳方法),而是包含所有必要的元素!

      第一步你需要你的模型,我称之为 Model_Sport

          public class Model_Sport : INotifyPropertyChanged
          {
              #region  Constructor
      
              public Model_Sport(string name, ICommand command)
              {
                  Name = name;
                  SportsResponseCommand = command;
              }
      
              #endregion
      
              static readonly PropertyChangedEventArgs _NameEventArgs = new PropertyChangedEventArgs("Name");
              private string _Name = null;
              public string Name
              {
                  get { return _Name; }
                  set
                  {
                      _Name = value;
                      OnPropertyChanged(_NameEventArgs);
                  }
              }
      
              static readonly PropertyChangedEventArgs _SportsResponseCommandEventArgs = new PropertyChangedEventArgs("SportsResponseCommand");
              private ICommand _SportsResponseCommand = null;
              public ICommand SportsResponseCommand
              {
                  get { return _SportsResponseCommand; }
                  set
                  {
                      _SportsResponseCommand = value;
                      OnPropertyChanged(_SportsResponseCommandEventArgs);
                  }
              }
      
              static readonly PropertyChangedEventArgs _IsCheckedEventArgs = new PropertyChangedEventArgs("IsChecked");
              private bool _IsChecked = false;
              public bool IsChecked
              {
                  get { return _IsChecked; }
                  set
                  {
                      _IsChecked = value;
                      OnPropertyChanged(_IsCheckedEventArgs);
                  }
              }
      
      
              #region INotifyPropertyChanged Members
      
              public event PropertyChangedEventHandler PropertyChanged;
              protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
              {
                  if (PropertyChanged != null)
                  {
                      PropertyChanged(this, eventArgs);
                  }
              }
      
              #endregion
          }
      

      现在您需要一种方法来委派您的命令“SportsResponseCommand”,DelegateCommand 对象将帮助您做到这一点

          public class DelegateCommand : ICommand
          {
              private readonly Action<object> _ExecuteMethod;
              private readonly Func< object, bool> _CanExecuteMethod;
      
              #region Constructors
      
              public DelegateCommand(Action<object>executeMethod, Func<object, bool> canExecuteMethod)
              {
                  if (null == executeMethod)
                  {
                      throw new ArgumentNullException("executeMethod", "Delegate Command Delegates Cannot Be Null");
                  }
      
                  _ExecuteMethod = executeMethod;
                  _CanExecuteMethod = canExecuteMethod;
      
              }
      
              public DelegateCommand(Action<object>executeMethod) : this(executeMethod, null) { }
      
              #endregion
      
      
              #region Methods
      
              public bool CanExecute(object parameter)
              {
                  if (_CanExecuteMethod == null) return true;
                  return _CanExecuteMethod(parameter);
              }
      
              public void Execute(object parameter)
              {
                  if (_ExecuteMethod == null) return;
                  _ExecuteMethod(parameter);
              }
      
              bool ICommand.CanExecute(object parameter)
              {
                  return CanExecute(parameter);
              }
      
              public event EventHandler CanExecuteChanged
              {
                  add { CommandManager.RequerySuggested += value; }
                  remove { CommandManager.RequerySuggested -= value; }
              }
      
              void ICommand.Execute(object parameter)
              {
                  Execute(parameter);
              }
      
              #endregion
      
          }
      

      现在是“视图模型”

          public class ViewModel
          {
              #region property
      
              public Dictionary<string, Model_Sport> Sports { get; set; }
              public DelegateCommand SportsResponseCommand { get; set; }
      
              #endregion
      
              public ViewModel()
              {
                  Sports = new Dictionary<string, Model_Sport>();
                  SportsResponseCommand = new DelegateCommand(p => execute_SportsResponseCommand(p));
      
                  buildSports();
              }
      
              private void buildSports()
              {
                  Model_Sport football = new Model_Sport("Football", SportsResponseCommand);
                  Model_Sport golf = new Model_Sport("Golf", SportsResponseCommand);
                  Model_Sport hockey = new Model_Sport("Hockey", SportsResponseCommand);
      
                  football.IsChecked = true; // just for test
      
                  Sports.Add(football.Name, football);
                  Sports.Add(golf.Name, golf);
                  Sports.Add(hockey.Name, hockey);
              }
      
              private void execute_SportsResponseCommand(object p)
              {
                  // TODO :what ever you want
                  MessageBox.Show(p.ToString());
              }
      
          }
      

      现在查看 请记住为您的窗口设置数据上下文 公共主窗口() {

              InitializeComponent();
              this.DataContext = new ViewModel();
      
      
          }
      

      然后在 XAML 中

          <StackPanel  HorizontalAlignment="Left" VerticalAlignment="Top" >
      
              <CheckBox DataContext="{Binding Path=Sports[Football]}"
                  IsChecked="{Binding IsChecked, Mode=TwoWay}"   
                          Command="{Binding Path=SportsResponseCommand}"  
                          CommandParameter="Football"    
                          Content="Football"   
                          Margin="5" />
      
              <CheckBox DataContext="{Binding Path=Sports[Hockey]}"
                  IsChecked="{Binding IsChecked, Mode=TwoWay}"  
                  Command="{Binding Path=SportsResponseCommand}"    
                  CommandParameter="Hockey"    
                  Content="Hockey"   
                  Margin="5" />
      
              <CheckBox DataContext="{Binding Path=Sports[Golf]}" IsChecked="{Binding IsChecked, Mode=TwoWay}" 
                          Command="{Binding Path=SportsResponseCommand}"
                          CommandParameter="Golf"   
                          Content="Golf" 
                          Margin="5" />
          </StackPanel>
      

      【讨论】:

      • 感谢 Fred Jand 的详细实施。我读过如果不在模型中实现 INotifyPropertyChanged 会很好。你是否同意这种说法?您对使用 RelayCommand 而不是 DelegateCommand 有何看法?你觉得有什么缺点吗?
      • 实际上 RelayCommand 和 DelgateCommad 几乎是相同的实体 RelayCommand 是 Microsoft Composite Application Library 中 DelegateCommand 的简化变体。我放在这里的实际上是一个 RelayCommand 如果您想要更真实的委托命令实现,请查看msdn.microsoft.com/en-us/library/ff654132.aspx#Y0 请注意,它们只是名称,多年来开发了两者的不同变体!关键是它们具有完全相同的概念。您可以创建自己的 delegatecommand 或 relaycommand 版本!就像我在这个例子中所做的那样:)
      • 关于“INotifyPropertyChanged”这取决于应用程序!如果模型表示独立更改的内容并且对应用程序的其他部分(视图模型除外)有副作用,则最好在模型中使用 INotifyPropertyChanged。但是,如果只有一个 ViewModel 呈现您的模型,您可以让 ViewModel 处理更改。注意 INotifyPropertyChanged 在 WPF 和 MVVM 模式之前确实存在(在 viewmodel 概念存在之前)
      猜你喜欢
      • 1970-01-01
      • 2018-12-27
      • 2014-01-23
      • 1970-01-01
      • 2013-07-08
      • 2013-03-03
      • 1970-01-01
      • 1970-01-01
      • 2021-01-02
      相关资源
      最近更新 更多