《C#高级编程(第8版)》之用C# 2012和.NET 4.5编写Windows 8样式应用程序技巧

 

本文介绍Windows 8应用程序的设计原则和WPF不支持的一些特殊XAML功能,演示操作Windows 8应用程序的几个方面,例如响应布局的变化,使用Windows Runtime存储API和文件选择器读写文件,使用协议与其他应用程序通信等。
 
一  Windows 8的现代UI设计
      Windows 8应用程序中第一个引人注目的地方是,它们看起来与桌面应用程序不同。UI设计着重强调了一点,就是用户在使用应用程序时应感到愉悦和享受。Windows 8应用程序如此注重设计,源自一些旧有的理念。其中一个理念是瑞士风格的图形设计,这是20世纪50年代提出的,强调干净(不凌乱)和易于理解。例如,飞机场和火车站的信号就基于这个概念,用户可以尽快处理信息。
     德国著名的包浩斯(Bauhaus)学校是现代UI设计的另一个发源地,它在1919年—1933年间极有影响力。这所学院的目标是将艺术、工艺和技术结合起来,根据功能而不是装饰来设计—— 没有任何多余的修饰。
第三个基础是电影艺术定义的动作。动画是给应用程序带来生气的重要工具,Windows 8框架提供了丰富的动画功能,使用户在传递信息时具有身历其境的体验,把使用应用程序变成一种享受。
1.1  内容,不是chrome设计
      设计Windows 8应用程序的指导原则是注重内容,这意味着,在任何时刻,都只向用户显示他们需要的信息,而不是用他们不需要的信息(即chrome,如菜单、工具栏等)。用户打开Internet Explorer时,内容就会占满整个视图。菜单是隐藏的,除非用户显式激活了它。
例如,网页占满了整个屏幕,用户可以快速搜寻需要的内容,而没有各种菜单和工具栏的打扰。图1显示了一个天气应用程序的主视图。注意大图形更便于用户迅速把注意力集中在需要的信息上。

 
图1
      当然,在应用程序内,用户也可以改变设置,使用命令。要修改设置,可以使用新的Charms工具栏(Charms bar)。要激活Charms工具栏,用户可以单击屏幕的右边界,调用改变应用程序设置所需的控件。
命令放在屏幕顶部或底部的app工具栏中,要激活这些命令,用户可以使用类似的方式,即单击屏幕的顶部或底部,打开它们。图2显示了Windows Store命令栏。在该例中,命令位于屏幕顶部。
 
图2
1.2  快速流畅
      快速流畅是Windows 8应用程序的另一个重要原则。在传统的用户界面中使用鼠标时,用户习惯于有一点儿延迟。同样,单击按钮或在屏幕上移动某些对象时,也习惯于有一点儿延迟。这种延迟在触摸模式下是不可接受的。如果在触摸后屏幕没有立即产生什么反映,或者UI没有响应,用户的体验就会很差。
新的Windows Runtime规定,如果一个方法的执行时间超过50ms,就只能异步执行。在.NET框架中,许多API调用既有同步版本,又有异步版本。
      因为同步编程比异步编程更容易创建,所以一般使用API的同步版本。使用C# 5.0中新的异步功能,以及async和await关键字,异步API调用也非常容易使用。第13章介绍了这些新关键字的所有细节。除了使用异步API之外,还应在应用程序中为执行很长时间的任务创建异步API。
      异步编程仅是实现快速流畅这一原则的一部分。如前所述,Windows 8能很好地支持动画,因为它们以自然、真实的方式把用户体验联系在一起,且不会分散注意力。内置控件已经有动画,允许编写出流畅的切换效果,而不是很突然的变化。使用这些内置控件,就不需要定义自定义动画,但如果愿意,也是可以定义自定义动画的。
1.3  可读性
可读性对于任何应用程序都很重要,而Windows 8提供了完整的样式应用规则集。这些规则覆盖了用户体验的所有排版方面,包括可读的字体、颜色和字母间距。例如,Segoe UI字体应用于UI元素(按钮、日期选择器),Calibri字体用于用户读写的文本,Cambria字体用于大文本块。
二  示例应用程序的核心功能
      本节开发的Windows 8示例应用程序用于创建菜单卡。后面看到的菜单和图片都来自我妻子在维也纳市中心开的饭店http://www.kantine.at,欢迎读者光临该饭店。
