【问题标题】:Caliburn Micro Navigation with MainMenu and SubMenu [closed]带有 MainMenu 和 SubMenu 的 Caliburn 微导航 [关闭]
【发布时间】:2013-04-17 09:06:41
【问题描述】:

首先感谢 Charleh 提供 caliburn.micro 导航解决方案。 如果导航其他页面,如何激活子菜单导航到第 2 页和停用?

ShellView.xaml

    <Window x:Class="Navigation.ShellView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600" WindowStartupLocation="CenterScreen" Width="800" Height="600">

    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!--Header-->
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
            </Grid.RowDefinitions>
            <ContentControl Grid.Column="1" Grid.Row="0" x:Name="MainMenuRegion" HorizontalContentAlignment="Stretch" HorizontalAlignment="Right" Margin="0,9,17,0" />
            <ContentControl Grid.Column="1" Grid.Row="1" x:Name="SubMenuRegion" HorizontalContentAlignment="Stretch" HorizontalAlignment="Right" Margin="0,0,17,0" />
        </Grid>    

        <!--Content-->
        <ContentControl Grid.Row="2" x:Name="ActiveItem"
                        HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
        </ContentControl>
    </Grid>
</Window>

ShellViewModel.cs

    namespace Navigation
    {
        public class ShellViewModel : Conductor<object>.Collection.OneActive, IShellViewModel, IHandle<NavigationEventMessage>
        {
            public ShellViewModel(IEventAggregator eventAggregator, INavigationService navigationService, IPage1ViewModel page1ViewModel, IPage2ViewModel page2ViewModel,IMainMenuViewModel mainMenuViewModel, ISubMenuViewModel subMenuViewModel)
            {
                _eventAggregator = eventAggregator;
                _eventAggregator.Subscribe(this);

                navigationService.Navigate(typeof(IPage1ViewModel), null);

                _page1ViewModel = page1ViewModel;
                _page2ViewModel = page2ViewModel;

                Items.Add(_page1ViewModel);
                Items.Add(_page2ViewModel);
                ActiveItem = _page1ViewModel;            
            }

            private readonly IEventAggregator _eventAggregator;
            private readonly IPage1ViewModel _page1ViewModel;
            private readonly IPage2ViewModel _paage2ViewModel;

            public IMainMenuViewModel MainMenuRegion { get; set; }
            public ISubMenuViewModel SubMenuRegion { get; set; }         

            public void Handle(NavigationEventMessage message)
            {
                ActivateItem(message.ViewModel);
            }
        }
    }
public interface IShellViewModel
{
}

public interface INavigationService
{
    void Navigate(Type viewModelType, object modelParams);
}

public class NavigationEventMessage
{
    public IScreen ViewModel { get; private set; }

    public NavigationEventMessage(IScreen viewModel)
    {
        ViewModel = viewModel;
    }
}

public class NavigationService : INavigationService
{
    // Depends on the aggregator - this is how the shell or any interested VMs will receive
    // notifications that the user wants to navigate to someplace else
    private IEventAggregator _aggregator;

    public NavigationService(IEventAggregator aggregator)
    {
        _aggregator = aggregator;
    }

    // And the navigate method goes:
    public void Navigate(Type viewModelType, object modelParams)
    {
        // Resolve the viewmodel type from the container
        var viewModel = IoC.GetInstance(viewModelType, null);

        // Inject any props by passing through IoC buildup
        IoC.BuildUp(viewModel);

        // Check if the viewmodel implements IViewModelParams and call accordingly
        var interfaces = viewModel.GetType().GetInterfaces()
               .Where(x => typeof(IViewModelParams).IsAssignableFrom(x) && x.IsGenericType);

        // Loop through interfaces and find one that matches the generic signature based on modelParams...
        foreach (var @interface in interfaces)
        {
            var type = @interface.GetGenericArguments()[0];
            var method = @interface.GetMethod("ProcessParameters");

            if (type.IsAssignableFrom(modelParams.GetType()))
            {
                // If we found one, invoke the method to run ProcessParameters(modelParams)
                method.Invoke(viewModel, new object[] { modelParams });
            }
        }

        // Publish an aggregator event to let the shell/other VMs know to change their active view
        _aggregator.Publish(new NavigationEventMessage(viewModel as IScreen));
    }
}

// This is just to help with some reflection stuff
public interface IViewModelParams { }

public interface IViewModelParams<T> : IViewModelParams
{
    // It contains a single method which will pass arguments to the viewmodel after the nav service has instantiated it from the container
    void ProcessParameters(T modelParams);
}

