现操week5

  • 要求使用标准的处理XML DOM方式创建动态磁贴
  • 要求采用Adaptive Tile (覆盖至少small、medium、wide)
  • 实现效果:要求每添加一条项目,磁贴能进行更新,并且更新的内容循环展示(1-2-3-4-5-1-2-3-4……)
    • 在MenuFlyoutItem中增加Share选项,点击后相应条目能进行共享
  • 实现文字共享功能,能显示共享UI,在邮件应用中显示共享内容正确(90%)
  • 实现图片共享功能,图片的动态绑定作为之前内容不做要求(10%)

基础知识

自适应磁贴

msdn:创建自适应磁贴

原理

使用一个 XMLDocument对象创建通知(TileNotification)—–将通知发送到磁贴 (发送的实质是新构造一个Tile)

public class TileService
    {
        static public void SetBadgeCountOnTile(int count)
        {
            // Update the badge on the real tile
            XmlDocument badgeXml =
           BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeNumber);
            XmlElement badgeElement =
           (XmlElement)badgeXml.SelectSingleNode("/badge");
            badgeElement.SetAttribute("value", count.ToString());  // 更新badge的值
            BadgeNotification badge = new BadgeNotification(badgeXml); // 构造新的BadgeNotification

            BadgeUpdateManager.CreateBadgeUpdaterForApplication().Update(badge); // 用构造的Updater更新磁贴的badge
        }
    }

通过在MainPage中调用这个服务(函数),就可以将数据更新到Badge上。

WinDevHOLs/03. Live Tiles and Notifications/03. Lab. Live Tiles and Notifications.pdf

Live Tiles简介: Live Tiles in Windows 10 use adaptive templates to deliver content customized to a device and screen
density. While legacy templates are still compatible with live tiles, adaptive templates give you more
freedom to choose how your content will display on all devices. Groups and subgroups allow you to
semantically link content within the tile. In this exercise, you will create an adaptive layout and display it
with static data on the tile.

在MainPage.cs中,编写构造磁贴的函数:

private void UpdatePrimaryTile(object sender, Windows.UI.Xaml.RoutedEventArgs
e)
{
 var xmlDoc = TileService.CreateTiles(new PrimaryTile()); 
 // 样例在C#中用'PrimaryTile'创建数据模型,
 // 用CreatTiles()函数创建xml
//  下列代码实现--用新数据更新了磁贴
 var updater = TileUpdateManager.CreateTileUpdaterForApplication();
 TileNotification notification = new TileNotification(xmlDoc);
 updater.Update(notification);
}

实现的本质:每次都利用最新数据新创建一个磁贴。(Every time you update a tile, you are actually creating a new instance of that tile. )

Group & subgroup

Adaptive Tile Templates – Schema and Documentatio

创建一个TileNotification

MSDN
使用C#:

// In a real app, these would be initialized with actual data
string from = "Jennifer Parker";
string subject = "Photos from our trip";
string body = "Check out these awesome photos I took while in New Zealand!";


// Construct the tile content
TileContent content = new TileContent()
{
  ...
    }
};

↓实现创建
// Create the tile notification
var notification = new TileNotification(content.GetXml());

↓ 实现发送
// Send the notification to the primary tile
TileUpdateManager.CreateTileUpdaterForApplication().Update(notification);

Q :如何用原生xml文件作为tile模板?

A:通过构建XmlDocument对象并调用成员方法。

// Construct the tile content as a string
string content = $@"
<tile>
    <visual>
..."

// Load the string into an  XmlDocument(使用字符串写入XmlDocument对象)
XmlDocument doc = new XmlDocument();
doc.LoadXml(content);

// Then create the tile notification
var notification = new TileNotification(doc);
// 使用本地.xml ——用XmlDocument类的Load()进行读写
XDocument document = new XmlDocument();
document.Load("books.xml");
// Load方法属于XDocument, 不属于XmlDocument!!!博客害人啊!!!

创建一个BadgeNotification

BadgeNotification有多种类型。

Create a numeric badge

