WPF自定义导航

1.导航接口

/// <summary>
    /// 自定义导航服务接口
    /// </summary>
    public interface ICustomNavigationService
    {
        /// <summary>
        /// 记录不同页面容器的当前页面键集合
        /// </summary>
        List<(string pageHostName, ApplicationPage page)> CurrentPageKeyList
        {
            get;
        }

        /// <summary>
        /// 回退到上一页
        /// </summary>
        void GoBack(string pageHostName);

        /// <summary>
        /// 导航到指定键页面
        /// </summary>
        /// <param name="pageHostName">容器名称</param>
        /// <param name="pageKey">页面ApplicationPage类型键</param>
        void NavigateTo(string pageHostName, ApplicationPage pageKey);

        /// <summary>
        /// 导航到指定键页面并且传递参数
        /// </summary>
        /// <param name="pageHostName">容器名称</param>
        /// <param name="pageKey">页面ApplicationPage类型键</param>
        /// <param name="parameter">页面间传递的参数:一般情况下,请直接在IOC容器中获取ViewModel并且传递值</param>
        void NavigateTo(string pageHostName, ApplicationPage pageKey, object parameter);
    }

2.实现

public class NavigationService : ICustomNavigationService
    {
        /// <summary>
        /// 锁对象
        /// </summary>
        private object _navLock = new object();

        /// <summary>
        /// 缓存页面键值对
        /// </summary>
        private Dictionary<ApplicationPage, BasePage> _navigationDic;

        /// <summary>
        /// 记录历史页面字典栈
        /// </summary>
        private Dictionary<string, Stack<ApplicationPage>> _historyStack;

        /// <summary>
        /// 记录不同页面容器的当前页面键集合
        /// </summary>
        public List<(string pageHostName, ApplicationPage page)> CurrentPageKeyList { get; set; }

        /// <summary>
        /// 构造器
        /// </summary>
        public NavigationService()
        {
            _navigationDic = new Dictionary<ApplicationPage, BasePage>();
            _historyStack=new Dictionary<string, Stack<ApplicationPage>>();
            CurrentPageKeyList = new List<(string, ApplicationPage)>();
        }

        /// <summary>
        /// 回退到上一页
        /// </summary>
        public void GoBack(string pageHostName)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 导航到指定键页面
        /// </summary>
        /// <param name="pageHostName">容器名称</param>
        /// <param name="pageKey">页面ApplicationPage类型键</param>
        public void NavigateTo(string pageHostName, ApplicationPage pageKey)
        {
            NavigateTo(pageHostName, pageKey, null);
        }

        /// <summary>
        /// 导航到指定键页面并且传递参数
        /// </summary>
        /// <param name="pageHostName">容器名称</param>
        /// <param name="pageKey">页面ApplicationPage类型键</param>
        /// <param name="parameter">页面间传递的参数:一般情况下,请直接在IOC容器中获取ViewModel并且传递值</param>
        public void NavigateTo(string pageHostName, ApplicationPage pageKey, object parameter)
        {
            lock (_navLock)
            {
                //导航页末调用Configure方法配置
                if (!_navigationDic.ContainsKey(pageKey))
                {
                    throw new ArgumentException("This key is not configured!");
                }

                //找到页面容器
                List<PageHost> pageHosts = new List<PageHost>();
                Application.Current.MainWindow.FindAllChilds<PageHost>(ref pageHosts);

                var pageHost = pageHosts.FirstOrDefault(t => t.Name == pageHostName);

                if (pageHost == null)
                {
                    throw new Exception("PageHost is not Configured!");
                }

                //当前页列表未记录当前键,初始化当前键与历史列表
                if (!CurrentPageKeyList.Any(t => t.pageHostName == pageHostName))
                {
                    CurrentPageKeyList.Add((pageHostName, pageKey));

                    _historyStack.Add(pageHostName, new Stack<ApplicationPage>());
                }
                else
                {
                    //将当前键加入历史列表栈
                    _historyStack[pageHostName].Push(CurrentPageKeyList.First(t => t.pageHostName == pageHostName).page);
                    CurrentPageKeyList.Remove(CurrentPageKeyList.First(t => t.pageHostName == pageHostName));
                    CurrentPageKeyList.Add((pageHostName, pageKey));
                }

                //获取页面框架
                var newPageFrame = pageHost.newPage;
                var oldPageFrame = pageHost.oldPage;

                //获取xaml传入的当前viewmodel
                var currentPageViewModel = pageHost.CurrentPageViewModel;

                //在页未改变情况下只更新ViewModel
                if (newPageFrame.Content is BasePage page && page == _navigationDic[pageKey])
                     page.ViewModelObject= currentPageViewModel;

                var oldPageContent = newPageFrame.Content;
                newPageFrame.Content = null;
                oldPageFrame.Content = oldPageContent;

                //动画效果
                if (oldPageContent is BasePage oldPage)
                {
                    oldPage.ShouldAnimateOut = true;

                    Task.Delay((int)(oldPage.SlideSeconds * 1000)).ContinueWith((t) =>
                    {
                        Application.Current.Dispatcher.Invoke(() => oldPageFrame.Content = null);
                    });
                }

                //切换页面
                BasePage showPage = _navigationDic[pageKey];
                showPage.ViewModelObject = currentPageViewModel;
                showPage.ExtraData = parameter;
                newPageFrame.Content = showPage;
            }
        }

        /// <summary>
        /// 配置页面键值对
        /// </summary>
        /// <param name="pageKey">页面ApplicationPage类型键</param>
        /// <param name="basePage">页面</param>
        /// <exception cref="ArgumentException">键或者值被引用过抛出异常</exception>
        public void Configure(ApplicationPage pageKey,BasePage basePage)
        {
            lock (_navLock)
            {
                if (_navigationDic.ContainsKey(pageKey))
                {
                    throw new ArgumentException("This key is already used:"+pageKey);
                }

                if (_navigationDic.Values.Any(t => t == basePage))
                {
                    throw new ArgumentException("This basepage is already configured with key "+ _navigationDic.First(t => t.Value == basePage).Key);
                }

                _navigationDic.Add(pageKey,basePage);
            }
        }
    }

