【问题标题】:Nested ViewModels: Button actions and Communication?嵌套视图模型:按钮操作和通信?
【发布时间】:2013-02-13 07:45:39
【问题描述】:

这将是一个冗长的问题,但长期以来我一直在通过构建丑陋的大型一体化课程来回避这个问题。

例如,我正在编写一个带有 MVVM 设计的 WPF 独立应用程序(我也在使用 Caliburn.Micro),并且有一个 MainViewModel 和一个 MainView。这个视图包含一个StackPanel,并且这个StackPanel的内容绑定到一个ViewModel CentralVM

<StackPanel DockPanel.Dock="Top">
     <ContentControl Margin="10" Name="CentralVM"/>
</StackPanel>

MainViewModel 类中,我还有几个其他 ViewModel,

private PropertyChangedBase _centralVM = new PropertyChangedBase();        
private LoggedInViewModel _loggedInVM = new LoggedInViewModel();
private LoginViewModel _logInVM = new LoginViewModel();

public PropertyChangedBase CentralVM {
    get { return _centralVM; }
    set { _centralVM = value; NotifyOfPropertyChange(() => CentralVM); }
}
public LoggedInViewModel LoggedInVM {
    get { return _loggedInVM; }
    private set { _loggedInVM = value; }
}
public LoginViewModel LoginVM {
    get { return _logInVM; }
    private set { _loginVM = value;}
}

现在,在我设置的MainViewModel 的构造函数中

CentralVM = LoginVM

然后StackPanel 会自动绑定到视图LoginViewLoginView 会按照您的猜测进行,即您可以输入(用户名,密码),并且有一个评估条目的按钮,如果正确,我想将CentralVM 设置为LoggedInVM。但是按钮事件“存在”在LoginViewModelLoginVM 实例中,那么如何访问MainViewModel 中的属性CentralVM

这当然只是一般问题的一个例子。我的第一个想法是执行以下操作:

-LoginVM 包含一个名为LoggedInAs 的属性(字符串类型),该属性在单击按钮时设置。 -我给MainViewModel添加一个方法,像这样:

private _loggedIn = false;
private void CheckForLoginChange() {
    if (_loggedIn == false && !String.IsNullOrEmpty(LoginVM.LoggedInAs)) {
        _loggedIn = true; 
        CentralVM = LoggedInVM;
    }
}

-最后,我将这个方法调用添加到LoginVM的setter中,即

public LoginViewModel LoginVM {
    get { return _logInVM; }
    private set { _logInVM = value; CheckForLoginChange(); }
}

但这不起作用。是不是因为虽然点击按钮事件时LoginVM发生了变化,但是没有调用setter?

感谢您在这方面的任何帮助。我非常感谢一个详细的答案,而不仅仅是一些对“EventAggregators”或“Messengers”的流行语引用——我知道它们与可能的解决方案有关,但我还没有找到我能理解的好的文档......

【问题讨论】:

  • 我不完全明白,但是为什么您不使用命令来单击按钮?
  • 因为我认为没有理由这样做,所以 Caliburn.Micro 会自动将 Button 连接到与该按钮同名的方法。在这里使用命令有什么好处?它不能解决我的问题,还是?
  • 好吧,我只是不熟悉 caliburn

标签: c# wpf mvvm caliburn.micro


【解决方案1】:

实际上,这正是 Event Aggregator 的工作。您在 Caliburn.Micro 中内置了一个。

这很简单,MainViewModelLoginViewModel 都应该将聚合器作为依赖项:

private readonly IEventAggregator eventAggregator;
public MainViewModel(IEventAggregator eventAggregator)
{
    this.eventAggregator = eventAggregator;
    this.eventAggregator.Subscribe(this);
}

LoginViewModel 也是如此。这里有一点警告,它们都应该接收到事件聚合器的 same 实例,因此事件可以正确传播(实际上,最好将 IoC 容器设置为注入 IEventAggregator 作为单例) .

现在MainViewModel 应该实现IHandle&lt;T&gt;,其中T 是用作消息的类,假设:

public class LogInSuccessful
{
    public readonly string LoggedInAs;
    public LogInSuccessful(string loggedInAs)
    {
        LoggedInAs = loggedInAs;
    }
}

然后

