首页 > 代码库 > 【WP8】扩展CM的WindowManager

【WP8】扩展CM的WindowManager

关于WindowManager,一直都很想写一篇博客分享一下,一直在忙别的,今天抽空把这个分享一下

在弹窗在移动开发是一个很常见的交互,很多时候我们都需要进行弹窗,比如我们需要询问用户的一些操作,提供更丰富的交互,又比如我们需要弹出一个框提示用户给我们好评

WP系统提供关于对话框的操作只有一个MessageBox,MessageBox样式简单,并且无法扩展,比如我们需要自定义按钮的文字都不可以,极大限制了我们的发挥,如果我们需要弹窗,还有Popup控件可以用,比如Coding4fun就对Popup进行了扩展,提供了更丰富对话框样式

用CM(Caluburn.Micro)也有一段时间了,CM不仅提供了MVVM的支持,还提供了IoC依赖注入容器,还提供了WindowManager的支持(这是一个非常值得深入研究的开源框架)可以在页面的ViewModel中直接通过控件对应的ViewModel构造控件进行显示,下面我们对控件进行

 

一、页面框架层次

首先说明一下页面框架的层次关系:

  Frame:最底层

  Page:页面在Frame的上层

  Popup:在页面的上层,弹出的控件会在覆盖在页面的上面

  ApplicationBar:应用程序栏不会被Popup覆盖,所以自定义的弹窗是不能把ApplicationBar给遮住的

  键盘和MessageBox:键盘在系统弹窗会把上面所有的元素都覆盖,如果ApplicationBar存在的时候,键盘会在ApplicationBar上面

 

二、WindowManager职责

  WindowManager通过封装Popup,给为外部提供很方便的弹窗操作,下面列举一下WindowManager关于弹窗所做的操作(基于CM)

    1、新建一个Host:当我们显示弹窗之前,我们需要一个容器(ContentControl)用于内部管理

    2、通过ViewModel找到对应的View(控件),并且将ViewModel绑定到View上(CM)

    3、Host.Content = view

    4、ApplySetting:通过反射设置信息,CM默认的WindowManager提供了对Host的设置

    5、判断ViewModel是否实现IActivate和IDeactivate事件,如果有,则绑定事件(在弹窗打开和关闭的时候触发)

    6、接管BackKey:比如用户按返回键是否需要关闭弹窗

    7、接管ApplicationBar:由于Popup在ApplicationBar下层,无法遮住ApplicationBar,一般的做法是隐藏掉ApplicationBar,或者是禁用掉ApplicationBar上的按钮和MenuItem,在弹窗关闭后,恢复ApplicationBar原有的状态

    8、接管Page.OrientationChanged事件:一般我们不处理该事件

    9、Popup.IsOpen = true;  打开弹窗(打开之前一般设置其Opacity为0,防止闪一下)

    10、开启弹窗动画(CM默认没有提供动画的支持,后面我们自定义的时候加入该支持)

 

定义WindowManager主要就是上面一些操作,这里不分析CM中的WindowManager的源码了,有兴趣的可以去看,但是CM提供的WindowManager还是不够用,我们需要可以更多自定义的一些配置,比如:

  1、弹窗的时候是否隐藏ApplicationBar

  2、弹窗的时候点击其他区域是否关闭弹窗

  3、弹窗是否需要用一个遮罩遮住原来页面

  4、弹窗是否支持动画注入(支持扩展)

  5、弹窗是否可以被返回键关闭