在该应用程序中,饭店可以创建菜单卡,例如早餐、午餐卡和汤羹卡等。通过这个功能,应用程序使用XAML和C#获得用户的信息,以写入数据,处理菜单卡的图像,以及与应用程序相关的其他任务。
示例应用程序的创建从Windows Store类别中的Blank App (XAML)模板开始,如图3所示。
 
图3
2.1  文件和目录
      在根据模板创建的项目中,解决方案包含几个目录和一些文件。Assets目录包含应用程序的徽标(Logo)图像和一个闪屏。Common目录包含从模板中创建和使用的标准样式和实用工具类。在Blank App (XAML)模板中,Common目录只包含样式(文件名为StandardStyles.xaml)。在给项目添加使用项模板的其他功能时,添加特性类。项目中最重要的文件是App.xaml及其代码文件App.xaml.cs,MainPage.xaml及其代码文件MainPage.xaml.cs,以及Package.appxmanifest。XAML和代码文件非常类似于第35章中WPF的结构。
Package.appxmanifest 是一个XML文件,描述了应用程序的打包和功能。用Visual Studio打开这个文件,会打开清单设计器Manifest Designer,如图4所示。其中定义了应用程序名、徽标图像和闪屏。图像所需的像素尺寸显示在这个编辑器中。徽标需要150×150像素,如果应用程序还支持宽徽标(用户可以选择它),则宽徽标需要310×150像素。闪屏需要620×300像素。可以添加PNG或JPG文件。
应用程序的入口点是App类。在这个类中实例化了主页。除了UI的定义之外,也使用该软件包指定了功能和声明。在Capabilities选项卡,应用程序指定是否希望访问麦克风或网络摄像头等设备。在Windows Store中安装应用程序时,会告诉用户应用程序有什么需求。如果没有声明,应用程序就不能使用这些设备。在Declarations选项卡,应用程序声明了它支持的功能,例如,它是否可用于搜索系统;或者它是否提供共享目标,以允许其他应用程序为它提供一些数据。
下面给应用程序添加一些页面。
 
图4
2.2  应用程序数据
      对于要在UI中使用的数据,应用程序在DataModel子目录中定义了几个类型。类MenuCard(代码文件MenuCard/DataModel/MenuCard.cs)表示包含应用程序主要数据的菜单卡。这个类定义了用于显示的属性Title、Description和Image。与用于数据绑定的所有类一样,MenuCard也派生自基类BindingBase。BindingBase提供了INotifyPropertyChanged的实现代码。SetProperty方法用属性的set访问器调用,由这个基类实现,用于更改通知。这个基类现在还不存在,3.3小节会使用Visual Studio项模板来创建它:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Wrox.Win8.Common;

namespace Wrox.Win8.DataModel
{
public class MenuCard : BindableBase
{
private string title;
public string Titled
{
get { return title; }
set
{
SetProperty(ref title, value);
SetDirty();
}
}

private string description;
public string Description
{
get { return description; }
set
{
SetProperty(ref description, value);
SetDirty();
}
}

private ImageSource image;
public ImageSource Image
{
get { return image; }
set { SetProperty(ref image, value); }
}

private string imagePath;
public string ImagePath
{
get { return imagePath; }
set { imagePath = value; }
}

public void SetDirty()
{
IsDirty = true;
}
public void ClearDirty()
{
IsDirty = false;
}
public bool IsDirty { get; private set; }

private readonly ICollection<MenuItem> menuItems =
new ObservableCollection<MenuItem>();
public ICollection<MenuItem> MenuItems
{
get { return menuItems; }
}

public void RestoreReferences()
{
foreach (var menuItem in MenuItems)
{
menuItem.MenuCard = this;
}
}

public override string ToString()
{
return Title;
}
}
}

        包含在MenuCard中的类MenuItem(代码文件MenuCard/DataModel/MenuItem.cs)还定义了带有更改通知的简单属性:
using Wrox.Win8.Common;

namespace Wrox.Win8.DataModel
{
public class MenuItem : BindableBase
{
private string text;
public string Text
{
get { return text; }
set
{
SetProperty(ref text, value);
SetDirty();
}
}

private void SetDirty()
{
if (MenuCard != null)
{
MenuCard.SetDirty();
}
}

private double price;
public double Price
{
get { return price; }
set
{
SetProperty(ref price, value);
SetDirty();
}
}

public MenuCard MenuCard { get; set; }

}
}

类AddMenuCardInfo(代码文件MenuCard/DataModel/AddMenuCardInfo.cs)用于创建新菜单卡。这个类也是用于数据绑定的简单类型

using Windows.UI.Xaml.Media;
using Wrox.Win8.Common;

