基于metro风格的Windows phone 8 应用提到了图块的概念,它就是指启动菜单中的快速启动图标。一般一个应用必须有一个默认图块,还可以有若干个次要图块。另外,通知与图块的关系比较密切,我们可以通过在接受到消息时动态更新图块来达到适时的效果。我们本节把图块和通知放在一起讲。
快速导航:
一、图块
二、图块更新计划
三、本地通知
四、推送通知
一、图块
1)定义默认图块
默认图块只能在清单文件中定义它,并且选定的图块模板后就不能再改变,除非重新发布应用,但是我们可以更新同类型的模板。应用安装后默认状态不会出现在开始菜单,需要在程序清单中右键选择固定到开始屏幕项。
图块模板类型:
1.图标模版:它可以包含大小两种图标,三种图块都可以用。必须为包含透明背景的png格式metro风格图片。
2.翻转模版:在中型、大型图块中可以实现翻转效果。
3.循环模板:在中型、大型图块中实现多张背景图轮流切换。
2)创建更新图块
图块的创建和更新可以通过两种方式,分别是定义模版xml或通过代码构建,下面的代码演示了如何创建和更新图块。
[C#]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
public
partial class MainPage : PhoneApplicationPage
{ // 构造函数
public
MainPage()
{
InitializeComponent();
// 用于本地化 ApplicationBar 的示例代码
//BuildLocalizedApplicationBar();
}
protected
override void OnNavigatedTo(NavigationEventArgs e)
{
base .OnNavigatedTo(e);
}
private
void Button_Click_1( object
sender, RoutedEventArgs e)
{
CreateTitle();
}
private
void ShowTitle()
{
var
tilte = ShellTile.ActiveTiles.FirstOrDefault();
if
(tilte != null )
{
MessageBox.Show(tilte.NavigationUri.ToString());
}
}
//图标图块
IconicTileData iconicTileData = new
IconicTileData()
{
Title = "标题" ,
Count = 5,
WideContent1 = "第一行文本" ,
WideContent2 = "第二行文本" ,
WideContent3 = "第三行文本" ,
SmallIconImage = new
Uri( "/Assets/Tiles/IconLarge.png" , UriKind.Relative),
IconImage = new
Uri( "/Assets/Tiles/IconSamall.png" , UriKind.Relative),
//透明度设置为255才会显示自定义颜色背景,否则显示为系统背景
BackgroundColor = new
Color { A = 255, R = 0, G = 148, B = 255 }
};
//图标图块模板
string
iconicTileXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<wp:Notification xmlns:wp=""WPNotification"" Version=""2.0"">
<wp:Tile Id=""titleid1"" Template=""IconicTile"">
<wp:SmallIconImage>/Assets/Tiles/IconLarge.png</wp:SmallIconImage>
<wp:IconImage>/Assets/Tiles/IconSamall.png</wp:IconImage>
<wp:WideContent1>第一行文本</wp:WideContent1>
<wp:WideContent2 Action=""Clear"">第二行文本</wp:WideContent2>
<wp:WideContent3>第三行文本</wp:WideContent3>
<wp:Count>6</wp:Count>
<wp:Title>标题</wp:Title>
<wp:BackgroundColor>#FF524742</wp:BackgroundColor>
</wp:Tile>
</wp:Notification>" ;
//可用于清除Count(如果加了Action="Clear",则清除该项的显示)
string
iconicTileXml2 = @"<?xml version=""1.0"" encoding=""utf-8""?>
<wp:Notification xmlns:wp=""WPNotification"" Version=""2.0"">
<wp:Tile Id=""titleid1"" Template=""IconicTile"">
<wp:Count Action=""Clear"">0</wp:Count>
</wp:Tile>
</wp:Notification>" ;
//翻转图块
FlipTileData flipTileData = new
FlipTileData()
{
Title = "标题" ,
BackTitle = "背面标题" ,
BackContent = "背面的文本内容部分" ,
WideBackContent = "在宽图块背面的文本内容" ,
Count = 5,
SmallBackgroundImage = new
Uri( "/Assets/Tiles/Samall.png" , UriKind.Relative),
BackgroundImage = new
Uri( "/Assets/Tiles/Medium.png" , UriKind.Relative),
//不设置背景图像则显示为系统背景色
//BackBackgroundImage = new Uri("Assets/Tiles/FlipCycleTileMedium.png", UriKind.Relative),
WideBackgroundImage = new
Uri( "/Assets/Tiles/Large.png" , UriKind.Relative),
//WideBackBackgroundImage = new Uri("/Assets/Tiles/IconicTileMediumLarge.png", UriKind.Relative)
};
//翻转图块模板
string
flipTileXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<wp:Notification xmlns:wp=""WPNotification"" Version=""2.0"">
<wp:Tile Id=""titleid2"" Template=""FlipTile"">
<wp:SmallBackgroundImage>/Assets/Tiles/Samall.png</wp:SmallBackgroundImage>
<wp:WideBackgroundImage>/Assets/Tiles/Large.png</wp:WideBackgroundImage>
<wp:WideBackBackgroundImage>/Assets/Tiles/IconicTileMediumLarge.png</wp:WideBackBackgroundImage>
<wp:WideBackContent>在宽图块背面的文本内容</wp:WideBackContent>
<wp:BackgroundImage>/Assets/Tiles/Medium.png</wp:BackgroundImage>
<wp:Count>6</wp:Count>
<wp:Title>标题</wp:Title>
<wp:BackBackgroundImage>Assets/Tiles/FlipCycleTileMedium.png</wp:BackBackgroundImage>
<wp:BackTitle>背面标题</wp:BackTitle>
<wp:BackContent>背面的文本内容部分</wp:BackContent>
</wp:Tile>
</wp:Notification>" ;
//循环图块
CycleTileData cycleTileData = new
CycleTileData()
{
Title = "标题" ,
Count = 10,
SmallBackgroundImage = new
Uri( "/Assets/Tiles/Samall.png" , UriKind.Relative),
CycleImages = new
Uri[]
{
new
Uri( "/Assets/Tiles/Title1.png" , UriKind.Relative),
new
Uri( "/Assets/Tiles/Title2.png" , UriKind.Relative),
new
Uri( "/Assets/Tiles/Title3.png" , UriKind.Relative),
}
};
//循环图块模板
string
cycleTileXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<wp:Notification xmlns:wp=""WPNotification"" Version=""2.0"">
<wp:Tile Id=""titleid3"" Template=""CycleTile"">
<wp:SmallBackgroundImage>/Assets/Tiles/Samall.png</wp:SmallBackgroundImage>
<wp:CycleImage1>/Assets/Tiles/Title1.png</wp:CycleImage1>
<wp:CycleImage2>/Assets/Tiles/Title2.png</wp:CycleImage2>
<wp:CycleImage3>/Assets/Tiles/Title3.png</wp:CycleImage3>
<wp:Count>6</wp:Count>
<wp:Title>标题</wp:Title>
</wp:Tile>
</wp:Notification>" ;
private
void CreateTitle()
{
//添加一个次要图块
ShellTile.Create( new
Uri( "/Page1.xaml" , UriKind.Relative), iconicTileData, true );
//通过xml模板添加
ShellTile.Create( new
Uri( "/Page1.xaml" , UriKind.Relative), new
IconicTileData(iconicTileXml), true );
}
private
void ClearCount()
{
//清除Count
ShellTile.ActiveTiles.ElementAt(1).Update( new
IconicTileData(iconicTileXml2));
}
private
void UpdateTitle()
{
//更新默认图块的内容,我们定义的翻转模版,这里不能修改模版类型
ShellTile.ActiveTiles.FirstOrDefault().Update(flipTileData);
}
} |
二、图块更新计划
我们可以定义一个更新计划,定期的更新图块的背景图像。当应用退出以后,这个更新计划依然能够在后台运行。它的实现代码如下:
[C#]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
ShellTileSchedule SampleTileSchedule = new
ShellTileSchedule();
//计划是否已经执行 bool TileScheduleRunning = false ;
//开始执行计划 private
void Button_Click_1( object
sender, RoutedEventArgs e)
{ //指定计划执行一次还是多次
SampleTileSchedule.Recurrence = UpdateRecurrence.Interval;
//指定计划的更新间隔时间
SampleTileSchedule.Interval = UpdateInterval.EveryHour;
//指定计划的执行次数,如果未设置,则为不确定次数
SampleTileSchedule.MaxUpdateCount = 50;
//指定计划的开始时间
SampleTileSchedule.StartTime = DateTime.Now;
//获取背景图像的网络URI
SampleTileSchedule.RemoteImageUri = new
Uri( @"http://images.cnblogs.com/cnblogs_com/lipan/319399/o_Large.png" );
SampleTileSchedule.Start();
} //停止计划 private
void Button_Click_2( object
sender, RoutedEventArgs e)
{ if
(TileScheduleRunning) SampleTileSchedule.Stop();
} |
三、本地通知
通知分为本地通知和推送通知。我们可以通过本地通知实现本地消息和提醒功能。
1)本地消息和提醒
我们可以定义提醒和闹钟的功能,在应用退出以后,当计划的提醒时间达到时,提醒或者闹钟功能将自动别触发,其中闹钟的功能我们还可以自定义铃声。下面看看代码实现的过程。
[XAML]<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <ListBox x:Name="listbox1" Width="440" MaxHeight="420" Margin="10,44,0,150"> <ListBox.ItemTemplate> <DataTemplate> <Grid> <StackPanel Orientation="Horizontal" Background="Blue" Width="440"> <TextBlock Width="120" Text="{Binding Title}" /> <TextBlock Text="{Binding BeginTime}" /> <TextBlock Text="{Binding RecurrenceType}" /> <TextBlock Text=" " /> <TextBlock Text="{Binding IsScheduled}" /> <TextBlock Text=" " /> <Button Margin="0" Tag="{Binding Name}" Click="deleteButton_Click" Content="X" BorderBrush="Red" Background="Red" Foreground="{StaticResource PhoneBackgroundBrush}" VerticalAlignment="Top" BorderThickness="0" Width="50" Padding="0,0,0,0"></Button> </StackPanel> </Grid> </DataTemplate> </ListBox.ItemTemplate> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="Margin" Value="5"/> </Style> </ListBox.ItemContainerStyle> </ListBox> <TextBlock HorizontalAlignment="Left" Margin="10,12,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Text="已注册的本地消息提醒:"> </TextBlock> <TextBox x:Name="textbox1" HorizontalAlignment="Left" Height="72" Margin="10,555,0,0" TextWrapping="Wrap" Text="标题" VerticalAlignment="Top" Width="125"/> <RadioButton x:Name="radioButton1" IsChecked="True" GroupName="radioButtonGroup" Content="Reminder" HorizontalAlignment="Left" Margin="135,532,0,0" VerticalAlignment="Top"/> <RadioButton x:Name="radioButton2" GroupName="radioButtonGroup" Content="Alarm" HorizontalAlignment="Left" Margin="134,604,0,0" VerticalAlignment="Top"/> <Button Content="注册" HorizontalAlignment="Left" Margin="328,569,0,0" VerticalAlignment="Top" Click="Button_Click_1"/> </Grid>
public partial class Page1 : PhoneApplicationPage { public Page1() { InitializeComponent(); } IEnumerable<ScheduledNotification> notifications; protected override void OnNavigatedTo(NavigationEventArgs e) { ListboxInit(); base.OnNavigatedTo(e); } private void ListboxInit() { //返回系统所有已注册的通知 notifications = ScheduledActionService.GetActions<ScheduledNotification>(); listbox1.ItemsSource = notifications; } //删除一个通知 private void deleteButton_Click(object sender, RoutedEventArgs e) { string name = (string)((Button)sender).Tag; ScheduledActionService.Remove(name); ListboxInit(); } //新增一个通知 private void Button_Click_1(object sender, RoutedEventArgs e) { String name = System.Guid.NewGuid().ToString(); if (radioButton1.IsChecked == true) { //名称,唯一标识 Reminder reminder = new Reminder(name); //消息标题 reminder.Title = textbox1.Text; reminder.Content = "这里是提醒的正文部分。"; //消息重现类型 reminder.RecurrenceType = RecurrenceInterval.Daily; //开始时间 reminder.BeginTime = DateTime.Now + new TimeSpan(0, 0, 30); //结束时间 reminder.ExpirationTime = DateTime.Now + new TimeSpan(0, 0, 45); //从提醒启动应用程序时的启动URI reminder.NavigationUri = new Uri("/Page2.xaml?a=test", UriKind.Relative); //注册 ScheduledActionService.Add(reminder); } else { //可以自定义铃声的通知 Alarm alarm = new Alarm(name); alarm.Content = "这里是闹钟的正文部分。"; //提醒时播放的文件 alarm.Sound = new Uri("/1.mp3", UriKind.Relative); //消息重现类型 alarm.RecurrenceType = RecurrenceInterval.Daily; //开始时间 alarm.BeginTime = DateTime.Now + new TimeSpan(0, 0, 30); //结束时间 alarm.ExpirationTime = DateTime.Now + new TimeSpan(0, 1, 30); //注册 ScheduledActionService.Add(alarm); } ListboxInit(); } }
2)本地Toast
通过本地Toast可以在实现Toast消息弹出,但是当应用运行时则不会弹出,所以一般在后台计划中被调用。详细情况请见《Windows phone 8 学习笔记 多任务 后台代理》 。
四、推送通知
推送通知都需要借助于微软推送云服务器,因为一般来讲,应用退出以后是不会保留后台服务去等待接受消息的,这种做法比较费电。推送通知的做法是,当有消息推送过来的时候,由系统去统一完成消息的接收,用户选择性的去启动应用。
1. 推送通知的类型
推送通知主要有三种类型,如下:
1.磁贴通知:消息到达时,将会更新应用的默认图块,这样直观的现实当前应用有更新内容。
2.Toast推送通知:消息到达时,将会在屏幕上方弹出一个Toast提示,用户单击即可启动应用。
3.raw通知:这个通知在应用运行的前提下,提供灵活的消息处理,但是非允许状态下将无法接受消息。
2. 推送通知的实现
要实现推送通知,首先我们需要建立推送通道。在Windows phone 中建立推送通道,并且得到通道的URI。代码如下:
[C# Windows phone]//磁贴通知 private void TileInit() { //推送服务通道 HttpNotificationChannel pushChannel; //通道名称 string channelName = "TileSampleChannel"; InitializeComponent(); //尝试发现是否已经创建 pushChannel = HttpNotificationChannel.Find(channelName); var newChannel = false; //没有发现,新建一个 if (pushChannel == null) { pushChannel = new HttpNotificationChannel(channelName); newChannel = true; } //通知通道关联URI改变时: pushChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(PushChannel_ChannelUriUpdated); //出错时: pushChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(PushChannel_ErrorOccurred); if (newChannel) { pushChannel.Open(); //将通知订阅绑定到默认图块 pushChannel.BindToShellTile(); } else { MessageBox.Show(String.Format("通道URI: {0}", pushChannel.ChannelUri.ToString())); } } //Toast通知 private void ToastInit() { //推送服务通道 HttpNotificationChannel pushChannel; //通道名称 string channelName = "ToastSampleChannel"; InitializeComponent(); //尝试发现是否已经创建 pushChannel = HttpNotificationChannel.Find(channelName); var newChannel = false; //没有发现,新建一个 if (pushChannel == null) { pushChannel = new HttpNotificationChannel(channelName); newChannel = true; } //通知通道关联URI改变时: pushChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(PushChannel_ChannelUriUpdated); //出错时: pushChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(PushChannel_ErrorOccurred); //收到Toast消息时:(如果程序未启动则弹出Toast,否则触发该事件) pushChannel.ShellToastNotificationReceived += new EventHandler<NotificationEventArgs>(PushChannel_ShellToastNotificationReceived); if (newChannel) { pushChannel.Open(); //将通知订阅绑定到ShellToast pushChannel.BindToShellToast(); } else { MessageBox.Show(String.Format("通道URI: {0}", pushChannel.ChannelUri.ToString())); } } //Row通知 private void RawInit() { //推送服务通道 HttpNotificationChannel pushChannel; //通道名称 string channelName = "RawSampleChannel"; InitializeComponent(); //尝试发现是否已经创建 pushChannel = HttpNotificationChannel.Find(channelName); var newChannel = false; //没有发现,新建一个 if (pushChannel == null) { pushChannel = new HttpNotificationChannel(channelName); newChannel = true; } //通知通道关联URI改变时: pushChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(PushChannel_ChannelUriUpdated); //出错时: pushChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(PushChannel_ErrorOccurred); //收到Raw通知时:(只有应用运行时才触发本事件) pushChannel.HttpNotificationReceived += new EventHandler<HttpNotificationEventArgs>(PushChannel_HttpNotificationReceived); if (newChannel) { pushChannel.Open(); //这里并没有绑定操作 } else { MessageBox.Show(String.Format("通道URI: {0}", pushChannel.ChannelUri.ToString())); } } //URI更新时 void PushChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e) { Dispatcher.BeginInvoke(() => { MessageBox.Show(String.Format("通道URI: {0}", e.ChannelUri.ToString())); }); } //遇到错误时 void PushChannel_ErrorOccurred(object sender, NotificationChannelErrorEventArgs e) { //处理错误 } //收到Toast通知时 void PushChannel_ShellToastNotificationReceived(object sender, NotificationEventArgs e) { StringBuilder message = new StringBuilder(); string relativeUri = string.Empty; message.AppendFormat("收到 Toast {0}:\n", DateTime.Now.ToShortTimeString()); foreach (string key in e.Collection.Keys) { message.AppendFormat("{0}: {1}\n", key, e.Collection[key]); if (key.ToLower() == "wp:param") relativeUri = e.Collection[key]; } Dispatcher.BeginInvoke(() => MessageBox.Show(message.ToString())); } //收到Raw通知时: void PushChannel_HttpNotificationReceived(object sender, HttpNotificationEventArgs e) { string message; using (System.IO.StreamReader reader = new System.IO.StreamReader(e.Notification.Body)) { message = reader.ReadToEnd(); } Dispatcher.BeginInvoke(() => MessageBox.Show(String.Format("收到 Row {0}:\n{1}", DateTime.Now.ToShortTimeString(), message)) ); }
得到推送URI后,我们需要一个web服务端,这个服务端就是我们用来向自己的应用发送推送消息的地方,如果web端用.net实现,那么实现方式如下:
[C# .Net]//发送磁贴消息 private void SendTile() { try { string subscriptionUri = TextBoxUri.Text.ToString(); HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(subscriptionUri); httpWebRequest.Method = "POST"; string tileMessage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<wp:Notification xmlns:wp=\"WPNotification\">" + "<wp:Tile>" + "<wp:BackgroundImage>/Assets/Tiles/FlipCycleTileMedium.png</wp:BackgroundImage>" + "<wp:Count>5</wp:Count>" + "<wp:Title>标题</wp:Title>" + "<wp:BackBackgroundImage></wp:BackBackgroundImage>" + "<wp:BackTitle>背面标题</wp:BackTitle>" + "<wp:BackContent>背面文本内容</wp:BackContent>" + "</wp:Tile> " + "</wp:Notification>"; byte[] notificationMessage = Encoding.Default.GetBytes(tileMessage); httpWebRequest.ContentLength = notificationMessage.Length; httpWebRequest.ContentType = "text/xml"; //X-WindowsPhone-Target设置为token httpWebRequest.Headers.Add("X-WindowsPhone-Target", "token"); //Tile消息类型为 1 httpWebRequest.Headers.Add("X-NotificationClass", "1"); using (Stream requestStream = httpWebRequest.GetRequestStream()) { requestStream.Write(notificationMessage, 0, notificationMessage.Length); } HttpWebResponse response = (HttpWebResponse)httpWebRequest.GetResponse(); string notificationStatus = response.Headers["X-NotificationStatus"]; string notificationChannelStatus = response.Headers["X-SubscriptionStatus"]; string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"]; } catch (Exception ex) { } } //发送Toast消息 private void SendToast() { try { //这个URI就是通道创建时由WP客户端获取到的,需要提交到服务端 string uri = TextBoxUri.Text.ToString(); HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(uri); httpWebRequest.Method = "POST"; string toastMessage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<wp:Notification xmlns:wp=\"WPNotification\">" + "<wp:Toast>" + "<wp:Text1>标题</wp:Text1>" + "<wp:Text2>内容部分</wp:Text2>" + "<wp:Param>/Page2.xaml?NavigatedFrom=ToastNotification</wp:Param>" + "</wp:Toast> " + "</wp:Notification>"; byte[] notificationMessage = Encoding.Default.GetBytes(toastMessage); //设置请求头 httpWebRequest.ContentLength = notificationMessage.Length; httpWebRequest.ContentType = "text/xml"; //X-WindowsPhone-Target设置为toast httpWebRequest.Headers.Add("X-WindowsPhone-Target", "toast"); //Toast消息类型为 2 httpWebRequest.Headers.Add("X-NotificationClass", "2"); using (Stream requestStream = httpWebRequest.GetRequestStream()) { requestStream.Write(notificationMessage, 0, notificationMessage.Length); } HttpWebResponse response = (HttpWebResponse)httpWebRequest.GetResponse(); //获取相应头包含的状态信息 string notificationStatus = response.Headers["X-NotificationStatus"]; string notificationChannelStatus = response.Headers["X-SubscriptionStatus"]; string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"]; } catch (Exception ex) { } } //发送Raw消息 private void SendRaw() { try { string subscriptionUri = TextBoxUri.Text.ToString(); HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(subscriptionUri); httpWebRequest.Method = "POST"; //这里的消息内容完全自定义,也可以为非xml string rawMessage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<root>" + "<Value1>a<Value1>" + "<Value2>b<Value2>" + "</root>"; byte[] notificationMessage = Encoding.Default.GetBytes(rawMessage); httpWebRequest.ContentLength = notificationMessage.Length; httpWebRequest.ContentType = "text/xml"; //没有 X-WindowsPhone-Target 头 //Raw消息类型为 3 httpWebRequest.Headers.Add("X-NotificationClass", "3"); using (Stream requestStream = httpWebRequest.GetRequestStream()) { requestStream.Write(notificationMessage, 0, notificationMessage.Length); } HttpWebResponse response = (HttpWebResponse)httpWebRequest.GetResponse(); string notificationStatus = response.Headers["X-NotificationStatus"]; string notificationChannelStatus = response.Headers["X-SubscriptionStatus"]; string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"]; } catch (Exception ex) { } }
出处:[http://www.cnblogs.com/lipan/]
版权声明:本文的版权归作者与博客园共有。转载时须注明原文出处以及作者,并保留原文指向型链接,不得更改原文内容。否则作者将保留追究其法律责任。