三、定义与实现

  原本是想直接继承WindowManager来做的,但是发现WindowManager提供的属性和方法太少了,很难扩展,所以下面通过自定义的方式扩展

  定义接口

 

    public interface ICustomWindowManager : IWindowManager    {        void ShowDialog(object rootModel, bool isTapClose = true, double maskOpacity = 0.8, IWindowAnimator windowAnimator = null, bool isHideApplicationBar = true, bool canClose = true);    }

 

  实现:

    动画接口

    /// <summary>    /// WindowManager动画的接口(用于扩展动画)    /// </summary>    public interface IWindowAnimator    {        void Enter(FrameworkElement viewContainer, FrameworkElement host);        void Exit(FrameworkElement viewContainer, FrameworkElement host, Action complete);    }

    WindowAnimator动画默认实现

    /// <summary>    /// 默认弹窗动画的实现(渐入和渐出)    /// </summary>    public class DefaultWindowAnimator : IWindowAnimator    {        public void Enter(FrameworkElement viewContainer, FrameworkElement host)        {            var storyboard = new Storyboard();            var doubleAnimation = new DoubleAnimation            {                Duration = new Duration(TimeSpan.FromSeconds(0.1)),                From = 0,                To = 1            };            Storyboard.SetTarget(doubleAnimation, host);            Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("Opacity", new object[0]));            storyboard.Children.Add(doubleAnimation);            storyboard.Begin();        }        public void Exit(FrameworkElement viewContainer, FrameworkElement host, Action complete)        {            var storyboard = new Storyboard();            var doubleAnimation = new DoubleAnimation            {                Duration = new Duration(TimeSpan.FromSeconds(0.1)),                From = 1,                To = 0            };            Storyboard.SetTarget(doubleAnimation, host);            Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("Opacity", new object[0]));            storyboard.Children.Add(doubleAnimation);            storyboard.Completed += (sender, e) => complete.Invoke();            storyboard.Begin();        }    }

    下面提供其他动画的实现

    /// <summary>    /// 翻转动画    /// </summary>    public class FlipWindowAnimator : IWindowAnimator    {        private const double DURATION_SECONDS = 0.15;        public void Enter(FrameworkElement viewContainer, FrameworkElement host)        {            viewContainer.Projection = new PlaneProjection();            var storyboard = new Storyboard();            var doubleAnimation = new DoubleAnimation            {                Duration = new Duration(TimeSpan.FromSeconds(DURATION_SECONDS)),                From = 90,                To = 0            };            Storyboard.SetTarget(doubleAnimation, viewContainer.Projection);            Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("RotationX", new object[0]));            storyboard.Children.Add(doubleAnimation);            doubleAnimation = new DoubleAnimation            {                Duration = new Duration(TimeSpan.FromSeconds(DURATION_SECONDS)),                From = 0,                To = 1            };            Storyboard.SetTarget(doubleAnimation, host);            Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("Opacity", new object[0]));            storyboard.Children.Add(doubleAnimation);            storyboard.Begin();        }        public void Exit(FrameworkElement viewContainer, FrameworkElement host, Action complete)        {            var storyboard = new Storyboard();            var doubleAnimation = new DoubleAnimation            {                Duration = new Duration(TimeSpan.FromSeconds(DURATION_SECONDS)),                From = 0,                To = 90            };            Storyboard.SetTarget(doubleAnimation, viewContainer.Projection);            Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("RotationX", new object[0]));            storyboard.Children.Add(doubleAnimation);            doubleAnimation = new DoubleAnimation            {                Duration = new Duration(TimeSpan.FromSeconds(DURATION_SECONDS)),                From = 1,                To = 0            };            Storyboard.SetTarget(doubleAnimation, host);            Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("Opacity", new object[0]));            storyboard.Children.Add(doubleAnimation);            storyboard.Completed += (sender, e) => complete.Invoke();            storyboard.Begin();        }    }