namespace Wrox.Win8.DataModel
{
public class AddMenuCardInfo : BindableBase
{
private string title;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}

private string description;
public string Description
{
get { return description; }
set { SetProperty(ref description, value); }
}

private ImageSource image;
public ImageSource Image
{
get { return image; }
set { SetProperty(ref image, value); }
}

private string imageFileName;
public string ImageFileName
{
get { return imageFileName; }
set { SetProperty(ref imageFileName, value); }
}
}
}

    类MenuCardFactory(代码文件MenuCard/DataModel/MenuCardFactory.cs)独立使用,可返回一个菜单卡列表。方法InitMenuCards用于初始化该集合,并把ObservableCollection<MenuCard>赋予cards变量:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace Wrox.Win8.DataModel
{
public class MenuCardFactory
{
private ICollection<MenuCard> cards;
public ICollection<MenuCard> Cards
{
get
{
return cards;
}
}

public void InitMenuCards(IEnumerable<MenuCard> menuCards)
{
cards = new ObservableCollection<MenuCard>(menuCards);
}

private static MenuCardFactory instance = null;
public static MenuCardFactory Instance
{
get
{
return instance ?? (instance = new MenuCardFactory());
}
}
}
}

       这个应用程序用于创建菜单卡,但在第一次启动时,最好向用户显示一些初始的菜单卡。要创建示例数据,可使用MenuCardDataFactory类中的GetSampleMenuCards方法返回一组填充了一些菜单的菜单卡。示例菜单卡的图像存储在Assets文件夹中,从该文件夹中引用,例如Breakfast.jpg:
public static ObservableCollection<MenuCard> GetSampleMenuCards()
{
Uri baseUri = new Uri("ms-appx:///");


var cards = new ObservableCollection<MenuCard>();
MenuCard card1 = new MenuCard
{
Title = "Breakfast"
};
card1.MenuItems.Add(new MenuItem
{
Text = "Spezialfrühstück",
Price = 5.4,
MenuCard = card1
});
card1.MenuItems.Add(new MenuItem
{
Text = "Wiener Frühstück",
Price = 4.4,
MenuCard = card1
});
card1.MenuItems.Add(new MenuItem
{
Text = "Schinken mit 3 Eiern",
Price = 4.4,
MenuCard = card1
});
card1.ImagePath = string.Format("{0}{1}", baseUri, "Assets/Breakfast.jpg");
cards.Add(card1);


//... more menu cards in the code download


2.3  应用程序页面
现在向应用程序添加一些UI页面。从模板添加的第一个页面是MainPage.xaml。根据Blank App (XAML)模板,该页面没有提供任何结构,内容是完全可定制的。如果不创建Windows 8游戏或需要特殊布局的其他应用程序,最好使用标准的格式和样式,把应用程序名放在Windows 8样式规则准确定义的位置上。在开始运行不同的Windows 8应用程序时,会发现它们有一些相似之处。为了不重复工作,可以直接通过Visual Studio项模板,使用预定义的样式,如图5所示。
 
图5
    在示例应用程序中,前面创建的主页用Items Page模板替代。该应用程序创建的其他页面有基本页面(Basic Page)AddMenuCardPage和项页面(Items Page)MenuItemsPage。
    基本页面提供了一个布局,把应用程序名放在用户习惯查看的顶部位置。分割页面(Split Page)把页面分成两半,一半是列表,另一半是细节。
    项页面包含GridView控件,它在一个网格中显示项列表。要使用项组,可以使用模板Grouped Items Page、Group Detail Page和Item Detail Page。Grouped Items Page用于显示不同的项组,并使用带GroupStyle设置的ListView,以及带分组的CollectionViewSource。Group Detail Page显示一个组及其详细信息,于是为这个任务使用GridView。Item Detail Page使用RichTextBlock显示一项的细节,允许使用FlipView在项之间切换。


    使用这些模板在项目的Common目录中再添加更多的类:BindableBase可以用作一些数据类的基类,因为它实现了INotifyPropertyChanged接口;LayoutAwarePage是自定义页面的一个新基类,它可以通知旋转更改,提供旋转的可视化状态。BooleanNegationConverter和BooleanToVisibility- Converter是XAML实现IValueConverter的转换器,RichTextColumns类可以与RichTextBlock控件一起用于文本溢出。最后,SuspensionManager用于在应用程序挂起时,存储和加载应用程序的状态。

1. 主页
应用程序的主页如图6所示,它显示了每个菜单卡的标题和图像。
 
图6
    为此,只需要对XAML代码(代码文件MainPage.xaml)进行一些小的调整,如下代码所示。在Items Page模板中,XAML代码包含一个GridView作为子元素,有变化的是项模板从默认的Standard250×250ItemTemplate变成MenuCardItemTemplate,再添加一个ItemClick事件处理程序,在单击项时触发它:
<GridView x:Name="itemGridView"
AutomationProperties.AutomationId="ItemsGridView"
AutomationProperties.Name="Items"
TabIndex="1"
Grid.Row="1"
Margin="0,-4,0,0"
Padding="116,0,116,46"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
ItemTemplate="{StaticResource MenuCardItemTemplate}"
IsItemClickEnabled="True"
ItemClick="OnMenuCardClick"/>


    GridView的源代码用ItemsSource属性定义,它引用了静态资源itemsViewSource。itemsViewSource是在刚刚绑定到Items属性的页面资源中指定的一个简单CollectionViewSource:
<CollectionViewSource
x:Name="itemsViewSource"
Source="{Binding Items}"/>
    MenuCardItemTemplate在自定义样式文件Styles\MenuCardStyles.xaml中定义。默认模板中的项使用两列建立,而这里的项由两行组成。尺寸较大,且绑定到Image和Title属性上。记住,前面定义的MenuCard类实现了这些属性:
<DataTemplate x:Key="MenuCardItemTemplate">
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Background="{StaticResource
ListViewItemPlaceholderBackgroundThemeBrush}" Width="450" Height="450">
<Image Source="{Binding Image}" Stretch="UniformToFill"/>
</Border>
<StackPanel Grid.Column="1" Margin="10,0,0,0">
<TextBlock Text="{Binding Title}" Style="{StaticResource ItemTextStyle}"
MaxHeight="40"/>
</StackPanel>
</Grid>
</DataTemplate>


    前面定义的CollectionViewSource绑定到Items集合上。Items集合在MainPage类的方LoadState法中赋值(代码文件MenuCard/MainPage.xaml.cs)。LoadState方法的实现代码指定了LayoutAwarePage基类的DefaultViewModel属性。这个属性返回IObservableMap<string, object>,其中任意数据对象都可以赋予一个键名。键名在XAML中用于引用数据。
