Skip to content

Instantly share code, notes, and snippets.

@andrewleader
Last active December 6, 2021 22:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewleader/e78deaf474649ff260c3313dbda99345 to your computer and use it in GitHub Desktop.
Save andrewleader/e78deaf474649ff260c3313dbda99345 to your computer and use it in GitHub Desktop.
Notifications Visualizer Library

Have you ever wanted to display a preview of your live tile in your app's settings page, so the user can instantly see how your various settings will affect their live tile?

Introducing the PreviewTile control! The PreviewTile is a XAML control that replicates the Start tiles, and fully supports displaying adaptive tile notifications. This is the same control that we use in the Notifications Visualizer.

Example of tile settings in a sample app

Using the PreviewTile control is simple. The API's to update properties and send notifications are very similar to the API's you already use for the actual live tiles.

How to use the PreviewTile

The short answer is to install the NuGet package "NotificationsVisualizerLibrary", utilize the NotificationsVisualizerLibrary namespace, and then use the PreviewTile control in your XAML/code.

1. Install the NuGet package

  • WinUI 3 apps: Install the NotificationsVisualizerLibrary.WinUI NuGet package.
  • UWP apps: Install the NotificationsVisualizerLibrary NuGet package.

2. Add a preview tile to your XAML page

In order to add the PreviewTile to XAML, you'll have to add a new XAML namespace so you can reference the control.

<Page
    x:Class="MyApp.SampleSettingsPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    ...
    xmlns:visualizer="using:NotificationsVisualizerLibrary">

Then, you can add the control wherever you'd like. We recommend disabling animations via the IsAnimationEnabled property when using this on a settings page, so the user sees instant changes rather than seeing a new notification animate in.

<visualizer:PreviewTile
    x:Name="WidePreviewTile"
    IsAnimationEnabled="False"
    TileSize="Wide"/>

3. Updating tile properties

In order to set tile properties like the display name, background color, or logos, you will have to use code. The API's are similar to the SecondaryTile API's (you set the properties, and then you call Update in order to actually commit your changes). You cannot use data binding, and you cannot set these properties via XAML, since the call to Update is required.

tile.DisplayName = "CSC 252";
tile.VisualElements.BackgroundColor = Colors.Red;
tile.VisualElements.ShowNameOnSquare150x150Logo = true;
tile.VisualElements.Square150x150Logo = new Uri("ms-appx:///Assets/PlannerAssets/Square150x150Logo.png");
tile.VisualElements.Wide310x150Logo = new Uri("ms-appx:///Assets/PlannerAssets/Wide310x150Logo.png");
tile.VisualElements.Square44x44Logo = new Uri("ms-appx:///Assets/PlannerAssets/Square44x44Logo.png");
 
// Commit the tile properties we changed
await tile.UpdateAsync();

4. Sending notifications

Sending notifications is very similar to the tile API's. You'll obtain a PreviewTileUpdater by using the CreateTileUpdater() method, and then you'll use the updater to clear or send notifications.

The updater takes the exact same TileNotification object as real tiles do, so you can use the same code to generate your tile notification, and then simply pass that to the PreviewTileUpdater.

// Create the updater
PreviewTileUpdater updater = tile.CreateTileUpdater();
 
// OPTIONAL: Clear the preview tile
updater.Clear();
 
// Create a notification via your normal app logic
TileNotification notif = CreateTileNotification();
 
// And send the notification to the preview tile
updater.Update(notif);

Known issues

  • Badge doesn't support glyphs, only integers
  • Tile queue isn't supported

Apps using this control

Let us know if your app is using this control, and we'll feature it here!

Full sample

Here's the entire (UWP) XAML and C# pages used to create the settings page you saw at the top.

<Page
    x:Class="MyApp.SampleSettingsPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    xmlns:visualizer="using:NotificationsVisualizerLibrary">
 
    <ScrollViewer Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" VerticalScrollBarVisibility="Auto">
 
        <StackPanel Margin="20">
             
            <visualizer:PreviewTile
                x:Name="MediumPreviewTile"
                IsAnimationEnabled="False"/>
             
            <visualizer:PreviewTile
                x:Name="WidePreviewTile"
                IsAnimationEnabled="False"
                TileSize="Wide"
                Margin="0,6,0,0"/>
             
            <ToggleSwitch
                x:Name="ToggleShowHomework"
                Header="Show incomplete homework"
                Margin="0,24,0,0"
                IsOn="True"/>
             
            <ToggleSwitch
                x:Name="ToggleShowExams"
                Header="Show upcoming exams"
                Margin="0,12,0,0"
                IsOn="True"/>
             
        </StackPanel>
         
    </ScrollViewer>
</Page>
using NotificationsExtensions.Tiles;
using NotificationsVisualizerLibrary;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.UI;
using Windows.UI.Notifications;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
 
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
 
