Wednesday, 19 July 2017

Toasts 01 Local while running

toasts are the little message boxes that pop-up in the left hand coroner of your windows 10, there is three ways to activate them

  • Scheduled: set using the "ScheduledToastNotification" class, it can be a one off or a recurring toast.
  • Local: can be fired from the application either while running or from a background task.
  • Push: from a push service in the cloud.

now a simple local toast that can be fired while our application is running can be accomplished as follows.

<Page
    x:Class="pc.toastExample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:pc.toastExample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <Button Click="Button_Click">Send toast</Button>
        </StackPanel>
    </Grid>
</Page>

and the codebehind

using Windows.UI.Notifications;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace pc.toastExample
{
    public sealed partial class MainPage : Page
    {
        public MainPage() { this.InitializeComponent(); }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var template = ToastTemplateType.ToastText02;
            var xmlDoc = ToastNotificationManager.GetTemplateContent(template);
            var xml = xmlDoc.GetXml();
            /*  <toast>
                    <visual>
                        <binding template="ToastText02">
                            <text id="1"></text>
                            <text id="2"></text>
                        </binding>
                    </visual>
                </toast>*/

            var textElements = xmlDoc.GetElementsByTagName("text");
            textElements[0].AppendChild(xmlDoc.CreateTextNode("Title text"));
            textElements[1].AppendChild(xmlDoc.CreateTextNode("body text"));

            var toast = new ToastNotification(xmlDoc);
            var notifier = ToastNotificationManager.CreateToastNotifier();
            notifier.Show(toast);
        }
    }
}


fairly simple, with the only use being able to notify the user of some piece of information. no real interactivity. lucky we can add some event recievers to our toastNotification object.

using Windows.UI.Notifications;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using System;

namespace pc.toastExample
{
    public sealed partial class MainPage : Page
    {
        public MainPage() { this.InitializeComponent(); }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var template = ToastTemplateType.ToastText02;
            var xmlDoc = ToastNotificationManager.GetTemplateContent(template);
            var xml = xmlDoc.GetXml();
            /*  <toast>
                    <visual>
                        <binding template="ToastText02">
                            <text id="1"></text>
                            <text id="2"></text>
                        </binding>
                    </visual>
                </toast>*/

            var textElements = xmlDoc.GetElementsByTagName("text");
            textElements[0].AppendChild(xmlDoc.CreateTextNode("Title text"));
            textElements[1].AppendChild(xmlDoc.CreateTextNode("body text"));

            var toast = new ToastNotification(xmlDoc);
            toast.Activated += Toast_Activated;
            toast.Dismissed += Toast_Dismissed;

            //if user disables notifications in general or for your application
            //int the action ceter, then the failed event will fire
            toast.Failed += Toast_Failed;

            var notifier = ToastNotificationManager.CreateToastNotifier();
            notifier.Show(toast);
        }

        private async void Toast_Failed(ToastNotification sender, ToastFailedEventArgs args)
        {
            await Dispatcher.RunAsync(
                Windows.UI.Core.CoreDispatcherPriority.Normal,
                async () => await new MessageDialog("failed toast").ShowAsync());
        }

        private async void Toast_Dismissed(ToastNotification sender, ToastDismissedEventArgs args)
        {
            await Dispatcher.RunAsync(
                Windows.UI.Core.CoreDispatcherPriority.Normal,
                async () => await new MessageDialog("dismissed toast").ShowAsync());
        }

        private async void Toast_Activated(ToastNotification sender, object args)
        {
            await Dispatcher.RunAsync(
                Windows.UI.Core.CoreDispatcherPriority.Normal,
                async () => await new MessageDialog("Activated toast").ShowAsync());
        }
    }
}


now we've added three events for toast interaction:
  • Activated: will fire if the user clicks on the toast
  • Dismissed: will fire if the user clicks the [x] to hide the toast
  • Failed: will fire if the user blocks all or just our app from firing toast notifications.
one final word of warning since we subscribed to events, if we navigate away from the page we should really unsubscribe from them as well. To do that successfully you probably do not want to create your toastNotification object locally in your button, because each time you click on it it'll create a new instance that'll sit in memory and not be collected by the GC. you'd want to create the ToastNotification object at the page or viewmodel level and then unsubscribe the events when you navigate away from the page or the object is no longer needed.

something along the lines of


using System;

using Windows.UI.Notifications;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace pc.toastExample
{
    public sealed partial class MainPage : Page
    {
        public MainPage() { this.InitializeComponent(); }

        ToastNotification toast;

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var template = ToastTemplateType.ToastText02;
            var xmlDoc = ToastNotificationManager.GetTemplateContent(template);
            var xml = xmlDoc.GetXml();
            /*  <toast>
                    <visual>
                        <binding template="ToastText02">
                            <text id="1"></text>
                            <text id="2"></text>
                        </binding>
                    </visual>
                </toast>*/

            var textElements = xmlDoc.GetElementsByTagName("text");
            textElements[0].AppendChild(xmlDoc.CreateTextNode("Title text"));
            textElements[1].AppendChild(xmlDoc.CreateTextNode("body text"));

            //use only one instance of toast
            if (toast == null)
            {
                toast = new ToastNotification(xmlDoc);

                toast.Activated += Toast_Activated;
                toast.Dismissed += Toast_Dismissed;

                //if user disables notifications in general or for your application
                //int the action ceter, then the failed event will fire
                toast.Failed += Toast_Failed;
            }

            var notifier = ToastNotificationManager.CreateToastNotifier();
            notifier.Show(toast);
        }

        private async void Toast_Failed(ToastNotification sender, ToastFailedEventArgs args)
        {
            await Dispatcher.RunAsync(
                Windows.UI.Core.CoreDispatcherPriority.Normal,
                async () => await new MessageDialog("failed toast").ShowAsync());
        }

        private async void Toast_Dismissed(ToastNotification sender, ToastDismissedEventArgs args)
        {
            await Dispatcher.RunAsync(
                Windows.UI.Core.CoreDispatcherPriority.Normal,
                async () => await new MessageDialog("dismissed toast").ShowAsync());
        }

        private async void Toast_Activated(ToastNotification sender, object args)
        {
            await Dispatcher.RunAsync(
                Windows.UI.Core.CoreDispatcherPriority.Normal,
                async () => await new MessageDialog("Activated toast").ShowAsync());
        }

        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            base.OnNavigatingFrom(e);

            //unsubscribe events when navigating away from page.
            if(toast != null)
            {
                toast.Activated -= Toast_Activated;
                toast.Dismissed -= Toast_Dismissed;
                toast.Failed -= Toast_Failed;
                toast = null;
            }
        }
    }
}

now you see how we declared our toast field at the class level and we test to make sure it's null before we instantiate and attach our event handlers, this ensures that we only have one instance of it in memory and when we navigate away from our page we unsubscribe our event handlers letting the Garbage collector take care of our toast variable