在UWP开发中,利用汉堡菜单实现导航是常见的方法。汉堡菜单导航一般都需要新建一个Frame控件,并对其进行导航,但是在MvvmLight框架默认的NavigationService中,只能对根Frame进行导航,这就需要我们实现自己的NavigationService了。
MvvmLight源码简析
GalaSoft.MvvmLight.Views命名空间下的NavigationService继承了同命名空间下的INavigationService接口但是并没有实现。
namespace GalaSoft.MvvmLight.Views
{
public class NavigationService : INavigationService
{
public const string RootPageKey =
"-- ROOT --";
public const string UnknownPageKey =
"-- UNKNOWN --";
public NavigationService();
public string CurrentPageKey {
get; }
public void Configure(
string key, Type pageType);
public void GoBack();
public void NavigateTo(
string pageKey);
public virtual void NavigateTo(
string pageKey,
object parameter);
}
}
具体的功能实现是在GalaSoft.MvvmLight.Platform命名空间下的NavigationService中,根据平台的不同有不同的实现。我们要做的就是自己实现INavigationService并自己定义导航的Frame控件
实现自己的NavigationService
很奇怪,官方公布源码的那个网站上没有找到Win10平台的Platform源码,所以我就把Win8.1的NavigationService实现复制下来,经测试能正常用。
在public virtual void NavigateTo(string pageKey, object parameter)方法下,我们可以看到这样一行代码: var frame = ((Frame)Window.Current.Content);
这个语句就定义了用来导航的Frame控件(不止这个方法中有,另外2个方法中也有类似的语句)。
我们把这几个地方的frame变量值设置成自己的Frame控件就可以了。
我的修改方法:
我的Frame控件是放在MainPage中的,主要是为了实现汉堡菜单导航,我想大部分有这种需求的人都是为了实现类似的导航吧。
首先在MainPage.xaml.cs里面加入一个MainPage类型的静态Public变量然后再构造函数中给变量赋值为this
还要给自己的Frame控件写一个属性来获取它
public static MainPage MPage;
public Frame MyFrame // 3.
{
get
{
return ContentFrame;
}
}
public MainPage()
{
InitializeComponent();
SystemNavigationManager.GetForCurrentView().BackRequested += SystemNavigationManagerBackRequested;
Loaded += (s, e) =>
{
Vm.RunClock();
};
MPage =
this;
}
然后就可以在自己的NavigationService里面使用自己的Frame了。
//var frame = ((Frame)Window
.Current.Content)
var frame = MainPage
.MPage.MyFrame 这样我们就把NavigationService的导航Frame设置成了MainPage里面的ContentFrame控件。
不过我这个方法还不是最优的,因为这样写就把MainPage和NavigationService直接联系起来了,增加了耦合性。我看到WPF中可以通过下面这个方法来获取指定名字的Frame控件,不过在UWP里面没有这个方法。 var frame = GetDescendantFromName(Application.Current.MainWindow, "MainFrame") as Frame;
如果谁有更好的办法的话麻烦在评论里面告诉我,万分感谢。
效果图
MyNavigationService代码
using GalaSoft.MvvmLight.Views;
using Mvvm_HutHelper.View;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Mvvm_HutHelper.Model
{
class MyNavigationService : INavigationService
{
public const string RootPageKey =
"-- ROOT --";
public const string UnknownPageKey =
"-- UNKNOWN --";
private readonly Dictionary<
string, Type> _pagesByKey =
new Dictionary<
string, Type>();
public string CurrentPageKey
{
get
{
lock (_pagesByKey)
{
var frame = MainPage.MPage.MyFrame;
if (frame.BackStackDepth ==
0)
{
return RootPageKey;
}
if (frame.Content ==
null)
{
return UnknownPageKey;
}
var currentType = frame.Content.GetType();
if (_pagesByKey.All(p => p.Value != currentType))
{
return UnknownPageKey;
}
var item = _pagesByKey.FirstOrDefault(
i => i.Value == currentType);
return item.Key;
}
}
}
public void GoBack()
{
var frame = MainPage.MPage.MyFrame;
if (frame.CanGoBack)
{
frame.GoBack();
}
}
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}. Did you forget to call NavigationService.Configure?",
pageKey),
"pageKey");
}
var frame = MainPage.MPage.MyFrame;
frame.Navigate(_pagesByKey[pageKey], parameter);
}
}
public void Configure(
string key, Type pageType)
{
lock (_pagesByKey)
{
if (_pagesByKey.ContainsKey(key))
{
throw new ArgumentException(
"This key is already used: " + key);
}
if (_pagesByKey.Any(p => p.Value == pageType))
{
throw new ArgumentException(
"This type is already configured with key " + _pagesByKey.First(p => p.Value == pageType).Key);
}
_pagesByKey.Add(
key,
pageType);
}
}
}
}
参考资料
MVVM Light 5.0: How to use the Navigation service
MvvmLight SourceCode