使用 Windows10 自定義交互消息通知


消息通知是最常用的應用功能之一了,但是由於平台的差異,IOS Android 以及 Windows 都有其特殊性,Android開發者在國內常常都是使用三方的一些推送服務,或者是使用自建的服務器為應用提供推送服務,方式多種多樣這里就不多闡述了,對比在國內可以使用的兩個推送系統 IOS 和 Windows 對比來說,經歷了 WindowsPhone 7 / 7.5 /8 / 8.1 /10 五代系統的迭代以后相比 IOS 系統的推送消息的種類來說更加豐富且更加人性化,今天給大家介紹一下如何在 Windows 10 推送消息中顯示圖片並且實現消息快速回復。

由於 Windows 10 Universal 應用架構這個功能可以實現一次開發可以實現在手機和平板上統統兼容。

image  image

由上圖看到左側是 Windows10 UWP 應用在Windows Phone上收到消息通知的樣子,右側的圖片是應用在 Windows 10 收到推送消息時的樣子。

都是由4個部分組成的:

消息主題:消息發送者照片 + 消息主題與消息文字內容。

消息圖片:如果消息發送者消息中包含圖片可以在消息預覽中直接將圖片顯示出來。

消息快速回復:如果是即時消息,消息的接收者可以在不啟動應用的情況下直接在 吐司消息 或 通知中心中直接回復消息。

消息快速反饋/行動(這個不知道怎么翻譯更確切):這里是可以讓用戶通過按鈕的形式對消息進行一個標記或者是一個簡短的回復,例如圖片中的點贊或打開應用查看詳情。

接下來我分享以下這個Toast消息的的XML結構來簡單分析以下:

<?xml version="1.0"?>
<toast launch="action=viewConversation conversationId=384928">
  <visual>
    <binding template="ToastGeneric">
      <text>Andrew sent you a picture</text>
      <text>Check this out, Happy Canyon in Utah!</text>
      <image src="http://blogs.msdn.com/cfs-filesystemfile.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-71-81-permanent/2727.happycanyon1_5B00_1_5D00_.jpg"/>
      <image src="ms-appdata:///local/Andrew.jpg" placement="appLogoOverride" hint-crop="circle"/>
    </binding>
  </visual>
  <actions>
    <input id="tbReply" type="text" placeHolderContent="Type a response"/>
    <action content="Reply" arguments="action=reply&amp;conversationId=384928" activationType="background" imageUri="Assets/Reply.png" hint-inputId="tbReply"/>
    <action content="Like" arguments="action=like&amp;conversationId=384928" activationType="background"/>
    <action content="View" arguments="action=viewImage&amp;imageUrl=http%3A%2F%2Fblogs.msdn.com%2Fcfs-filesystemfile.ashx%2F__key%2Fcommunityserver-blogs-components-weblogfiles%2F00-00-01-71-81-permanent%2F2727.happycanyon1_5B00_1_5D00_.jpg"/>
  </actions>
</toast>

通過代碼我們可以清晰的看到這條消息主要分為兩部分