private void setBadgeNumber(int num)
{

    // Get the blank badge XML payload for a badge number
    XmlDocument badgeXml = 
        BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeNumber);

    // Set the value of the badge in the XML to our number
    XmlElement badgeElement = badgeXml.SelectSingleNode("/badge") as XmlElement;
    badgeElement.SetAttribute("value", num.ToString());

    // Create the badge notification
    BadgeNotification badge = new BadgeNotification(badgeXml);

    // Create the badge updater for the application
    BadgeUpdater badgeUpdater = 
        BadgeUpdateManager.CreateBadgeUpdaterForApplication();

    // And update the badge
    badgeUpdater.Update(badge);

}

Q:如何往xml模板中填入数据?


// build badge
var type = BadgeTemplateType.BadgeNumber;
var xml = BadgeUpdateManager.GetTemplateContent(type);

// update element
var elements = xml.GetElementsByTagName("badge"); // <=======关键在此,类似js的语法
var element = elements[0] as Windows.Data.Xml.Dom.XmlElement;
element.SetAttribute("value", "47");

// perform update
var updator = BadgeUpdateManager.CreateBadgeUpdaterForApplication();
var notification = new BadgeNotification(xml);
updator.Update(notification);

完整示例

GitHub

需求

在ToDoList条目变化时,磁贴更新

思路
—在PropertyChanged事件调用新建磁贴的函数

Step1 画磁贴

Step2 编写更新磁贴服务类

读.xml文件时老找不到文件
win10 uwp 访问解决方案文件
最后查了好多网页 才知道原来要这样子写


            StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Services/TileTemplate.xml"));

一开始还是显示不出来,经舍友提醒才知道要肉眼Debug XML文件,xml是不报错的,错了就没法显示Σ(っ °Д °;)っ
去掉失效的<image>标签之后就好了!

Step3 在应用中添加磁贴服务

—在调用ViewModel的新增函数时调用新建磁贴的函数
Xml节点操作参见W3scool相关手册。
Q:如何更新数据?
A:
- 以

<group>
    <subgroup>
       <text> (for title)
       <text> (for details)

为单位。新建这个结构。
- 将这个插入到方案TileWideTileLarge

DOM操作—-涉及大量XmlDocument和XmlElement的API
方案<binding>标签分别是.getElementsByTagName(“binding”)[2]和[3]
先.appendchildnode插入group
再.chilnode.appen…插入subgroup
通过两次.childNodes[0] <group>=><subgroup>=>再插入两个text节点

写好Dom操作了,不知道为什么没办法显示在磁贴上。。。

—API错误使用?

XmlElement Node_tileWide = dcm.GetElementsByTagName("binding")[2]

这个变量应该是XmlNode类型。而非XmlElement。
统一使用XmlNode类型,代码修改为:

      IXmlNode title = dcm.CreateElement("text");
            title.InnerText = item.title;
            IXmlNode detail = dcm.CreateElement("text");
            detail.InnerText = item.description;
            XmlNodeList bindingNodes = dcm.GetElementsByTagName("binding");
            IXmlNode Node_tileWide = bindingNodes.Item(2);
            IXmlNode newGroup = dcm.CreateElement("group");
            IXmlNode subgroup = dcm.CreateElement("subgroup");
            subgroup.AppendChild(title);
            subgroup.AppendChild(detail);
            newGroup.AppendChild(subgroup);
            Node_tileWide.AppendChild(newGroup);

成功解决!
搜索时用“c# uwp ixmlnode”找不出什么东西,原来IxmlNode是Windows应用专有的

In windows app store they changed the XmlNode to IXmlNode

找来找去都找不到怎么改IXmlNode的Attribute。。
—-将需要修改Attribute的使用XmlElement

XmlElement title = dcm.CreateElement("text");
            title.InnerText = item.title;
            title.SetAttribute("hint-style", "subtitle");

也成功解决问题。

更新的内容循环展示(1-2-3-4-5-1-2-3-4……)

使用通知队列
msdn:如何借助本地通知使用通知队列 (XAML)

