问题
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