Windows Phone 自定义弹出框和 Toast 通知

   工程源码下载

 

注:这篇文章主要介绍在 应用全局所有页面使用的自定义弹出框,如果仅在 MainPage 页面询问用户是否

是否退出,可参考: 简化版“询问用户是否退出”

 

1、 首先介绍如何自定义弹出框

    在 WP 中,系统默认的弹出框有几个问题,首先是背景比较简陋,而且样式的扩展性比较低,另一个问题就

是会阻碍 UI 线程。当在页面的调用下面的代码:

            if(MessageBoxResult.OK != MessageBox.Show("确定要退出吗?", "温馨提示", MessageBoxButton.OKCancel))

            {

                e.Cancel = true;

            }


当在 MainPage 页面点击 Back 键,会弹出:

Windows Phone 自定义弹出框和 Toast 通知

 当在10秒钟的时候,如果用户没有回馈,那么应用就会被系统终结了。

        下面开始做一个自定义的弹出框:

1)自定义一个控件,定义在 MyDialog.xaml 文件中:

 <UserControl.Resources>

        <!--自定义 “确定/取消”弹出框 播放动画-->

        <Storyboard x:Name="ShowMessageBoxSB">

            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationX)" Storyboard.TargetName="gridBox">

                <EasingDoubleKeyFrame KeyTime="0" Value="-30"/>

                <EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="0"/>

            </DoubleAnimationUsingKeyFrames>

        </Storyboard>

    </UserControl.Resources>

    <Grid>

        

        <!--自定义 “确定/取消”弹出框,弹出框为蓝色,背景为半透明黑色-->

        <Grid  x:Name="gridDialog" Background="#33000000">

            <Grid Height="280" x:Name="gridBox" VerticalAlignment="Top" Background="#ff1ba0e1">

                <Grid.Projection>

                    <PlaneProjection/>

                </Grid.Projection>

                <Grid.RowDefinitions>

                    <RowDefinition Height="60"/>

                    <RowDefinition Height="150"/>

                    <RowDefinition Height="70"/>

                </Grid.RowDefinitions>

                <TextBlock Text="提示信息" x:Name="txtTitle" FontSize="25" Margin="25,10, 0, 0"/>


<!-- ContentControl 可以设置成任何其他的 Element,因为它的 Content 属性是 Object 类型的--> <ContentControl HorizontalAlignment="Left" FontSize="30" Margin="40, 0, 0, 0" Content="文本内容文本" Grid.Row="1" x:Name="contentContainer"/> <StackPanel Grid.Row="2" Orientation="Horizontal"> <Button x:Name="btnOk" Content="OK" Width="200" Click="btnOk_Click"/> <Button x:Name="btnCancle" Content="Cancel" Width="200" Click="btnCancle_Click"/> </StackPanel> </Grid> </Grid> <!--自定义 Toast 通知,背景颜色和手机主题一致--> <Border x:Name="borderToast" Height="50" Width="480" Margin="0,-50,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Background="{StaticResource PhoneAccentBrush}" RenderTransformOrigin="0.5,0.5"> <Border.RenderTransform> <CompositeTransform/> </Border.RenderTransform> <TextBlock Margin="30,20,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" x:Name="txtToast" FontSize="22"/> </Border> </Grid> </UserControl>


  相应的 C# 页面:

  public MyDialog()

        {

            InitializeComponent();



            gridDialog.Visibility = System.Windows.Visibility.Collapsed;



            // 把第一个实例赋值给全局静态对象

            if (_instance == null)

                _instance = this;

        }



        #region “确定”弹出框

        //用来控制异步线程中 弹出框 结果返回的时机

        private static AutoResetEvent myResetEvent = new AutoResetEvent(false);



        static MessageBoxResult messageBoxResult;



        //用一个单一实例,使得应用中的所有页面使用同一个实例

        static MyDialog _instance;

        static MyDialog Instance

        {

            get

            {

                //if (_instance == null)

                //    _instance = new MyDialog();



                return _instance;

            }

        }



        // 用来控制当弹出框显示的时候,如果用户点击 Back 按键,则隐藏弹出框,

        // 在 App.xaml.cs 中的 RootFrame_Navigating 事件中调用

        public static bool DialogIsOpen

        {

            get

            {

                if (Instance != null && Instance.gridDialog.Visibility == Visibility.Visible)

                {

                    Instance.btnCancle_Click(null, null);



                    return true;

                }

                else

                {

                    return false;

                }

            }

        }



        /// <summary>

        /// 显示包含指定文本和“确定”按钮的消息框

        /// </summary>

        /// <param name="messageBoxText">要显示的消息</param>

        /// <returns> 在所有情况下均为 System.Windows.MessageBoxResult.OK</returns>

        public static Task<MessageBoxResult> Show(string messageBoxText)

        {

            return Task<MessageBoxResult>.Factory.StartNew(() =>

            {

                Instance.Dispatcher.BeginInvoke(delegate

                       {

                           Instance.gridDialog.Visibility = Visibility.Visible;



                           Instance.contentContainer.Content = messageBoxText;



                           Instance.txtTitle.Text = "";



                           Instance.btnCancle.Visibility = Visibility.Collapsed;





                           Instance.ShowMessageBoxSB.Stop();

                           Instance.ShowMessageBoxSB.Begin();

                       });



                myResetEvent.WaitOne();

                return messageBoxResult;

            });

        }



        /// <summary>

        /// 显示包含指定文本、标题栏标题和响应按钮的消息框

        /// </summary>

        /// <param name="messageBoxText">要显示的消息</param>

        /// <param name="caption">消息框的标题</param>

        /// <param name="button">一个值,用于指示要显示哪个按钮或哪些按钮</param>

        /// <returns>一个值,用于指示用户对消息的响应</returns>

        public static Task<MessageBoxResult> Show(string messageBoxText, string caption, MessageBoxButton button)

        {

            return Task<MessageBoxResult>.Factory.StartNew(() =>

            {

                Instance.Dispatcher.BeginInvoke(delegate

                       {



                           Instance.gridDialog.Visibility = Visibility.Visible;



                           Instance.contentContainer.Content = messageBoxText;



                           Instance.txtTitle.Text = caption;



                           if (button == MessageBoxButton.OKCancel)

                           {

                               Instance.btnCancle.Visibility = Visibility.Visible;

                           }

                           else

                           {

                               Instance.btnCancle.Visibility = Visibility.Collapsed;

                           }



                           //Instance.UpdateLayout();





                           Instance.ShowMessageBoxSB.Stop();

                           Instance.ShowMessageBoxSB.Begin();

                       });





                myResetEvent.WaitOne();



                return messageBoxResult;

            });

        }



        private void btnOk_Click(object sender, RoutedEventArgs e)

        {

            txtTitle.Text = "";

            contentContainer.Content = null;



            gridDialog.Visibility = System.Windows.Visibility.Collapsed;



            messageBoxResult = MessageBoxResult.OK;



            // 使异步线程的 Show() 方法继续执行

            myResetEvent.Set();

        }



        private void btnCancle_Click(object sender, RoutedEventArgs e)

        {

            txtTitle.Text = "";

            contentContainer.Content = null;



            gridDialog.Visibility = System.Windows.Visibility.Collapsed;



            messageBoxResult = MessageBoxResult.Cancel;



            myResetEvent.Set();

        }

        #endregion


     另一个问题就是,让这个自定义控件变成全局的,也就是所有工程里的页面,共享

      这一个实例。思路就是,因为 App 类中的一个 RootFrame 对象,它是 PhoneApplicationFrame 类型的