protected override async void LoadState(Object navigationParameter,
Dictionary<String, Object> pageState)
{
var storage = new MenuCardStorage();
MenuCardFactory.Instance.InitMenuCards(new ObservableCollection<MenuCard>(
await storage.ReadMenuCardsAsync()));
this.DefaultViewModel["Items"] = MenuCardFactory.Instance.Cards;
}


2. 添加菜单卡页面
    为了添加新的菜单卡,添加了AddMenuCardPage。这里使用的模板只是Basic Page模板。但是,其中没有太多的内容要定义。用户只需要给菜单卡指定标题、描述和图像。UI如图7所示,只需两个文本框、一个按钮和一个Image控件。
 
图7
    在AddMenuCard.xaml文件中定义主要控件的XAML代码如下所示。注意这里的两个要点:控件绑定到Image、Title和Description属性上,赋予父控件(Grid)的数据上下文被设置为Item属性:
<Grid Grid.Row="1" DataContext="{Binding Item}">
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="300" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border Grid.Row="0" Grid.RowSpan="3" Grid.Column="0" Grid.ColumnSpan="2">
<Image Source="{Binding Image, Mode=OneWay}" Stretch="UniformToFill" />
</Border>
<TextBlock Text="Name:" Style="{StaticResource TitleTextStyle}" Margin="20"
VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBox Grid.Column="1" Text="{Binding Title, Mode=TwoWay}" Margin="20"
VerticalAlignment="Center" />
<TextBlock Grid.Row="1" Text="Description:"
Style="{StaticResource TitleTextStyle}" Margin="20"
VerticalAlignment="Center"
HorizontalAlignment="Right" />
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding Description, Mode=TwoWay}"
Margin="20" MaxHeight="100" VerticalAlignment="Center" />
<Button HorizontalAlignment="Center" VerticalAlignment="Center"
Visibility="{Binding ImageUploaded,
Converter={StaticResource visibilityConverter}}" Content="Upload Image"
Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
Style="{StaticResource TextButtonStyle}" Click="OnUploadImage"
Padding="10" Margin="20" />
</Grid>


    在代码文件中,Item属性被赋予LoadState方法中AddMenuCardInfo类型的对象(它包含在XAML代码中绑定的属性) (代码文件AddMenuCardPage.xaml.cs):
