Windows 8 Metro 应用开发入门(三):工具栏和对话框
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这里就不再介绍了,感兴趣的可以去查找相关资料。