,它是用来显示工程内所有页面的对象,并且控制各个页面的导航等功能的,它有一个 Template 属性,只要在

默认的框架上,加入自定义的控件,那么这个控件就可以在所有页面中显示了,相应的样式(在 App.xaml 中定义):

  <Style x:Key="MyPhoneApplicationFrameStyle" TargetType="phone:PhoneApplicationFrame">

            <Setter Property="IsTabStop" Value="False"/>

            <Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>

            <Setter Property="FontSize" Value="{StaticResource PhoneFontSizeNormal}"/>

            <Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilyNormal}"/>

            <Setter Property="HorizontalAlignment" Value="Stretch"/>

            <Setter Property="VerticalAlignment" Value="Stretch"/>

            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>

            <Setter Property="VerticalContentAlignment" Value="Stretch"/>

            <Setter Property="Background" Value="Transparent"/>

            <Setter Property="BorderThickness" Value="0"/>

            <Setter Property="BorderBrush" Value="{x:Null}"/>

            <Setter Property="Padding" Value="0"/>

            <Setter Property="Template">

                <Setter.Value>

                    <ControlTemplate TargetType="phone:PhoneApplicationFrame">

                        <Grid>

                            <Border x:Name="ClientArea" 

                                BorderBrush="{TemplateBinding BorderBrush}" 

                                BorderThickness="{TemplateBinding BorderThickness}" 

                                Background="{TemplateBinding Background}" 

                                HorizontalAlignment="{TemplateBinding HorizontalAlignment}" 

                                VerticalAlignment="{TemplateBinding VerticalAlignment}">

                                <ContentPresenter 

                                ContentTemplate="{TemplateBinding ContentTemplate}" 

                                Content="{TemplateBinding Content}" 

                                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 

                                Margin="{TemplateBinding Padding}" 

                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>

                            </Border>

                            <Dia:MyDialog xmlns:Dia="clr-namespace:DialogTemplate" x:Name="myDialog"/>

                        </Grid>

                    </ControlTemplate>

                </Setter.Value>

            </Setter>

        </Style>


在 App 的构造函数中,重新设置 RootFrame 的样式,为上面定义的样式:

   RootFrame.Style = App.Current.Resources["MyPhoneApplicationFrameStyle"] as Style;

 

在 App 的 InitializePhoneApplication() 方法中,注册 Navigating 事件:

  RootFrame.Navigating += RootFrame_Navigating;

