Caliburn Micro中如何实现从LoginView到SignUpView的导航?
解决Caliburn.Micro中LoginView按钮无法导航到SignUpView的问题
让我们一步步拆解你的问题,核心问题出在ViewModel的继承关系和导航逻辑的解耦上,下面是具体的修复方案:
1. 修正LoginViewModel的继承关系(关键错误)
你之前让LoginViewModel继承ShellViewModel是完全错误的——ShellViewModel作为Conductor<object>是整个应用的导航中枢,而LoginViewModel应该是一个普通的视图模型(Screen),它不应该拥有自己的导航能力,而是要通知Shell去完成导航。
修改后的LoginViewModel代码:
using Caliburn.Micro; public class LoginViewModel : Screen { private readonly IEventAggregator _eventAggregator; // 通过构造函数注入EventAggregator,用于发送导航事件 public LoginViewModel(IEventAggregator eventAggregator) { _eventAggregator = eventAggregator; } // 对应LoginView上的ShowSignUp按钮,Caliburn.Micro会自动绑定 public void ShowSignUp() { // 发布导航事件,通知Shell切换到SignUpView _eventAggregator.PublishOnUIThread(new ActionInvokedMessage(new SignUpViewModel())); } }
2. 修复ShellViewModel的构造函数(消除DI歧义)
你的ShellViewModel有两个构造函数,这会导致依赖注入容器无法正确选择要使用的构造函数,进而导致ViewModel实例初始化混乱。我们需要合并成一个统一的构造函数,确保所有依赖都通过DI注入:
using Caliburn.Micro; using System.Windows.Threading; public class ShellViewModel : Conductor<object>, IHandle<ActionInvokedMessage> { private readonly DispatcherTimer _dt = new DispatcherTimer(); private readonly SplashScreenViewModel _splashVM; private readonly LoginViewModel _loginVM; private readonly SignUpViewModel _signUpVM; private readonly IEventAggregator _eventAggregator; // 合并构造函数,所有依赖都通过DI注入 public ShellViewModel(SplashScreenViewModel splashVM, LoginViewModel loginVM, SignUpViewModel signUpVM, IEventAggregator eventAggregator) { _splashVM = splashVM; _loginVM = loginVM; _signUpVM = signUpVM; _eventAggregator = eventAggregator; // 订阅导航事件 _eventAggregator.Subscribe(this); // 启动闪屏逻辑 ActivateItem(_splashVM); _dt.Tick += Dt_Tick; _dt.Interval = new TimeSpan(0, 0, 2); _dt.Start(); } private void Dt_Tick(object sender, EventArgs e) { _dt.Stop(); ActivateItem(_loginVM); } // 处理导航事件,切换ActiveItem public void Handle(ActionInvokedMessage message) { ActivateItem(message.Page); } }
3. 确保ActionInvokedMessage消息类正确定义
这个类用于在LoginVM和ShellVM之间传递导航目标,确保它是一个简单的POCO类:
public class ActionInvokedMessage { public object Page { get; } public ActionInvokedMessage(object page) { Page = page; } }
为什么之前的代码不工作?
- 错误的继承关系:当LoginViewModel继承ShellViewModel时,每个LoginVM实例都是一个独立的Conductor,你点击按钮调用的是LoginVM自己的
ShowSignUp方法,而不是负责显示ActiveItem的Shell实例的方法,自然不会切换视图。 - 构造函数歧义:两个构造函数导致DI容器可能初始化了一个没有正确注入
_splashVM、_loginVM的Shell实例,导航逻辑完全混乱。 - 直接依赖问题:即使把
ShowSignUp移到LoginVM,直接创建SignUpVM并调用ActivateItem也只会影响LoginVM自己的ActiveItem,而不是Shell的。
额外验证点
- 确保你的DI容器(比如Autofac、Unity等)正确注册了所有ViewModel和
IEventAggregator(Caliburn.Micro自带的EventAggregator可以直接注册为单例)。 - 确认LoginView中的按钮
x:Name="ShowSignUp"和LoginViewModel中的方法名完全一致,Caliburn.Micro依赖约定绑定,大小写敏感。
内容的提问来源于stack exchange,提问作者Martin
相关产品推荐
相关产品推荐