步骤 1: 添加命名空间声明
步骤 2: 设置选项以启用通知循环
运行应用时,仅需要发起此调用一次,但再次调用也无妨。建议你在你的应用的初始化代码中放置此次调用。这样可确保在你采用本地方式更新磁贴、请求推送通知通道,或开始对磁贴进行定期更新之前发起此次调用。
TileUpdateManager.CreateTileUpdaterForApplication().EnableNotificationQueue(true);
步骤 3: 创建磁贴通知
步骤 4: 授予通知标记
标记不超过 16 个字母数字字符的字符串加上终止 NULL 字符,用于唯一标识应用中的通知
启用队列时,最多可在磁贴上自动循环显示五个磁贴通知。默认情况下,队列中通知的替换策略是先进先出 (FIFO);当队列排满且到达新通知时,最早的通知将被删除。请注意,通知显示顺序不遵守严格的线性模式。用户可看到通知的顺序与其到达时的顺序并不相同。
若要覆盖 FIFO 队列行为,则可以为通知赋予标记。如果到达的新通知与现有通知拥有相同的标记,则新通知将替换现有通知,而不管现有通知在队列中位于何处。例如,如果你的磁贴显示股票价格,而你希望显示最新的信息。使用标记替换队列中的通知是可选项。
有关对通知队列使用标记的详细信息,请参阅使用通知队列。下例展示如何为本地通知设置标记

tileNotification.Tag = "stockMSFT";
 有关为定期更新设置标记的信息,请参阅 Tileupdater.StartPeriodicUpdateBatch。

有关为推送通知设置标记的信息,请参阅推送通知服务请求和响应头。
步骤 5: 向磁贴发送通知

App.xaml.cs中加入

TileUpdateManager.CreateTileUpdaterForApplication().Clear(); // 清空队列
            TileUpdateManager.CreateTileUpdaterForApplication().EnableNotificationQueue(true); // 实现通知循环

因为发现上一次打开程序时新建的通知依然会出现在磁贴上,就加上了清空队列。
这个就是一行代码的事情啊……

实现与应用共享数据

—用Share Contrast实现进程间通信
- - 在MenuFlyoutItem中增加Share选项,点击后相应条目能进行共享
- 实现文字共享功能,能显示共享UI,在邮件应用中显示共享内容正确(90%)
- 实现图片共享功能,图片的动态绑定作为之前内容不做要求(10%)

实现也很简单,跟着PPT做就行。
重点:将选中的item数据填入dataPackage。还好之前做啦哈哈哈哈!

MenuFlyoutItem se = sender as MenuFlyoutItem;
            var dc = se.DataContext as TodoItem;
            ViewModel.SelectedItem = dc;

为了发送图片&图片绑定,还得把之前图片绑定的代码继续完善
——– 在TodoItem里增加BitmapImage成员。
private BitmapImage _todoImage;修改默认构造函数。

理解BitmapImage使用原理
1.得到一个Windows.Storage.StorageFile对象(file)
2.新建BitmapImage bitmap = new BitmapImage();
3.

using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
                {
                    bitmap.SetSource(stream);
                }

用“stream”作为bitmapfile的桥梁
4.用BitmapImage对象设置<image>Source属性:

image.Source = bitmap;