相应的函数:

   void RootFrame_Navigating(object sender, NavigatingCancelEventArgs e)

        {

            // 如果自定义弹出框在显示,则隐藏它,并且取消导航

            if (MyDialog.DialogIsOpen && e.NavigationMode == NavigationMode.Back)

            {

                e.Cancel = true;

            }

        }

 

 

在页面中调用弹出框的方法:

       private async void Button_Click_OkAndCancel(object sender, RoutedEventArgs e)

        {

            if (MessageBoxResult.OK == await MyDialog.Show("Hello 北京天安门", "提示", MessageBoxButton.OKCancel))

            {

                txtTile.Text = "点击了OK";

            }

            else

            {

                txtTile.Text = "点击了Cancle";

            }

        }


显示结果:

Windows Phone 自定义弹出框和 Toast 通知

 

当用户在 MainPage 页面,点击 Back 物理按键时,因为此时不会触发 RootFrame.Navigating 的导航事件,所以

需要在 MainPage 中判断一下是否有其它的弹出框,判断如果没有其它的弹出框,则弹出询问用户

是否关闭当前应用的弹出框:

        protected override async void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)

        {

            // 首先取消默认 Back 键关闭应用

            e.Cancel = true;



            // 如果 MainPage 页面中,弹出了其它的弹出框,关闭它,

            // 在 DialogIsOpen 属性中实现

            if (MyDialog.DialogIsOpen)

            {

                //可以做其它事情

            }

            else

            {

                if (MessageBoxResult.OK == await MyDialog.Show("确定要退出吗?", "温馨提示:", MessageBoxButton.OKCancel))

                {

                    //  终止当前应用程序。该方法是在 WP8 中加入的,WP7 中木有

                    App.Current.Terminate();

                }

            }

            base.OnBackKeyPress(e);

        }


显示效果:

Windows Phone 自定义弹出框和 Toast 通知

因为本工程中自定义弹出框和 自定义Toast 通知都为系统级别的,所以所有的页面共享这个

单一实例。如果页面只有在推出时,询问用户 “是否退出应用” 则只需要参考自定义控件,把它

简化后,添加到 MainPage 中使用即可,不需要放到 PhoneApplicationFrame 的 Template

属性中。

 

2) 自定义一个 Toast 通知,目的是增加对消息样式的控制,并且可以增加消息弹出的动画等。

      自定义的 Toast 显示框已经在 上面的 MyDialog.xaml 文件中定义了,下面定义 C# 页面:

  #region 自定义 Toast 消息框

        const string myToast = @" <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:2"" Value=""50"">

                        <EasingDoubleKeyFrame.EasingFunction>

                            <ExponentialEase EasingMode=""EaseOut"" Exponent=""6""/>     

                        </EasingDoubleKeyFrame.EasingFunction>

                    </EasingDoubleKeyFrame>

                    <EasingDoubleKeyFrame KeyTime=""0:0:5"" Value=""-50""/>

                    <EasingDoubleKeyFrame KeyTime=""0:0:6"" Value=""0"">

                        <EasingDoubleKeyFrame.EasingFunction>

                           <ExponentialEase EasingMode=""EaseInOut"" Exponent=""6""/>

                        </EasingDoubleKeyFrame.EasingFunction>

                    </EasingDoubleKeyFrame>

                </DoubleAnimationUsingKeyFrames>

                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.Opacity)"">

                    <EasingDoubleKeyFrame KeyTime=""0"" Value=""0""/>

                    <EasingDoubleKeyFrame KeyTime=""0:0:2"" Value=""1""/>

                    <EasingDoubleKeyFrame KeyTime=""0:0:5"" Value=""1""/>

                    <EasingDoubleKeyFrame KeyTime=""0:0:6"" Value=""0"">

                        <EasingDoubleKeyFrame.EasingFunction>

                            <ExponentialEase EasingMode=""EaseIn"" Exponent=""6""/>

                        </EasingDoubleKeyFrame.EasingFunction>

                    </EasingDoubleKeyFrame>

                </DoubleAnimationUsingKeyFrames>

            </Storyboard>";



        // 把上面动画字符串转换成相应的 XAML 动画对象

        Storyboard StoryBoardToast = System.Windows.Markup.XamlReader.Load(myToast) as Storyboard;



        // 显示自定义 Toast 消息

        public static void ShowToastMessage(string message)

        {

            Instance.StoryBoardToast.Stop();



            Instance.txtToast.Text = message;





            foreach (var t in Instance.StoryBoardToast.Children)

                Storyboard.SetTarget(t, Instance.borderToast);



            Instance.StoryBoardToast.Begin();

        }

        #endregion


在任何一个 Page 页面中调用:

  private void Button_Click_Toast(object sender, RoutedEventArgs e)

        {

            MyDialog.ShowToastMessage("收到 10 条新消息");

        }

 

则,显示结果:
Windows Phone 自定义弹出框和 Toast 通知

 

 

 

你可能感兴趣的:(windows phone)