UWP自行实现Frame.Navigate的页面缓存

我们都知道,在UWP里面,页面间的跳转必须通过Frame.Navigate进行,除了ContentDialog以外,Page是没法通过new去Show的。

当我们通过Frame.Navigate进行页面跳转时,我们很自然的希望,在按返回键退回之前的页面时,页面可以保留,无需再重新刷新。对于这一点,微软提供了解决方案,可以通过将Page的NavigationCacheMode属性设为Enabled或Required,就可以将页面的内容记录下来,用于返回时自动加载。(存在一点小瑕疵,比如对ListView的滚动位置记录不一定准确,有时需要通过另外的手段去记录ListView的滚动位置,在此不详述)

在一般情况下,微软提供的Cache已足以满足要求,但是在某些情况下,就不能完全达到目的了。

比如,我们的页面跳转是这样的,A->B1->C1->D1->B2->C2->D2,这里,第一次的B、C、D和第二次的B、C、D传入的参数不一样,即内容不一样,当我们按返回键时,会出现什么情况呢?

当我们返回到C2、B2时,内容正确,当我们返回到C1、B1时,发现其内容不是C1、B1,而是C2、B2。即微软的缓存并不能支持对一个页面多次进入的情况(也有可能是我不知道,但是我在网上也没搜到什么官方的解决办法)。

而我做的一个应用就有同一个页面可通过不同途径多次进入的情况,显然仅仅只是用官方提供的Cache,已经不能满足应用的正常使用了,在经过半个晚上的思考后,决定自己来实现这个页面跳转的缓存。

下面,将这个实现过程写出来,抛砖引玉,看看有没有更好的或者更官方的实现方法。


我们从两个方面来考虑这个页面缓存,一个是同一个页面需要有多个缓存,并且顺序必须保证一致;一个是页面的缓存要包含哪些数据。

针对第一个问题,我们可以将问题简化,只考虑页面跳转的New和Back两种模式,这样的话,对每个页面,创建一个Stack,即可完美的满足多个缓存和顺序要求;针对第二个问题,经过我的实践,定义了一个类来作为缓存的数据,类中包含两个object类型的对象,PageContent和PageParameter,Content代表整个页面的控件内容,Parameter代表Page中所有的类成员变量(非控件)。

类代码如下:

class PageStackContent
{
	public PageStackContent()
	{
	}
	public PageStackContent(object pageContent, object pageParameter = null)
	{
		this.PageContent = pageContent;
		this.PageParameter = pageParameter;
	}

	public object PageContent { get; set; }
	public object PageParameter { get; set; }
}

然后,我们定义一个static的Dictionary,用于缓存数据,代码如下:

private static readonly Dictionary<Type, Stack<PageStackContent>> DictPageContent 
	= new Dictionary<Type, Stack<PageStackContent>>();

同时,定义一个全局变量用于保存Frame,在App.xaml创建时,就将其赋值,代码如下:

public static Frame MainContentFrame { get; set; }

然后,写一个方法,用于在Page重写OnNavigatedTo时调用,代码如下:

public static void OnNavigatedTo(Type pageType, NavigationMode mode, Action newPageCallBack = null,
	Action<object> backPageCallBack = null)
{
	if (mode == NavigationMode.New || mode == NavigationMode.Refresh)
	{
		newPageCallBack?.Invoke();
	}
	else if (mode == NavigationMode.Back)
	{
		object pageParameter = null;
		if (DictPageContent.ContainsKey(pageType) && DictPageContent[pageType].Count != 0)
		{
			var temp = DictPageContent[pageType].Pop();
			MainContentFrame.Content = temp.PageContent;
			pageParameter = temp.PageParameter;
		}
		backPageCallBack?.Invoke(pageParameter);
	}
}

该段代码的主要作用,就是在后退到该页面时,将该页面的缓存从栈中Pop出来,Content直接赋值给Frame的Content,而Parameter传回页面的回调函数中去执行。

再写一个方法,在Page重写OnNavigatingFrom中调用,代码如下:

public static void OnNavigatingFrom(Type pageType, NavigatingCancelEventArgs e, object pageContent,
	ICloneable pageParameter = null, Action newPageCallBack = null, Action backPageCallBack = null)
{
	if (e.NavigationMode == NavigationMode.New)
	{
		if (!DictPageContent.ContainsKey(pageType))
		{
			DictPageContent.Add(pageType, new Stack<PageStackContent>());
		}
		DictPageContent[pageType].Push(new PageStackContent
			(pageContent, pageParameter?.Clone()));
		newPageCallBack?.Invoke();
	}
	else if (e.NavigationMode == NavigationMode.Back)
	{
		backPageCallBack?.Invoke();
	}
}

该段代码的主要作用,是在通过New的模式离开该页面时,将其Content和Parameter入栈。这里有一点值得特别注意的是,我传入的parameter不是object类型,而是实现了ICloneable接口的类型,这是因为在Page中定义的Parameter必须为static的,这样就不能将其直接入栈了,否则页面那边变了,栈里面的也会跟着变,必须将其复制后再入栈。

以上静态的方法,我都写在了一个NavigateHelper类中。

然后,就是在页面中的调用了,需要先做两个准备工作。首先,将Page的NavigationCacheMode设为Disabled,这是因为如果用Enabled或Required,Page会启用单实例模式,缓存到栈中的实例仍然会在页面跳转时发生变化;第二,将Page中所有的自定义类成员变量组合成一个Parameter类,实现ICloneable接口,并在Page中定义一个static的Parameter对象。UWP中似乎没有ICloneable接口,自定义即可,代码如下:

interface ICloneable
{
	object Clone();
}

当然,不定义ICloneable接口,而通过反射等方式去复制对象,应该也是可行的。

这里将Parameter定义为static的原因是,如果不定义成static,Page_Load的执行是早于OnNavigatedTo的,所以当使用了Page_Load时,会发生值不正确的问题。没有测试过重写OnNavigatingTo,或许可以。

然后,重写OnNavigatedTo和OnNavigatingFrom两个方法即可。

具体代码如下:

private static PageParameters _parameters = new PageParameters();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
	NavigateHelper.OnNavigatedTo(this.GetType(), e.NavigationMode, async () =>
	{
		if (e.Parameter is String)
		{
			_parameters.ID = e.Parameter.ToString();
			await LoadData();
		}
	}, o =>
	{
		if (o is PageParameters p)
		{
			_parameters = p;
		}
	});
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
	NavigateHelper.OnNavigatingFrom(this.GetType(), e, Frame.Content, _parameters);
}

这里的PageParameters根据页面的不同,可能每个页面需要单独定义一个。

经过上述的经过,就能实现一个页面的多次缓存了,如果一个页面不需多次进入,那么还是可以继续用官方提供的Cache来缓存。另外,传入的参数可能也可以再优化下。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值