private AddMenuCardInfo info = new AddMenuCardInfo();


protected override void LoadState(Object navigationParameter,
Dictionary<String, Object> pageState)
{
this.DefaultViewModel["Item"] = info;
}


3. 菜单项页面
    应用程序的第三个页面是MenuItemsPage,如图8所示。这个页面显示了一个菜单卡中的菜单项,并允许修改数据。
 
图8
    这个页面也基于Items Page模板,在LoadState方法中绑定到一组菜单项上(代码文件MenuItems- Page.xaml.cs):
protected override void LoadState(Object navigationParameter,
Dictionary<String, Object> pageState)
{
card = navigationParameter as MenuCard;
if (card != null)
{
this.DefaultViewModel["Items"] = card.MenuItems;
}
}


    有了3个页面后,就准备导航Windows 8应用程序。
三  应用程序工具栏
     尽管在chrome之前放置内容是Windows 8应用程序的一个重要设计方面,但显然用户需要一种方式与UI交互操作。现在这由新的应用程序工具栏(App Bar)提供。Windows以前的版本默认显示命令,而现在用户可以选择何时显示应用程序命令。
    通过触摸,轻击屏幕的底边界或顶边界时,应用程序工具栏就会显示出来。使用鼠标时,单击鼠标右键,会调用应用程序工具栏。使用键盘时,用户可以单击上下文菜单按钮。
    可以在页面的BottomAppBar和TopAppBar属性中定义应用程序工具栏。大多数应用程序都在底部显示应用程序工具栏。如果应用程序在顶部和底部都使用应用程序工具栏,它们就同时用相同的样式显示出来。
    下面的代码段(代码文件MainPage.xaml)在页面的BottomAppBar属性中定义了一个AppBar元素。在AppBar中,可以使用任何XAML元素定义应用程序工具栏的内容和布局。在这个示例中,添加了两个Button(按钮)控件,它们使用预定义的样式,并给Click事件添加了处理程序:
<Page.BottomAppBar>
<AppBar>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*"/>
<ColumnDefinition Width="50*"/>
</Grid.ColumnDefinitions>
<StackPanel x:Name="LeftCommands" Orientation="Horizontal" Grid.Column="0"
HorizontalAlignment="Left">
<Button Style="{StaticResource AddAppBarButtonStyle}"
HorizontalAlignment="Left" Tag="Add" Click="OnAddMenuCard" />
<Button Style="{StaticResource DeleteAppBarButtonStyle}"
HorizontalAlignment="Left" Tag="Delete" Click="OnDeleteMenuCard" />
</StackPanel>
<StackPanel x:Name="RightCommands" Orientation="Horizontal" Grid.Column="1"
HorizontalAlignment="Right">
</StackPanel>
</Grid>
</AppBar>
</Page.BottomAppBar>


    在Visual Studio模板生成的样式文件中,已经有一些用于应用程序工具栏的预定义按钮。下面是示例代码使用的样式AddAppBarButtonStyle。这个样式仅使用Segoe UI Symbol字体系列定义了字符的值,以显示加号。除此之外,还用基本样式AppBarButtonStyle定义了轮廓线字形和TextBlock,来显示按钮的文本。
<Style x:Key="AddAppBarButtonStyle" TargetType="Button"
BasedOn="{StaticResource AppBarButtonStyle}">
<Setter Property="AutomationProperties.AutomationId" Value="AddAppBarButton"/>
<Setter Property="AutomationProperties.Name" Value="Add"/>
<Setter Property="Content" Value=""/>
</Style>


图9显示了带有应用程序工具栏的应用程序。
 
图9

小结:
    本文介绍了编写Windows 8应用程序的许多不同方面。XAML与编写WPF应用程序非常相似。数据绑定、内容控件和项控件一起使用。利用Visual State Manager处理不同的布局变化。Windows Runtime访问存储器,以读写数据和图像,使用移动存储器。使用FileOpenPicker,通过与用户交互来上传文件。本文还介绍了共享协定,它定义了提供数据的共享源和接收数据的共享目标。
    当然,设计Windows 8应用程序还有许多内容。还有更多的选择器(如联系人选择器),为应用程序提供文件打开选择器的协定;改进的搜索功能(允许应用程序使用toast给用户提供信息)等。



《C#高级编程(第8版)》试读电子书免费提供,有需要的留下邮箱,一有空即发送给大家。 别忘啦顶哦!
  • 14
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 46
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 46
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值