public class MainViewModel : ... , IHandle<LogInSuccessful>
{
    ....
    public void Handle(LogInSuccessful message)
    {
        //here you can change the VM and access message.LoggedInAs string. 
        //This method will be called when there's an appropriate event published
        //to the same event aggregator that the MainViewModel is subscribed to.
    }
}

要发布事件,您必须在LoginViewModel 中获取事件聚合器,然后在某个时间点调用:

eventAggregator.Publish(new LogInSuccessful("Admin"));

进一步编辑

这样,LoginViewModel 只做一件事 - 验证凭据。如果它们有效,它会将事件发布到MainViewModel,后者管理屏幕并应采取适当的措施。 LoginViewModel 不应该“手动”更改主视图模型上的任何屏幕,这不是它的工作。

【讨论】:

  • 感谢您的快速回答,但是:什么是 IoC 容器? 'injectas singleton' 是什么意思?我必须承认,迄今为止我一直使用 Caliburn.Micro 作为通过约定绑定的便捷方式,但还不了解更深层次的功能 - 也是因为我没有找到足够详细的文档并且我不擅长从示例中学习,我喜欢抽象,完整的解释(我是数学家...)
  • @ToaoG 好的,这与dependency injection 相当相关,并且与您现在的问题正交,但这里有一个非常简短的描述。 IoC 是“控制反转”,而“IoC 容器”是一种在应用程序启动时为您连接依赖关系的机制。 “作为单例注入”意味着所提到的容器将返回一个已解析对象的相同实例。在这种情况下,这意味着所有对象都获得相同的事件聚合器实例。
  • 所以要接收“相同的实例”,我是否应该在 MainViewModel 的构造函数中创建一个实例,然后将此实例传递给其他 ViewModel 的构造函数(我也在 MainViewModel 的构造函数中执行)?
  • 谢谢,不要粗鲁,但我之前读过关于什么是 IoC 容器的描述性答案,但它到底是什么?让我天真地问:它是一个类,我必须创建一个实例吗?如果没有,它住在哪里?等等。抱歉,我没有得到这些一般性的答案。
  • IoC 容器是一个对象。在Caliburn.Micro 中,它是在应用程序启动时在AppBootstrapper 中创建的,它解决了所有依赖项(以及视图、视图模型、其他接口等)。是的,您创建容器,注册依赖项并保留它。然后 Caliburn.Micro 使用它“自动”解决依赖关系。如果您不需要它,您可以在构造函数中“手动”设置依赖关系,尽管从长远来看,这可能容易出错且令人厌烦。不幸的是,在 cmets 中没有足够的空间来深入探讨这个话题;)
【解决方案2】:

在回答您上一条评论时,您可以自定义引导程序,但老实说,如果您不打算使用 IoC 容器并且对该抽象级别不感兴趣,您可以只使用静态类来保存聚合器的一个实例。

当然,您与实现耦合,但如果您只运行一个小项目并且对 DI/IoC 部分不感兴趣,它会做。

简单的类可以是

static class EventAggregatorProvider 
{ 
    private static EventAggregator _aggregator = new EventAggregator();

    public static EventAggregator Aggregator { get { return _aggregator; } }
}

然后在您的代码中通过静态类访问它:

public void SomeMethod()
{
    // Do something
    EventAggregatorProvider.Aggregator.Publish(new SomeMessage());
}

【讨论】:

  • 感谢您的回答!我完全理解这个:-) 我已经开始定制我的引导程序并让这种方法发挥作用,但我会定义的。在我的下一个小项目中试试这个。如果就这么简单,为什么我以前从未见过这种方法……
  • 可能是因为大多数人会使用 IoC 方法,因为它最灵活 - 但老实说,它只有在您使用 IoC 提供的好处时才有用 - 如果您对使用它不感兴趣(特别是对于小型项目)那么您不必这样做。这也意味着您的代码不太灵活,因为您无法注入聚合器的特定实现,并且在运行单元测试时需要存在静态类,而不仅仅是注入它 - 这意味着您无法在其中运行 VM没有静态实现的隔离
猜你喜欢
  • 2017-04-10
  • 2011-05-20
  • 1970-01-01
  • 1970-01-01
  • 2013-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-08
相关资源
最近更新 更多