namespace MyApp
{
    /// 
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// 
    public sealed partial class SampleSettingsPage : Page
    {
        public SampleSettingsPage()
        {
            this.InitializeComponent();
        }
 
        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);
 
            foreach (var tile in AllTiles())
            {
                await UpdateTilePropertiesAsync(tile);
            }
 
            UpdateTileNotifications();
 
            ToggleShowExams.Toggled += ToggleShowExams_Toggled;
            ToggleShowHomework.Toggled += ToggleShowHomework_Toggled;
        }
 
        private async Task UpdateTilePropertiesAsync(PreviewTile tile)
        {
            tile.DisplayName = "CSC 252";
            tile.VisualElements.BackgroundColor = Colors.Red;
            tile.VisualElements.ShowNameOnSquare150x150Logo = true;
            tile.VisualElements.Square150x150Logo = new Uri("ms-appx:///Assets/PlannerAssets/Square150x150Logo.png");
            tile.VisualElements.Wide310x150Logo = new Uri("ms-appx:///Assets/PlannerAssets/Wide310x150Logo.png");
            tile.VisualElements.Square44x44Logo = new Uri("ms-appx:///Assets/PlannerAssets/Square44x44Logo.png");
 
            // Commit the tile properties we changed
            await tile.UpdateAsync();
        }
 
        private IEnumerable AllTiles()
        {
            return new PreviewTile[]
            {
                MediumPreviewTile,
                WidePreviewTile
            };
        }
 
        private void UpdateTileNotifications()
        {
            // If both are disabled, there's nothing to show on the tile
            if (!ToggleShowHomework.IsOn && !ToggleShowExams.IsOn)
            {
                // Clear current content
                foreach (var tile in AllTiles())
                    tile.CreateTileUpdater().Clear();
 
                return;
            }
 
            // Using NotificationsExtensions.Win10 NuGet package
            TileBindingContentAdaptive bindingContent = new TileBindingContentAdaptive();
 
            // NOTE: In a real app, this data would probably be dynamically generated from actual user data
 
            // Add the date header
            bindingContent.Children.Add(new TileText()
            {
                Text = "In two days"
            });
 
            // Add exams
            if (ToggleShowExams.IsOn)
            {
                bindingContent.Children.Add(new TileText()
                {
                    Text = "Exam 2",
                    Style = TileTextStyle.CaptionSubtle
                });
            }
 
            // Add homework
            if (ToggleShowHomework.IsOn)
            {
                bindingContent.Children.Add(new TileText()
                {
                    Text = "Bookwork Pg 37-39",
                    Style = TileTextStyle.CaptionSubtle
                });
 
                bindingContent.Children.Add(new TileText()
                {
                    Text = "Lab report 4",
                    Style = TileTextStyle.CaptionSubtle
                });
            }
 
            TileBinding binding = new TileBinding()
            {
                Content = bindingContent
            };
 
            TileContent content = new TileContent()
            {
                Visual = new TileVisual()
                {
                    TileMedium = binding,
                    TileWide = binding,
 
                    Branding = TileBranding.NameAndLogo
                }
            };
 
            // And send the notification
            foreach (var tile in AllTiles())
                tile.CreateTileUpdater().Update(new TileNotification(content.GetXml()));
        }
 
        private void ToggleShowHomework_Toggled(object sender, RoutedEventArgs e)
        {
            UpdateTileNotifications();
        }
 
        private void ToggleShowExams_Toggled(object sender, RoutedEventArgs e)
        {
            UpdateTileNotifications();
        }
    }
}
@andrewleader
Copy link
Author

Hey @filiphsandstrom, nah no plans either way... what would you need it open sourced for?

The difficulty in open sourcing it is we (at Microsoft) also internally used this same codebase to prototype new toast and tile features, so the code has some private unreleased features that we'd have to sweep out of it, and if we wanted to continue prototyping features like that, it'd be more difficult.

That said, I was the only person who would do any prototyping via the Visualizer, and I don't work on notifications anymore... so I'm more open to the idea of just cleaning and open sourcing the code. I'd like to know why you'd need it open source though!

@andrewleader
Copy link
Author

Thanks Filiph! I wish the code didn't have so much internal prototyping, I'll see if I can take a look at the code again this or next week, but I think the most I could devote to publishing it is an hour (since it really doesn't have that much value going forward since tiles aren't part of Win11).

I'd give it a 50/50 chance I can publish the code.

@andrewleader
Copy link
Author

@filiphsandstrom lucky day, it only took 53 minutes! Here's the code: https://github.com/andrewleader/NotificationsVisualizerLibrary

If you have bug fixes, those could be welcome as a PR, however net-new features are unlikely to be accepted unless they're simply replicating the inbox OS behavior. This library is meant to represent Windows inbox tiles/toasts as closely as possible, if you have changes to improve that replication, those would be accepted (but I also won't have too much time to review, so massive changes are unlikely to be accepted).

Thanks!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment