Caliburn Micro Navigation with MainMenu and SubMenu [closed]

﹥>﹥吖頭↗ 提交于 2019-12-13 02:40:42

问题


The first thanks Charleh for caliburn.micro navigation solution. How to Active SubMenu navigate to Page2 and Deactive if navigate other page?

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:


Not really an answer to the actual question but more a warning: You have some other issues with the IoC container and how you are using it

You are creating a new navigation service instance for each command in your viewmodels...that's not following the IoC or service methodology.

The navigation service should be resolved by the container so your viewmodel constructor should contain an INavigationService parameter

e.g. your constructor for MainMenuViewModel should look like this:

private INavigationService _navigationService;

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

... and usage:

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

This is because the container will automatically inject the INavigationService implementation into your VM. You don't need a reference to IEventAggregator implementation (unless your VM is dependent on it, which it doesn't appear to be) and you should not be manually instantiating the NavigationService instance since this is the job of the container

Is this your first time using IoC or MVVM? Can you post some more information (maybe with screenshots) on what you are experiencing and what you expect?

Edit:

Ok this is all I can give you until I know what you are doing with the project you sent me (prism or move to CM?)

Caliburn.Micro uses IConductor<T> as the base for all windows which may conduct/manage an active screen/item. The Conductor<T> is the standard implementation of this interface and is usually used to manage IScreen based concretions.

IConductor interface

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

and implementation

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

The default conductor manages 1 screen. There are a couple of nested classes which implement multiple screens - Conductor<T>.Collection.OneActive and Conductor<T>.Collection.AllActive allowing either one or all of its items to be active at a time - (e.g. an example of OneActive is the Internet Explorer window with tabs and an example of AllActive is Visual Studio 2010 tool windows)

In order to work with this conductor implementation you should ideally use IScreen (or the concrete Screen class)

IScreen interface

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

and implementation

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

Basically, with these implementations, if the main window is deactivated, it is easy to bubble the 'deactivate' message down to all the children and their children and so on. It also means that a screen gets a notification that it has been activated (OnActivate etc)

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

It's important to note that Conductor<T> subclasses Screen so it's possible to have child conductors and grandchild conductors that will all obey lifecycle.

I'm not sure if there are Prism equivalents

There is some good Caliburn Micro documentation regarding screens and lifecycle:

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

If you are still using Prism and not Caliburn.Micro and there is no Prism equivalent, then at least the Caliburn.Micro implementations will give you some guidance



来源:https://stackoverflow.com/questions/16055955/caliburn-micro-navigation-with-mainmenu-and-submenu

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!