Caliburn.Micro + MEF +现代用户界面:IContent事件

我已经使用Caliburn.Micro和Modern UI( https://mui.codeplex.com )开始了一个项目,并且在我的视图模型上触发IContent的导航事件时遇到了一些困难。 我已经让两个人联系起来,彼此合作以下内容:

CM Bootstrapper:

public class CMBootstrapper : Bootstrapper { private CompositionContainer container; private DirectoryCatalog catalog; public CMBootstrapper() { } protected override void Configure() { catalog = new DirectoryCatalog(".", "*.*"); container = new CompositionContainer(catalog); var compositionBatch = new CompositionBatch(); compositionBatch.AddExportedValue(new WindowManager()); compositionBatch.AddExportedValue(new EventAggregator()); compositionBatch.AddExportedValue(container); container.Compose(compositionBatch); } protected override IEnumerable SelectAssemblies() { List assemblies = new List(); assemblies.Add(Assembly.GetExecutingAssembly()); return assemblies; } protected override object GetInstance(Type serviceType, string key) { string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key; var exports = container.GetExportedValues(contract); if (exports.Count() > 0) return exports.First(); throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract)); } protected override IEnumerable GetAllInstances(Type serviceType) { return container.GetExportedValues(AttributedModelServices.GetContractName(serviceType)); } protected override void BuildUp(object instance) { container.SatisfyImportsOnce(instance); } } 