本来想给TodoItem构造函数里设置默认图片的,但是构造函数貌似不能调用异步方法。。只能留空了。
(一个解决办法,看不懂:msdn问答

还要补充ListView里的绑定实现。
注意选择图片时picker已经取得了bitmap值,直接写入todoItem对象即可。

悲伤的事情来了,大部分的ViewModel函数都要修改….还有CreateButton_Clicked…

教训就是尽量结构体传参,如果传多个成员变量,增删成员时有你受的/(ㄒoㄒ)/~~

将Image.Source转换成BitmapImage时会警告,现在先假设它们是可以强制转换的。。。
—-运行一下,好像都挺顺利地绑定上了。( •̀ ω •́ )y

问题在于如何给那两个默认的项添加上图片/ - \
——思路:修改MainPage的OnNavigatedTo或构造?
解决:修改ViewModel。
用一个方法addPicsForDefaultItem包装需要await的方法,再在构造中调用。
这样能成功,但从原理上来说好像不太合理……(没理由嵌套一个就不用await了呀)可能在别的场景下就会失败了。

        private Models.TodoItem di1;
        private Models.TodoItem di2;

        public TodoItemViewModel() {
            ...
            addPicsForDefaultItem();
        }
        public async void addPicsForDefaultItem(){
            // 为ViewModel默认项目添加图片
            StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/default.jpg"));
            if (file != null){ 
            BitmapImage bitmap = new BitmapImage();
                using (var stream = await file.OpenAsync(FileAccessMode.Read))
                { bitmap.SetSource(stream);  }
                di1.todoImage = bitmap;
                di2.todoImage = bitmap;
            }
        }

最后,实现图片共享功能(貌似做完这个就差不多了。。)

根据文档,核心代码是:

        StorageFile imageFile = 
            await Package.Current.InstalledLocation.GetFileAsync("Assets\\Logo.png");
        request.Data.SetBitmap(RandomAccessStreamReference.CreateFromFile(imageFile));

那么问题来了。
它需要使用的是StorageFile对象,而我全程使用的都是直接使用BitmapImage对象。
????又要改????
理论上来说把BitmapImage全部改成StorageFile,再在使用时增加读入的环节就可以了。
但这样工作量好大,尝试有没有别的办法…
好吧,打开了item.todoImage,并没有发现类似源路径的东西。
乖乖改吧/(ㄒoㄒ)/
其实只有在DataPackage中才会用到这个图像相对应的StorageFile,总觉得为了这个就修改TodoItem的模型实在不太合理……

思路:是否可以通过Bitmap的UriSource属性传参?
读取文件时:

BitmapImage bitmap = new BitmapImage();
                using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
                {
                    bitmap.SetSource(stream);
                    bitmap.UriSource = new Uri(file.Path); // <======= 测试
                }

再将shareRequested的handler处理为


            StorageFile imageFile = await StorageFile.GetFileFromApplicationUriAsync(item.todoImage.UriSource) ;

如能共享图片就成功了。

运行过程中执行bitmap.UriSource = new Uri(file.Path);就会使picker的功能废掉。

想说可能是file.Path的问题。

看文档看着看着发现这么一段:

DisplayName
Gets a user-friendly name for the file.

?????????这根本毫无帮助啊??????文档有时候真是……太反人类了……
这里写图片描述

根据这些信息,应该可以写出一个合法的路径了。
现在要去实训了/(ㄒoㄒ)/~~


 bitmap.UriSource = new Uri("ms-appx:///Assets/"+ file.DisplayName+".jpg");

如果这么写显然不是个好方案:不能加载ASSETS文件夹意外的图片。根本方法还是要找到PathUri的方法。
这个问答提供了两种方法:

convert-file-path-to-a-file-uri

都不work啊。。文件路径跟uri的关系:
Uri uriAddress2 = new Uri(“file://H:/BTS/JK/%51.jpg”);
string testuri = uriAddress2.LocalPath; // file:///H:/BTS/JK/Q.jpg
也就是说用%+number表示的才是合法uri?查了查这是HTML Encoding,
如果用这个方法还是不行,那真的是没sei了。。。

查着查着,终于知道为什么不行了

This is not possible since a UWP app runs in a sandboxed environment. It cannot access any files on the hard drive. It can only access certain file system locations by default. Please refer to the following page on MSDN for more information: https://msdn.microsoft.com/en-us/library/windows/apps/mt188700.aspx
So you should add the file to your UWP project and then use an ms-appx URI to display it like you are doing. Using an absolute path that refers to some file on the disk won’t work.

总的来说就是,试图访问硬盘上的地址来构建一个Bitmap是不可行的,因为UWP运行在一个sandboxed environment中。
回到应用,如果依然用bitmapimage.urisource实现共享时的发送的话,就只能选择Assets下的文件。如果想选择别的,就只能用StorageFile保存了。
还有另一种思路:利用上周的appdata,将文件路径保存起来,共享时再读取—–这样的存储好像是不必要的,而且如果这样子实现,还不如直接修改todoitem,虽然后者工作量较大。
那就还只能增设一个storagefile属性了。。。如果为了好看, 可以在addappbarbutton的功能里增加设置默认图片的功能。
或者使用storageFile,通过localFolder保存。。

图片绑定细节

点击listitem时要更换图片,点击addAppBarButton要更换图片。
注意把listitem的绑定模式设置为OneWay.

实验截图

这里写图片描述
这里写图片描述这里写图片描述
这里写图片描述

感想

  1. 大多数的时间都花在查找API上了
  2. 不要轻易使用var,容易出现类型转换的问题

拓展

使用后台任务机制来更新磁贴

不同类型的Badge通知

GetDeferral()的使用场景

// Because we are making async calls in the DataRequested event handler,
    //  we need to get the deferral first.
    DataRequestDeferral deferral = request.GetDeferral();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值