嵌套ViewModels:按钮操作和通信?

时间:2013-02-27 13:20:54

标签: c# wpf mvvm caliburn.micro

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

作为一个例子,我正在编写一个带有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。但是按钮事件“存在”LoginVM的{​​{1}}实例中,那么如何访问LoginViewModel中的媒体资源CentralVM

这当然只是一般问题的一个例子。我的第一个想法是做以下事情:

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

MainViewModel

- 最后,我将此方法调用添加到private _loggedIn = false; private void CheckForLoginChange() { if (_loggedIn == false && !String.IsNullOrEmpty(LoginVM.LoggedInAs)) { _loggedIn = true; CentralVM = LoggedInVM; } } 的setter,即

LoginVM

但这不起作用。是因为虽然单击按钮事件时public LoginViewModel LoginVM { get { return _logInVM; } private set { _logInVM = value; CheckForLoginChange(); } } 会发生变化,但是不会调用setter吗?

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

2 个答案:

答案 0 :(得分:4)

实际上,它正是Event Aggregator的工作。你有一个内置的Caliburn.Micro。

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

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

同样适用于LoginViewModel。这里有一点警告,它们都应该接收事件聚合器的相同的实例,因此事件正确传播(实际上,实际上最好将IoC容器设置为注入IEventAggregator as单)。

现在MainViewModel应该实现IHandle<T>,其中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不应该“手动”更改主视图模型上的任何屏幕,这不是它的工作。

答案 1 :(得分:1)

在回答您的上一条评论时,您可以自定义引导程序,但说实话,如果您不打算使用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());
}