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

原文:[WP8] 使用ApplicationMenu与使用者互动

[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 日记 - 可重用的弹出框容器

时间: 2025-01-25 03:19:06

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

魔鬼藏在细节里:互动按钮大小事

当一个产品完成核心部分的需求之后,我们就可以慢慢准备开始研究细节的问题 一个产品几乎每个层面都可以谈论细节:其中还包括表面上看得到的,以及表面上看不到的.表面上看得到的细节很简单,花时间去做.去尝试.去犯错.去修正就好了.而看不到的细节诸如产品定位.使用者体验等等,往往依靠不长期经验的累积.研究与得到使用者反馈外,很难清楚的明了到底哪边该怎么去制作与修正. 笔者曾经做过一些平面设计,深刻的了解到「东西如果会被别人拿着摆着看细节,那么每个部分的细节都必须细心追求」.一张海报传单印刷出去,很容易就被

魔鬼藏在细节里:网页互动按钮大小事

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 当一个产品完成核心部分的需求之后,我们就可以慢慢准备开始研究细节的问题. 一个产品几乎每个层面都可以谈论细节:其中还包括表面上看得到的,以及表面上看不到的.表面上看得到的细节很简单,花时间去做.去尝试.去犯错.去修正就好了.而看不到的细节诸如产品定位.用户体验等等,往往依靠不长期经验的累积.研究与得到使用者反馈外,很难清楚的明了到底哪边该怎么

智能关闭系统中无用的服务

把下面的代码拷到一个记事本,后缀.bat.另存后执行就行了. =====================================rem WINDOS XP 服务优化批处理文件!!@pause @rem Alerter@rem 微软: 通知选取的使用者及计算机系统管理警示.如果停止这个服务,使用系统管理@rem 警示的程序将不会收到通知.@rem 补充: 一般家用计算机根本不需要传送或接收计算机系统管理来的警示(Administrative Alerts),除非你的计算机用在局域网络上@r

HTML 4.0 语法表单标签

  [表单的用途] 对于一般的网页设计初学者而言,表单功能其实并不常用,因为表单通常必须配合着CGI.JAVA Script程式或是ASP程式来运作,不然表单单独存在的意义并不大.不过,一旦有机会将表单运用到网页中时,您的网页将摆脱单向呈现,而开始迈入和使用者互动的阶段喔! 本单元纯粹以介绍各式表单为主,至于一些CGI或ASP观念在此我就不提出了,因为提供零碎的观念,倒不如去看看专门介绍CGI的书籍来的妥当. [各种输入类型] 文字输入列:每个表单之所以会有不同的类型,原因就在于TYPE="表单

台湾设计师谈资讯与视觉设计的绝妙平衡

作者陈威帆是台湾科技博客 Desiring Clicks 的创始人,也是一名 UI/UE 设计师.本文是陈威帆向爱范儿投递的稿件,主要谈资讯获取方式与设计之间的关系.Desiring Clicks 专注于介绍用户界面.用户体验.视觉设计和资讯架构的科技媒体,感兴趣的朋友可以关注作者本人的 Facebook或他的网站. 所谓的讯息就是为了沟通而产生,而使用者界面就是承载着资讯的载体.使用者透过界面,和各式各样的系统进行五花八门的资讯交换.资讯可能由使用者产生,例如某个人在 Twitter 上发了一

哪些不必要的服务可以关闭

  关闭不必要的服务 Alerter 微软: 通知选取的使用者及计算机系统管理警示.如果停止这个服务,使用系统管理警示的程序将不会 收到通知.如果停用这个服务,所有依存于它的服务将无法启动. 补充: 一般家用计算机根本不需要传送或接收计算机系统管理来的警示(Administrative Alerts),除非 你的计算机用在局域网络上 依存: Workstation 建议: 禁用 Application Layer Gateway Service 微软: 提供因特网联机共享和因特网联机防火墙的第三

Windows服务器安全配置_Windows2003

服务器安全配置(只针对WIN系统) 一. 原则关掉所有不使用的服务,不安装所有与服务器无关的软件,打好所有补丁 修改3389 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Terminal Server\Wds\Repwd\Tds\Tcp, 看到那个PortNumber没有?0xd3d,这个是16进制,就是3389啦,我改XXXX这个值是RDP(远程桌面协议)的默认值,也就是说用来配置以后新建的RDP服务的,要改已经建立的RDP服务,我们

《Android应用开发从入门到精通》——第2章,第2.3节创建第一个Android项目

2.3 创建第一个Android项目 Android应用开发从入门到精通 2.3.1 创建新项目 打开Eclipse,选择 File→New→Android Application Project.如果没有Android Application Project这一条,这时可以选择Other找到Android Application Project. 进入New Project对话框,分别进行如下设置,如图2.7所示. 词条解释如下. Project Name 包含这个项目的资料夹的名称. App

Web 2.0的迷思与真实

中介交易 SEO诊断淘宝客 站长团购 云主机 技术大厅 我连续好几个月,用不同的角度.层级及方法,说明了「小圈圈」这种在 Web 2.0 时代尤其需要被重视的人际关系.无独有偶,黄彦达最近在<数字之墙>中,也明白指出(人与人的)「关系」纔是 Web 2.0 的重点. 不过,在小圈圈.在人际关系之外,许多人口中的 Web 2.0 还充斥着各式各样的迷思,觉得 Web 2.0 就是如何如何,或者如何如何即能成就 Web 2.0:我在最近的一场公开演说中,恰好准备了一份「额外附赠」的讲题,谈论到 W