【问题标题】:How to create a MVVM for a user control without exposing all the internals to the consumer?如何为用户控件创建 MVVM 而不将所有内部结构暴露给消费者?
【发布时间】:2012-04-22 13:27:28
【问题描述】:

我正在使用 MVVM 设计一些复杂的用户控件。 UserControls 的数据上下文处理所有逻辑,并将 UserControl 交互的结果提供给控件的使用者。

我面临的问题是,看起来 internal 不适用于绑定,如果我尝试绑定到非公共绑定的源属性,则该属性不起作用。如果我必须打开(amek public)我的 VM 顶部的所有源属性能够绑定它们,那么我将打开 UserControl 和消费者的内部:

  1. 对使用什么属性感到困惑。
  2. 可以使用不适合外部使用的属性破坏事物。

有什么想法吗?

提前致谢。

编辑:嗯,看看this 看起来源必须始终是公开的,对吧。但后来我仍然面临公开内部信息的问题。有什么解决办法吗?

示例:

您创建一个通用的用户控件。该控件应该是一个黑盒,它接受最终用户提供的查询,向他显示(以某种方式)查询返回的行,并让 UserControl 使用者获取选定的值。该控件是使用显示行的组合框实现的。

UserControl 位于 UserControl.xaml 中,并且有一个 UserControlVM.cs 文件,其中包含 UserControl 的 ViewModel。

ViewModel 包含要执行的查询、查询返回的项目列表和所选项目。项目列表和选定项目是公开的,以便能够绑定到 UserControl。

消费者在其窗口内使用一个 UserControl 实例,在其窗口视图模型内使用一个 UserControlVM 实例。我面临的问题是最终用户有权访问查询返回的项目列表,而他只应该有权访问所选项目。

【问题讨论】:

    标签: wpf mvvm binding user-controls


    【解决方案1】:

    我找到了一个解决方案(但如果您的 ViewModel 需要构造函数参数,我会提前告诉您该解决方案不起作用)。

    1. 在保持绑定属性公开的同时将 ViewModel 设为内部。
    2. 创建一个“UserControlData”类,其中包含用户控件将提供给最终用户的数据
    3. 通过 UserControl 依赖属性提供该类的实例。
    4. 用户控件使用者必须在其视图模型中绑定 UserControlData 实例才能获得用户控件结果。

    【讨论】:

      【解决方案2】:

      作为你的类的实现细节的数据不应该是公开的,但在我看来,你甚至不应该想绑定到这种类型的数据。绑定到视图的数据应该是视图模型的公共属性和命令。请记住,视图模型定义了您的 UI 是什么

      用户界面不允许更改的公共属性应该只实现getter,而不是setter。只能在某些条件下更改的公共属性应在设置器中强制执行这些条件。视图模型应提供并容纳所有 UI 逻辑(属性和命令)。

      您还应该将视图模型包装在测试所有这些的单元测试中。

      根据评论反馈更新:

      class MyViewModel : ViewModelBase
      {
           private bool _showSomething;
           public bool ShowSomething
           {
               get { return _showSomething; }
               set
               {
                   _showSomething = value;
                   RaisePropertyChanged("ShowSomething");
                   RaisePropertyChanged("TheThing");
               }
           }
      
           public Something TheThing
           { 
               get
               {
                   if(_showSomething) { return _theThing; }
                   return _theOtherThing;
               }
           }
      
           private Something _theThing;
           private Something _theOtherThing;
      
      }
      

      编辑*:以下可能更接近基于 cmets 的期望。

      public interface IQueryControl
      {
          string Query { get; set; } //view passes query in
          ReadOnlyCollection<string> QueryResultDescriptions { get; } //bind to combo items
          string SelectedQueryDescription { get; set; } //bind to combo selection
          object SelectedItem { get; } //resulting object
      }
      
      public class UserControlVM : ViewModelBase, IQueryControl
      {
          private string _query;
          private ObservableCollection<object> _queryResults;
          private ReadOnlyCollection<string> _externalResults;
          private object _selectedResult;
      
          public string Query 
          { 
              get { return _query; } 
              set 
              {
                  _query = value; 
                  RaisePropertyChanged("Query");
                  UpdateQueryResults(); 
              }
          }
      
          private void UpdateQueryResults()
          {
              //Do query which allocates / updates _queryResults;
              _externalResults = new ReadOnlyCollection<string>((from entry in _queryResults select entry.ToString()).ToList<string>());
              RaisePropertyChanged("QueryResultDescriptions");
          }
      
          public ReadOnlyCollection<string> QueryResultDescriptions
          {
              get { return _externalResults; }
          }
      
          public string SelectedQueryDescription
          {
              get { return _selectedResult.ToString(); }
              set
              {
                  SelectResult(value);
              }
          }
      
          private void SelectResult(string value)
          {
              Dictionary<string, object> lookup = _queryResults.ToDictionary<object, string>((result) => { return result.ToString(); });
              if (lookup.ContainsKey(value))
              {
                  _selectedResult = lookup[value];
                  RaisePropertyChanged("SelectedQueryDescription");
                  RaisePropertyChanged("SelectedItem");
              }
              else
              {
                  //throw something
              }
          }
      
          public object SelectedItem
          {
              get { return _selectedResult; }
          }
      
      }
      

      【讨论】:

      • 我有一个 UserControl 允许根据其他事物选择一件事。我想只给 UserControl 的消费者选择的东西(因为他不应该关心其他东西)。该控件具有从 Db 和其他完全符合 MVVM 范例的数据读取的列表。这就是为什么我认为“绑定到视图的数据应该是公开的”是错误的。在一个完美的世界中,绑定应该与“内部”修饰符一起使用,因为它是数据的真正含义,它是 UserControl 的内部,但我需要绑定它。
      • 你可以做到这一点。您将事物的主列表存储在内部,公开提供他们需要查看的事物的公共属性,当相关属性发生更改时,更新公共“事物”并引发公共事物的属性通知更改。我会详细更新答案。
      • 很好的例子,考虑将 public bool ShowSomething 更改为 public ObservableCollection {get;私人集;}。我将此属性绑定到 ComboBox 并使用所选项目更新 TheThing。当最终用户不需要此数据时,您需要公开 ObservableCollection 属性(这只是 UserControl 需要的东西)
      • 最好用代码详细信息更新原始帖子。听起来您可能有一个与视图不存在 1-1 关系的视图模型(用户控件是一个视图,可能有第二个视图可能会消耗用户控件)。它不需要是您的生产代码,但应该代表您的生产代码。
      • 我添加了一个概念示例,它可能比代码更好地呈现问题。告诉我您在阅读编辑后是否仍然喜欢一些代码。感谢您的帮助。
      【解决方案3】:

      Binding 类的所有属性目标都需要公开

      这是一个可能的替代解决方案:

      您的DataContext(如果是ViewModel)很可能实现INotifyPropertyChanged。 如果是这样,那么当您在UserControl 上设置DataContext (ViewModel) 时,只需订阅PropertyChanged 事件处理程序,然后在您的代码隐藏中编写逻辑以根据更改的数据操作您的控件。

      【讨论】:

      • 但是你将逻辑转移到 UI 并且 MVVM 失去了它的好处之一:S
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-11-27
      • 2019-06-25
      • 1970-01-01
      • 1970-01-01
      • 2015-08-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多