Metro UI與Windows Phone一樣在提供了布局在屏幕下文的應用程序工具欄BottomAppBar,由於平板設備特有的應用,Metro UI還提供了布局在屏幕上方的導航欄TopAppBar。另外,Metro UI還提供了獨特的對話框。這一章我們來介紹一下工具欄與導航欄的應用,最后再介紹一下彈出對話框。
應用程序工具欄BottomAppBar默認是隱藏在屏幕的下方,當用手上屏幕上向上滑動或是在屏幕上點擊鼠標右鍵,BottomAppBar會從下方滑出。BottomAppBar和TopAppBar依托於Page類,如下:
public class Page { public AppBar BottomAppBar { get; set; } public AppBar TopAppBar { get; set; } //... }
無論是上方的導航欄還是下方的工具欄,都是AppBar類型,我們來看一下AppBar的定義:
public class AppBar : ContentControl { public bool IsOpen { get; set; } public static DependencyProperty IsOpenProperty { get; } public bool IsSticky { get; set; } public static DependencyProperty IsStickyProperty { get; } public event EventHandler<object> Closed; public event EventHandler<object> Opened; }
IsOpen 指明工具欄是否可見
IsSticky 如果工具欄可見,指明工具欄是否一直可見,即使失去焦點時
Closed 工具欄完全退出后觸發事件
Opened 工具欄完全打開后觸發事件
AppBar的定義非常簡單,如果要使用它,還得自定義其展示視圖,不過添加其子元素很方便。使用BottomAppBar有兩種方式:XAML和后台Code。
(1)XAML方式
如下XAML我們向Page添加擁有五個按鈕的下方工具欄:
<Page.BottomAppBar> <AppBar IsSticky="True"> <Grid> <StackPanel Orientation="Horizontal"> <Button x:Name="btn1" Style="{StaticResource AppBarButtonStyle}" AutomationProperties.Name="添加"/> <Button x:Name="btn2" Style="{StaticResource EditAppBarButtonStyle}" Click="btn2_Click_1"/> <Button x:Name="btn3" Style="{StaticResource SaveAppBarButtonStyle}" Click="btn3_Click_1"/> <Button x:Name="btn4" Style="{StaticResource DeleteAppBarButtonStyle}"/> <Button x:Name="btn5" Style="{StaticResource DiscardAppBarButtonStyle}"/> </StackPanel> </Grid> </AppBar> </Page.BottomAppBar>
IsSticky="True"表明當工具欄打開后讓它一直顯示在屏幕上,在工具欄內先是放置了一個Grid,其內置一個StackPanel,最后是在內部擺放了5個按鈕,並且還為按鈕btn1設置了名字AutomationProperties.Name="添加",如果要讓按鈕響應事件,可以為每個按鈕注冊事件處理程序,就像btn2和btn3一樣注冊了Click事件。在創建Metro應用程序項目的時候,在Common文件夾下有一個默認的樣式StandardStyles.xaml,里面有一系列的工具欄按鈕樣式,但是被注釋掉的,我們可以取消注釋,然后在我們的程序中使用它們,大概在StandardStyles.xaml文檔的249行開始,有一個基本樣式AppBarButtonStyle,其他的如EditAppBarButtonStyle和SaveAppBarButtonStyle等都是基於AppBarButtonStyle進行實現的,上面XAML中用到了AppBarButtonStyle、EditAppBarButtonStyle、SaveAppBarButtonStyle、DeleteAppBarButtonStyle等,我們來看一下上面XAML最終展示的效果:
(2)Code方式使用AppBar
代碼創建工具欄只需要實例化AppBar且向其添加子元素即可,最后將AppBar實例給當前頁的上/下方工具欄,如下代碼:
private void CreateAppBar() { AppBar bottomBar = new AppBar(); StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal }; Button btn1 = new Button(); btn1.Style = (Style)App.Current.Resources["AddAppBarButtonStyle"]; sp.Children.Add(btn1); Button btn2 = new Button(); btn2.Style = (Style)App.Current.Resources["EditAppBarButtonStyle"]; sp.Children.Add(btn2); bottomBar.Content = sp; this.BottomAppBar = bottomBar; }
TopAppBar與BottomAppBar使用方法相同,無非就是名字不一樣,而對於Page來說,TopAppBar在上方,更多的時候稱為導航欄,BottomAppBar在下方,稱為應用程序工具欄。在這里我們只展示如何用XAML創建一個導航欄TopAppBar:
<Page.TopAppBar> <AppBar> <Grid> <StackPanel Orientation="Horizontal"> <Button x:Name="btnTop1" Style="{StaticResource AddAppBarButtonStyle}" AutomationProperties.Name="添加s"/> <Button x:Name="btnTop2" Style="{StaticResource EditAppBarButtonStyle}"/> <Button x:Name="btnTop3" Style="{StaticResource SaveAppBarButtonStyle}"/> <Button x:Name="btnTop4" Style="{StaticResource DeleteAppBarButtonStyle}"/> <Button x:Name="btnTop5" Style="{StaticResource DiscardAppBarButtonStyle}"/> </StackPanel> </Grid> </AppBar> </Page.TopAppBar>
只要注意一點即可,那就是以Page.TopAppBar指明該AppBar為上方的導航欄,其他與BottomAppBar完全一樣,效果如下:
這一次我們既指定了按鈕的Content,又指定了其文字下標。
以上我們討論的都是使用Metro應用程序項目默認的模板樣式,如果你覺得上面的樣式太單調,當然可以自定義按鈕樣式,比如使用圖標,甚至你可以使用動畫效果,接下來我們看看如何自定義樣式。
在前面的討論中我們知道AppBar是一個容器控件,並且只能包含一個子元素,所以我們在AppBar內放置一個Grid作為布局父控件,然后再將相應的按鈕元素分配到Grid的各個單元格中。如下我們定義了一個背景具有漸變效果的Grid,且放置了三個按鈕的下方應用程序工具欄,如圖:
這一次我們並沒有使用StandardStyles.xaml中的樣式,只是簡單為按鈕放置了一個圖標,XAML部分:
<Page.BottomAppBar> <AppBar IsSticky="True"> <Grid Margin="0" > <Grid.Background> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#B24FF53E"/> <GradientStop Color="#B2F9F6F3" Offset="0.6"/> <GradientStop Color="#B2FB8421" Offset="1"/> </LinearGradientBrush> </Grid.Background> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition Width="100"/> <ColumnDefinition Width="100"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Button x:Name="btn1" Grid.Column="0"> <Image Source="Assets/user.png"/> </Button> <Button x:Name="btn2" Grid.Column="1"> <Image Source="Assets/Cycle Racer.png"/> </Button> <Button x:Name="btn3" Grid.Column="2"> <Image Source="Assets/Add to Cart.png"/> </Button> </Grid> </AppBar> </Page.BottomAppBar>
這里當然也可以使用StackPanel等其他布局控件,你完全可以按照你自己的要求設計出各種各樣的Metro UI 風格的工具欄。
使用工具欄的目的就是要觸發一定的動作,所以要想讓工具欄里的按鈕響應事件,則必須要為每個按鈕注冊事件處理程序。比如第1節示例中的btn2和btn3:
<Button x:Name="btn2" Style="{StaticResource EditAppBarButtonStyle}" Click="btn2_Click_1"/> <Button x:Name="btn3" Style="{StaticResource SaveAppBarButtonStyle}" Click="btn3_Click_1"/>
當為按鈕注冊Click事件后,在后台的事件處理程序中就可以執行相應的操作,如下:
private void btn2_Click_1(object sender, RoutedEventArgs e) { //Do Something } private void btn3_Click_1(object sender, RoutedEventArgs e) { //Do Something }
我們知道Silverlight是基於異步編程模型,同樣在Metro App中也是基於異步編程模型,所以對於有耗時計算的,建議在工具欄的按鈕處理程序中使用異步編程,這樣不影響UI的流暢度,也是微軟一直鼓勵的做法。當然,在必要的時候或者是你喜歡的時候,也可以使用async/await來實現異步編程的不一樣體驗,盡管在.NET Framework4.5中增強了對異步的實現,但還是建議使用異步處理。在大家都很忙且有點急促的今天,給用戶一個“不用等待”的體現,有什么不好呢?
其實關於應用程序工具欄和導航欄的使用,微軟還是建議不要在主視圖中使用太多的按鈕,盡量把命令按鈕放在工具欄中,為MetroUI提供一致的用戶體驗。
以往的開發中我們常常會彈出一個模態對話框來等待用戶的響應,silverlight中是使用MessageBox,但在Metro UI中提供了一個全新的對話框MessageDialog,它是以異步方式彈出,你當然可以使用await來等待用戶的響應,它還有一個更炫耀的功能就是可以在一個對話框讓指定多個命令!你在以往往的開發中如果想實現類似的功能,是不是得自己實現?MessageDialog只提供了一個彈出方法:
public IAsyncOperation<IUICommand> ShowAsync();
(1)只有提示消息對話框
使用一個構造函數的MessageDialog可以實例化一個對話框,然后以異步方式打開它,此時它會默認顯示一個“關閉”按鈕:
MessageDialog md = new MessageDialog("保存成功,請注意查收。"); md.ShowAsync();
效果圖:
(2)帶有標題的對話框
MessageDialog md = new MessageDialog("保存成功,請注意查收。", "提示");
效果圖:
(3)指定自定義命令的對話框
MessageDialog類有一個重要的成員,可以在當前對話框中呈現多個命令按鈕:
public IList<IUICommand> Commands { get; }
可以看到,只要你願意,你可以向Commands注入多個命令,有意思吧?有一個已經實現了接口IUICommand的類UICommand,這個類就是對命令的處理,它不僅接收一個標簽文本,還可以接收一個處理程序的委托UICommandInvokedHandler,UICommand類的構造函數有四個:
public UICommand(); public UICommand(string label); public UICommand(string label, UICommandInvokedHandler action); public UICommand(string label, UICommandInvokedHandler action, object commandId);
來看一下如何注冊命令的處理程序:
MessageDialog md = new MessageDialog("確定要提交當前數據嗎?", "詢問"); md.Commands.Add(new UICommand("確定", cmd => { Debug.WriteLine("確定"); })); md.Commands.Add(new UICommand("放棄", cmd => { Debug.WriteLine("放棄"); })); md.ShowAsync();
效果圖:
如果你覺得上面的處理還不過癮,請看下面。
(4)使用具有命令Id的命令
細心的你一定能發現上面UICommand的最后一個構造函數:
public UICommand(string label, UICommandInvokedHandler action, object commandId);
最后一個參數可以指定命令的Id,也就是說,在下文中我們可以根據這個Id來進行不同的操作,這個object類型的Id允許你給它任意類型的數據。下面的代碼我們取消了注冊命令處理程序,而是為指定了命令Id:
MessageDialog md = new MessageDialog("確定要提交當前數據嗎?", "詢問"); md.Commands.Add(new UICommand("確定", null, 0)); md.Commands.Add(new UICommand("放棄", null, 1)); md.Commands.Add(new UICommand("幫組", null, 2)); md.DefaultCommandIndex = 0; md.CancelCommandIndex = 1; var flg = await md.ShowAsync(); //var flg = md.ShowAsync(); switch (flg.Id) { case 0: //Do Something break; case 1: //Do Something break; case 2: //Go to Help break; default: break; }
效果圖:
在前面我們看到MessageDialog是以異步方式打開,所以我們可以根據需要獲取ShowAsync()的響應結果,根據命令Id執行進一步的操作。使用DefaultCommandIndex指定當我們按下Enter鍵時響應的按鈕,CancelCommandIndex指定當按下Esc鍵時應的按鈕。
很遺憾的是MessageDialoge不能定義對話框的樣式, 如何想創建更個性的對話框,可以使用Popup 來模擬對話框,關於Popup這里就不再介紹了,感興趣的可以去查找相關資料。