現操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();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章