翻转动画:FlipWindowAnimator

    滑动动画参考自WPToolkit

    /// <summary>    /// 上下滑动动画    /// </summary>    public class SlideUpWindowAnimator : IWindowAnimator    {        /// <summary>        /// 向上显示动画        /// </summary>        private const string SLIDE_UP_STORYBOARD = @"        <Storyboard  xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.RenderTransform).(CompositeTransform.TranslateY)"">                <EasingDoubleKeyFrame KeyTime=""0"" Value=""150""/>                <EasingDoubleKeyFrame KeyTime=""0:0:0.35"" Value=""0"">                    <EasingDoubleKeyFrame.EasingFunction>                        <ExponentialEase EasingMode=""EaseOut"" Exponent=""6""/>                    </EasingDoubleKeyFrame.EasingFunction>                </EasingDoubleKeyFrame>            </DoubleAnimationUsingKeyFrames>            <DoubleAnimation Storyboard.TargetProperty=""(UIElement.Opacity)"" From=""0"" To=""1"" Duration=""0:0:0.350"">                <DoubleAnimation.EasingFunction>                    <ExponentialEase EasingMode=""EaseOut"" Exponent=""6""/>                </DoubleAnimation.EasingFunction>            </DoubleAnimation>        </Storyboard>";        /// <summary>        /// 向下消失动画        /// </summary>        private const string SLIDE_DOWN_STORYBOARD = @"        <Storyboard  xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.RenderTransform).(CompositeTransform.TranslateY)"">                <EasingDoubleKeyFrame KeyTime=""0"" Value=""0""/>                <EasingDoubleKeyFrame KeyTime=""0:0:0.25"" Value=""150"">                    <EasingDoubleKeyFrame.EasingFunction>                        <ExponentialEase EasingMode=""EaseIn"" Exponent=""6""/>                    </EasingDoubleKeyFrame.EasingFunction>                </EasingDoubleKeyFrame>            </DoubleAnimationUsingKeyFrames>            <DoubleAnimation Storyboard.TargetProperty=""(UIElement.Opacity)"" From=""1"" To=""0"" Duration=""0:0:0.25"">                <DoubleAnimation.EasingFunction>                    <ExponentialEase EasingMode=""EaseIn"" Exponent=""6""/>                </DoubleAnimation.EasingFunction>            </DoubleAnimation>        </Storyboard>";        public void Enter(FrameworkElement viewContainer, FrameworkElement host)        {            var storyboard = XamlReader.Load(SLIDE_UP_STORYBOARD) as Storyboard;            if (storyboard != null)            {                foreach (var t in storyboard.Children)                {                    Storyboard.SetTarget(t, host);                }                storyboard.Begin();            }        }        public void Exit(FrameworkElement viewContainer, FrameworkElement host, Action complete)        {            var storyboard = XamlReader.Load(SLIDE_DOWN_STORYBOARD) as Storyboard;            if (storyboard != null)            {                foreach (var t in storyboard.Children)                {                    Storyboard.SetTarget(t, host);                }                storyboard.Completed += (sender, e) => complete.Invoke();                storyboard.Begin();            }        }    }