public class ViewInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        // The 'true' here on the InSameNamespaceAs causes windsor to look in all sub namespaces too

        container.Register(Component.For<IShellViewModel>().ImplementedBy<ShellViewModel>().LifestyleSingleton());

        container.Register(Component.For<IPage1ViewModel>().ImplementedBy<Page1ViewModel>().LifestyleSingleton());

        container.Register(Component.For<IPage2ViewModel>().ImplementedBy<Page2ViewModel>().LifestyleSingleton());

        container.Register(Component.For<IMainMenuViewModel>().ImplementedBy<MainMenuViewModel>().LifestyleSingleton());

        container.Register(Component.For<ISubMenuViewModel>().ImplementedBy<SubMenuViewModel>().LifestyleSingleton());

    }
}

public class NavigationInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<INavigationService>().ImplementedBy<NavigationService>());
    }
}

public class CaliburnMicroInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        // Register the window manager
        container.Register(Component.For<IWindowManager>().ImplementedBy<WindowManager>());

        // Register the event aggregator
        container.Register(Component.For<IEventAggregator>().ImplementedBy<EventAggregator>());
    }
}

MainMenuView.xaml

<UserControl x:Class="Navigation.Views.MainMenuView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"   
             xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
             mc:Ignorable="d">

    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
            <RadioButton IsChecked="True" Content="Page1" cal:Message.Attach="[Event Checked]=[Action Page1Checked]"/>
            <RadioButton Content="Page2" cal:Message.Attach="[Event Checked]=[Action Page2Checked]"/>
        </StackPanel>    
    </Grid>

</UserControl>

MainMenuViewModel.cs

namespace Navigation.ViewModels
{
    public class MainMenuViewModel : Conductor<object>.Collection.OneActive, IMainMenuViewModel
    {
        private readonly IEventAggregator _eventAggregator;
        private bool _isAssemblyManagementModule;

        public MainMenuViewModel(IEventAggregator eventAggregator)
        {
            _eventAggregator = eventAggregator; 
        }      

        public void Page1Checked()
        {
            NavigationService navigationService = new NavigationService(_eventAggregator);
            navigationService.Navigate(typeof(IPage1ViewModel), null);

        }

        public void Page2Checked()
        {    
            NavigationService navigationService = new NavigationService(_eventAggregator);
            navigationService.Navigate(typeof(IPage2ViewModel), null);

        }
    }

    public interface IMainMenuViewModel
    {
    }
}

SubMenuView.xaml

<UserControl x:Class="Navigation.Views.SubMenuView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"   
             xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
             mc:Ignorable="d">

    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
            <RadioButton IsChecked="True" Content="SubPage1" cal:Message.Attach="[Event Checked]=[Action SubPage1Checked]"/>
            <RadioButton Content="SubPage2" cal:Message.Attach="[Event Checked]=[Action SubPage2Checked]"/>
        </StackPanel>    
    </Grid>     
</UserControl>

SubMenuViewModel.cs

namespace Navigation.ViewModels
{
    public class SubMenuViewModel : Conductor<object>.Collection.OneActive, ISubMenuViewModel
    {
        private readonly IEventAggregator _eventAggregator;
        private bool _isAssemblyManagementModule;

        public SubMenuViewModel(IEventAggregator eventAggregator)
        {
            _eventAggregator = eventAggregator; 
        }      

        public void SubPage1Checked()
        {
            NavigationService navigationService = new NavigationService(_eventAggregator);
            navigationService.Navigate(typeof(ISubPage1ViewModel), null);

        }

        public void SubPage2Checked()
        {    
            NavigationService navigationService = new NavigationService(_eventAggregator);
            navigationService.Navigate(typeof(ISubPage2ViewModel), null);

        }
    }

    public interface ISubMenuViewModel
    {
    }
}

【问题讨论】:

  • 你真的可以更清楚你的问题是什么以及你想要帮助什么,而不是一堵代码墙。
  • 我认为了解你在做什么而不是仅仅复制/粘贴代码很重要,在这个实现中使用容器存在一些基本问题(即在依赖项等中实例化依赖项等)

标签: caliburn.micro


【解决方案1】:

不是对实际问题的真正答案,而是更多警告:您在 IoC 容器以及如何使用它方面存在一些其他问题

您正在为视图模型中的每个命令创建一个新的导航服务实例...这不遵循 IoC 或服务方法。

导航服务应由容器解析,因此您的视图模型构造函数应包含INavigationService 参数

例如MainMenuViewModel 的构造函数应如下所示:

private INavigationService _navigationService;

public MainMenuViewModel(INavigationService navigationService)
{
    _navigationService = navigationService;
}      

