嵌套视图模型:按钮动作和通信

本文关键字:通信 按钮 视图 模型 嵌套 | 更新日期: 2023-09-27 18:01:35

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

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

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

MainViewModel类,我有几个其他的ViewModels,

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做你猜的,即你可以输入(用户名,pwd),并且有一个按钮来评估条目,如果它是正确的,我想切换设置CentralVMLoggedInVM。但是按钮事件"生活"在LoginViewModelLoginVM实例中,所以我如何访问MainViewModel中的CentralVM属性?

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

- LoginVM包含一个名为LoggedInAs的属性(字符串类型),当点击按钮时设置。-I添加一个方法到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"或"messenger"的流行词——我知道它们与可能的解决方案有关,但我没有找到我能理解的好的文档……

嵌套视图模型:按钮动作和通信

实际上,这正是事件聚合器的工作。你在calburn有一个。微型,内置。

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

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

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

现在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不应该"手动"改变主视图模型上的任何屏幕,这不是它的工作。

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