基于metro风格的Windows phone 8 应用提到了图块的概念,它就是指启动菜单中的快速启动图标。一般一个应用必须有一个默认图块,还可以有若干个次要图块。另外,通知与图块的关系比较密切,我们可以通过在接受到消息时动态更新图块来达到适时的效果。我们本节把图块和通知放在一起讲。
一、图块
1)定义默认图块
默认图块只能在清单文件中定义它,并且选定的图块模板后就不能再改变,除非重新发布应用,但是我们可以更新同类型的模板。应用安装后默认状态不会出现在开始菜单,需要在程序清单中右键选择固定到开始屏幕项。
图块模板类型:
1.图标模版:它可以包含大小两种图标,三种图块都可以用。必须为包含透明背景的png格式metro风格图片。
2.翻转模版:在中型、大型图块中可以实现翻转效果。
3.循环模板:在中型、大型图块中实现多张背景图轮流切换。
2)创建更新图块
图块的创建和更新可以通过两种方式,分别是定义模版xml或通过代码构建,下面的代码演示了如何创建和更新图块。
[C#]
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#]
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>
[C#]
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) { } }