本篇文章介紹如何實做ApplicationMenu,用以在點選ApplicationBar的選項之後,在同一個頁面顯示選單,為自己留個紀錄也希望能幫助到有需要的開發人員。
[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; } } }
參考資料
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。