上下滑动动画:SlideUpWindowAnimator

 

  WindowManager实现:大部分参考自原来的WindowManager实现

    /// <summary>    /// 自定义窗口管理器(基于Caliburn.Micro)    /// </summary>    public class CustomWindowManager : ICustomWindowManager    {        public static Func<Uri, bool> IsSystemDialogNavigation = uri => uri != null && uri.ToString().StartsWith("/Microsoft.Phone.Controls.Toolkit");        public virtual void ShowDialog(object rootModel, object context = null, IDictionary<string, object> settings = null)        {            var navigationSvc = IoC.Get<INavigationService>();            var host = new DialogHost(navigationSvc);            var view = ViewLocator.LocateForModel(rootModel, host, context);            host.Content = view as FrameworkElement;            host.SetValue(View.IsGeneratedProperty, true);            ViewModelBinder.Bind(rootModel, host, null);            host.SetActionTarget(rootModel);            ApplySettings(host, settings);            var activatable = rootModel as IActivate;            if (activatable != null)            {                activatable.Activate();            }            var deactivator = rootModel as IDeactivate;            if (deactivator != null)            {                host.Closed += delegate { deactivator.Deactivate(true); };            }            host.Open();        }        public virtual void ShowPopup(object rootModel, object context = null, IDictionary<string, object> settings = null)        {            var popup = CreatePopup(rootModel, settings);            var view = ViewLocator.LocateForModel(rootModel, popup, context);            popup.Child = view;            popup.SetValue(View.IsGeneratedProperty, true);            ViewModelBinder.Bind(rootModel, popup, null);            var activatable = rootModel as IActivate;            if (activatable != null)            {                activatable.Activate();            }            var deactivator = rootModel as IDeactivate;            if (deactivator != null)            {                popup.Closed += delegate { deactivator.Deactivate(true); };            }            popup.IsOpen = true;        }        public void ShowDialog(object rootModel, bool isTapClose = true, double maskOpacity = 0.5,            IWindowAnimator windowAnimator = null, bool isHideApplicationBar = true, bool canClose = true)        {            var navigationSvc = IoC.Get<INavigationService>();            var host = new DialogHost(navigationSvc, isTapClose, maskOpacity, isHideApplicationBar, windowAnimator, canClose);            var view = ViewLocator.LocateForModel(rootModel, host, null);            host.Content = view as FrameworkElement;            host.SetValue(View.IsGeneratedProperty, true);            ViewModelBinder.Bind(rootModel, host, null);            host.SetActionTarget(rootModel);            var activatable = rootModel as IActivate;            if (activatable != null)            {                activatable.Activate();            }            var deactivator = rootModel as IDeactivate;            if (deactivator != null)            {                host.Closed += delegate { deactivator.Deactivate(true); };            }            host.Open();        }        protected virtual Popup CreatePopup(object rootModel, IDictionary<string, object> settings)        {            var popup = new Popup();            ApplySettings(popup, settings);            return popup;        }        private static void ApplySettings(object target, IEnumerable<KeyValuePair<string, object>> settings)        {            if (settings != null)            {                var type = target.GetType();                foreach (var pair in settings)                {                    var propertyInfo = type.GetProperty(pair.Key);                    if (propertyInfo != null)                        propertyInfo.SetValue(target, pair.Value, null);                }            }        }        [ContentProperty("Content")]        public class DialogHost : FrameworkElement        {            readonly INavigationService navigationSvc;            PhoneApplicationPage currentPage;            Popup hostPopup;            bool isOpen;            ContentControl viewContainer;            Border pageFreezingLayer;            Border maskingLayer;            private FrameworkElement host;            private readonly IWindowAnimator animator;            private readonly double maskOpacity;            private readonly bool isTapClose;            private readonly bool canClose;            private readonly bool isHideApplicationBar;            private readonly Dictionary<IApplicationBarIconButton, bool> appBarButtonsStatus =                new Dictionary<IApplicationBarIconButton, bool>();            bool appBarMenuEnabled;            public DialogHost(INavigationService navigationSvc, bool isTapClose = true, double maskOpacity = 0.5, bool isHideApplicationBar = true, IWindowAnimator animator = null, bool canClose = true)            {                this.navigationSvc = navigationSvc;                this.canClose = canClose;                currentPage = navigationSvc.CurrentContent as PhoneApplicationPage;                if (currentPage == null)                {                    throw new InvalidOperationException(                        string.Format("In order to use ShowDialog the view currently loaded in the application frame ({0})"                                      + " should inherit from PhoneApplicationPage or one of its descendents.", navigationSvc.CurrentContent.GetType()));                }                navigationSvc.Navigating += OnNavigating;                navigationSvc.Navigated += OnNavigated;                this.maskOpacity = maskOpacity;                this.isTapClose = isTapClose;                this.isHideApplicationBar = isHideApplicationBar;                CreateUiElements();                this.animator = animator ?? new DefaultWindowAnimator();            }            public EventHandler Closed = delegate { };            public void SetActionTarget(object target)            {                Caliburn.Micro.Action.SetTarget(viewContainer, target);            }            public virtual FrameworkElement Content            {                get { return (FrameworkElement)viewContainer.Content; }                set { viewContainer.Content = value; }            }            public void Open()            {                if (isOpen)                {                    return;                }                isOpen = true;                if (currentPage.ApplicationBar != null)                {                    DisableAppBar();                }                ArrangePlacement();                currentPage.BackKeyPress += CurrentPageBackKeyPress;                currentPage.OrientationChanged += CurrentPageOrientationChanged;                hostPopup.IsOpen = true;            }            public void Close()            {                Close(reopenOnBackNavigation: false);            }            void Close(bool reopenOnBackNavigation)            {                if (!isOpen)                {                    return;                }                isOpen = false;                animator.Exit(Content, host, () => { hostPopup.IsOpen = false; });                if (currentPage.ApplicationBar != null)                {                    RestoreAppBar();                }                currentPage.BackKeyPress -= CurrentPageBackKeyPress;                currentPage.OrientationChanged -= CurrentPageOrientationChanged;                if (!reopenOnBackNavigation)                {                    navigationSvc.Navigating -= OnNavigating;                    navigationSvc.Navigated -= OnNavigated;                    Closed(this, EventArgs.Empty);                }            }            protected IWindowAnimator CreateElementsAnimator()            {                return new DefaultWindowAnimator();            }            protected void CreateUiElements()            {                var alpha = Convert.ToByte(maskOpacity * 255);                viewContainer = new ContentControl                {                    HorizontalContentAlignment = HorizontalAlignment.Stretch,                    VerticalContentAlignment = VerticalAlignment.Stretch,                };                maskingLayer = new Border                {                    Child = viewContainer,                    Background = new SolidColorBrush(Color.FromArgb(alpha, 0, 0, 0)),                    VerticalAlignment = VerticalAlignment.Stretch,                    HorizontalAlignment = HorizontalAlignment.Stretch,                    Width = Application.Current.Host.Content.ActualWidth,                    Height = Application.Current.Host.Content.ActualHeight                };                if (isTapClose)                {                    maskingLayer.Tap += (s, e) =>                    {                        if (e.OriginalSource == maskingLayer)                        {                            Close();                        }                    };                }                pageFreezingLayer = new Border                {                    Background = new SolidColorBrush(Colors.Transparent),                    Width = Application.Current.Host.Content.ActualWidth,                    Height = Application.Current.Host.Content.ActualHeight                };                var panel = new Grid { RenderTransform = new CompositeTransform() };                panel.Children.Add(pageFreezingLayer);                panel.Children.Add(maskingLayer);                host = panel;                hostPopup = new Popup { Child = panel };            }            private bool applicationBarVisible;            void DisableAppBar()            {                if (isHideApplicationBar && currentPage.ApplicationBar.IsVisible)                {                    applicationBarVisible = currentPage.ApplicationBar.IsVisible;                    currentPage.ApplicationBar.IsVisible = false;                }                else                {                    appBarMenuEnabled = currentPage.ApplicationBar.IsMenuEnabled;                    appBarButtonsStatus.Clear();                    currentPage.ApplicationBar.Buttons.Cast<IApplicationBarIconButton>()                        .Apply(b =>                        {                            appBarButtonsStatus.Add(b, b.IsEnabled);                            b.IsEnabled = false;                        });                    currentPage.ApplicationBar.IsMenuEnabled = false;                }            }            void RestoreAppBar()            {                if (isHideApplicationBar)                {                    if (applicationBarVisible)                    {                        currentPage.ApplicationBar.IsVisible = applicationBarVisible;                    }                }                else                {                    if (!appBarMenuEnabled)                    {                        currentPage.ApplicationBar.IsMenuEnabled = appBarMenuEnabled;                        currentPage.ApplicationBar.Buttons.Cast<IApplicationBarIconButton>()                            .Apply(b =>                            {                                bool status;                                if (appBarButtonsStatus.TryGetValue(b, out status))                                    b.IsEnabled = status;                            });                    }                }            }            void ArrangePlacement()            {                //设置Opacity为0防止闪屏                host.Opacity = 0;                maskingLayer.Dispatcher.BeginInvoke(() => animator.Enter(Content, host));            }            Uri currentPageUri;            void OnNavigating(object sender, System.Windows.Navigation.NavigatingCancelEventArgs e)            {                if (IsSystemDialogNavigation(e.Uri))                {                    currentPageUri = navigationSvc.CurrentSource;                }            }            void OnNavigated(object sender, System.Windows.Navigation.NavigationEventArgs e)            {                if (IsSystemDialogNavigation(e.Uri))                {                    Close(currentPageUri != null);                }                else if (e.Uri.Equals(currentPageUri))                {                    currentPageUri = null;                    //refreshes the page instance                    currentPage = (PhoneApplicationPage)navigationSvc.CurrentContent;                    Open();                }                else                {                    Close(reopenOnBackNavigation: false);                }            }            void CurrentPageBackKeyPress(object sender, CancelEventArgs e)            {                e.Cancel = true;                if (canClose)                {                    Close();                }            }            void CurrentPageOrientationChanged(object sender, OrientationChangedEventArgs e)            {                ArrangePlacement();            }        }        //TODO:待改        public void ShowDialog1(object rootModel, IWindowAnimator windowAnimator = null,            bool isTapClose = true, double maskOpacity = 0.8,            bool isHideApplicationBar = true, bool canClose = true)        {        }    }