visual binding 部分中主要用來描述消息標題和消息文字內容,其中還包括了兩張圖片分別是用戶頭像和消息圖片,通過兩個 image 標簽可以理解到實際上消息內容是一個網絡圖片,而發送者的頭像則是一張應用的本地圖片(通過ms-appdata:///local可以看出來)。

其次Action結點下的內容就是一個輸入框和三個按鈕了,很明顯在 content屬性中標明了他們的作用,並且請大家注意一下argument屬性標明的內容會傳入應用用來標記通知或者會話的ID,activationType是用來標記這個按鈕觸發事件以后,的代碼執行位置,Background是一個后台任務(這樣就意味着應用可以不啟動就可以執行一些操作)。

接着我們來看一下這個應用的聲明文件(package.appxmanifest)中聲明了這個應用支持 background tasks 並且task類型是一個 System event

image

<Extensions>
        <Extension Category="windows.backgroundTasks" EntryPoint="BackgroundTaskComponent.ToastNotificationBackgroundTask">
          <BackgroundTasks>
            <Task Type="systemEvent" />
          </BackgroundTasks>
        </Extension>
      </Extensions>

接着我們看看如何在后台任務中處理這個消息通知。首先通過 details.Argument來獲取到之前在XML中設置的消息參數,通過swith case按鈕的conten屬性中的內容來進行邏輯判斷從而執行不同的應用程序邏輯。

public async void Run(IBackgroundTaskInstance taskInstance)
        {
            // Get a deferral since we're executing async code
            var deferral = taskInstance.GetDeferral();

            try
            {
                // If it's a toast notification action
                if (taskInstance.TriggerDetails is ToastNotificationActionTriggerDetail)
                {
                    // Get the toast activation details
                    var details = taskInstance.TriggerDetails as ToastNotificationActionTriggerDetail;

                    // Deserialize the arguments received from the toast activation
                    QueryString args = QueryString.Parse(details.Argument);

                    // Depending on what action was taken...
                    switch (args["action"])
                    {
                        // User clicked the reply button (doing a quick reply)
                        case "reply":
                            await HandleReply(details, args);
                            break;

                        // User clicked the like button
                        case "like":
                            await HandleLike(details, args);
                            break;

                        default:
                            throw new NotImplementedException();
                    }
                }

                // Otherwise handle other background activations
                else
                    throw new NotImplementedException();
            }

            finally
            {
                // And finally release the deferral since we're done
                deferral.Complete();
            }
        }

在實現代碼中可以再次獲取更詳細的參數獲得消息會話ID以及用戶在快速回復對話框中輸入的內容從而與服務器進行交互。

private async Task HandleReply(ToastNotificationActionTriggerDetail details, QueryString args)
        {
            // Get the conversation the toast is about
            int conversationId = int.Parse(args["conversationId"]);

            // Get the message that the user typed in the toast
            string message = (string)details.UserInput["tbReply"];

            // In a real app, this would be making a web request, sending the new message
            await Task.Delay(TimeSpan.FromSeconds(2.3));

            // In a real app, you most likely should NOT notify your user that the request completed (only notify them if there's an error)
            SendToast("Your message has been sent! Your message: " + message);
        }

        private async Task HandleLike(ToastNotificationActionTriggerDetail details, QueryString args)
        {
            // Get the conversation the toast is about
            int conversationId = int.Parse(args["conversationId"]);

            // In a real app, this would be making a web request, sending the like command
            await Task.Delay(TimeSpan.FromSeconds(1.1));

            // In a real app, you most likely should NOT notify your user that the request completed (only notify them if there's an error)
            SendToast("Your like has been sent!");
        }

當然也不要忘記在應用啟動的時候注冊這個BackgroundTask,這部分代碼可以放在應用 Onlaunched 和 OnActivated事件中。

private void RegisterBackgroundTask()
        {
            const string taskName = "ToastBackgroundTask";

            // If background task is already registered, do nothing
            if (BackgroundTaskRegistration.AllTasks.Any(i => i.Value.Name.Equals(taskName)))
                return;

            // Otherwise create the background task
            var builder = new BackgroundTaskBuilder()
            {
                Name = taskName,
                TaskEntryPoint = typeof(ToastNotificationBackgroundTask).FullName
            };

            // And set the toast action trigger
            builder.SetTrigger(new ToastNotificationActionTrigger());

            // And register the task
            builder.Register();
        }

另外剛才除了后台執行代碼的兩個按鈕還有查看消息和查看圖片按鈕的執行邏輯,這里應該是放在Launched 和 Activated方法中的首先要判斷應用的 IActivatedEventArgs類型是否ToastNotificationActivatedEventArgs如果是嘗試模仿后台任務中查找按鈕參數的方法找到會話ID和照片連接並導航到應用的正確頁面去。

private async Task OnLaunchedOrActivated(IActivatedEventArgs e)
        {
            // Initialize things like registering background task before the app is loaded
            await InitializeApp();

#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                this.DebugSettings.EnableFrameRateCounter = true;
            }
#endif

            Frame rootFrame = Window.Current.Content as Frame;

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new Frame();

                rootFrame.NavigationFailed += OnNavigationFailed;

                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    // TODO: Load state from previously suspended application
                }

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }

            // Handle toast activation
            if (e is ToastNotificationActivatedEventArgs)
            {
                var toastActivationArgs = e as ToastNotificationActivatedEventArgs;

                // If empty args, no specific action (just launch the app)
                if (toastActivationArgs.Argument.Length == 0)
                {
                    if (rootFrame.Content == null)
                        rootFrame.Navigate(typeof(MainPage));
                }

                // Otherwise an action is provided
                else
                {
                    // Parse the query string
                    QueryString args = QueryString.Parse(toastActivationArgs.Argument);

                    // See what action is being requested 
                    switch (args["action"])
                    {
                        // Open the image
                        case "viewImage":

                            // The URL retrieved from the toast args
                            string imageUrl = args["imageUrl"];

                            // If we're already viewing that image, do nothing
                            if (rootFrame.Content is ImagePage && (rootFrame.Content as ImagePage).ImageUrl.Equals(imageUrl))
                                break;

                            // Otherwise navigate to view it
                            rootFrame.Navigate(typeof(ImagePage), imageUrl);
                            break;
                            

                        // Open the conversation
                        case "viewConversation":

                            // The conversation ID retrieved from the toast args
                            int conversationId = int.Parse(args["conversationId"]);

                            // If we're already viewing that conversation, do nothing
                            if (rootFrame.Content is ConversationPage && (rootFrame.Content as ConversationPage).ConversationId == conversationId)
                                break;

                            // Otherwise navigate to view it
                            rootFrame.Navigate(typeof(ConversationPage), conversationId);
                            break;


                        default:
                            throw new NotImplementedException();
                    }

                    // If we're loading the app for the first time, place the main page on the back stack
                    // so that user can go back after they've been navigated to the specific page
                    if (rootFrame.BackStack.Count == 0)
                        rootFrame.BackStack.Add(new PageStackEntry(typeof(MainPage), null, null));
                }
            }

            // Handle launch activation
            else if (e is LaunchActivatedEventArgs)
            {
                var launchActivationArgs = e as LaunchActivatedEventArgs;

                // If launched with arguments (not a normal primary tile/applist launch)
                if (launchActivationArgs.Arguments.Length > 0)
                {
                    // TODO: Handle arguments for cases like launching from secondary Tile, so we navigate to the correct page
                    throw new NotImplementedException();
                }

                // Otherwise if launched normally
                else
                {
                    // If we're currently not on a page, navigate to the main page
                    if (rootFrame.Content == null)
                        rootFrame.Navigate(typeof(MainPage));
                }
            }

            else
            {
                // TODO: Handle other types of activation
                throw new NotImplementedException();
            }


            // Ensure the current window is active
            Window.Current.Activate();
        }