现代UI内容加载器:

 [Export] public class MuiContentLoader : DefaultContentLoader { protected override object LoadContent(Uri uri) { var content = base.LoadContent(uri); if (content == null) return null; // Locate VM var viewModel = ViewModelLocator.LocateForView(content); if (viewModel == null) return content; // Bind VM if (content is DependencyObject) ViewModelBinder.Bind(viewModel, content as DependencyObject, null); return content; } } 

MuiView.xaml(Shell)

              

MuiViewModel

 [Export(typeof(IShell))] public class MuiViewModel : Conductor.Collection.OneActive, IShell { } 

每个子视图都被导出并实现IContent,如下所示:

 [Export] [PartCreationPolicy(CreationPolicy.Shared)] public class SettingsViewModel : Screen, IContent { #region IContent Implementation public void OnFragmentNavigation(FragmentNavigationEventArgs e) { Console.WriteLine("SettingsViewModel.OnFragmentNavigation"); } public void OnNavigatedFrom(NavigationEventArgs e) { Console.WriteLine("SettingsViewModel.OnNavigatedFrom"); } public void OnNavigatedTo(NavigationEventArgs e) { Console.WriteLine("SettingsViewModel.OnNavigatedTo"); } public void OnNavigatingFrom(NavigatingCancelEventArgs e) { Console.WriteLine("SettingsViewModel.OnNavigatingFrom"); } #endregion } 

但这些都没有开火。 经过一些调试后,我发现ModernFrame正在检查(SettingsView as IContent)事件,因为它只是一个普通的UserControl而没有它们。 所以我创建了一个自定义UserControl类,试图将事件传递给ViewModel:

MuiContentControl

 public delegate void FragmentNavigationEventHandler(object sender, FragmentNavigationEventArgs e); public delegate void NavigatedFromEventHandler(object sender, NavigationEventArgs e); public delegate void NavigatedToEventHandler(object sender, NavigationEventArgs e); public delegate void NavigatingFromEventHandler(object sender, NavigatingCancelEventArgs e); public class MuiContentControl : UserControl, IContent { public event FragmentNavigationEventHandler FragmentNavigation; public event NavigatedFromEventHandler NavigatedFrom; public event NavigatedToEventHandler NavigatedTo; public event NavigatingFromEventHandler NavigatingFrom; public MuiContentControl() : base() { } public void OnFragmentNavigation(FragmentNavigationEventArgs e) { if(FragmentNavigation != null) FragmentNavigation(this, e); } public void OnNavigatedFrom(NavigationEventArgs e) { if (NavigatedFrom != null) NavigatedFrom(this, e); } public void OnNavigatedTo(NavigationEventArgs e) { if(NavigatedTo != null) NavigatedTo(this, e); } public void OnNavigatingFrom(NavigatingCancelEventArgs e) { if(NavigatingFrom != null) NavigatingFrom(this, e); } } 

然后我修改了视图以使用Message.Attach监听事件:

SettingsView

         

唯一不会触发的事件是NavigatedTo所以我相信在调度事件之后才会应用Message.Attach。 我可能这样做的方式非常错误,我很乐意进行大规模重建。

好吧,这最终并没有那么糟糕 – 它确实让生活变得更容易,试图将事件传递给VM

我为ModernFrame控件创建了一个导体,该控件存在于ModernWindow控件模板中

你需要在OnViewLoaded的VM的OnViewLoaded事件中创建一个导体实例,因为这似乎是最好的地方(即没有导航发生但控件已完全加载并已解析它的模板)

 // Example viewmodel: public class ModernWindowViewModel : Conductor.Collection.OneActive { protected override void OnViewLoaded(object view) { base.OnViewLoaded(view); // Instantiate a new navigation conductor for this window new FrameNavigationConductor(this); } } 

导体代码如下:

 public class FrameNavigationConductor { #region Properties // Keep a ref to the frame private readonly ModernFrame _frame; // Keep this to handle NavigatingFrom and NavigatedFrom events as this functionality // is usually wrapped in the frame control and it doesn't pass the 'old content' in the // event args private IContent _navigatingFrom; #endregion public FrameNavigationConductor(IViewAware modernWindowViewModel) { // Find the frame by looking in the control template of the window _frame = FindFrame(modernWindowViewModel); if (_frame != null) { // Wire up the events _frame.FragmentNavigation += frame_FragmentNavigation; _frame.Navigated += frame_Navigated; _frame.Navigating += frame_Navigating; } } #region Navigation Events void frame_Navigating(object sender, NavigatingCancelEventArgs e) { var content = GetIContent(_frame.Content); if (content != null) { _navigatingFrom = content; _navigatingFrom.OnNavigatingFrom(e); } else _navigatingFrom = null; } void frame_Navigated(object sender, NavigationEventArgs e) { var content = GetIContent(_frame.Content); if (content != null) content.OnNavigatedTo(e); if (_navigatingFrom != null) _navigatingFrom.OnNavigatedFrom(e); } void frame_FragmentNavigation(object sender, FragmentNavigationEventArgs e) { var content = GetIContent(_frame.Content); if (content != null) content.OnFragmentNavigation(e); } #endregion #region Helpers ModernFrame FindFrame(IViewAware viewAware) { // Get the view for the window var view = viewAware.GetView() as Control; if (view != null) { // Find the frame by name in the template var frame = view.Template.FindName("ContentFrame", view) as ModernFrame; if (frame != null) { return frame; } } return null; } private IContent GetIContent(object source) { // Try to cast the datacontext of the attached viewmodel to IContent var fe = (source as FrameworkElement); if (fe != null) { var content = fe.DataContext as IContent; if (content != null) return content; } return null; } #endregion } 

现在,只要导航发生,您添加IContent接口的任何视图都将自动获取帧调用的方法

 public class TestViewModel : Conductor, IContent { public void OnFragmentNavigation(FragmentNavigationEventArgs e) { // Do stuff } public void OnNavigatedFrom(NavigationEventArgs e) { // Do stuff } public void OnNavigatedTo(NavigationEventArgs e) { // Do stuff } public void OnNavigatingFrom(NavigatingCancelEventArgs e) { // Do stuff } } 

我已经测试过,这适用于IContent上出现的所有4个导航事件 – 因为它通过EventArgs您可以直接从VM取消导航事件,或者在视图中执行任何通常的操作

我认为这可能是我能想到的最无痛的方式 – 在窗口中实际上是一行代码并在VM上实现接口,你就完成了:)

编辑:

我可能添加的唯一一件事是在将导体添加到窗口时抛出或者调试日志通知,以防由于某种原因导致无法找到帧(可能在以后更改帧的名称)释放m:ui)

我对我的IContent Views进行了跟进,并在我的ViewModel上实现了IContent。

  public void OnFragmentNavigation(FirstFloor.ModernUI.Windows.Navigation.FragmentNavigationEventArgs e) { if (this.DataContext != null) { var viewModel = this.DataContext as IContent; if (viewModel != null) { viewModel.OnFragmentNavigation(e); } } } public void OnNavigatedFrom(FirstFloor.ModernUI.Windows.Navigation.NavigationEventArgs e) { if (this.DataContext != null) { var viewModel = this.DataContext as IContent; if (viewModel != null) { viewModel.OnNavigatedFrom(e); } } } public void OnNavigatedTo(FirstFloor.ModernUI.Windows.Navigation.NavigationEventArgs e) { if (this.DataContext != null) { var viewModel = this.DataContext as IContent; if (viewModel != null) { viewModel.OnNavigatedTo(e); } } } public void OnNavigatingFrom(FirstFloor.ModernUI.Windows.Navigation.NavigatingCancelEventArgs e) { if (this.DataContext != null) { var viewModel = this.DataContext as IContent; if (viewModel != null) { viewModel.OnNavigatingFrom(e); } } }