CustomWindowManager

 

四、使用

 

  使用很简单,首先我们定义一个自定义窗口

<UserControl x:Class="XTuOne.Friday.Controls.Views.CommonDialogView"             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"             VerticalAlignment="Top"             FontFamily="{StaticResource PhoneFontFamilyNormal}"             FontSize="{StaticResource PhoneFontSizeNormal}"             Foreground="{StaticResource PhoneForegroundBrush}"             d:DesignHeight="480"             d:DesignWidth="480"             mc:Ignorable="d">    <Grid Background="#1F1F1F">        <StackPanel Margin="12 48 12 12">            <TextBlock x:Name="Title"                       Margin="{StaticResource PhoneHorizontalMargin}"                       FontFamily="{StaticResource PhoneFontFamilySemiBold}"                       FontSize="{StaticResource PhoneFontSizeLarge}"                       Foreground="White"                       TextWrapping="Wrap" />            <TextBlock x:Name="Text"                       Margin="12 24"                       Foreground="White"                       Style="{StaticResource PhoneTextTitle3Style}"                       TextWrapping="Wrap" />            <Grid>                <Grid.ColumnDefinitions>                    <ColumnDefinition />                    <ColumnDefinition />                </Grid.ColumnDefinitions>                <Button x:Name="Ok"                        Grid.Column="0"                        BorderBrush="White"                        Foreground="White">                    <TextBlock x:Name="OkText" Text="Ok" />                </Button>                <Button x:Name="Cancel"                        Grid.Column="1"                        BorderBrush="White"                        Foreground="White">                    <TextBlock x:Name="CancelText" Text="Cancel" />                </Button>            </Grid>        </StackPanel>    </Grid></UserControl>
自定义弹窗控件:CommonDialogView

  后台没有内容,就不贴,然后定义控件对应的ViewModel

    public enum DialogResult    {        Cancel,        Ok,        Close,    }    public class CommonDialogViewModel : Screen    {        //返回值        public DialogResult Result { get; private set; }        //对话框的标题        public string Title { get; set; }        //对话框的文字        public string Text { get; set; }        //左边按钮的文字        public string OkText { get; set; }        //右边按钮的文字        public string CancelText { get; set; }        public CommonDialogViewModel()        {            Result = DialogResult.Close;            OkText = "Ok";            CancelText = "Cancel";        }        public void Ok()        {            Result = DialogResult.Ok;            TryClose();        }        public void Cancel()        {            Result = DialogResult.Cancel;            TryClose();        }    }
CommonDialogViewModel

  接下来是使用

    var windowAnimator = new FlipWindowAnimator();    var customWindowManager = new CustomWindowManager();    var commonDialog = new CommonDialogViewModel    {        Title = "提示",        Text = "该手机号已经注册过,您可以:",        CancelText = "换个手机",        OkText = "直接登录"    };    commonDialog.Deactivated += (s, e) =>    {        if (commonDialog.Result == DialogResult.Ok)        {            //用户点击左边按钮                   }        else if (commonDialog.Result == DialogResult.Cancel)        {            //用户点击右边按钮        }        else        {            //非用户点击按钮关闭(用户点击返回键或离开App)        }    };    customWindowManager.ShowDialog(commonDialog, false, 0.8, flipWindowAnimator, false);

  效果图

 

 

注意:  

  为了防止多个弹窗导致HideApplicationBar冲突问题
  由于这里是通过ApplicationBar.IsMenuEnable来判断是否被禁用的
  所以请保证ApplicationBar.IsMenuEnable为true

 

个人能力有限,如果上文有误或者您有更好的实现,可以给我留言

转载请注明出处:http://www.cnblogs.com/bomo/p/3952419.html

【WP8】扩展CM的WindowManager