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

范例下载

范例程序代码:点此下载

功能说明

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

  • 功能画面

使用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();
    }
    }
  • 执行结果

当然,如果一个页面中有超过一个以上的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>
  • 执行画面

功能设计

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

选择 Popup

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

  • 程序代码(.XAML)

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

加入 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>
  • 执行画面

但是当遇到开发情景,需要使用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>
  • 执行画面

为了处理使用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>
  • 执行画面

设计 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的长宽,会依照内容的实际长宽来呈现在整个页面底端。

  • 示意画面

  • 程序代码(.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所定义的显示位置。

  • 示意画面

  • 程序代码(.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,那滑出动画的设计也就没有意义了。

  • 示意画面

  • 程序代码(.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 日记 - 可重用的弹出框容器

[WP8] 使用ApplicationMenu与使用者互动的更多相关文章

  1. oGrid 介绍如何从 server 取的资料

    接着前次 oGrid 初探,其中有介绍如何操作local 资料,本次介绍如何从 server 取的资料. 依照 MVC 架构原理以及一条小龙本身经验来看,一个好的架构,必须要有着分工明确的设计层次,让 ...

  2. shell下的作业管理[转]

    作业管理 举例来说,我们在登陆 bash 后, 想要一边复制文件.一边进行数据搜寻.一边进行编译,还可以一边进行 vi 程序撰写! 当然我们可以重复登陆那六个文字介面的终端机环境中,不过,能不能在一个 ...

  3. 第十七章、程序管理与 SELinux 初探 工作管理 (job control)

    工作管理 (job control) 这个工作管理 (job control) 是用在 bash 环境下的,也就是说:『当我们登陆系统取得 bash shell 之后,在单一终端机介面下同时进行多个工 ...

  4. bug终结者 团队作业第二周

    bug终结者 团队作业第二周 我们小组选取游戏"开心消消乐",回答问题: 1. 此类软件是什么时候开始出现的, 这些软件是怎么说服你(陌生人)成为他们的用户的? 他们的目标都是盈利 ...

  5. Linux - 工作管理(job control),jobs,fg,bg,kill

    什么是工作管理? 『进行工作管理的行为中, 其实每个工作都是目前 bash 的子程序,亦即彼此之间是有相关性的. 我们无法以 job control 的方式由 tty1 的环境去管理 tty2 的 b ...

  6. [转载]Cortana 设计指导方针

    来源:@微软中国MSDN,源地址:http://weibo.com/p/1001603898586285925224 使用语音命令,延伸 Cortana 与您的应用程序所提供的功能.启动应用程序,启动 ...

  7. 摘要JSR168 PORLET标准手册汉化整理

    本规范汉化资源搜集整理于网上并由我作了些修改和添加,主要为适应大陆的语辞.用语及其他未译之处. 由于本人于水平有限,如有错误,请各位高手指正:若有高见,希望不吝言辞,同为中国开源作项献. 特此严重感谢 ...

  8. WPF中的事件列表 .

    以下是WPF中的常见事件汇总表(按字母排序),翻译不见得准确,但希望对你有用. 事件 描述 Annotation.AnchorChanged 新增.移除或修改 Anchor 元素时发生. Annota ...

  9. (转)Oracle存储过程中的事务

    本文转载自:http://www.cnblogs.com/linjiqin/archive/2011/04/18/2019990.html 1.事务用于确保数据的一致性,由一组相关的DML语句组成,该 ...

随机推荐

  1. ubuntu系统从中文环境改成英文环境

      我们在 安装ubuntu server版的时候,有人可能选择了中文环境安装,因为那样好设置时区等参数,可是安装好了后,运行某些命令的时候会有中文乱码提示,看起很是头蛋疼, 我们就需要将其改成英文环 ...

  2. ASP.NET 如何发现问题的方法

    1.打开IntelliTrace,查看遇到的问题: 2.打开IE8以上,按F12,启动js调试,查看遇到的错误:

  3. windbg加载sos.dll

    SOS.dll 中提供的 Son of Strike 扩展 (SOS),用于调试 WinDbg 中的托管代码.在启动了调试程序并将其附加到托管进程(或加载故障转储) .load C:\Windows\ ...

  4. java框架篇---spring aop两种配置方式

    第一种:注解配置AOP 注解配置AOP(使用 AspectJ 类库实现的),大致分为三步: 1. 使用注解@Aspect来定义一个切面,在切面中定义切入点(@Pointcut),通知类型(@Befor ...

  5. MySQL不能插入中文字符及中文字符乱码问题

    MySQL的默认编码是Latin1,不支持中文,要支持中午需要把数据库的默认编码修改为gbk或者utf8.在安装后MySQL之后,它的配置文件不是很给力,不知道你们的是不是,反正我的是! 开始插入中文 ...

  6. linux VM复制多个IP配置出错的处理

    device eth0 does not seem to be present, delaying initialization (2012-09-13 21:16:38) 转载▼ 标签: 杂谈   ...

  7. Windows及Linux平台下的计时函数总结

    本文对Windows及Linux平台下常用的计时函数进行总结,包括精度为秒.毫秒.微秒三种精度的各种函数.比如Window平台下特有的Windows API函数GetTickCount().timeG ...

  8. ActiveMQ学习(四)——应用程序接口

    在 Java 里有 JMS的多个实现.其中 apache 下的 ActiveMQ就是不错的选择. 用 ActiveMQ最好还是了解下 JMS JMS 公共 点对点域 发布/订阅域 Connection ...

  9. 点餐系统web版功能需求

                餐厅到店点餐系统需求分析 (版本v1.0.0)               成文信息 主题词: 需求分析 作  者: 14商软ETC 文档类别: 审  核: 批  准: 文档性 ...

  10. List<?>和List<T>的区别?

    出自:https://www.zhihu.com/question/31429113