在the latest release of MVVM Light note中,已经表明MVVM Light现在提供“导航服务”。
但我和我的朋友谷歌无法找到如何使用它。
我可以看到我可以向ServiceLocator询问INavigationService
,所以我看到我可以要求转到另一个页面,但是:
INavigationService
这个图书馆有官方文件吗?因为目前我发现它很好地编码并且工作正常,但是当我要搜索如何使用它时,我从来没有找到如何使用的文档/示例,除了他的博客有一些条目。这非常令人沮丧。我找到的唯一文件是this,我对Pluralsight不太熟悉,但似乎必须每月订阅一次(作为个人,我试图在我的空闲时间申请,是不可能的)。
答案 0 :(得分:32)
是的,MvvmLight
在其上一个版本but they did't offer any implementation regarding Wpf
中引入了NavigationService
(您可以使用WP,Metroapps中的已实施NavigationService
..)但遗憾的是Wpf
MvvmLight
1}},你需要自己实现,
这里我目前是如何做到的(credit)
首先创建实现INavigationService
public interface IFrameNavigationService:INavigationService
{
object Parameter { get; }
}
Parameter
ViewModels
用于在INavigationService
之间传递对象,GalaSoft.MvvmLight.Views
是class FrameNavigationService : IFrameNavigationService,INotifyPropertyChanged
{
#region Fields
private readonly Dictionary<string, Uri> _pagesByKey;
private readonly List<string> _historic;
private string _currentPageKey;
#endregion
#region Properties
public string CurrentPageKey
{
get
{
return _currentPageKey;
}
private set
{
if (_currentPageKey == value)
{
return;
}
_currentPageKey = value;
OnPropertyChanged("CurrentPageKey");
}
}
public object Parameter { get; private set; }
#endregion
#region Ctors and Methods
public FrameNavigationService()
{
_pagesByKey = new Dictionary<string, Uri>();
_historic = new List<string>();
}
public void GoBack()
{
if (_historic.Count > 1)
{
_historic.RemoveAt(_historic.Count - 1);
NavigateTo(_historic.Last(), null);
}
}
public void NavigateTo(string pageKey)
{
NavigateTo(pageKey, null);
}
public virtual void NavigateTo(string pageKey, object parameter)
{
lock (_pagesByKey)
{
if (!_pagesByKey.ContainsKey(pageKey))
{
throw new ArgumentException(string.Format("No such page: {0} ", pageKey), "pageKey");
}
var frame = GetDescendantFromName(Application.Current.MainWindow, "MainFrame") as Frame;
if (frame != null)
{
frame.Source = _pagesByKey[pageKey];
}
Parameter = parameter;
_historic.Add(pageKey);
CurrentPageKey = pageKey;
}
}
public void Configure(string key, Uri pageType)
{
lock (_pagesByKey)
{
if (_pagesByKey.ContainsKey(key))
{
_pagesByKey[key] = pageType;
}
else
{
_pagesByKey.Add(key, pageType);
}
}
}
private static FrameworkElement GetDescendantFromName(DependencyObject parent, string name)
{
var count = VisualTreeHelper.GetChildrenCount(parent);
if (count < 1)
{
return null;
}
for (var i = 0; i < count; i++)
{
var frameworkElement = VisualTreeHelper.GetChild(parent, i) as FrameworkElement;
if (frameworkElement != null)
{
if (frameworkElement.Name == name)
{
return frameworkElement;
}
frameworkElement = GetDescendantFromName(frameworkElement, name);
if (frameworkElement != null)
{
return frameworkElement;
}
}
}
return null;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
命名空间的一部分
然后实现这样的界面
MainFrame
上面代码中的Frame
是x:简单Xaml
控件的名称viewmodellocator
中的定义,用于在页面之间导航(根据您的需要进行自定义)
第二次:在SetupNavigation()
中,初始化导航服务(static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SetupNavigation();
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<LoginViewModel>();
SimpleIoc.Default.Register<NoteViewModel>();
}
private static void SetupNavigation()
{
var navigationService = new FrameNavigationService();
navigationService.Configure("LoginView", new Uri("../Views/LoginView.xaml",UriKind.Relative));
navigationService.Configure("Notes", new Uri("../Views/NotesView.xaml", UriKind.Relative));
SimpleIoc.Default.Register<IFrameNavigationService>(() => navigationService);
}
),以便在视图模型中使用它:
public LoginViewModel(IFrameNavigationService navigationService)
{
_navigationService = navigationService;
...
_navigationService.NavigateTo("Notes",data);
..
第三:最后,使用该服务,例如
{{1}}
编辑
可在此repo找到明确的样本。
答案 1 :(得分:2)
我不知道mvvm light中是否有导航功能。我用contentControl绑定实现了它:
<xcad:LayoutDocumentPane>
<xcad:LayoutDocument x:Name="DetailDoc" CanClose="False">
<ContentControl Content="{Binding DisplayedDetailViewModel}"/>
</xcad:LayoutDocument>
</xcad:LayoutDocumentPane>
然后是viewmodel属性。它继承自mvvm light ViewModelBase类。
public ViewModelBase DisplayedDetailViewModel
{
get
{
return displayedDetailViewModel;
}
set
{
if (displayedDetailViewModel == value)
{
return;
}
displayedDetailViewModel = value;
RaisePropertyChanged("DisplayedDetailViewModel");
}
要使内容控件知道它必须使用哪个用户控件,请在app.xaml中定义DataTemplates:
<Application.Resources>
<ResourceDictionary>
<!--
We define the data templates here so we can apply them across the
entire application.
The data template just says that if our data type is of a particular
view-model type, then render the appropriate view. The framework
takes care of this dynamically. Note that the DataContext for
the underlying view is already set at this point, so the
view (UserControl), doesn't need to have it's DataContext set
directly.
-->
<DataTemplate DataType="{x:Type viewModel:LoggerViewModel}">
<views:LogView />
</DataTemplate>
LogView是UserControl。您只需将LoggerViewModel分配给DisplayedDetailViewModel,框架就可以完成工作。
答案 2 :(得分:2)
我宁愿选择 ViewModelFirst 导航服务。
在我看来,它更容易使用,并且在创建一对新的View / ViewModel时可以减少添加的代码。
为此你需要一些东西:
首先是一个NavigableViewModel抽象类,其中包含一些以两种方式处理导航的方法。您的所有viewModel都将继承此类:
<强> NavigableViewModel.cs 强>
public abstract class NavigableViewModel : ViewModelBase
{
public abstract void OnNavigatedTo(object parameter = null);
public abstract void OnNavigatingTo(object parameter = null);
}
包含导航发生的框架的MainWindow,只需考虑使用 NavigationUIVisibility =&#34; Hidden&#34; 隐藏默认导航控件:
<强> MainWindow.xaml 强>
<Window x:Class="YourProject.Views.MainWindow"
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:local="clr-namespace:SS3DViewModelFirstMvvmLightProject"
mc:Ignorable="d"
DataContext="{Binding Main, Source={StaticResource Locator}}"
Title="MainWindow" Height="350" Width="525">
<-- Just remeber to replace x:Class="YourProject.Views.MainWindow" with your actual project path-->
<Frame x:Name="Frame" NavigationUIVisibility="Hidden">
</Frame>
</Window>
还有一些代码可以处理ViewModels的更改(让我们通知每个页面的viewModel):
<强> MainWindow.xaml.cs 强>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
((MainViewModel)this.DataContext).ShowFirstView(); // we need to have our view loaded to start navigating
Frame.LoadCompleted += (s, e) => UpdateFrameDataContext();
Frame.DataContextChanged += (s, e) => UpdateFrameDataContext();
}
private void UpdateFrameDataContext()
{
Page view = (Page)Frame.Content;
if (view != null)
{
view.DataContext = Frame.DataContext;
}
}
}
在你的MainViewModel中,这个小方法导航到你的第一个ViewModel(这里是LoginViewModel):
<强> MainViewModel.cs 强>
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
}
public void ShowFirstView()
{
ServiceLocator.Current.GetInstance<ViewModelFirstNavigationService>().NavigateTo<LoginViewModel>();
//To navigate wherever you want you just need to call this method, replacing LoginViewModel with YourViewModel
}
}
要进行此ServiceLocator调用,我们需要向ViewModelLocator添加一些内容:
<强> ViewModelLocator.cs 强>
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
ViewModelFirstNavigationService navService = new ViewModelFirstNavigationService(Main);
SimpleIoc.Default.Register<LoginViewModel>();
navService.AddNavigableElement(SimpleIoc.Default.GetInstance<LoginViewModel>);
// so whenever you want to add a new navigabel View Model just add these lines here
// SimpleIoc.Default.Register<YourViewModel>();
// navService.AddNavigableElement(SimpleIoc.Default.GetInstance<YourViewModel>);
SimpleIoc.Default.Register<ViewModelFirstNavigationService>(() => navService);
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
}
}
现在你已经掌握了所有的东西,我们可以添加系统的核心,导航服务(这是一个棘手的部分):
<强> ViewModelFirstNavigationService 强>
public class ViewModelFirstNavigationService
{
private Dictionary<Type, Uri> _registeredViews;
private Dictionary<Type, Func<NavigableViewModel>> _registeredViewModels;
private List<string> _allXamlPages;
private MainViewModel _mainContainerViewModel;
public NavigableViewModel CurrentViewModel;
public ViewModelFirstNavigationService(MainViewModel mainContainerViewModel)
{
_mainContainerViewModel = mainContainerViewModel;
_registeredViews = new Dictionary<Type, Uri>();
_registeredViewModels = new Dictionary<Type, Func<NavigableViewModel>>();
_allXamlPages = GetAllXamlPages();
}
private List<string> GetAllXamlPages()
{
// this part is a bit tricky. We use it to find all xaml pages in the current project.
// so you need to be sure that all your pages you want to use with your viewmodles need to end with page.xaml
// Example : LoginPage.xaml will work fine. Parameters.xaml won't.
System.Reflection.Assembly viewModelFirstProjectAssembly;
viewModelFirstProjectAssembly = System.Reflection.Assembly.GetExecutingAssembly();
var stream = viewModelFirstProjectAssembly.GetManifestResourceStream(viewModelFirstProjectAssembly.GetName().Name + ".g.resources");
var resourceReader = new ResourceReader(stream);
List<string> pages = new List<string>();
foreach (DictionaryEntry resource in resourceReader)
{
Console.WriteLine(resource.Key);
string s = resource.Key.ToString();
if (s.Contains("page.baml"))
{
pages.Add(s.Remove(s.IndexOf(".baml")));
}
}
return pages;
}
private Type ResolveViewModelTypeFromSingletonGetterFunc<T>(Func<T> viewModelSingletonGetterFunc)
{
MethodInfo methodInfo = viewModelSingletonGetterFunc.Method;
return methodInfo.ReturnParameter.ParameterType;
}
private Uri ResolvePageUriFromViewModelType(Type viewModelType)
{
string pageName = String.Empty;
int index = viewModelType.Name.IndexOf("ViewModel");
pageName = viewModelType.Name.Remove(index);
string pagePath = String.Format("{0}.xaml", _allXamlPages.Where(page => page.Contains(pageName.ToLower())).FirstOrDefault());
string cleanedPath = pagePath.Remove(0, "views/".Length); //obviously for this to work you need to have your views in a Views folder at the root of the project. But you are alowed yo reat sub folders in it
return new Uri(cleanedPath, UriKind.Relative);
}
public void AddNavigableElement(Func<NavigableViewModel> viewModelSingletonGetter)
{
//Where the magic happens !
//If your are wondering why a Func, it's because we want our viewmodels to be instantiated only when we need them via IOC.
//First we ge the type of our viewmodel to register for the Func.
Type vmType = ResolveViewModelTypeFromSingletonGetterFunc(viewModelSingletonGetter);
Uri uriPage = ResolvePageUriFromViewModelType(vmType);
_registeredViews.Add(vmType, uriPage);
_registeredViewModels.Add(vmType, viewModelSingletonGetter);
}
public void NavigateTo<GenericNavigableViewModelType>(object parameter = null)
{
Type key = typeof(GenericNavigableViewModelType);
NavigateTo(key, parameter);
}
public void NavigateTo(Type key, object parameter = null)
{
CurrentViewModel?.OnNavigatingTo(parameter);
CurrentViewModel = _registeredViewModels[key].Invoke();
Uri uri = _registeredViews[key];
((MainWindow)Application.Current.MainWindow).Frame.Source = uri;
((MainWindow)Application.Current.MainWindow).Frame.DataContext = CurrentViewModel;
CurrentViewModel.OnNavigatedTo(parameter);
}
}
现在你一切正常!华友世纪!让我们用我们的示例LoginViewModel(只包含黑色方块中的美丽helloworld)来演示:
<强> LoginPage.xaml 强>
<Page x:Class="YourProject.Views.LoginPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SS3DViewModelFirstMvvmLightProject.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="LoginPage">
<Grid Background="Gray">
<Label Content="{Binding HelloWorld}" Foreground="White" Background="Black" Width="150" Height="150"></Label>
</Grid>
</Page>
它的viewmodel:
<强> LoginViewModel.cs 强>
public class LoginViewModel : NavigableViewModel
{
private string _helloWorld;
public string HelloWorld
{
get
{
return _helloWorld;
}
set
{
_helloWorld = value;
RaisePropertyChanged(() => HelloWorld);
}
}
public LoginViewModel()
{
HelloWorld = "Hello World";
}
public override void OnNavigatedTo(object parameter = null)
{
// whatever you want to happen when you enter this page/viewModel
}
public override void OnNavigatingTo(object parameter = null)
{
// whatever you want to happen when you leave this page/viewmodel
}
}
我承认你需要一些代码才能开始。但是当一切正常时,你最终会得到一个非常容易使用的系统。
想要导航到某个viewModel? 只需使用myNavigationService.NavigateTo(someParam);
想要添加一对新的View / ViewModel? 只需在一些IOC容器中添加你的viewModel(在我的项目中,我使用自己的ioc,允许我随时卸载我的viewmodels并给出一些精美的导航堆栈)并给它你的导航服务。