好了這樣你的應用就可以處理 Windows10 可交互性的自定義消息通知了。

並且為了測試方便你可以在應用中使用 NotificationsExtensions.Win10 這個擴展類庫進行測試,在nuget上可以直接下載到。

https://www.nuget.org/packages/NotificationsExtensions.Win10/

image

測試代碼如下:

private void ButtonSendToast_Click(object sender, RoutedEventArgs e)
        {
            // In a real app, these would be initialized with actual data
            string title = "Andrew sent you a picture";
            string content = "Check this out, Happy Canyon in Utah!";
            string image = "http://blogs.msdn.com/cfs-filesystemfile.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-71-81-permanent/2727.happycanyon1_5B00_1_5D00_.jpg";
            string logo = "ms-appdata:///local/Andrew.jpg";
            int conversationId = 384928;

            // Construct the visuals of the toast
            ToastVisual visual = new ToastVisual()
            {
                TitleText = new ToastText()
                {
                    Text = title
                },

                BodyTextLine1 = new ToastText()
                {
                    Text = content
                },

                InlineImages =
                {
                    new ToastImage()
                    {
                        Source = new ToastImageSource(image)
                    }
                },

                AppLogoOverride = new ToastAppLogo()
                {
                    Source = new ToastImageSource(logo),
                    Crop = ToastImageCrop.Circle
                }
            };

            // Construct the actions for the toast (inputs and buttons)
            ToastActionsCustom actions = new ToastActionsCustom()
            {
                Inputs =
                {
                    new ToastTextBox("tbReply")
                    {
                        PlaceholderContent = "Type a response"
                    }
                },

                Buttons =
                {
                    new ToastButton("Reply", new QueryString()
                    {
                        { "action", "reply" },
                        { "conversationId", conversationId.ToString() }

                    }.ToString())
                    {
                        ActivationType = ToastActivationType.Background,
                        ImageUri = "Assets/Reply.png",

                        // Reference the text box's ID in order to
                        // place this button next to the text box
                        TextBoxId = "tbReply"
                    },

                    new ToastButton("Like", new QueryString()
                    {
                        { "action", "like" },
                        { "conversationId", conversationId.ToString() }

                    }.ToString())
                    {
                        ActivationType = ToastActivationType.Background
                    },

                    new ToastButton("View", new QueryString()
                    {
                        { "action", "viewImage" },
                        { "imageUrl", image }

                    }.ToString())
                }
            };


            // Now we can construct the final toast content
            ToastContent toastContent = new ToastContent()
            {
                Visual = visual,
                Actions = actions,

                // Arguments when the user taps body of toast
                Launch = new QueryString()
                {
                    { "action", "viewConversation" },
                    { "conversationId", conversationId.ToString() }

                }.ToString()
            };


            // And create the toast notification
            ToastNotification notification = new ToastNotification(toastContent.GetXml());

            
            // And then send the toast
            ToastNotificationManager.CreateToastNotifier().Show(notification);
        }

有即時消息需求的應用是非常適合這個功能的,行動起來快試試把這個功能加到應用中吧。

參考資料:

Adaptive Tile Templates - Schema and Documentation

What's new with live tiles in Windows 10

What's new/different with toast notifications and action center in Windows 10

Quickstart: Sending a local toast notification and handling activations from it (Windows 10)

Adaptive and interactive toast notifications for Windows 10

希望上的總結可以幫助到大家, 同時歡迎大家在這里和我溝通交流或者在新浪微博上 @王博_Nick


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM