老周最近熱衷于講故事,接下來還是講故事時間。
有人問我:你上大學的時候,有加入過學生會嗎?讀大學有沒有必要加入學生會?
哎喲,這怎麼回答呢,從短期來說,加入學生會有點用,至少可以娛樂一下,運氣好的話,說不定能遇到紅顔知己,但這機率相當低。從長遠發展看嘛,是沒什麼用。老周當年讀了四年大學,在學生會混了四年,什麼名堂也沒混出來。
一方面老周向來不求虛名,是以也沒去參選所謂的什麼部長、主席之類的,這些“官銜”聽起來很高大上,實際上很庸俗蠢。既然沒興趣,因而老周更不需要去搞那些見不得人的勾當,什麼勾當?你懂的。其次,老周沒感覺到這些職務,對将來的職業生涯發展有什麼幫助,基本上屬于混日子罷了。
老周當初一上大學,本不想進什麼學生會天地會之類的,之是以進了,純屬意外,簡直太意外了,事情是這樣的。話說那天學生會招新,盡管老周的母校比較老舊,但校園裡還是被各處擺攤招新的學兄學姐們弄得像大觀園似的,熱鬧非常。
誰不知道,大觀園裡美女多,于是我宿舍的室友開始動心了,有兩個人鼓起勇氣,決定明天去面試。本來跟我沒什麼關系,誰想第二天,又有一位室友感興趣了,他們索性商量全宿舍一起出動。老周本不想去,就被他們硬拉着去。
一夥漫無目的地随處看,我以為他們是去看美女的,原來他們還真的去面試了,走着走着,我們進入了一個教室,裡面坐着幾位師兄和師姐。進去前要在門口登記,寫上個人資訊,然後排隊面試(教室裡都沒幾個人,排什麼隊),我說我不是來面試的,是陪室友來玩玩的。聽我一說完,一位師兄叫我出去走廊外面等。
正當我往外走時,另一位師兄又把我叫回來,說既然來了就面面試吧,沒事的。這時候我才知道,他們是要招書法高手的。老周雖不敢當什麼高手,但從小就握過幾年毛筆,不防耍耍。就這樣,老周就莫名其妙地進了學生會。
想來有趣,本來想進的,沒進;我本不想進的,反而進了。真是世事無常,禍福相依啊。
=========================================================
故事講完了,下面說正經話。
話說上一回,老周給大夥伴們介紹了win 10中Toast通知的新型模闆,既包含有舊版本的相容格式,也有用于通用平台的“自适應”通知。前一文章中,老周主要介紹了XML文檔的基本格式,并示範了兩個例子。
今天,咱們探讨一下如何在Toast通知中使用互動指令,并通過互動指令激活應用程式。所謂互動指令,就是在Toast通知的界面上添加一些可以讓使用者操作的元素,來與應用程式進行互動。這些互動元素主要有:
1、按鈕。用action元素來指定,使用者點選按鈕後會激活應用程式,并把使用者所點選的按鈕的參數作為激活參數傳遞給應用程式。
2、輸入框。就是一個文本框,使用者可以在裡面輸入文本。
3、選擇清單。類似于下拉清單框,使用者可以在裡面選擇一個項。
這些互動元素都統一放到actions元素下面。
舉個例子,比如這樣:
<toast>
<visual>
<binding template="ToastGeneric">
<text>唱山歌</text>
<text>一起去唱山歌吧.</text>
<image placement="appLogoOverride" src="c.png" />
</binding>
</visual>
<actions>
<action content="報名" arguments="join" imageUri="jn.png" />
<action content="不去" arguments="cancel" />
</actions>
</toast>
大家可以想象一下這個Toast通知是什麼樣子的,現在不截圖給你看,待會兒咱們做示例時再看。
首先,visual元素在前一篇爛文中講過,是描述toast通知的可視化部分,第一個text被視為标題,是以顯示出來字型較大,第二個text作為正文,顯示一行文本。image元素指定一個圖像,因為設定了placement="appLogoOverride",表明這個圖像會替換應用的預設圖示,顯示在通知的左上角。
接下來,重點關注actions元素,actions元素下面專門用來放置互動指令,有兩個元素可用:
<action>:表示定義一個指令按鈕。例如:
<action content = "确定" arguments = "yes" activationType ="foreground或background" imageUrl="b.png" />
content表示要在按鈕上顯示的文本,上面例子在按鈕上顯示“确定”。imageUrl表示顯示在按鈕上的圖示,如果不用圖示,就可以不設定imageUrl屬性。arguments用來指定一個參數,這個參數是你自己定的,比如我這裡叫yes,當使用者點選按鈕後,arguments的值會傳遞給應用程式,這樣應用程式才會知道你到底點選了哪個按鈕。
activationType訓示以何種方式激活應用程式,如果值為foreground,則表明toast通知将在前台激活應用程式,這時候使用者可以看見應用程式;如果值為background,表明toast通知通過背景方式激活應用,此時使用者看不到應用程式,背景激活必須指定一個背景任務,當激活時就會執行背景任務。如果值為protocol,表示将通過協定來激活應用程式。
如果希望使用者可以在Toast通知上輸入内容,可以這樣定義XML:
<input id="name" type="text" />
Toast通知上會顯示一個文本框,id屬性是必須指定的,而且必須是actions中的唯一值,不能重複,這個id值在激活應用程式時會傳遞給應用。type屬性指定input元素的類型,text表示文本框,讓使用者輸入文本。如果是selection表示顯示一個清單選擇框,使用者隻能從中選擇一個項。
如果type為selection,那麼input元素下會包含N個selection元素,每個selection元素表示一個選項,如
<input id="age" type="selection">
<selection content="五歲" id="5" />
<selection content="七歲" id="7" />
</input>
上面例子,定義了兩個選項,每個selection的id值必須唯一,它表示該項的值,這個id也會傳遞給被激活的程式。content表示項中顯示的文本,用來給你看的。即當toast彈出時,在下拉清單中,你看到的是五歲、七歲兩個選項。
下面我們來實戰一下,先看Toast通知如何從前台激活應用。
首先構造XML文檔。
string visual = "<visual>" +
"<binding template=\"ToastGeneric\">" +
"<text>宇宙第一應用</text>" +
"<text>應用正在收集您的資訊。</text>" +
"</binding>" +
"</visual>";
string actions = "<actions>" +
"<input id=\"name\" type=\"text\" placeHolderContent=\"請輸入姓名\" />" +
"<input id=\"city\" type=\"text\" placeHolderContent=\"請輸入城市\" />" +
"<action content=\"确定\" arguments=\"ok\" />" +
"<action content=\"取消\" arguments=\"cancel\" activationType=\"foreground\" />" +
"</actions>";
string toastXml = $"<toast>{visual}{actions}</toast>";
有一點大夥要注意,當input和action元素同時使用時,input元素必須放在action元素的前面。placeHolderContent屬性主要設定占位文本,即當文本框中沒有輸入任何内容時顯示的文本。
上面的Toast通知定義了兩個可供輸入的文本框,以及兩個指令按鈕。
下面代碼顯示通知。
// 加載XML文檔
XmlDocument doc = new XmlDocument();
doc.LoadXml(toastXml);
// 顯示通知
ToastNotification notification = new ToastNotification(doc);
ToastNotifier notifier = ToastNotificationManager.CreateToastNotifier();
notifier.Show(notification);
正因為應用程式是被前台激活的,是以肯定要為Application類作激活處理,在App類中,重寫基類的OnActivated方法。
protected override void OnActivated(IActivatedEventArgs args)
{
// 判斷激活類型
// 确認是由Toast通知激活應用
if (args.Kind == ActivationKind.ToastNotification)
{
// 轉換參數類型
ToastNotificationActivatedEventArgs toastargs = (ToastNotificationActivatedEventArgs)args;
// 擷取頁面引用
Frame root = Window.Current.Content as Frame;
if (root == null)
{
root = new Frame();
Window.Current.Content = root;
}
if (root.Content == null)
{
root.Navigate(typeof(MainPage));
}
MainPage page = (MainPage)root.Content;
// 判斷使用者點選哪個按鈕
string activeargs = toastargs.Argument;
if (activeargs == "ok") //确定按鈕
{
// 擷取使用者輸入的内容
string name = toastargs.UserInput["name"] as string;
string city = toastargs.UserInput["city"] as string;
page.ShowTextFromForeactivation($"使用者點選了确定按鈕,輸入的内容為:\n姓名:{name}\n城市:{city}。");
}
else //取消按鈕
{
page.ShowTextFromForeactivation("使用者點選了取消按鈕。");
}
}
Window.Current.Activate();
}
先要通過方法參數的Kind屬性來判斷一下應用程式是不是被Toast通知所激活的,如果值是ToastNotification,證明應用程式是被Toast通知激活的。
然後要把方法參數的類型轉化為ToastNotificationActivatedEventArgs類型,ToastNotificationActivatedEventArgs類專用于Toast通知的激活。
Argument屬性中取得的值就是被點選的action的arguments屬性中的值,上面我定義的toast有兩個action,arguments的值分别為ok和cancel,如果使用者點選了确定按鈕,那麼從ToastNotificationActivatedEventArgs對象的Argument屬性得到的值為ok,否則就是cancel。
UserInput屬性擷取的就是input元素的值,屬性類型為ValueSet,實際上是一個字典類型。Key為Toast通知中input元素的id,Value的值是input元素中輸入的内容。通過UserInput屬性,程式就知道使用者在文本框中輸入了什麼内容。
運作應用程式,當通知彈出時,輸入相關内容,如下圖。
輸入一些文本後,然後點選确定按鈕,然後應用程式被激活,會看到首頁面上顯示從Toast通知傳遞到應用程式的輸入資料。如下圖所示。
---------------------------------------------------------------------------------------------------
上面示範的是Toast通知從前台激活應用程式,下面來看看背景激活。Toast通知從背景激活應用程式,使用者不會看到應用程式界面,但應用程式會在背景任務中處理從Toast通知傳遞到應用程式的資料。
要實作從背景處理Toast通知,首先要實作一個背景任務。記住背景任務要在一個獨立的Runtime元件項目中。背景的實作代碼如下:
public sealed class ToastBgTask : IBackgroundTask
{
public async void Run(IBackgroundTaskInstance taskInstance)
{
var deferral = taskInstance.GetDeferral();
ToastNotificationActionTriggerDetail details = taskInstance.TriggerDetails as ToastNotificationActionTriggerDetail;
if (details != null)
{
// 先确認使用者點選了yes按鈕
string cmdargs = details.Argument;
if (cmdargs == "yes")
{
// 擷取選擇的項
object value = details.UserInput["ut"];
// 儲存資料
StorageFolder local = ApplicationData.Current.LocalFolder;
JsonObject jsobj = new JsonObject();
jsobj.SetNamedValue("updatetime", JsonValue.CreateNumberValue(Convert.ToDouble(value)));
StorageFile newFile = await local.CreateFileAsync("data.json", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(newFile, jsobj.Stringify(), Windows.Storage.Streams.UnicodeEncoding.Utf8);
}
}
deferral.Complete();
}
}
當Toast通知激活背景任務後,可以從 taskInstance.TriggerDetails屬性擷取到一個ToastNotificationActionTriggerDetail對象執行個體。和前台激活一樣,通過Argument屬性可以擷取Toast通知中被使用者點選的action元素的參數。從UserInput屬性可以擷取到input元素的id值。
背景任務完成後,記得要在主項目中引用,這個老周已經重複了幾千遍了。
然後打開清單檔案,在Package/Applications/Application節點下添加背景任務聲明。
<Extensions>
<Extension Category="windows.backgroundTasks" EntryPoint="BackgroundTasks.ToastBgTask">
<BackgroundTasks>
<Task Type="general"/>
</BackgroundTasks>
</Extension>
<
/Extensions>
由于在背景任務中,是把使用者在Toast通知上選擇的項儲存到本地檔案中,是以在主應用中,可以讀出檔案的内容,并顯示出來。
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
try
{
// 讀入背景儲存的内容
StorageFolder local = ApplicationData.Current.LocalFolder;
StorageFile dataFile = await local.GetFileAsync("data.json");
if (dataFile == null) return;
string jsstr = await FileIO.ReadTextAsync(dataFile, Windows.Storage.Streams.UnicodeEncoding.Utf8);
JsonObject jsobj = null;
if (JsonObject.TryParse(jsstr, out jsobj))
{
double d = jsobj.GetNamedNumber("updatetime");
tbResultFromBackActivate.Text = $"你選擇了每隔{d:N0}天更新一次。";
}
await dataFile.DeleteAsync();
}
catch(Exception ex)
{
Debug.WriteLine($"檔案讀取異常:{ex.Message},異常類型:{ex.GetType().Name}。");
}
}
最後,我們實作顯示Toast通知的代碼。
string visual = "<visual>" +
"<binding template=\"ToastGeneric\">" +
"<text>無敵應用</text>" +
"<text>請選擇更新間隔天數。</text>" +
"</binding>" +
"</visual>";
string actions = "<actions>" +
"<input id=\"ut\" type=\"selection\" defaultInput=\"5\">"+
"<selection id=\"3\" content=\"3天\" />" +
"<selection id=\"5\" content=\"5天\" />" +
"<selection id=\"10\" content=\"10天\" />" +
"</input>" +
"<action content=\"是\" activationType=\"background\" arguments=\"yes\" />" +
"<action content=\"否\" activationType=\"background\" arguments=\"no\" />" +
"</actions>";
string toastXml = $"<toast>{visual}{actions}</toast>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(toastXml);
ToastNotification notification = new ToastNotification(doc);
ToastNotifier notifier = ToastNotificationManager.CreateToastNotifier();
notifier.Show(notification);
input元素的type為selection,表示Toast通知上的輸入行為為清單選擇,并使用三個selection元素定義了三個選項。注意,在input元素中,defaultInput屬性指定輸入控件的預設值。如果type為text,該屬性設定預設的文本;在本例中,type為selection,是以預設值就是希望預設被選中的項的id值。
似乎一切就緒了,實際上我們還有很關鍵一步沒有做——注冊背景任務,清單檔案中僅僅是聲明,而要希望讓Toast通知的操作激活背景任務,還需要對背景任務進行注冊。
private async void RegToastBackgroundTask()
{
// 判斷一下是否允許通路背景任務
var res = await BackgroundExecutionManager.RequestAccessAsync();
if (res == BackgroundAccessStatus.Denied || res == BackgroundAccessStatus.Unspecified)
{
return;
}
Type taskType = typeof(BackgroundTasks.ToastBgTask);
var task = BackgroundTaskRegistration.AllTasks.Values.FirstOrDefault(t => t.Name == taskType.Name);
if (task == null)
{
// 注冊背景任務
BackgroundTaskBuilder bd = new BackgroundTaskBuilder();
bd.Name = taskType.Name;
bd.TaskEntryPoint = taskType.FullName;
// 聲明觸發器
ToastNotificationActionTrigger trigger = new ToastNotificationActionTrigger();
bd.SetTrigger(trigger);
task = bd.Register();
}
}
背景的觸發器便用 ToastNotificationActionTrigger類型。
現在我們來看一下,運作程式,等Toast通知出現後,把程式關了。然後在Toast通知上選擇一個項,然後送出。
然後再次啟動應用程式,可以看到結果了。
如何,這Win10的Toast通知是不是很牛X呢。由于小妹妹正在拿着我的920玩,是以就不用手機運作了,手機模拟器沒有安裝,大夥兒有興趣的可以耍耍。
示例源碼:https://files.cnblogs.com/files/tcjiaan/toastActivationApp.zip