3.使用mvvmlight自定义viewmodel

public class BaseViewModel:ViewModelBase
    {
        protected object mPropertyValueCheckLock=new object();

        protected async Task RunCommandAsync(Expression<Func<bool>> flagUpdating,Func<Task> action)
        {
            lock (mPropertyValueCheckLock)
            {
                if (flagUpdating.GetPropertyValue())
                {
                    return;
                }

                flagUpdating.SetPropertyValue(true);
            }

            try
            {
                await action();
            }
            finally
            {
                flagUpdating.SetPropertyValue(false);
            }
        }

        protected async Task<T> RunCommandAsync<T>(Expression<Func<bool>> flagUpdating, Func<Task<T>> action,T defaultValue)
        {
            lock (mPropertyValueCheckLock)
            {
                if (flagUpdating.GetPropertyValue())
                {
                    return defaultValue;
                }

                flagUpdating.SetPropertyValue(true);
            }

            try
            {
                return await action();
            }
            finally
            {
                flagUpdating.SetPropertyValue(false);
            }
        }
    }
public static class DependencyObjectHelpers
    {
        public static void FindAllChilds(this DependencyObject d, ref List<DependencyObject> dependencyObjects)
        {
            var count = VisualTreeHelper.GetChildrenCount(d);

            for (int i = 0; i < count; i++)
            {
                DependencyObject dependencyObject = VisualTreeHelper.GetChild(d, i);
                dependencyObjects.Add(dependencyObject);

                FindAllChilds(dependencyObject, ref dependencyObjects);
            }
        }

        public static void FindAllChilds<T>(this DependencyObject d, ref List<T> dependencyObjects) where T:class
        {
            var count = VisualTreeHelper.GetChildrenCount(d);

            for (int i = 0; i < count; i++)
            {
                DependencyObject dependencyObject = VisualTreeHelper.GetChild(d, i);

                if (dependencyObject.GetType() == typeof(T))
                {
                    dependencyObjects.Add(dependencyObject as T);
                }

                FindAllChilds(dependencyObject, ref dependencyObjects);
            }
        }
    }
public enum ApplicationPage
    {
        None=0
    }
