使用MainMenu和SubMenu进行Caliburn微导航

时间:2013-04-17 09:06:42

标签: caliburn.micro

首先感谢Charleh的caliburn.micro导航解决方案。 如何激活SubMenu导航到Page2和Deactive如果导航到其他页面?

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
    {
    }
}

1 个答案:

答案 0 :(得分:1)

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

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

导航服务应由容器解析,以便您的viewmodel构造函数应包含INavigationService参数

e.g。 MainMenuViewModel的构造函数应如下所示:

private INavigationService _navigationService;

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

......和用法:

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

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

这是您第一次使用IoC还是MVVM?你能发布一些关于你正在经历的和你期望的更多信息(可能有截图)吗?

编辑:

好的,这是我能给你的全部,直到我知道你发送给我的项目你做了什么(棱镜或转移到CM?)

Caliburn.Micro使用IConductor<T>作为可以进行/管理活动屏幕/项目的所有窗口的基础。 Conductor<T>是此界面的标准实现,通常用于管理基于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<T>.Collection.OneActiveConductor<T>.Collection.AllActive允许其中一个或所有项目一次处于活动状态 - (例如OneActive的示例是带有选项卡的Internet Explorer窗口和AllActive的示例是Visual Studio 2010工具窗口)

为了使用此导体实现,理想情况下应使用IScreen(或具体的Screen类)

IScreen界面

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等)

e.g。

class ParentWindow : Conductor<IScreen>
{

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

    override OnActivate() 
    {
        // Activate children or whatever

    }
}

class SomeChildWindow : Screen
{
}

重要的是要注意Conductor<T>子类Screen,这样就可以让子导体和孙子导体都遵守生命周期。

我不确定是否有Prism等价物

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

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

如果您仍在使用Prism而不是Caliburn.Micro并且没有Prism等效,那么至少Caliburn.Micro实现将为您提供一些指导