【本系列需要具有一定开发基础】
我们在开发中经常遇到这样的场景:
1.呈现详细信息,且包含一些操作。如:查看原图,支持放大,缩小,多图。
2.执行特定的行为,且要有回执结果。如:选择联系人,选中某图,用户登录。
普遍的解决方案就是封装一个UserControl放到页面里,控制其显隐性。如果功能很少,那无所谓,可稍微复杂一点的,封装成单独的一个页面不是更好吗?还能节省当前页面的资源。此方法能够解决场景1,但是场景2需要回执结果,又该怎么办,总不能用全局变量吧。PageUserControl就是主要解决这些问题而封装,它将包含特定逻辑的页面封装成伪控件,使其可以单独调用,且可以反馈执行结果。
调用方法如图:
PageUserControl
PageUserControl是一个抽象的泛型类,作为封装控件的父类。原理:监听Frame的Navigated事件,利用缓存的两个页面变量,区别出是Forward还是Back,然后分别做传值和取值操作。废话不多说,直接上代码:
public abstract class PageUserControl<TPage> where TPage : Page { private const string _FrameNameInFramePage = "childrenFrame"; private Frame _frame; private object _frameContentWhenOpened; private TPage _page; /// <summary> /// 获取是否优先呈现在ChildrenFrame中。 /// </summary> public bool IsChildrenFrameFirst { get; protected set; } #region Methods protected void ShowPage() { this.OpenPickerPage(); } protected void ShowPage(object parameter) { this.OpenPickerPage(parameter); } //若需向调用者返回某值,则需要实现此方法。 protected virtual void CommitValue(TPage page) { } private void OpenPickerPage(object parameter = null) { if (null == _frame) { _frame = Window.Current.Content as Frame; if (null != _frame) { //这里是约定MainPage页中childrenFrame是子Frame。 //此方法并非绝对,仍有很多灵活的方法可以扩展,比如附加属性来指定谁是ChildrenFrame。 if (this.IsChildrenFrameFirst && this._frame.CurrentSourcePageType.Equals(typeof(Pages.MainPage))) { var framePage = (Pages.MainPage)_frame.Content; var frameInFramePage = framePage.FindName(_FrameNameInFramePage) as Frame; if (frameInFramePage != null) { this._frame = frameInFramePage; } } _frameContentWhenOpened = _frame.Content; _frame.Navigated += OnFrameNavigated; _frame.NavigationStopped += OnFrameNavigationStopped; _frame.NavigationFailed += OnFrameNavigationFailed; if (parameter == null) { _frame.Navigate(typeof(TPage)); } else { _frame.Navigate(typeof(TPage), parameter); } } } } private void ClosePickerPage() { // 注销事件 if (null != _frame) { _frame.Navigated -= OnFrameNavigated; _frame.NavigationStopped -= OnFrameNavigationStopped; _frame.NavigationFailed -= OnFrameNavigationFailed; _frame = null; _frameContentWhenOpened = null; } //若缓存页面有值,则尝试做提交处理。 if (null != this._page) { this.CommitValue(this._page); this._page = null; } } #endregion #region Events private void OnFrameNavigated(object sender, NavigationEventArgs e) { //若是Back则做关闭处理,若是Forward则把新页缓存。 if (e.Content == _frameContentWhenOpened) { ClosePickerPage(); } else if (null == this._page) { var page = e.Content as TPage; if (page != null) { this._page = page; } } } private void OnFrameNavigationFailed(object sender, NavigationFailedEventArgs e) { ClosePickerPage(); } private void OnFrameNavigationStopped(object sender, NavigationEventArgs e) { ClosePickerPage(); } #endregion }
以上的代码对Frame做了简单扩展,使其能支持在子Frame中呈现(主要是考虑到UWP的SpiltView),但是采用的固定约束,并不灵活。各位看官可以自行扩展,比如:使用附加属性来标识某一个Frame,这里就不实现了。
PageUserControl泛型类的使用参考如下:
public class ImageChooser : PageUserControl<ImageChooserPage> { public ImageChooser() { //优先在ChildrenFrame呈现。 base.IsChildrenFrameFirst = true; } public void Show() { base.ShowPage(); } protected override void CommitValue(ImageChooserPage page) { base.CommitValue(page); //若标识结果的页面属性值有效,则通过事件抛给调用者。 if (!string.IsNullOrWhiteSpace(page.Value)) { this.OnCompleted(page.Value); } } public event EventHandler<ChooseImageCompletedEventArgs> Completed; private void OnCompleted(string image) { var handler = this.Completed; if (handler != null) { handler(this, new ChooseImageCompletedEventArgs(image)); } } } public class ChooseImageCompletedEventArgs : EventArgs { public string Image { get; private set; } internal ChooseImageCompletedEventArgs(string image) { this.Image = image; } }
以上代码是针对需要返回值的场景,如果无须返回值则留空或者不重写CommitValue方法即可。
注意:调用页和控件页需要对NavigationCacheMode操作如下图,使其保证PageUserControl的页面变量唯一性,具体原因参考MSDN-NavigationCacheMode属性介绍。
public HomePage() { this.InitializeComponent(); this.NavigationCacheMode = NavigationCacheMode.Required; } protected override void OnNavigatedFrom(NavigationEventArgs e) { base.OnNavigatedFrom(e); if (e.NavigationMode == NavigationMode.Back) { this.NavigationCacheMode = NavigationCacheMode.Disabled; } }
如何正确应用在MVVM模式中?使用Behavior!
参考示例代码ListPicker。在本示例代码中封装了一个名为ListPicker的PageUserControl,它接受ItemsSources,ItemTemplate,SelectedItem参数,分别对应ListPickerPage中ListView的相同属性。ShowListPickerAction封装了对ListPicker的调用。
<Button Content="图片" Grid.Row="1"> <i:Interaction.Behaviors> <core:EventTriggerBehavior EventName="Click"> <behaviors:ShowListPickerAction ItemsSource="{Binding Images}" ItemTemplate="{ThemeResource ImageItemTemplate}" ItemsPickedCommand="{Binding ImagePickedCommand}" ItemsPickedInputConverter="{StaticResource ListPickerItemsPickedEventArgsConverter}"/> </core:EventTriggerBehavior> </i:Interaction.Behaviors> </Button>
详细实现过程,请参考示例:
https://github.com/rolerzhang/UWP-DevSkills
转载请注明出处。