• [WP8] 使用ApplicationMenu与使用者互动


    [WP8] 使用ApplicationMenu与使用者互动

    范例下载

    范例程序代码:点此下载

    功能说明

    使用过Lumia系列手机的开发人员,对于内建的相机功能相信都很熟悉。在Lumia内建的相机功能中,提供用户变更相片参数、变更影片参数...等等的设定功能,都是如下图所示意的:点选ApplicationBar的选项之后,在同页面中显示设置选单,来提供使用者设定参数。而这样,点选ApplicationBar的选项之后,在同一个页面显示选单的功能,我自己给它一个名字叫做:「ApplicationMenu」。

    • 功能画面

      功能说明01

    使用ApplicationMenu与使用者互动,有着下表所列的种种优缺点,开发人员可以依照系统需求来做评估与选择。而目前.NET Framework并没有提供内建的ApplicationMenu,开发人员必须自己实做。本篇文章介绍如何实做ApplicationMenu,用以在点选ApplicationBar的选项之后,在同一个页面显示选单,为自己留个纪录也希望能帮助到有需要的开发人员。

    • 优点

      • 减少切换页面时,使用者等待新页面的等候时间。
      • 减少切换页面后,使用者对于新页面的学习恐惧。
      • 减少撰写程序时,开发人员对于状态维持、参数传递、状态恢复等等功能的设计。
      • ......
    • 缺点

      • 页面选单过多时,增加使用者的学习负担。
      • 将选单加入页面,意味着页面功能增加,增加了执行时的内存。
      • 将选单加入页面,意味着页面职责增加,增加了维护时的复杂度。
      • ......

    功能使用

    在开始介绍如何实做ApplicationMenu之前,先介绍如何使用ApplicationMenu,避免开发人员看到大量的实做程序代码就直接昏迷不醒。

    本篇文章所实做的ApplicationMenu,最终是将功能封装成为Popup类别的扩充方法:

    • ApplicationMenuExtension

      public static class ApplicationMenuExtension
      {
          // Methods              
          public static void ShowApplicationMenu(this Popup popup)
          {
              // ...
          }
      
          public static void HideApplicationMenu(this Popup popup)
          {
              // ...
          }
      }
      

    开发人员要在页面加入ApplicationMenu的时候,只需要先在页面的XAML内,定义一个做为ApplicationMenu的Popup类别、以及用来开启这个ApplicationMenu的ApplicationBar类别:

    • ApplicationBar

      <!--ApplicationBar-->
      <phone:PhoneApplicationPage.ApplicationBar>
          <shell:ApplicationBar Mode="Default" IsVisible="True">
              <shell:ApplicationBarIconButton IconUri="/Assets/ApplicationIcon.png" Text="Action" />
              <shell:ApplicationBar.MenuItems>
                  <shell:ApplicationBarMenuItem Text="setting..." Click="BeginSettingButton_Click" />
              </shell:ApplicationBar.MenuItems>
          </shell:ApplicationBar>
      </phone:PhoneApplicationPage.ApplicationBar>
      
    • ApplicationMenu

      <!--ApplicationMenu-->
      <Popup x:Name="SettingMenu001">
          <!--MenuContent-->
          <Grid Background="{StaticResource PhoneChromeBrush}">
              <Grid.RowDefinitions>
                  <RowDefinition Height="*"/>
                  <RowDefinition Height="Auto"/>
              </Grid.RowDefinitions>
      
              <!--Detail-->
              <StackPanel Grid.Row="0">
                  <TextBlock Text="Setting:" />
                  <CheckBox Content="Argument001" />
                  <CheckBox Content="Argument002" />
                  <CheckBox Content="Argument003" />
                  <TextBox Text="Argument004" />
                  <TextBox Text="Argument005" />
              </StackPanel>
      
              <!--Action-->
              <Button Grid.Row="1" Content="Save" Click="EndSettingButton_Click" />
          </Grid>
      </Popup>
      

    接着只需要在页面的事件处理函式中,呼叫ShowApplicationMenu、HideApplicationMenu这两个Popup类别的扩充方法,就可以在页面中显示、隐藏ApplicationMenu:

    • MainPage

      public partial class MainPage : PhoneApplicationPage
      {
          // Handlers
          private void BeginSettingButton_Click(object sender, EventArgs e)
          {
              // Show
              this.SettingMenu001.ShowApplicationMenu();
          }
      
          private void EndSettingButton_Click(object sender, EventArgs e)
          {
              // Hide
              this.SettingMenu001.HideApplicationMenu();
          }
      }
      
    • 执行结果

      功能使用01

    当然,如果一个页面中有超过一个以上的ApplicationMenu,也是依照上列的方式来反复建立,就可以在页面中使用多个ApplicationMenu。

    • 程序代码(.CS)

      public partial class MainPage : PhoneApplicationPage
      {
          // Constructors
          public MainPage()
          {
              // Initialize
              this.InitializeComponent();
          }
      
      
          // Handlers
          private void BeginSetting001Button_Click(object sender, EventArgs e)
          {
              // Show
              this.SettingMenu001.ShowApplicationMenu();
          }
      
          private void EndSetting001Button_Click(object sender, EventArgs e)
          {
              // Hide
              this.SettingMenu001.HideApplicationMenu();
          }
      
      
          private void BeginSetting002Button_Click(object sender, EventArgs e)
          {
              // Show
              this.SettingMenu002.ShowApplicationMenu();
          }
      
          private void EndSetting002Button_Click(object sender, EventArgs e)
          {
              // Hide
              this.SettingMenu002.HideApplicationMenu();
          }
      }
      
    • 程序代码(.XAML)

      <!--ApplicationBar-->
      <phone:PhoneApplicationPage.ApplicationBar>
          <shell:ApplicationBar Mode="Default" IsVisible="True">
              <shell:ApplicationBarIconButton IconUri="/Assets/ApplicationIcon.png" Text="Action" />
              <shell:ApplicationBar.MenuItems>
                  <shell:ApplicationBarMenuItem Text="setting001..." Click="BeginSetting001Button_Click" />
                  <shell:ApplicationBarMenuItem Text="setting002..." Click="BeginSetting002Button_Click" />
              </shell:ApplicationBar.MenuItems>
          </shell:ApplicationBar>
      </phone:PhoneApplicationPage.ApplicationBar>
      
      
      <!--PageRoot-->
      <Grid>
      
      
          <!--LayoutRoot-->
          <Grid x:Name="LayoutRoot">
              <Grid.RowDefinitions>
                  <RowDefinition Height="Auto"/>
                  <RowDefinition Height="*"/>
              </Grid.RowDefinitions>
      
              <!--TitlePanel-->
              <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
                  <TextBlock Text="我的应用程序" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
                  <TextBlock Text="页面名称" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
              </StackPanel>
      
              <!--ContentPanel-->
              <StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
      
              </StackPanel>
          </Grid>
      
      
          <!--ApplicationMenu-->
          <Popup x:Name="SettingMenu001">            
              <!--MenuContent-->
              <Grid Background="{StaticResource PhoneChromeBrush}">
                  <Grid.RowDefinitions>
                      <RowDefinition Height="*"/>
                      <RowDefinition Height="Auto"/>
                  </Grid.RowDefinitions>
      
                  <!--Detail-->
                  <StackPanel Grid.Row="0">
                      <TextBlock Text="Setting001:" />
                      <CheckBox Content="Argument001" />
                      <CheckBox Content="Argument002" />
                      <CheckBox Content="Argument003" />
                      <TextBox Text="Argument004" />
                      <TextBox Text="Argument005" />
                  </StackPanel>
      
                  <!--Action-->
                  <Button Grid.Row="1" Content="Save" Click="EndSetting001Button_Click" />                
              </Grid>            
          </Popup>
      
          <Popup x:Name="SettingMenu002">
              <!--MenuContent-->
              <Grid Background="{StaticResource PhoneChromeBrush}">
                  <Grid.RowDefinitions>
                      <RowDefinition Height="*"/>
                      <RowDefinition Height="Auto"/>
                  </Grid.RowDefinitions>
      
                  <!--Detail-->
                  <StackPanel Grid.Row="0">
                      <TextBlock Text="Setting002:" />
                      <TextBox Text="Argument006" />
                      <CheckBox Content="Argument007" />
                      <CheckBox Content="Argument008" />
                  </StackPanel>
      
                  <!--Action-->
                  <Button Grid.Row="1" Content="Save" Click="EndSetting002Button_Click" />
              </Grid>
          </Popup>
      
      
      </Grid>
      
    • 执行画面

      功能使用02

      功能使用03

    功能设计

    了解如何使用ApplicationMenu之后,就可以开始介绍如何实做ApplicationMenu的功能。

    选择 Popup

    首先分析设计ApplicationMenu的显示模式,会发现ApplicationMenu是显示在整个页面之上,并且不影响整体页面的Layout。而ApplicationMenu这样的显示模式,跟Popup类别的显示模式近乎是一模一样,就这个角度选择Popup类别来实做ApplicationMenu,应该会是一个不错的选择。

    • 程序代码(.XAML)

      <!--AppMenu-->
      <Popup x:Name="SettingMenu001" >
      
      </Popup>
      
    • 功能画面

      功能设计01

    加入 PageRoot

    在开始使用Popup类别来实做ApplicationMenu之前,要先来处理Popup类别的显示定位问题。

    使用Visual Studio建立Windows Phone项目,在预设的状态下,会在MainPage中提供一个Grid类别做为LayoutRoot,让开发人员以这个LayoutRoot做为基础来添加各种控件。而将Popup类别加入做为LayoutRoot的Grid类别里,只要不为Popup类别定义Grid.Row、Grid.Column等等参数,Popup类别显示的时候,透过Grid类别显示机制,会以LayoutRoot的左上角做为显示定位的基准。

    • 程序代码(.XAML)

      <!--LayoutRoot-->
      <Grid x:Name="LayoutRoot">
          <Grid.RowDefinitions>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="*"/>
          </Grid.RowDefinitions>
      
          <!--TitlePanel-->
          <StackPanel x:Name="TitlePanel" Grid.Row="0">
              <TextBlock Text="我的应用程序" Style="{StaticResource PhoneTextNormalStyle}"/>
              <TextBlock Text="页面名称" Style="{StaticResource PhoneTextTitle1Style}"/>
          </StackPanel>
      
          <!--ContentPanel-->
          <Grid x:Name="ContentPanel" Grid.Row="1">
      
          </Grid>
      
          <!--AppMenu-->
          <Popup x:Name="SettingMenu001" IsOpen="True">
              <Border BorderThickness="6" BorderBrush="Red" Width="400" Height="300" />
          </Popup>        
      </Grid>
      
    • 执行画面

      功能设计02

    但是当遇到开发情景,需要使用StackPanel类别来做为LayoutRoot的时候。将Popup类别加入做为LayoutRoot的StackPanel类别里,并且没有为Popup类别定义相关定位参数,会发现Popup类别显示的时候,透过StackPaneld类别显示机制,会以在LayoutRoot中的排列位置做为显示定位的基准。

    也就是说,当开发情景需要以StackPanel类别来做为LayoutRoot的时候,Popup类别的显示定位会是浮动的。

    • 程序代码(.XAML)

      <!--LayoutRoot-->
      <StackPanel x:Name="LayoutRoot">
      
          <!--TitlePanel-->
          <StackPanel x:Name="TitlePanel" Height="200">
              <TextBlock Text="我的应用程序" Style="{StaticResource PhoneTextNormalStyle}"/>
              <TextBlock Text="页面名称" Style="{StaticResource PhoneTextTitle1Style}"/>
          </StackPanel>
      
          <!--ContentPanel-->
          <Grid x:Name="ContentPanel" Height="100">
      
          </Grid>
      
          <!--AppMenu-->
          <Popup x:Name="SettingMenu001" IsOpen="True">
              <Border BorderThickness="6" BorderBrush="Red" Width="400" Height="300" />
          </Popup>
      </StackPanel>
      
    • 执行画面

      功能设计03

    为了处理使用Grid类别、StackPanel类别...等等类别做为LayoutRoot,而造成Popup类别显示定位不一的问题,可以在LayoutRoot之外再加上一层使用Grid类别所设计的PageRoot,并且将Popup类别从LayoutRoot移除、加入到PageRoot。透过这样加入以Grid类别来做为PageRoot的设计,就可以忽略设计为LayoutRoot的类别,透过Grid类别显示机制,统一将Popup类别以PageRoot的左上角做为显示定位的基准。

    • 程序代码(.XAML)

      <!--PageRoot-->
      <Grid>
      
          <!--LayoutRoot-->
          <StackPanel x:Name="LayoutRoot">
      
              <!--TitlePanel-->
              <StackPanel x:Name="TitlePanel" Height="200">
                  <TextBlock Text="我的应用程序" Style="{StaticResource PhoneTextNormalStyle}"/>
                  <TextBlock Text="页面名称" Style="{StaticResource PhoneTextTitle1Style}"/>
              </StackPanel>
      
              <!--ContentPanel-->
              <Grid x:Name="ContentPanel" Height="100">
      
              </Grid>
          </StackPanel>
      
          <!--AppMenu-->
          <Popup x:Name="SettingMenu001" IsOpen="True">
              <Border BorderThickness="6" BorderBrush="Red" Width="400" Height="300" />
          </Popup>
      
      </Grid>
      
    • 执行画面

      功能设计04

    设计 ApplicationMenu

    处理Popup类别的显示定位之后,就可以来设计ApplicationMenu的内容。ApplicationMenu的内容其实还蛮单纯的,主要就是将内容分为三大部分来做排版设计:

    MenuRoot:使用Grid类别设计。MenuRoot的主要用途,是用来撑起整个ApplicationMenu的长宽,并且做为MenuMask、MenuContent的容器。而因为是以Popup类别来实做ApplicationMenu,而Popup类别又透过PageRoot的加入,将显示定位在PageRoot的左上角(也就是整个显示屏幕的左上角)。所以将MenuRoot的长宽,定义为整个应用程序的长宽,也就可以让Popup类别在显示的时候覆盖整个应用程序。

    MenuMask:使用Rectangle类别设计。在ApplicationMenu显示的时候,如果用户点击到MenuContent之外的画面,系统必须要自动隐藏ApplicationMenu。而MenuMask的主要用途,就是使用一个透明的控件来覆盖MenuContent显示范围之外的区域,用来拦截MenuContent之外的所有Tap点击,后续步骤会处理这些Tap点击用来关闭ApplicationMenu。

    MenuContent:依照内容类设计。MenuContent的主要用途,是实际用来显示ApplicationMenu的内容,并且这个MenuContent的长宽,会依照内容的实际长宽来呈现在整个页面底端。

    • 示意画面

    功能设计05

    • 程序代码(.CS)

      public partial class MainPage : PhoneApplicationPage
      {        
          // Constructors
          public MainPage()
          {
              // Initialize
              this.InitializeComponent();        
      
              // Events
              this.Loaded += this.PhoneApplicationPage_Loaded;
          }
      
      
          // Handlers
          private void PhoneApplicationPage_Loaded(object s, RoutedEventArgs e)
          {
              // MenuRoot
              this.MenuRoot.Height = Application.Current.Host.Content.ActualHeight - (SystemTray.IsVisible == true ? 32 : 0);
              this.MenuRoot.Width = Application.Current.Host.Content.ActualWidth;
          }
      }
      
    • 程序代码(.XAML)

      <!--AppMenu-->
      <Popup x:Name="SettingMenu001" IsOpen="True" >            
          <!--MenuRoot-->
          <Grid x:Name="MenuRoot">
              <Grid.RowDefinitions>
                  <RowDefinition Height="*"/>
                  <RowDefinition Height="Auto"/>
              </Grid.RowDefinitions>
      
              <!--MenuMask-->
              <Rectangle x:Name="MenuMask" Grid.Row="0" Fill="Transparent">
      
              </Rectangle>                
      
              <!--MenuContent-->
              <Grid x:Name="MenuContent" Grid.Row="1" Background="{StaticResource PhoneChromeBrush}">
                  <Grid.RowDefinitions>
                      <RowDefinition Height="*"/>
                      <RowDefinition Height="Auto"/>
                  </Grid.RowDefinitions>
      
                  <!--Detail-->
                  <StackPanel Grid.Row="0">
                      <TextBlock Text="Setting:" />
                      <CheckBox Content="Argument001" />
                      <CheckBox Content="Argument002" />
                      <CheckBox Content="Argument003" />
                      <TextBox Text="Argument004" />
                      <TextBox Text="Argument005" />
                  </StackPanel>
      
                  <!--Action-->
                  <Button Grid.Row="1" Content="Save" />                    
              </Grid>
          </Grid>            
      </Popup>    
      

    显示 ApplicationMenu

    设计好ApplicationMenu的内容之后,就可以着手处理ApplicationMenu的显示功能。ApplicationMenu显示功能比较棘手的地方,是需要显示下列示意画面所描绘的滑入动画,因为如果没有使用动画来让ApplicationMenu滑入而直接显示,会让用户觉得非常突兀,并且大幅降低使用者的耐心。

    而实做ApplicationMenu的滑入动画其实很简单,因为MenuRoot中已经定义好MenuContent的显示位置,让MenuContent显示在整个页面的底端。做滑入动画只需要在这个显示基础上做Y轴偏移的动画,一开始先将将Y轴的偏移量设定为整个MenuContent的高度,接着透过DoubleAnimation来持续递减Y轴的偏移量到零为止,就可以将MenuContent从画面外滑入到原本MenuRoot所定义的显示位置。

    • 示意画面

    功能设计06

    • 程序代码(.CS)

      public partial class MainPage : PhoneApplicationPage
      {        
          // Constructors
          public MainPage()
          {
              // Initialize
              this.InitializeComponent();        
      
              // Events
              this.Loaded += this.PhoneApplicationPage_Loaded;
          }
      
      
          // Properties
          private Storyboard ShowStoryboard { get; set; }
      
          private DoubleAnimation ShowAnimation { get; set; }
      
      
          // Methods  
          private void ShowApplicationMenu()
          {
              // Display
              this.SettingMenu001.IsOpen = true;
              this.ApplicationBar.IsVisible = false;
              this.SettingMenu001.UpdateLayout();
      
              // Animation
              this.ShowAnimation.From = this.MenuContent.ActualHeight;
              this.ShowAnimation.To = 0;
              this.ShowStoryboard.Begin();
          }
      
      
          // Handlers
          private void PhoneApplicationPage_Loaded(object s, RoutedEventArgs e)
          {
              // MenuContent
              this.MenuContent.RenderTransform = new CompositeTransform();
      
              // ShowAnimation
              this.ShowAnimation = new DoubleAnimation();
              this.ShowAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(200));
              Storyboard.SetTarget(this.ShowAnimation, this.MenuContent.RenderTransform);
              Storyboard.SetTargetProperty(this.ShowAnimation, new PropertyPath("TranslateY"));
      
              // ShowStoryboard
              this.ShowStoryboard = new Storyboard();
              this.ShowStoryboard.Children.Add(this.ShowAnimation);
          }
      }
      

    隐藏 ApplicationMenu

    先前已经实做ApplicationMenu的滑入动画,那实做ApplicationMenu的隐藏功能所需要的滑出动画,其实也是大同小异,单纯就只是将Y轴的偏移量设定为零,接着透过DoubleAnimation来持续递增Y轴的偏移量到MenuContent的高度为止,就可以将MenuContent从原本MenuRoot所定义的显示位置滑出到画面之外。

    但在这边有个比较特别需要注意的小地方是,要透过递增DoubleAnimation的Storyboard的Completed事件处理函式,来让做为ApplicationMenu的Popup类别,能够在滑出动画结束之后才真正被隐藏。毕竟如果在滑出动画结束前,先把做为ApplicationMenu的Popup类别隐藏了,这时画面上就看不到ApplicationMenu,那滑出动画的设计也就没有意义了。

    • 示意画面

    功能设计07

    • 程序代码(.CS)

      public partial class MainPage : PhoneApplicationPage
      {        
          // Constructors
          public MainPage()
          {
              // Initialize
              this.InitializeComponent();        
      
              // Events
              this.Loaded += this.PhoneApplicationPage_Loaded;
          }
      
      
          // Properties
          private Storyboard HideStoryboard { get; set; }
      
          private DoubleAnimation HideAnimation { get; set; }
      
      
          // Methods  
          private void HideApplicationMenu()
          {           
              // Display
              this.SettingMenu001.IsOpen = true;
              this.ApplicationBar.IsVisible = false;
              this.SettingMenu001.UpdateLayout();
      
              // Animation
              this.HideAnimation.From = 0;
              this.HideAnimation.To = this.MenuContent.ActualHeight;
              this.HideStoryboard.Begin();
          }
      
      
          // Handlers
          private void PhoneApplicationPage_Loaded(object s, RoutedEventArgs e)
          {           
              // MenuContent
              this.MenuContent.RenderTransform = new CompositeTransform();
      
              // HideAnimation
              this.HideAnimation = new DoubleAnimation();
              this.HideAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(200));
              Storyboard.SetTarget(this.HideAnimation, this.MenuContent.RenderTransform);
              Storyboard.SetTargetProperty(this.HideAnimation, new PropertyPath("TranslateY"));
      
              // HideStoryboard
              this.HideStoryboard = new Storyboard();
              this.HideStoryboard.Children.Add(this.HideAnimation);
              this.HideStoryboard.Completed += delegate(object sender, EventArgs eventArgs)
              {
                  this.SettingMenu001.IsOpen = false;
                  this.ApplicationBar.IsVisible = true;
              };
          }
      }
      

    处理 Tap

    设计出显示ApplicationMenu的ShowApplicationMenu函式、隐藏ApplicationMenu的HideApplicationMenu函式之后,就可以来处理一些使用者的操作事件。

    首先来处理MenuMask的Tap事件。在ApplicationMenu显示的时候,如果用户点击到MenuContent之外的画面,系统必须要自动隐藏ApplicationMenu。而MenuMask的主要用途,就是使用一个透明的控件来覆盖MenuContent显示范围之外的区域,用来拦截MenuContent之外的所有Tap点击。开发人员只要在这个MenuMask的Tap事件处理函式中,呼叫先前设计的HideApplicationMenu函式,就可以完成这个自动隐藏ApplicationMenu的功能。

    • 程序代码(.XAML)

      <!--MenuMask-->
      <Rectangle x:Name="MenuMask" Grid.Row="0" Fill="Transparent" Tap="MenuMask_Tap">
      
      </Rectangle>           
      
    • 程序代码(.CS)

      // Handlers
      private void MenuMask_Tap(object sender, System.Windows.Input.GestureEventArgs e)
      {
          // Hide
          this.HideApplicationMenu();
      }
      

    处理 BackKey

    最后来处理手机的BackKey事件。在ApplicationMenu显示的时候,如果用户点击手机上的BackKey按钮,系统必须要自动隐藏ApplicationMenu。开发人员只要覆写PhoneApplicationPage的OnBackKeyPress方法,并且在其中加入如果ApplicationMenu正在显示,就呼叫HideApplicationMenu函式的条件判断,就可以完成这个自动隐藏ApplicationMenu的功能。

    • 程序代码(.CS)

      // Methods  
      protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
      {
          // Menu
          if (this.SettingMenu001.IsOpen == true)
          {
              // Hide
              this.HideApplicationMenu();
      
              // Cancel
              e.Cancel = true;
          }
      
          // Base
          base.OnBackKeyPress(e);
      }
      

    精炼 Code

    ApplicationMenu的功能实做,到目前为止已经可以在画面上,显示一个ApplicationMenu并且能够提供上述的种种功能设计,到此为止的相关程序代码放置在,范例方案中的ApplicationMenuSample001项目内。

    但是要重用ApplicationMenu这个功能,依照目前的程序代码去重用,还需要撰写数量不小的程序代码。为了能够更方便的重用ApplicationMenu功能,接着可以将ApplicationMenu功能精炼成为用户控件、扩充方法...等等更加方便的重用方式。在本篇文章中,选择将ApplicationMenu的功能,封装成为Popup类别的扩充方法,并且放置在范例方案中的ApplicationMenuSample002项目内。

    后续开发人员只需要引用这个Popup类别的扩充方法到项目后,接着按照先前「功能使用」段落中所介绍的使用方式,就可以重复在每个项目中,使用ApplicationMenu与使用者互动,用以提供使用者更好的使用体验。

    • 程序代码(.CS)

      public static class ApplicationMenuExtension
      {
          // Fields
          private static Dictionary<Popup, MenuStruct> _menuStructDictionary = new Dictionary<Popup, MenuStruct>();
      
      
          // Methods              
          public static void ShowApplicationMenu(this Popup popup)
          {
              #region Contracts
      
              if (popup == null) throw new ArgumentNullException();
      
              #endregion
      
              // MenuStruct
              var menuStruct = GetMenuStruct(popup);
              if (menuStruct == null) throw new InvalidOperationException();
      
              // MenuState
              if (menuStruct.MenuState != MenuState.Close) return;
              menuStruct.MenuState = MenuState.Change;
      
              // MenuBar
              menuStruct.MenuBar = (menuStruct.MenuHost.ApplicationBar != null ? (menuStruct.MenuHost.ApplicationBar.IsVisible == true ? menuStruct.MenuHost.ApplicationBar : null) : null);
      
              // Show
              if (menuStruct.MenuBar != null) menuStruct.MenuBar.IsVisible = false;
              popup.IsOpen = true;
              menuStruct.ShowStoryboard.Begin();
          }
      
          public static void HideApplicationMenu(this Popup popup)
          {
              #region Contracts
      
              if (popup == null) throw new ArgumentNullException();
      
              #endregion
      
              // MenuStruct
              var menuStruct = GetMenuStruct(popup);
              if (menuStruct == null) throw new InvalidOperationException();
      
              // MenuState
              if (menuStruct.MenuState != MenuState.Open) return;
              menuStruct.MenuState = MenuState.Change;
      
              // Hide            
              EventHandler completedDelegate = null;
              completedDelegate = delegate(object sender, EventArgs e)
              {
                  menuStruct.HideStoryboard.Completed -= completedDelegate;
                  popup.IsOpen = false;
                  if (menuStruct.MenuBar != null) menuStruct.MenuBar.IsVisible = true;
              };
              menuStruct.HideStoryboard.Completed += completedDelegate;
              menuStruct.HideStoryboard.Begin();
          }
      
      
          private static MenuStruct GetMenuStruct(Popup popup)
          {
              #region Contracts
      
              if (popup == null) throw new ArgumentNullException();
      
              #endregion
      
              // Cache
              if (_menuStructDictionary.ContainsKey(popup) == true)
              {
                  return UpdateMenuStruct(_menuStructDictionary[popup], popup);
              }
      
              // MenuRoot
              var menuRoot = new Grid();
              menuRoot.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
              menuRoot.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
      
              // MenuMask
              var menuMask = new Rectangle();
              menuMask.Fill = new SolidColorBrush(Colors.Transparent);
              Grid.SetRow(menuMask, 0);
              menuRoot.Children.Add(menuMask);
      
              // MenuContent
              var menuContent = popup.Child as FrameworkElement;
              if (menuContent == null) throw new InvalidOperationException();
              popup.Child = null;
              Grid.SetRow(menuContent, 1);
              menuRoot.Children.Add(menuContent);
              menuContent.RenderTransform = new CompositeTransform();
      
              // ShowAnimation
              var showAnimation = new DoubleAnimation();
              showAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(200));
              Storyboard.SetTarget(showAnimation, menuContent.RenderTransform);
              Storyboard.SetTargetProperty(showAnimation, new PropertyPath("TranslateY"));
      
              // ShowStoryboard
              var showStoryboard = new Storyboard();
              showStoryboard.Children.Add(showAnimation);
      
              // HideAnimation
              var hideAnimation = new DoubleAnimation();
              hideAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(200));
              Storyboard.SetTarget(hideAnimation, menuContent.RenderTransform);
              Storyboard.SetTargetProperty(hideAnimation, new PropertyPath("TranslateY"));
      
              // HideStoryboard
              var hideStoryboard = new Storyboard();
              hideStoryboard.Children.Add(hideAnimation);
      
              // Struct
              var menuStruct = new MenuStruct();
              menuStruct.MenuState = (popup.IsOpen == true ? MenuState.Open : MenuState.Close);
              menuStruct.MenuHost = GetMenuHost(popup);
              menuStruct.MenuRoot = menuRoot;
              menuStruct.MenuContent = menuContent;
              menuStruct.ShowStoryboard = showStoryboard;
              menuStruct.ShowAnimation = showAnimation;
              menuStruct.HideStoryboard = hideStoryboard;
              menuStruct.HideAnimation = hideAnimation;
      
              // Events 
              menuMask.Tap += delegate(object sender, System.Windows.Input.GestureEventArgs e) { popup.HideApplicationMenu(); };
              showStoryboard.Completed += delegate(object sender, EventArgs e) { menuStruct.MenuState = MenuState.Open; };
              hideStoryboard.Completed += delegate(object sender, EventArgs e) { menuStruct.MenuState = MenuState.Close; };
              menuStruct.MenuHost.BackKeyPress += delegate(object sender, System.ComponentModel.CancelEventArgs e)
              {
                  if (menuStruct.MenuState != MenuState.Close)
                  {
                      popup.HideApplicationMenu();
                      e.Cancel = true;
                  }
              };
      
              // Attach        
              popup.Child = menuStruct.MenuRoot;
              _menuStructDictionary.Add(popup, menuStruct);
      
              // Return
              return UpdateMenuStruct(menuStruct, popup);
          }
      
          private static MenuStruct UpdateMenuStruct(MenuStruct menuStruct, Popup popup)
          {
              #region Contracts
      
              if (menuStruct == null) throw new ArgumentNullException();
              if (popup == null) throw new ArgumentNullException();
      
              #endregion
      
              // Update
              if (menuStruct.MenuRoot.ActualWidth != Application.Current.Host.Content.ActualWidth)
              {
                  // Layout
                  popup.IsOpen = true;
                  popup.UpdateLayout();
      
                  // MenuRoot
                  menuStruct.MenuRoot.Height = Application.Current.Host.Content.ActualHeight - (SystemTray.IsVisible == true ? 32 : 0);
                  menuStruct.MenuRoot.Width = Application.Current.Host.Content.ActualWidth;
      
                  // ShowAnimation
                  menuStruct.ShowAnimation.From = menuStruct.MenuContent.ActualHeight;
                  menuStruct.ShowAnimation.To = 0;
      
                  // HideAnimation
                  menuStruct.HideAnimation.From = 0;
                  menuStruct.HideAnimation.To = menuStruct.MenuContent.ActualHeight;
              }
      
              // Return
              return menuStruct;
          }
      
          private static PhoneApplicationPage GetMenuHost(Popup popup)
          {
              #region Contracts
      
              if (popup == null) throw new ArgumentNullException();
      
              #endregion
      
              // Variables
              PhoneApplicationPage menuHost = null;
              FrameworkElement element = popup;
      
              // Search            
              while (true)
              {
                  // Element
                  element = element.Parent as FrameworkElement;
                  if (element == null) throw new InvalidOperationException();
      
                  // MenuHost
                  menuHost = element as PhoneApplicationPage;
                  if (menuHost != null) break;
              }
      
              // Return
              return menuHost;
          }
      
      
          // Enumerations
          private enum MenuState
          {
              Open,
              Close,
              Change,
          }
      
      
          // Class
          private class MenuStruct
          {
              // Properties
              public MenuState MenuState { get; set; }
      
              public PhoneApplicationPage MenuHost { get; set; }
      
              public IApplicationBar MenuBar { get; set; }
      
      
              public Grid MenuRoot { get; set; }
      
              public FrameworkElement MenuContent { get; set; }
      
      
              public Storyboard ShowStoryboard { get; set; }
      
              public DoubleAnimation ShowAnimation { get; set; }
      
      
              public Storyboard HideStoryboard { get; set; }
      
              public DoubleAnimation HideAnimation { get; set; }
          }
      }
      

    参考数据

    Nami 的 Windows Phone 7 日记 - 可重用的弹出框容器

  • 相关阅读:
    system函数
    如何:配置 ClickOnce 信任提示行为
    linux中shell变量$#,$@,$0,$1,$2的含义解释 (转载)
    C/C++中如何在main()函数之前执行一条语句?
    循环小数表示法
    struct/class等内存字节对齐问题详解
    malloc(0)
    C语言实现程序跳转到绝对地址0x100000处执行
    嵌入式程序设计中C/C++代码的优化
    backtrace和backtrace_symbols
  • 原文地址:https://www.cnblogs.com/clark159/p/3571993.html
Copyright © 2020-2023  润新知