`public partial class PageHost : UserControl
    {
        public ApplicationPage CurrentPage
        {
            get { return (ApplicationPage)GetValue(CurrentPageProperty); }
            set { SetValue(CurrentPageProperty, value); }
        }

        public static readonly DependencyProperty CurrentPageProperty =
            DependencyProperty.Register("CurrentPage", typeof(ApplicationPage), typeof(PageHost), new PropertyMetadata(default(ApplicationPage),null, CurrentPagePropertyChanged));

        public BaseViewModel CurrentPageViewModel
        {
            get { return (BaseViewModel)GetValue(CurrentPageViewModelProperty); }
            set { SetValue(CurrentPageViewModelProperty, value); }
        }

        public static readonly DependencyProperty CurrentPageViewModelProperty =
            DependencyProperty.Register("CurrentPageViewModel", typeof(BaseViewModel), typeof(PageHost), new PropertyMetadata(0));

        public PageHost()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                newPage.Content = null;
        }

        private static object CurrentPagePropertyChanged(DependencyObject d, object value)
        {
            //ioc navigation

            return value;
        }
    }
```csharp
public class BasePage : UserControl
    {
        #region Private Member

        /// <summary>
        /// The View Model associated with this page
        /// </summary>
        private object mViewModel;

        #endregion

        #region Public Properties

        /// <summary>
        /// The animation the play when the page is first loaded
        /// </summary>
        public PageAnimation PageLoadAnimation { get; set; } = PageAnimation.SlideAndFadeInFromRight;

        /// <summary>
        /// The animation the play when the page is unloaded
        /// </summary>
        public PageAnimation PageUnloadAnimation { get; set; } = PageAnimation.SlideAndFadeOutToLeft;

        /// <summary>
        /// The time any slide animation takes to complete
        /// </summary>
        public float SlideSeconds { get; set; } = 0.4f;

        /// <summary>
        /// A flag to indicate if this page should animate out on load.
        /// Useful for when we are moving the page to another frame
        /// </summary>
        public bool ShouldAnimateOut { get; set; }

        /// <summary>
        /// The View Model associated with this page
        /// </summary>
        public object ViewModelObject
        {
            get => mViewModel;
            set
            {
                // If nothing has changed, return
                if (mViewModel == value)
                    return;

                // Update the value
                mViewModel = value;

                // Fire the view model changed method
                OnViewModelChanged();

                // Set the data context for this page
                DataContext = mViewModel;
            }
        }

        public object ExtraData { get; set; }

        #endregion

        #region Constructor

        /// <summary>
        /// Default constructor
        /// </summary>
        public BasePage()
        {
            // Don't bother animating in design time
            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            // If we are animating in, hide to begin with
            if (PageLoadAnimation != PageAnimation.None)
                Visibility = Visibility.Collapsed;

            // Listen out for the page loading
            Loaded += BasePage_LoadedAsync;
        }

        #endregion

        #region Animation Load / Unload

        /// <summary>
        /// Once the page is loaded, perform any required animation
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void BasePage_LoadedAsync(object sender, System.Windows.RoutedEventArgs e)
        {
            // If we are setup to animate out on load
            if (ShouldAnimateOut)
                // Animate out the page
                await AnimateOutAsync();
            // Otherwise...
            else
                // Animate the page in
                await AnimateInAsync();
        }

        /// <summary>
        /// Animates the page in
        /// </summary>
        /// <returns></returns>
        public async Task AnimateInAsync()
        {
            // Make sure we have something to do
            if (PageLoadAnimation == PageAnimation.None)
                return;

            switch (PageLoadAnimation)
            {
                case PageAnimation.SlideAndFadeInFromRight:

                    // Start the animation
                    await this.SlideAndFadeInAsync(AnimationSlideInDirection.Right, false, SlideSeconds, size: (int)Application.Current.MainWindow.Width);

                    break;
            }
        }

        /// <summary>
        /// Animates the page out
        /// </summary>
        /// <returns></returns>
        public async Task AnimateOutAsync()
        {
            // Make sure we have something to do
            if (PageUnloadAnimation == PageAnimation.None)
                return;

            switch (PageUnloadAnimation)
            {
                case PageAnimation.SlideAndFadeOutToLeft:

                    // Start the animation
                    await this.SlideAndFadeOutAsync(AnimationSlideInDirection.Left, SlideSeconds);

                    break;
            }
        }

        #endregion

        /// <summary>
        /// Fired when the view model changes
        /// </summary>
        protected virtual void OnViewModelChanged()
        {

        }
    }

    /// <summary>
    /// A base page with added ViewModel support
    /// </summary>
    public class BasePage<VM> : BasePage
        where VM : BaseViewModel, new()
    {
        #region Public Properties

        /// <summary>
        /// The view model associated with this page
        /// </summary>
        public VM ViewModel
        {
            get => (VM)ViewModelObject;
            set => ViewModelObject = value;
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Default constructor
        /// </summary>
        public BasePage() : base()
        {
            // If in design time mode...
            if (DesignerProperties.GetIsInDesignMode(this))
                // Just use a new instance of the VM
                ViewModel = new VM();
            else
                // Create a default view model
                ViewModel = SimpleIoc.Default.GetInstance<VM>() ?? new VM();
        }

        /// <summary>
        /// Constructor with specific view model
        /// </summary>
        /// <param name="specificViewModel">The specific view model to use, if any</param>
        public BasePage(VM specificViewModel = null) : base()
        {
            // Set specific view model
            if (specificViewModel != null)
                ViewModel = specificViewModel;
            else
            {
                // If in design time mode...
                if (DesignerProperties.GetIsInDesignMode(this))
                    // Just use a new instance of the VM
                    ViewModel = new VM();
                else
                {
                    // Create a default view model
                    ViewModel = SimpleIoc.Default.GetInstance<VM>() ?? new VM();
                }
            }
        }

        #endregion
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值