...和用法:

public void Page1Checked()
{
    _navigationService.Navigate(typeof(IPage1ViewModel), null);
}

这是因为容器会自动将INavigationService 实现注入到您的虚拟机中。您不需要对 IEventAggregator 实现的引用(除非您的 VM 依赖于它,但它似乎并不依赖它)并且您不应该手动实例化 NavigationService 实例,因为这是容器的工作

这是您第一次使用 IoC 或 MVVM 吗?您能否发布更多关于您的体验和预期的信息(可能带有屏幕截图)?

编辑:

好吧,在我知道你对发送给我的项目做了什么之前,我只能给你这些了(棱镜还是转移到 CM?)

Caliburn.Micro 使用IConductor&lt;T&gt; 作为所有可以执行/管理活动屏幕/项目的窗口的基础。 Conductor&lt;T&gt; 是该接口的标准实现,通常用于管理基于IScreen 的混凝土。

IConductor 接口

http://caliburnmicro.codeplex.com/SourceControl/changeset/view/ae25b519bf1e46a506c85395f04aaffb654c0a08#src/Caliburn.Micro.Silverlight/IConductor.cs

和实施

http://caliburnmicro.codeplex.com/SourceControl/changeset/view/ae25b519bf1e46a506c85395f04aaffb654c0a08#src/Caliburn.Micro.Silverlight/Conductor.cs

默认指挥管理 1 个屏幕。有几个嵌套类实现了多个屏幕 - Conductor&lt;T&gt;.Collection.OneActiveConductor&lt;T&gt;.Collection.AllActive 允许一次或所有项目处于活动状态 - (例如,OneActive 的示例是带有选项卡和选项卡的 Internet Explorer 窗口AllActive 的一个例子是 Visual Studio 2010 工具窗口)

为了使用此导体实现,您最好使用IScreen(或具体的Screen 类)

屏幕界面

http://caliburnmicro.codeplex.com/SourceControl/changeset/view/ae25b519bf1e46a506c85395f04aaffb654c0a08#src/Caliburn.Micro.Silverlight/IScreen.cs

和实施

http://caliburnmicro.codeplex.com/SourceControl/changeset/view/ae25b519bf1e46a506c85395f04aaffb654c0a08#src/Caliburn.Micro.Silverlight/Screen.cs

基本上,通过这些实现,如果主窗口被停用,很容易将“停用”消息冒泡到所有孩子及其孩子等等。这也意味着屏幕会收到已激活的通知(OnActivate 等)

例如

class ParentWindow : Conductor<IScreen>
{

    void DoSomething() 
    {
        ActivateItem(ItemToActivate); // Previous item is automatically deactivated etc
    }

    override OnActivate() 
    {
        // Activate children or whatever

    }
}

class SomeChildWindow : Screen
{
}

重要的是要注意Conductor&lt;T&gt;Screen 的子类,因此可以有子导体和孙导体都遵守生命周期。

我不确定是否有 Prism 等价物

有一些关于屏幕和生命周期的优秀 Caliburn Micro 文档:

http://caliburnmicro.codeplex.com/wikipage?title=Screens%2c%20Conductors%20and%20Composition&referringTitle=Documentation

如果您仍在使用 Prism 而不是 Caliburn.Micro 并且没有 Prism 等价物,那么至少 Caliburn.Micro 实现会给您一些指导

【讨论】:

  • 感谢回复,我有一个使用我写的 prism 框架的导航系统,我们决定迁移到 caliborn.micro,但是 caliburn.micro WPF 不支持像 prism 这样的导航系统,如果你希望我可以将我使用 prism 框架编写的导航系统的源代码发送给您。现在我的问题是如何在运行时导航中激活和停用 SubMenu,例如 SubMenu 仅显示 MainMenu Clicked Page2。
  • 你能发一些截图 Alex 吗?我无法理解您的意思 - 如果您能告诉我正在发生的事情以及您期望发生的事情,我可能会进一步帮助您
  • 抱歉回复晚了,我不得不用prism框架编写示例导航项目并上传源代码和编译项目。 solidfiles.com/d/dfb5cf4e92 非常感谢。
  • 你好 Charleh,你需要看看这个项目吗?谢谢。
  • 现在看它,虽然它没有运行,因为看起来您正试图将 silverlight XAP 加载到插件框架中,但插件是 c# 库并且没有 appmanifest.xaml。 .. 这可以在你的机器上运行吗?
猜你喜欢
  • 2016-09-17
  • 1970-01-01
  • 1970-01-01
  • 2016-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-05
相关资源
最近更新 更多