【问题标题】:How to Access Data between ViewModels?如何在 ViewModel 之间访问数据?
【发布时间】:2017-03-25 20:01:48
【问题描述】:

我目前正在做一个项目,为了简化解释,假设一个 TabControl 中有两个选项卡...

在一个选项卡中,您可以将文件夹添加到 ListBox。

在另一个选项卡中,有一个列表框,显示您添加的所有文件夹中的所有项目。

每个选项卡都是一个 ViewModel(这是为了简化代码,因为将所有代码转储到一个 ViewModel 会导致阅读和理解变得困难)。

为了让这个程序正常工作,两个 ViewModel 都需要访问项目列表:一个是因为它必须显示它们,另一个是因为它必须添加到它们。

我无法弄清楚如何执行此操作。我最初认为共享数据是不好的,这本来就不应该发生,但后来我意识到我想不出任何其他方法来做到这一点。

我是 MVVM 新手(这是我第一个使用 MVVM 的真正应用程序)并且最初开始使用它,因为我无法在类之间访问数据,并认为 MVVM 会以某种方式解决这个问题,但我又来了。

如果有人能告诉我如何做到这一点,并可能用示例代码解释它,我将不胜感激。我也乐于接受对我的方法提出的建议和建设性的批评。

【问题讨论】:

    标签: c# wpf mvvm viewmodel


    【解决方案1】:

    首先你应该明白什么是MVVM中的View。把它看成一个面板。可以嵌入到 Window、TabControl 甚至是 ListBox 项中的面板。也可以包含子面板的面板。基本上,如果您的应用程序不仅仅是一个简单的输入表单,那么它很可能会有多个 View。不要试图将所有内容都放在一个 View/ViewModel 中,因为这会使以后的事情变得非常复杂。您希望拥有所谓的 View 层次结构 及其对应的 ViewModels。会有很多 Views/ViewModels 但它们相对简单且易于维护(这里是使用 PRISM 在视图之间切换的小示例,但您可以获得主要的想法https://youtu.be/ZfBy2nfykqY?t=45m34s)。

    每个选项卡都是一个 ViewModel(这是为了简化代码,因为将所有代码转储到一个 ViewModel 会导致阅读和理解变得困难)。

    这是正确的方法。下面是一些伪代码,描述了您的示例应用程序方案的外观:

    // MODEL:
    
    public class Model
    {
        ObservableCollection<Item> ListOfItems;
    }
    
    public class Item
    {
    }
    
    // VIEWMODELS:
    
    public class MainWindowViewModel
    {
        Model Model { get; set; }
    
        Tab1ViewModel Tab1ViewModel { get; set; }
        Tab2ViewModel Tab2ViewModel { get; set; }
    
        public MainWindowViewModel()
        {
            Model = new Model();
            Tab1ViewModel = new Tab1ViewModel(Model);
            Tab2ViewModel = new Tab2ViewModel(Model);
        }
    }
    
    public class Tab1ViewModel
    {
        ObservableCollection<ItemViewModel> ItemViewModels { get; set; } // Bind it to ListBox's ItemsSource
    
        public Tab1ViewModel(Model m)
        {
            ItemViewModels = new ObservableCollection<ItemViewModel>();
            // Populate ItemViewModels and keep it in sync with ListOfItems model by subscribing to its CollectionChanged event.
        }
    }
    
    public class Tab2ViewModel
    {
        ObservableCollection<ItemViewModel> ItemViewModels { get; set; } // Bind it to ListBox's ItemsSource
    
        public Tab2ViewModel(Model m)
        {
            ItemViewModels = new ObservableCollection<ItemViewModel>();
            // Populate ItemViewModels and keep it in sync with ListOfItems model by subscribing to its CollectionChanged event.
        }
    
    }
    
    public class ItemViewModel
    {
        Item Item { get; set; } // Model
    
        public ItemViewModel(Item item)
        {
            Item = item;
        }
    }
    

    现在您可以在不同的Views中显示相同的数据并对其执行不同的操作。每个 View 都会自动更新,因为它引用了相同的 Model

    您也可以使用 EventAggregator 或类似的东西在 ViewModels 之间进行通信。

    尽量避免使用可以从应用程序中的任何位置访问的数据的静态类/单例,因为这违反了封装原则。

    【讨论】:

      【解决方案2】:

      您可以拥有一个单例对象并从任何地方获取/设置其属性。

      看这个例子;

      public sealed class ApplicationState
      {
          private static readonly ApplicationState instance = new ApplicationState();
      
          static ApplicationState()
          {
          }
      
          private ApplicationState()
          {
          }
      
          public static ApplicationState Instance
          {
              get
              {
                  return instance;
              }
          }
      
      
          public string SharedString {get;set;}
      }
      

      现在您可以在任何地方设置此 SharedString 属性;

      ApplicationState.Instance.SharedString = "hello from VM1"
      

      并从另一个视图模型中读取它;

      Debug.WriteLine(ApplicationState.Instance.SharedString)
      

      您可以查看this link 以了解有关单例的更多信息

      您甚至可以将 ApplicationState 单例设置为 ObservableObject 并从您的视图中绑定到它的属性,例如;

      Value="{Binding SharedString, Source={x:Static ApplicationState.Instance}}"
      

      【讨论】:

      • 好主意,当使用 DI 容器时,您无需自己实现单例模式即可。
      【解决方案3】:

      因为将所有代码都转储到一个 ViewModel 中会让人难以阅读和理解)。

      如果一个页面上有一组主要的数据要使用,该页面通常有一个视图模型。除非每个选项卡都是可在其他地方使用的可重用单元,否则我似乎没有必要为每个选项卡页设置一个虚拟机。

      难以阅读和理解

      我不敢苟同,这就是 cmets 用于提供使用逻辑的原因。

      我的建议是把它带到一个虚拟机上并解决这个问题。

      两个 ViewModel 都需要访问项目列表:

      您可以将静态属性放在程序的应用程序上,并将 VM 实例分配给这些静态属性。然后每个 VM 都可以访问其他 VM 的数据。

      我之前在需要访问虚拟机的地方做过类似的事情,我使用通用应用程序作为访问位置。

      我是 MVVM 新手

      MVVM 只是一个 3 层数据系统,用于将业务逻辑(VM)与实体(模型)从数据视图中分离出来。不要拘泥于试图让 MVVM 成为教条。

      【讨论】:

        【解决方案4】:

        另一种方法是使用消息系统。例如,在 PRISM 中,您可以使用 IEventAggregator 在应用程序内部发送消息。我猜IEventAggregator 可以作为独立的 dll 使用。

        它非常强大且易于使用。您定义一条消息,可以添加项目的 ViewModel 发送此消息的实例(以项目作为参数)。带有列表的 ViewModel 可以捕获此消息并将项目添加到它的列表中。

        一个优点是两个 ViewModel 不必相互了解。

        【讨论】:

          【解决方案5】:

          使用单个父视图模型,该视图模型引用了您描述的两个子视图模型。我假设在您的视图模型中,用户从您那里选择将视图绑定到 ObservableCollection。

          在您的父视图模型中,您可以订阅源视图模型中 ObservableCollection 上的更改通知事件,然后调用第二个视图模型上的方法来填充更改。

          使用 ObservableCollection.CollectionChanged 事件的示例:https://dotnetcodr.com/2015/05/29/getting-notified-when-collection-changes-with-observablecollection-in-c-net/

          根据您使用的 MVVM 框架,另一种选择是使用消息传递模式在断开连接的 ViewModel 之间传递消息 - Use MVVM Light's Messenger to Pass Values Between View Model

          【讨论】:

            【解决方案6】:

            只需使用 mvvm 库,例如 mvvm light、prism ... 每个 mvvm 库都有用于在您可以使用的视图模型之间进行通信的结构体。 - 不要写新的。不要重新发明轮子。 - 不要在另一个视图模型中使用参数视图模型实例。如果你这样做,你会很痛苦

            【讨论】:

              猜你喜欢
              • 2013-11-30
              • 2019-07-02
              • 1970-01-01
              • 2012-05-25
              • 2018-04-07
              • 1970-01-01
              • 2022-07-29
              • 2011-05-24
              • 1970-01-01
              相关资源
              最近更新 更多