Category Archives: UWP Community Toolkit

Displaying Dynamic SQL Results in a UWP DataGrid

In this article we’ll show how you can display the result of any query against a SQL Server database directly in a UWP DataGrid. The grid control that we use is the new Windows Community Toolkit DataGrid. The companion sample app hosts 3 different TSQL queries (feel free to add your own), but only one instance of the DataGrid. Here’s how it looks like:

Grid_1

We’ll show you how to

  • connect to a SQL Server database from UWP,
  • read a dynamic TSQL statement into a DataTable,
  • modify the content of the DataTable progammatically – if needed,
  • bind the DataTable to a DataGrid, and
  • sort the grid on clicking a header column.

This type of dynamic data binding and visualization is particularly useful in the parts of an app that deal with administration, diagnostics, monitoring, or trouble shooting – where you don’t necessarily know upfront which queries to launch and don’t have the possibility (or resources, or priority) to define a full entity model and/or specialized UI controls.

Get that DataGrid

The DataGrid XAML Control is a port from the popular Silverlight control with the same name. Most -but not all- of the functionality has been migrated. This article does NOT focus on its full feature set, but only on dynamic data binding. Please check the official documentation and the source code of a complete sample app for more info. Its documentation is not yet fully uploaded, so some of the hyperlinks in this article will point to the (2011!) Silverlight version.

The DataGrid Control is distributed as a NuGet package within Windows Community Toolkit, currently as a prerelease :

Grid_NuGet

Here’s how the data grid is defined in the sample app. With no columns specified and AutoGenerateColumns to false it is ready for dynamic data binding:

<controls:DataGrid x:Name="ResultsGrid"
                    RowDetailsTemplate="{StaticResource DetailsTemplate}"
                    RowDetailsVisibilityMode="VisibleWhenSelected"
                    CanUserSortColumns="True"
                    Sorting="ResultsGrid_Sorting"
                    BorderThickness="2"
                    BorderBrush="DarkSlateGray"
                    AlternatingRowBackground="BlanchedAlmond"
                    GridLinesVisibility="All"
                    AutoGenerateColumns="False"
                    SelectionMode="Single" />

So let’s create some tabular data.

Populating a DataTable

DataTable is one of the core classes of the ADO.NET library. It represents an in-memory, database-agnostic structure of rows and strongly typed columns. An easy way to populate a DataTable is calling Fill() against a SqlDataAdapter that executes a SqlCommand on a SqlConnection. Here’s how this looks like in the sample app:

using (var connection = new SqlConnection(connectionString))
{
    await connection.OpenAsync();
    var command = connection.CreateCommand();
    command.CommandText = query;
    dataTable = new DataTable();

    using (var dataAdapter = new SqlDataAdapter(command))
    {
        dataAdapter.Fill(dataTable);
    }
}

Defining the DataGrid structure

In WPF, it would suffice to set AutoGenerateColumns to true and then run the following to populate the DataGrid from the DataTable:

ResultsGrid.ItemsSource = dataTable;

[Actually just data binding would even do the trick.]

The Silverlight -and hence UWP- version of the control doesn’t allow this, so there’s a tiny bit more programming required.

We’ll create a list of DataGridTextColumn instances -one for each column in the DataTable- and assign their Header text and and index Binding:

for (int i = 0; i < table.Columns.Count; i++)
{
        grid.Columns.Add(new DataGridTextColumn()
        {
            Header = table.Columns[i].ColumnName,
            Binding = new Binding { Path = new PropertyPath("[" + i.ToString() + "]") }
        });
}

If you would want to rename columns here, or skip some, or add some new ones, then this would be the place to do these changes.

Populating the DataGrid

The DataGrid is now ready to accept content. This content comes from an ObservableCollection that we populate with the ItemArray of each row in the DataTable. The collection is then set as ItemsSource of the DataGrid:

var collection = new ObservableCollection<object>();
foreach (DataRow row in table.Rows)
{
    collection.Add(row.ItemArray);
}

grid.ItemsSource = collection;

Here’s another view on the result. Same grid, same code, different query:

Grid_2

Tweaking the content

In the sample app, we manipulated the content of one of the columns. Some of the queries contain the text of a TSQL query, a long string that may contain visual formatting with tabs, carriage returns and series of blanks. That’s good to display in the details template – which is what we do. For a regular column it makes more sense to truncate the content, and get rid of the carriage returns.

Fortunately it’s easy to iterate through the rows of a DataTable and update one or more of its columns). Here’s how we remove obsolete white space from a string (using a Split() with StringSplitOptions.RemoveEmptyEntries), and truncate the SQL statements:

if (table.Columns.Contains("SQL Statement"))
{
    var column = table.Columns["SQL Statement"];

    foreach (DataRow row in table.Rows)
    {
        string sqlStatement = ((row[column] as string) ?? string.Empty).Trim();
        row[column] = string.Join(' ', sqlStatement.Split(default(string[]), StringSplitOptions.RemoveEmptyEntries)).Substring(0, 80) + "...";
    }

    table.AcceptChanges();
}

The call to AcceptChanges() is not strictly necessary but we do it to save memory. The call locally commits the changes inside the DataTable and clears the –potentially bloated- row versioning information.

Here’s how the sample app looks like for a query that has a “SQL Statement” column. The manipulated value goes to a regular column and the full text appears in the details of the selected row:

Grid_3

Sorting

Last but not least, we implemented the canonical single-column-sorting-on-header-click behavior. This is only enabled when CanUserSort on the column or CanUserSortColumns on the DataGrid is set to true. In the current release, the DataGrid only covers the visual part: it displays an arrow in the column header according the current SortDirection. We have to implement the sort ourselves in a handler hooked to the Sorting event that exposes the column and the sort direction. We set that sort direction for the clicked column and reset I for the others:

var currentSortDirection = e.Column.SortDirection;

foreach (var column in ResultsGrid.Columns)
{
    column.SortDirection = null;
}

var sortOrder = "ASC";

if ((currentSortDirection == null || currentSortDirection == DataGridSortDirection.Descending))
{
    e.Column.SortDirection = DataGridSortDirection.Ascending;
}
else
{
    sortOrder = "DESC";
    e.Column.SortDirection = DataGridSortDirection.Descending;
}

Then we sort the content itself. We assign the Sort property of the DataTable’s DefaultView, transform the resulting DataView back to a DataTable, and update the binding:

var dataView = dataTable.DefaultView;
dataView.Sort = e.Column.Header + " " + sortOrder;
dataTable = dataView.ToTable();

RefreshContents(dataTable, ResultsGrid);

Code

The sample app lives here on GitHub. When playing with it, don’t forget to bring your own connection string to MainPage.xaml.cs.

Enjoy!

Advertisements

Printing with the UWP Community Toolkit

In this article we describe some advanced printing scenarios on top of the PrintHelper from the UWP Community Toolkit. This helper is specialized in interacting with the built-in print dialogs, and in printing one UI element on one page. The latter sounds pretty limited, but in the next couple of paragraphs we’ll show things like

  • wrapping the helper in an MVVM service,
  • adding a header and/or a footer to the print pages,
  • adding page numbers to the print pages,
  • printing a list of items, each on its own page, and
  • printing a more complex report.

That should suffice for most UWP apps that have printing on their list of requirements – basically all apps that don’t need dynamic page breaks.

As usual, there’s a sample app on GitHub. It comes with several pages with a large ‘Print’ button:

 ContentPage

Here are the main classes in that sample app:

  • Content pages: the app’s pages that provide content and layout of the information to be printed,
  • PrintService: a class that provides printing-as-a-service in an MVVM architecture,
  • PrintPage: a helper class that prepares the print content (a FrameworkElement) with header, footer, and page numbering, and
  • PrintHelper: the toolkit’s print helper that knows how to print a FrameworkElement on a single page.

Here’s a class diagram of the core print engine:

ClassDiagram1

Creating an MVVM PrintService

The Print Service is the central place to which the different content pages send the elements to be printed:

  • one header, one footer, and one place to put the page for the entire job, and
  • one main content per individual print page.

The service registers the handlers for the system print dialog events (to deal with success, failure, or cancellation), manipulates the content through the smart PrintPage (see further), and hands the result over to the toolkit PrintHelper.

Printing XAML –with or without the PrintHelper- requires the XAML elements to be rendered on an invisible panel in your Visual Tree before they’re moved to separate print preview pages in the system print dialog. Your app needs to provide that invisible panel.

In an MVVM environment the so-called Shell (the page that hosts the navigation panel) is an ideal place to host this panel. We’ll call it the PrintingContainer:

    <Grid>
        <!-- Required for the PrintService -->
        <Grid x:Name="PrintingContainer"
              Opacity="0" />

        <!-- SplitView -->
        <SplitView x:Name="SplitView"
        <!-- ... -->
        </SplitView>

        <!-- Hamburger Button -->
        <Button x:Name="HamburgerButton">
        <!-- ... -->
        </Button>
    </Grid>

When the app starts, it provides this field to the central print service:

// Initialize Printing Service.
PrintService.PrintingContainer = PrintingContainer;

The PrintService class takes a Header, Footer and PageNumbering property. For the rest it host all the code for working with the UWP Community Toolkit’s PrintHelper class that you find in the official documentation. In the Print method you see that the service creates a PrintPage (see further) for each page to be sent to the PrintHelper. The print service protects the content pages from all that knowledge.

Here’s the whole class:

public class PrintService
{
    private static Panel _printingContainer;

    private PrintHelper _printHelper;
    private List<FrameworkElement> _content = new List<FrameworkElement>();
    private FrameworkElement _header;
    private FrameworkElement _footer;
    private PageNumbering _pageNumbering = PageNumbering.None;

    public PrintService()
    {}

    public static Panel PrintingContainer
    {
        set { _printingContainer = value; }
    }

    public FrameworkElement Header
    {
        set { _header = value; }
    }

    public FrameworkElement Footer
    {
        set { _footer = value; }
    }

    public PageNumbering PageNumbering
    {
        set { _pageNumbering = value; }
    }

    public void AddPrintContent(FrameworkElement content)
    {
        _content.Add(content);
    }

    public void Print()
    {
        _printHelper = new PrintHelper(_printingContainer);

        PrintPage.StartPageNumber = 1;
        foreach (var content in _content)
        {
            var page = new PrintPage(content, _header, _footer, _pageNumbering);
            _printHelper.AddFrameworkElementToPrint(page);
        }

        _printHelper.OnPrintFailed += printHelper_OnPrintFailed;
        _printHelper.OnPrintSucceeded += printHelper_OnPrintSucceeded;
        _printHelper.OnPrintCanceled += printHelper_OnPrintCanceled;

        _printHelper.ShowPrintUIAsync("Print Sample");
    }

    private void printHelper_OnPrintCanceled()
    {
        ReleasePrintHelper();
    }

    private void printHelper_OnPrintSucceeded()
    {
        Toast.ShowInfo("Printed.");
        ReleasePrintHelper();
    }

    private void printHelper_OnPrintFailed()
    {
        Toast.ShowError("Sorry, printing failed.");
        ReleasePrintHelper();
    }

    private void ReleasePrintHelper()
    {
        _printHelper.Dispose();
    }
}

Here’s how a successful print notification looks like, in the sample app:

Notification

Adding headers and footers

The main job of the PrintPage class in the sample app is to transform all print content for a single page (main content, header, footer, page numbering) into a single FrameworkElement, since the toolkit’s PrintHelper only accepts single FrameworkElement instances to be printed.

The PrintPage takes all print content and lays it out in a 3-row Grid (header – main beef – content):

Grid _printArea;

public PrintPage(FrameworkElement content, FrameworkElement header = null, FrameworkElement footer = null, PageNumbering pageNumbering = PageNumbering.None)
{
    _printArea = new Grid();
    _printArea.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
    _printArea.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
    _printArea.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
    _printArea.Children.Add(new Canvas() { Width = 10000 }); // Horizontally stretches the Grid.

    Content = _printArea;

    AddContent(content);
    Header = header;
    Footer = footer;
}

When the Header is assigned, it is immediately cloned -because it will possibly be used on multiple page instances- and placed in the top row of the grid:

public FrameworkElement Header
{
    set
    {
        if (value != null)
        {
            var header = value.DeepClone();
            Grid.SetRow(header, 0);
            _printArea.Children.Add(header);
        }
    }
}

The header and footer are cloned because they will appear on each page in the print job and a UI element can only have one parent. I would have preferred to use a XamlWriter to transform the element into a XAML string, and then a XamlReader to instantiate a clone from this XAML. Unfortunately XamlWriter does not exist in UWP, so we have to rely on reflection.

The DeepClone method that we’re using, is all over the internet – it’s impossible to find the original source. It checks the origin type, creates a new instance from it, and then recursively clones its properties (except the Name) and children.

Here’s how the first content page in the sample app talks to the print service – instantiate, assign header and footer, add print pages, call Print method:

var service = new PrintService();

// Define Header, Footer, and Page Numbering.
service.Header = new TextBlock() { Text = "Header", Margin = new Thickness(0, 0, 0, 20) };
service.Footer = new TextBlock() { Text = "Footer", Margin = new Thickness(0, 20, 0, 0) };
service.PageNumbering = PageNumbering.TopRight;

// Add three pages.
service.AddPrintContent(new TextBlock() { Text = "Hello World!" });
service.AddPrintContent(new Image() { Source = new BitmapImage(new Uri("ms-appx:///Assets/world.png")) });
service.AddPrintContent(new TextBlock() { Text = "Goodbye World!" });

service.Print();

Adding page numbering

Once we know how to add a header and a footer to the print pages, adding page numbers is trivial. Let’s start with defining an enumeration for page number positions:

public enum PageNumbering
{
    None,
    TopLeft,
    TopMiddle,
    TopRight,
    BottomLeft,
    BottomMidle,
    BottomRight
}

Each time we add a new print page to the print job with AddContent(), we check if a page number is required. If so, we increment the value and place a TextBlock in the appropriate place (be careful: the page number will appear in row 0 or 2 of the print page grid, so it may clash with header or footer – it’s up to you to avoid this):

public void AddContent(FrameworkElement content)
{
    Grid.SetRow(content, 1);
    _printArea.Children.Add(content);

    if (PageNumbering != PageNumbering.None)
    {
        _pageNumber += 1;
        var pageNumberText = new TextBlock() { Text = _pageNumber.ToString() };

        switch (PageNumbering)
        {
            case PageNumbering.None:
                break;
            case PageNumbering.TopLeft:
                Grid.SetRow(pageNumberText, 0);
                pageNumberText.Margin = new Thickness(0, 0, 0, 20);
                _printArea.Children.Add(pageNumberText);
                break;
            case PageNumbering.TopMiddle:
                Grid.SetRow(pageNumberText, 0);
                pageNumberText.Margin = new Thickness(0, 0, 0, 20);
                pageNumberText.HorizontalAlignment = HorizontalAlignment.Stretch;
                pageNumberText.HorizontalTextAlignment = TextAlignment.Center;
                _printArea.Children.Add(pageNumberText);
                break;
            case PageNumbering.TopRight:
                Grid.SetRow(pageNumberText, 0);
                Grid.SetColumn(pageNumberText, 1);
                pageNumberText.Margin = new Thickness(0, 0, 0, 20);
                pageNumberText.HorizontalAlignment = HorizontalAlignment.Stretch;
                pageNumberText.HorizontalTextAlignment = TextAlignment.Right;
                _printArea.Children.Add(pageNumberText);

                break;
            case PageNumbering.BottomLeft:
                Grid.SetRow(pageNumberText, 2);
                pageNumberText.Margin = new Thickness(0, 20, 0, 0);
                _printArea.Children.Add(pageNumberText);
                break;
            case PageNumbering.BottomMidle:
                Grid.SetRow(pageNumberText, 2);
                pageNumberText.Margin = new Thickness(0, 20, 0, 0);
                pageNumberText.HorizontalAlignment = HorizontalAlignment.Stretch;
                pageNumberText.HorizontalTextAlignment = TextAlignment.Center;
                _printArea.Children.Add(pageNumberText);
                break;
            case PageNumbering.BottomRight:
                Grid.SetRow(pageNumberText, 2);
                pageNumberText.Margin = new Thickness(0, 20, 0, 0);
                _printArea.Children.Add(pageNumberText);
                break;
            default:
                break;
        }
    }
}

Here’s how all of this looks like at runtime in the sample app:

HeaderFooterPageNumbers

Printing a list on separate pages

Maybe your app needs to print a collection of items, typically displayed through some kind of ItemsControl. The UWP Community Toolkit’s PrintHelper can’t do dynamic page breaks, so we’ll start each item on a new page.

The print content instances are created from a DataTemplate, like this:

<Page.Resources>
    <DataTemplate x:Name="MoonTemplate">
        <StackPanel>
            <TextBlock Text="{Binding Name}"
                        FontSize="48"
                        Margin="60" />
            <Image Source="{Binding ImagePath}"
                    Margin="120 0" />
        </StackPanel>
    </DataTemplate>
</Page.Resources>

To print the collection, just iterate through it and create a ContentControl for each item, load the data template in its ContentTemplate, assign the DataContext to update all bindings, and add the new content to the print job:

var service = new PrintService();
service.Header = new TextBlock { Text = "List of items with page break" };
service.PageNumbering = PageNumbering.BottomMidle;

foreach (var moon in Moons)
{
    // The secret is to NOT use an ItemsControl.
    var cont = new ContentControl();
    cont.ContentTemplate = Resources["MoonTemplate"] as DataTemplate;
    cont.DataContext = moon;
    service.AddPrintContent(cont);
}

service.Print();

Here’s how this looks like in the sample app:

ListItems

Printing a report

Maybe your app has a more or less complex dashboard page, with different texts and images, and controls like gauges and diagrams. That page may have a background image, light text on a dark background, and/or a horizontal layout. So it’s probably not a very good idea to emulate a screenshot and send that page as-is to a printer. For printed reports we expect a white background, probably no background images, and a vertical (portrait) layout. But we still want to reuse as much as possible XAML elements in both ‘pages’. So we’re going to rely on data templates again.

Some data templates can be very simple, like this one that just displays an image:

<DataTemplate x:Name="ImageTemplate">
    <Image Source="{Binding ImagePath}"
            Stretch="Uniform"
            Margin="0" />
</DataTemplate>

But they can also be a bit more complex, like this grid that hosts a list of characteristics:

<DataTemplate x:Name="CharacteristicsTemplate">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>
        <TextBlock Text="Planet"
                    FontWeight="SemiBold"
                    HorizontalAlignment="Right"
                    Margin="0 0 10 10" />
        <TextBlock Text="{Binding Planet}"
                    Margin="10 0 0 10"
                    Grid.Column="1" />
        <TextBlock Text="Mass"
                    FontWeight="SemiBold"
                    HorizontalAlignment="Right"
                    Margin="0 0 10 10"
                    Grid.Row="1" />
        <TextBlock Text="{Binding Mass}"
                    Margin="10 0 0 10"
                    Grid.Row="1"
                    Grid.Column="1" />
        <TextBlock Text="Albedo"
                    FontWeight="SemiBold"
                    Margin="0 0 10 10"
                    HorizontalAlignment="Right"
                    Grid.Row="2" />
        <TextBlock Text="{Binding Albedo}"
                    Margin="10 0 0 10"
                    Grid.Row="2"
                    Grid.Column="1" />
        <TextBlock Text="Orbital Eccentricity"
                    FontWeight="SemiBold"
                    HorizontalAlignment="Right"
                    Margin="0 0 10 10"
                    Grid.Row="3" />
        <TextBlock Text="{Binding OrbitalEccentricity}"
                    Margin="10 0 0 10"
                    Grid.Row="3"
                    Grid.Column="1" />
    </Grid>
</DataTemplate>

Here’s the horizontal layout of the dashboard page in the sample app. It gets its details from the different data templates:

<ListView SelectionMode="None"
            ScrollViewer.HorizontalScrollMode="Enabled"
            ScrollViewer.IsHorizontalRailEnabled="True"
            ScrollViewer.HorizontalScrollBarVisibility="Auto"
            Grid.Row="1">
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
    <Grid Height="500"
            Width="500">
        <ContentControl DataContext="{x:Bind SelectedMoon}"
                        ContentTemplate="{StaticResource ImageTemplate}" />
    </Grid>
    <Grid Height="500"
            Width="500">
        <ContentControl DataContext="{x:Bind SelectedMoon}"
                        ContentTemplate="{StaticResource DescriptionTemplate}" />
    </Grid>
    <Grid Height="500"
            Width="250">
        <ContentControl DataContext="{x:Bind SelectedMoon}"
                        ContentTemplate="{StaticResource CharacteristicsTemplate}" />
    </Grid>
    <Grid Height="500"
            Width="500">
        <ContentControl DataContext="{x:Bind Moons}"
                        ContentTemplate="{StaticResource BarChartTemplate}" />
    </Grid>
</ListView>

Here’s the template for the vertical print page. It reuses as much of these data templates as possible, but it hangs these in a container with a different look. The data template for the report is in the page’s resources:

<DataTemplate x:Name="ReportTemplate">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <TextBlock DataContext="{Binding SelectedMoon}"
                    Text="{Binding Name}"
                    FontSize="36"
                    Margin="0 10"
                    Grid.ColumnSpan="2" />
        <ContentControl DataContext="{Binding SelectedMoon}"
                        ContentTemplate="{StaticResource ImageTemplate}"
                        Margin="0 0 10 10"
                        Grid.Row="1" />
        <TextBlock DataContext="{Binding SelectedMoon}"
                    Text="{Binding Description}"
                    TextWrapping="Wrap"
                    Margin="10 0 0 10"
                    Grid.Row="1"
                    Grid.Column="1" />
        <ContentControl DataContext="{Binding SelectedMoon}"
                        ContentTemplate="{StaticResource CharacteristicsTemplate}"
                        HorizontalAlignment="Center"
                        Margin="0 0 10 10"
                        Grid.Row="2" />
        <ContentControl RequestedTheme="Dark"
                        DataContext="{Binding Moons}"
                        ContentTemplate="{StaticResource BarChartTemplate}"
                        HorizontalAlignment="Center"
                        Margin="0 0 0 0"
                        Grid.ColumnSpan="2"
                        Grid.Row="3" />
    </Grid>
</DataTemplate>

I also added an example of reusing a more advanced control – a Telerik BarChart. I had to hard code the color scheme (blue with a black border) for the bars, because by default they became white or transparent in the print page:

<DataTemplate x:Name="BarChartTemplate">
    <telerikChart:RadCartesianChart>
        <telerikChart:RadCartesianChart.VerticalAxis>
            <telerikChart:LinearAxis Minimum="0"
                                        Maximum="1"
                                        Title="Albedo"
                                        FontFamily="Segoe UI" />
        </telerikChart:RadCartesianChart.VerticalAxis>
        <telerikChart:RadCartesianChart.HorizontalAxis>
            <telerikChart:CategoricalAxis />
        </telerikChart:RadCartesianChart.HorizontalAxis>
        <telerikChart:BarSeries ItemsSource="{Binding}">
            <telerikChart:BarSeries.DefaultVisualStyle>
                <Style TargetType="Border">
                    <Setter Property="Background"
                            Value="DodgerBlue" />
                    <Setter Property="BorderBrush"
                            Value="Black" />
                    <Setter Property="BorderThickness"
                            Value="1" />
                </Style>
            </telerikChart:BarSeries.DefaultVisualStyle>
            <telerikChart:BarSeries.CategoryBinding>
                <telerikChart:PropertyNameDataPointBinding PropertyName="Name" />
            </telerikChart:BarSeries.CategoryBinding>
            <telerikChart:BarSeries.ValueBinding>
                <telerikChart:PropertyNameDataPointBinding PropertyName="Albedo" />
            </telerikChart:BarSeries.ValueBinding>
        </telerikChart:BarSeries>
    </telerikChart:RadCartesianChart>
</DataTemplate>

Here are the two pages, in the sample app:

DashboardToReport

In this article we showed some easy tricks to create more complex print jobs on top of the UWP Community Toolkit. If your app needs more complexity than this, then I would suggest to take a look at my dynamic page-break solution or let your app expose its information through PDF or HTML.

The sample app lives here on GitHub.

Enjoy!

Milestone User Prompts in UWP

In this article we describe how to put the logic and the UI in a UWP application to execute some code and display a dialog on important milestones in its lifetime. We provide an implementation for the following milestones:

  • the application was run for the first time,
  • the application updated to a new release,
  • a trial version was upgraded to a purchase,
  • it’s time to rate and review.

Here’s one of the dialogs in action, in a sample application:

FirstUseDialog

The architecture of the solution comes with one helper service and one content dialog for each milestone, and one central activation Service to rule them all. Exactly the same constituents are found in a Windows Template Studio based project (in Basic MVVM and Code Behind style projects). The logic to detect whether or not a milestone is hit, is powered by helper classes from UWP Community Toolkit v2.1.

Milestone Prompts in Windows Template Studio

Service and Dialogs

The requirement for ‘First Run Prompt’ and ‘What’s New Prompt’ dialogs is often only identified when your application is almost ready to be shipped. If you created your project with Windows Template Studio that’s not a problem. Template Studio allows you to add features to your generated solution as an afterthought:

TemplateStudio

After you selected the feature(s) to be added, there’s even an overview of all the changes that come with them so you can bail out when there’s an unexpected modification of your existing code:

TemplateStudioCode

Here’s a screenshot of the dialog from the  ‘First Run Prompt’ feature. All you need to do is replace its content of with your own:

TemplateStudioDialog

The logic to open these dialogs is implemented around a central Activation Service. This service brings your application startup code –which is usually hidden in app.xaml.cs– conveniently together with the rest of your services code. The UI element for each milestone feature is a ContentDialog. These dialogs are added to the Views section:

TemplateStudioArchitecture

When the application is launched, the activation service navigates to the main page, and then calls StartupAsync:

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

// Tasks after activation
await StartupAsync();

This anchor method is the place where the dialog services hook in their calls:

private async Task StartupAsync()
{
    await WhatsNewDisplayService.ShowIfAppropriateAsync();
    await FirstRunDisplayService.ShowIfAppropriateAsync();
}

Windows Template Studio provides a clean architecture and a straightforward way of introducing new user prompt services and any other service that needs to run on application startup.

Let’s now take a look at the implementation of the logic. Here’s how the What’s New Prompt feature determines whether or not to pop up the dialog:

public class WhatsNewDisplayService
{
    internal static async Task ShowIfAppropriateAsync()
    {
        var currentVersion = PackageVersionToReadableString(Package.Current.Id.Version);

        var lastVersion = await Windows.Storage.ApplicationData.Current.LocalSettings.ReadAsync<string>(nameof(currentVersion));

        if (lastVersion == null)
        {
            await Windows.Storage.ApplicationData.Current.LocalSettings.SaveAsync(nameof(currentVersion), currentVersion);
        }
        else
        {
            if (currentVersion != lastVersion)
            {
                await Windows.Storage.ApplicationData.Current.LocalSettings.SaveAsync(nameof(currentVersion), currentVersion);

                var dialog = new WhatsNewDialog();
                await dialog.ShowAsync();
            }
        }
    }

    private static string PackageVersionToReadableString(PackageVersion packageVersion)
    {
        return $"{packageVersion.Major}.{packageVersion.Minor}.{packageVersion.Build}.{packageVersion.Revision}";
    }
}

It also looks very straightforward, but allow me to show you how this code would look like when using the latest version of UWP Community Toolkit:

if (SystemInformation.IsAppUpdated)
{
    var dialog = new WhatsNewDialog();
    await dialog.ShowAsync();
}

Let’s take a look at some other relevant goodies that this toolkit provides.

Milestone Prompts in UWP Community Toolkit

Logic and Persistency

UWP Community Toolkit is well-known for its controls, its animations and its services. But it also contains a huge amount of helper classes for different scenarios. Notably the SystemInformation and LocalObjectStorageHelper have some members that may help in determining whether or not an application lifetime milestone was reached, based on information that was stored in local settings. Here’s the UML for these two classes:

UwpCommunityToolkitHelpers

Here’s the subset of SystemInformation members that may help to detect milestones:

Property Purpose
ApplicationVersion Gets the application’s version as a PackageVersion
IsFirstRun Gets a value indicating whether the app is being used for the first time since it was installed.
IsAppUpdated Gets a value indicating whether the app is being used for the first time since being upgraded from an older version.
LaunchTime Gets the DateTime (in UTC) that this instance of the app was launched.
LastLaunchTime Gets the DateTime (in UTC) that this was previously launched.
LaunchCount Gets the number of times the app has been launched.
AppUptime Gets the length of time this instance of the app has been running.
FirstVersionInstalled Gets the first version of the app that was installed.
FirstUseTime Gets the DateTime (in UTC) that the app as first used.
Method Description
LaunchStoreForReviewAsync() Launch the store app so the user can leave a review.
TrackAppUse() Track app launch time and count.

And here’s the list for LocalObjectStorageHelper:

Property Purpose
Settings Gets or sets settings container
Method Description
bool KeyExists(string key) Detect if a setting already exists
bool KeyExists(string compositeKey, string key) Detect if a setting already exists in composite.
T Read<T>(string key, T) Retrieve single item by its key.
T Read<T>(string compositeKey, string key, T) Retrieve single item by its key in composite.
void Save<T>(string compositeKey, IDictionary values) Retrieve single item by its key in composite.
Save(String, IDictionary) Save a group of items by its key in a composite.
void Save<T>(string key, T value) Save single item by its key.

These classes will definitely allow you to write the complex milestone scenarios that your application may require.

UWP Community Toolkit provides the helpers to implement the logic and persist the settings for user prompt services and any other service that needs to run on application startup.

Let’s say you want to execute some stuff the first time the application is started after a milestone, but you want to keep on opening the user prompt dialog on startup until the user explicitly opts out via the don’t-show-this-again checkbox. Well, that’s exactly what we’re going to do.

Rolling your own Milestone Prompts

Here’s the structure of my sample application, with a central activation service, and a service-and-dialog pair per milestone (just like in Windows Template Studio):

MySolutionArchitecture

Activation Service

The core member of the Activation Service is LaunchAsync which is called by app.xaml.cs when the application is launched. It allows other services to execute some initialization code, then navigates to the main page (using a nice fluent API that I couldn’t resist writing), and then allows the services to execute the rest of the startup code:

public async Task LaunchAsync(LaunchActivatedEventArgs e)
{
    // Custom pre-launch service calls.
    await PreLaunchAsync(e);

    // Navigate
    Window
      .Current
      .EnsureRootFrame()
      .NavigateIfAppropriate(typeof(Shell), e.Arguments)
      .Activate();

    // Custom post-launch service calls.
    await PostLaunchAsync(e);
}

The PreLaunchAsync() method hosts the initialization code, which is executed before the UI is ready. Use it for things like

  • selecting the culture,
  • setting the theme,
  • starting to log, and
  • determining the page to navigate to.

Here’s how it looks like in the sample application:

private async Task PreLaunchAsync(LaunchActivatedEventArgs e)
{
    Theme.ApplyToContainer(); // await ThemingService.PreLaunchAsync(e);

    await FirstUseActivationService.PreLaunchAsync(e);
    await NewReleaseActivationService.PreLaunchAsync(e);
    await TrialToPurchaseActivationService.PreLaunchAsync(e);
    await RateAndReviewActivationService.PreLaunchAsync(e);
}

The PostLauncAsync() method is the anchor for the startup code that requires the visual elements, like

  • moving around and resizing controls, and
  • opening a Content Dialog.

This is the code from the sample application:

private async Task PostLaunchAsync(LaunchActivatedEventArgs e)
{
    // await ThemingService.PostLaunchAsync(e);
    CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true; 

    await FirstUseActivationService.ShowIfAppropriateAsync(e);
    await NewReleaseActivationService.ShowIfAppropriateAsync(e);
    await TrialToPurchaseActivationService.ShowIfAppropriateAsync(e);
    await RateAndReviewActivationService.ShowIfAppropriateAsync(e);
}

Dialog Services

The milestone dialog services that I present in this article all come with these PreLaunchAsync() and PostLaunchAsync() methods. On top of that, they all have an instance of a LocalObjectStorageHelper to read and write local Settings, and a variable to hold whether or not the user still wants to see the dialog. Last but not least all dialogs have a Reset() and a Deactivate() method:

private const string HasShown = "FirstUseActivation_HasShown";
private static readonly LocalObjectStorageHelper _localSettings = new LocalObjectStorageHelper();

internal static void Reset()
{
    _localSettings.Save(HasShown, false);
}

internal static void Deactivate()
{
    _localSettings.Save(HasShown, true);
}

Each milestone service also comes with a Content Dialog. I added an extra buttons to the dialogs that I copied from Windows Template Studio, since I want to reshow the dialog until the user opts out of it:

DialogDetail

Let’s dive in the details for each milestone.

First Use Prompt Dialog

The SystemInformation helper from UWP Community Toolkit has an IsFirsRun() method to detect whether or not the application is started for the first time. So we’ll call this in our PreLaunchAsync() together with the other initialization code, including a call to Reset() to initialize the dialog logic:

internal static async Task PreLaunchAsync(LaunchActivatedEventArgs e)
{
    if (SystemInformation.IsFirstRun)
    {
        // First-time app initialization.
        // ...
        Reset();
    }

    await Task.CompletedTask;
}

After navigating to the main page, we call the ShowIfAppropriateAsync() where we verify if we should open the dialog based on the saved Settings. When the user closes that dialog with the ‘Never Again’ button, we deactivate the service:

internal static async Task ShowIfAppropriateAsync(LaunchActivatedEventArgs e)
{
    bool hasShownFirstRun = _localSettings.Read(HasShown, false);

    if (!hasShownFirstRun)
    {
        var dialog = new FirstUseDialog();
        var response = await dialog.ShowAsync();
        if (response == ContentDialogResult.Secondary)
        {
            Deactivate();
        }
    }
}

Here’s how the dialog looks like in the sample application:

FirstUseDialog

All other milestone services follow the same pattern.

New Release Prompt Dialog

The ‘New Release Prompt’ service is identical to the ‘First Use Prompt’ service, except that it uses SystemInformation’s IsAppUpdated() method:

internal static async Task PreLaunchAsync(LaunchActivatedEventArgs e)
{
    if (SystemInformation.IsAppUpdated)
    {
        // New release app initialization.
        // ...
        NewReleaseActivationService.Reset();
    }

    await Task.CompletedTask;
}

internal static async Task ShowIfAppropriateAsync(LaunchActivatedEventArgs e)
{
    var currentVersion = SystemInformation.ApplicationVersion;

    if (currentVersion.ToFormattedString() == SystemInformation.FirstVersionInstalled.ToFormattedString())
    {
        // Original version. Ignore.
        return;
    }

    var hasShown = _localSettings.Read(HasShown, false);

    if (!hasShown)
    {
        // New release dialog.
        var dialog = new NewReleaseDialog();
        var response = await dialog.ShowAsync();
        if (response == ContentDialogResult.Secondary)
        {
            Deactivate();
        }
    }
}

[For a brand new application, the comparison between ApplicationVersion and FirstVersionInstalled is probably obsolete, but I’m planning to add these services to existing applications so I’m playing a bit with alternative algorithms.]

Here’s how the dialog looks like in the sample application:

NewReleaseDialog

Trial-to-Purchase Prompt Dialog

You don’t need UWP Community Toolkit to figure out if a trial version of the application is running and when that version upgrades to an officially purchased one.  That’s because there are already two ways of doing that in the current SDKs. You can use the members of the (now becoming legacy) Windows.ApplicationModel.Store namespace or the ones in the newer Windows.Services.Store namespace. I’m going for the former version, which is based on the LicenceInformation that you get from CurrentAppSimulator during development and from CurrentApp in the deployed version of your application:

_licenseInformation = CurrentAppSimulator.LicenseInformation;

The pattern is again the same as for the other milestone services, with one extension. The user may decide to buy your application when it is running, so the service is not only called on launch, but also in the LicenseChanged event handler:

internal static async Task PreLaunchAsync(LaunchActivatedEventArgs e)
{
    if (_licenseInformation.IsTrial)
    {
        // Trial: set HasShown to false to trigger detection.
        Reset();
        _licenseInformation.LicenseChanged += LicenseInformation_LicenseChanged;
    }
    else
    {
        // Purchased: set HasShown to true to inhibit detecting, but only if we were not detecting already.
        var hasShown = _localSettings.Read(HasShown, true);
        if (hasShown)
        {
            Deactivate();
        }
    }

    await Task.CompletedTask;
}
private async static void LicenseInformation_LicenseChanged()
{
    await ShowIfAppropriateAsync(null);
}
internal static async Task ShowIfAppropriateAsync(LaunchActivatedEventArgs e)
{
    if (_licenseInformation.IsTrial)
    {
        return;
    }

    var hasShown = _localSettings.Read(HasShown, true);

    if (!hasShown)
    {
        // New trial-to-purchase dialog.
        var dialog = new TrialToPurchaseDialog();
        var response = await dialog.ShowAsync();
        if (response == ContentDialogResult.Secondary)
        {
            Deactivate();
        }
    }
}

You can easily test the service by simulating a purchase with RequestAppPurchaseAsync(), like this:

internal async static Task SimulatePurchase()
{
    try
    {
        var result = await CurrentAppSimulator.RequestAppPurchaseAsync(false);

        // Purchased.

    }
    catch (Exception)
    {
        // Purchase failed.
    }

    await Task.CompletedTask;
}

The simulator will then open a new dialog where you can specify the answer to test (purchase or not, or an error):

TrialToPurchaseDialogTest

Here’s the Trial-to-Purchase dialog in the sample application:

TrialToPurchaseDialog

Rate and Review Prompt Dialog

For the last milestone service in this article, the UWP Community is back in full force. There’s no ideal moment for popping up a dialog to send the user to the store to rate or review. But for some applications this is important, and the SystemInformation helper has all the members to brew your own logic: when the application was first launched, how many times it has been launched, how long the current instance is running, and so on.

These settings are only maintained if you activate application tracking with a call to TrackAppUse(). So that’s what we do in PreLauncAsync(). The following code snippet also shows a small hack to transform the standard AppUptime to a cumulative version:

internal static async Task PreLaunchAsync(LaunchActivatedEventArgs e)
{
    // Start tracking app usage (launch count, uptime, ...)
    try
    {
        // Make the AppUptime cumulative over sessions.
        var uptimeSoFar = TimeSpan.FromTicks(new LocalObjectStorageHelper().Read<long>("AppUptime", 0));

        SystemInformation.TrackAppUse(e); // Resets AppUptime to 0.
        SystemInformation.AddToAppUptime(uptimeSoFar);
    }
    catch (Exception)
    {
        SystemInformation.TrackAppUse(e);
    }

    await Task.CompletedTask;
}

I’m not going to dive into the logic to open the dialog. It will be different for each application. The dialog itself has a hyperlink that points to the Store’s Rate and Review page for the application:

<TextBlock TextWrapping="WrapWholeWords">
    <Run>Replace the content of this dialog with whatever content is appropriate to your app.</Run>
    <LineBreak /><LineBreak />
    <Hyperlink Click="Hyperlink_Click">Rate and Review this app</Hyperlink>
    <LineBreak /><LineBreak />
    <Run>Don't feel restricted to just text. You can also include images and animations if you wish too.</Run>
</TextBlock>

When that link is clicked UWP Community Toolkit comes to the rescue again, with LaunchStoreForReviewAsync():

private async void Hyperlink_Click(Hyperlink sender, HyperlinkClickEventArgs args)
{
    await SystemInformation.LaunchStoreForReviewAsync();
}

Of course this only works when the application is associated with the store. Here’s the corresponding dialog:

RateAndReviewDialog

And that concludes the list of milestone user prompt services. I think that the architecture and implementation is generic enough so you can use some of it in your own applications.

Styling the Dialog

To give the dialogs in the sample application a more contemporary look, I extended the ContentDialog Style to have a DropShadowPanel (again from UWP Community Toolkit) around the border. After all, Depth is one of the building blocks of Fluent Design:

<!-- Added -->
<toolkit:DropShadowPanel Color="{StaticResource CustomDialogBorderColor}"
                            BlurRadius="20"
                            HorizontalContentAlignment="Stretch"
                            VerticalContentAlignment="Stretch">

All the code and more

The sample application lives here on GitHub. Feel free to reuse it or launch pull requests to improve and enhance it. Also don’t hesitate to dive into the source code of Windows Template Studio and UWP Community Toolkit. There’s a lot of gems in there…

Enjoy!

An elementary Dialog Service for UWP

In this article we present a Service that provides the elementary Modal Views in a UWP application. The dialogs and flyouts guidance learns us that dialogs are UI overlays that provide contextual app information and block interaction with the app window until they are explicitly dismissed. That makes them different from Flyouts, Tooltips or Toast Notifications, which are easily dismissed or even close automatically. Dialogs are typically used in the following scenarios, and these are exactly the views that are exposed by the ModalView service:

  • to display an important message to the user,
  • to request for confirmation from the user,
  • or to request long and short text input from the user.

Dialogs should be callable from anywhere inside your app’s code base: from a View, from a UserControl, from a ViewModel, from Business Logic, etcetera. In an MVVM(ish) architecture functionality like this typically ends up in a so-called Service. Here’s where I located the service in my Visual Studio solution, and the class diagram that shows its API:

ServiceAPI

The ModalView service comes with a small sample app that calls most of the methods in its API. Here’s how that sample app looks like:

MainPage

Basically we need a control to display some text(s), one, two, or three buttons, an optional input field, and an overlay to cover the app window to make it inaccessible. Well, that’s exactly what ContentDialog does. By the way: try to get rid of the old Windows 8 MessageDialog.

To open a message box, al you need to do is provide values for the Title, the Content (subTitle) and the CloseButtonText of the ContentDialog, call ShowAsyc(), and … sit and wait:

public static async Task MessageDialogAsync(
	string title, 
	string message, 
	string buttonText)
{
    var dialog = new ContentDialog
    {
        Title = title,
        Content = message,
        CloseButtonText = buttonText
    };

    await dialog.ShowAsync();
}

That’s exactly what the ModalView service does, and it provides an overload with a default value (“OK”) for the close button text. Here’s the service call from the sample app:

await ModalView.MessageDialogAsync(
	"Ready to go?", 
	"Place a cat, a flask of poison, and a radioactive source in a sealed box.", 
	"Got it");

And this is how it looks like:

MessageBox

The call to open a confirmation box is almost identical. There are only two differences:

  • instead of a single button, there will be two (yes, no) or three (yes, no, cancel), and
  • the caller would want to know which of these buttons was pressed.

All we need to do is provide values for PrimaryButtonText and SecondaryButtonText. You don’t need to specify IsSecondaryButtonEnabled (and the corresponding setting for the other buttons). If you don’t specify a text, the button will not appear. Here’s the service’s implementation of ConfirmationDialogAsync, which returns a nullable boolean to represent the yes/no/cancel response from the user, translated from ContentDialogResult:

public static async Task<bool?> ConfirmationDialogAsync(
	string title, 
	string yesButtonText, 
	string noButtonText, 
	string cancelButtonText)
{
    var dialog = new ContentDialog
    {
        Title = title,
        //IsPrimaryButtonEnabled = true,
        PrimaryButtonText = yesButtonText,
        SecondaryButtonText = noButtonText,
        CloseButtonText = cancelButtonText
    };
    var result = await dialog.ShowAsync();

    if (result == ContentDialogResult.None)
    {
        return null;
    }

    return (result == ContentDialogResult.Primary);
}

Again, there are overloads that use default values for the buttons: the not highly original “Yes”, “No”, and “Cancel”.

Here’s how the sample app opens a two-button confirmation dialog:

private bool? confirmed;

confirmed = await ModalView.ConfirmationDialogAsync(
        "Are you planning to open the box?",
        "Sure",
        "No, thanks"
   );

Here’s the result:

2ButtonConfirmation

When the app window becomes too small in height or width, the ContentDialog automagically snaps to the sides, like this:

Stretched

Here’s a call for a three-button confirmation dialog. It also shows how to create a multi-line title:

confirmed = await ModalView.ConfirmationDialogAsync(
        "So, what's the status the cat?\nHint: use Quantum Mechanics.",
        "It's alive",
        "It's dead",
        "It's both"
    );

And this is how it looks like at runtime:

3ButtonConfirmation

To transform a content dialog into an input dialog, it suffices to replace the default text Content by an input control, such as a TextBox. The ModalView Service provides two input dialogs: one for a string value, and one for a larger text value. Feel free to add your own versions to request for a number, a date, or a selection from a list.

Here’s the implementation of InputStringDialogAsync, it requests for a string without line breaks (hence value for AcceptsReturn). You can provide a default value for the response, and we will place the caret at the end through SelectionStart:

public static async Task<string> InputStringDialogAsync(
	string title, 
	string defaultText, 
	string okButtonText, 
	string cancelButtonText)
{
    var inputTextBox = new TextBox
    {
        AcceptsReturn = false,
        Height = 32,
        Text = defaultText,
        SelectionStart = defaultText.Length,
        BorderThickness = new Thickness(1),
        BorderBrush = new SolidColorBrush((Color)Application.Current.Resources["CustomDialogBorderColor"])
    };
    var dialog = new ContentDialog
    {
        Content = inputTextBox,
        Title = title,
        IsSecondaryButtonEnabled = true,
        PrimaryButtonText = okButtonText,
        SecondaryButtonText = cancelButtonText
    };

    if (await dialog.ShowAsync() == ContentDialogResult.Primary)
    {
        return inputTextBox.Text;
    }
    else
    {
        return string.Empty;
    }
}

Here’s the call from the sample app:

private string inputString;

inputString = await ModalView.InputStringDialogAsync(
        "How do you want to call this phenomenon?",
        "Verschränkung",
        "Claim",
        "Forget it"
    );

And here’s the corresponding UI:

StringInput

The InputTextDialogAsync method listens for a possibly longer text input, and looks very similar to the previous one. The input TextBox is higher, accepts line feeds, and does TextWrapping:

public static async Task<string> InputTextDialogAsync(
	string title, 
	string defaultText, 
	string okButtonText, 
	string cancelButtonText)
{
    var inputTextBox = new TextBox
    {
        AcceptsReturn = true,
        Height = 32 * 6,
        Text = defaultText,
        TextWrapping = TextWrapping.Wrap,
        SelectionStart = defaultText.Length,
        Opacity = 1,
        BorderThickness = new Thickness(1),
        BorderBrush = new SolidColorBrush((Color)Application.Current.Resources["CustomDialogBorderColor"])
    };
    var dialog = new ContentDialog
    {
        Content = inputTextBox,
        Title = title,
        IsSecondaryButtonEnabled = true,
        PrimaryButtonText = okButtonText,
        SecondaryButtonText = cancelButtonText
    };

    if (await dialog.ShowAsync() == ContentDialogResult.Primary)
    {
        return inputTextBox.Text;
    }
    else
    {
        return string.Empty;
    }
}

By the way: if you try to use WrapWholeWords as a value forTextBox.TextWrapping an exception is thrown. You can guess what my first assignment was…

Here’s the call from the sample app:

var inputText = await ModalView.InputTextDialogAsync(
        "What whas your point actually?",
        "Some large string containing line break (\n) characters."
    );

And the corresponding UI:

TextInput

Did you observe that I slightly pimped the default style of the ContentDialog? To add some contrast –and to get rid of the default AccentColor– I applied a custom BorderBrush to the dialog itself and to the input TextBoxes. I also added a DropShadowPanel from UWP Community Toolkit around it. The new Style is available in a ResourceDictionary (don’t forget to register it in your app.xaml). Here are the upgraded parts:


<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:toolkit="using:Microsoft.Toolkit.Uwp.UI.Controls">

    <!-- Custom style for Windows.UI.Xaml.Controls.ContentDialog -->
    <Color x:Key="CustomDialogBorderColor">#FFF05F66</Color>
    <SolidColorBrush x:Key="CustomDialogBorderBrush"
                     Color="#FFF05F66" />
    <Color x:Key="CustomDialogBackgroundColor">#FF4F2316</Color>
    <SolidColorBrush x:Key="CustomDialogBackgroundBrush"
                     Color="#FF4F2316" />

    <!-- From  \(Program Files)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.10586.0\Generic  -->
    <Style TargetType="ContentDialog">
        <!-- .. -->
            <Setter.Value>
                <ControlTemplate TargetType="ContentDialog">
                    <Border x:Name="Container">
                        <!-- ... -->
                        <Grid x:Name="LayoutRoot">
                            <!-- Added -->
                            <toolkit:DropShadowPanel Color="{StaticResource CustomDialogBorderColor}"
                                                     BlurRadius="20">
                                <!-- Modified Background and BorderBrush -->
                                <Border x:Name="BackgroundElement"
                                        Background="{StaticResource CustomDialogBackgroundBrush}"
                                        FlowDirection="{TemplateBinding FlowDirection}"
                                        BorderThickness="{ThemeResource ContentDialogBorderWidth}"
                                        BorderBrush="{StaticResource CustomDialogBorderBrush}"
                                        MaxWidth="{TemplateBinding MaxWidth}"
                                        MaxHeight="{TemplateBinding MaxHeight}"
                                        MinWidth="{ThemeResource ContentDialogMinWidth}"
                                        MinHeight="{ThemeResource ContentDialogMinHeight}">
                                    <!-- .. -->
                                </Border>
                            </toolkit:DropShadowPanel>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

This is how the InputStringDialog looks like, without the customization:

StringInputDefaultStyle

Feel free to add your company’s or app’s logo to the template, and/or a background image. Here’s an example of ModalView in a real Store app:

RealAppSample

Be aware: if you retemplate built-in controls you have to closely follow up and verify your styles against all the new SDK-releases. Future native ContentDialogs may have a different structure or visual states, or will make use of new features like the AcrylicBrush (for the overlay) or RevealBrush on the button(s). I’m pretty sure you wouldn’t want to miss these.

For the sake of completeness, here’s the XAML code behind the sample app’s buttons. It uses different types of commanding and binding, just to illustrate its architectural neutrality:

<!-- Click Event Handler in the View -->
<Button Content="Message Dialog"
        Click="MessageBox_Click" />
<!-- Binding to Command in ViewModel -->
<Button Content="2-Button Confirmation Dialog"
        Command="{Binding ConfirmationCommandYesNo}" />
<!-- Binding to Command in ViewModel -->
<Button Content="3-Button Confirmation Dialog"
        Command="{Binding ConfirmationCommandYesNoCancel}" />
<!-- x:Bind to Command in ViewModel -->
<Button Content="String Input Dialog"
        Command="{x:Bind ViewModel.InputStringCommand}" />
<!-- x:Bind to Method in ViewModel -->
<Button Content="Text Input Dialog"
        Click="{x:Bind ViewModel.InputText_Click}" />

The ModalView Service and its sample app live in this GitHub repo.

Enjoy!

A lap around the UWP Community Toolkit Radial Gauge control

The XAML and Composition API Radial Gauge control that I developed a while ago is now part of to the UWP Community Toolkit on GitHub. Its code was cleaned up, thanks to peer pressure from fellow MVP’s. At the same time the gauge’s functionality was extended, based on community feedback. V1.1 of the UWP Community Toolkit was just released, here is the new official Radial Gauge documentation.

The new Radial Gauge comes with a lot more configurable properties. Its default look and feel and its basic constituents did not change however:

compositiongauge_anatomy

Properties

Here’s the alphabetic list of public dependency properties. The properties that are tagged with an asterisk are brand new:

IsInteractive* bool, default false Determines whether the control accepts changing its Value through interaction.
MaxAngle* int, default 150 The stop angle of the scale, in degrees.
Maximum double, default 100 The maximum value on the scale.
MinAngle* int, default -150 The start angle of the scale, in degrees.
Minimum double, default 0 The minimum value on the scale.
NeedleBrush SolidColorBrush, default Red The color of the needle.
NeedleLength* double, default 100 The length of the needle, in percentage of the gauge radius.
NeedleWidth* double, default 5 The width of the needle, in percentage of the gauge radius.
ScaleBrush Brush, default solid DarkGray The background color of the scale.
ScaleTickBrush Brush, default solid Black The color of the ticks on the scale.
ScaleTickWidth* double, default 2.5 The scale tick width, in percentage of the gauge radius.
ScaleWidth double, default 26 The thickness of the scale in pixels, in percentage of the gauge radius.
StepSize* double, default 0 The rounding interval for the Value.
TickBrush SolidColorBrush, default White The color of the outer ticks.
TickLength* double, default 18 The outer tick length, in percentage of the gauge radius.
TickSpacing int, default 10 The spacing between ticks, in Value units.
TickWidth* double, default 5 The outer tick width, in percentage of the gauge radius.
TrailBrush Brush, default solid Orange The color of the trail following the needle. 
Unit string The unit measure to display.
UnitBrush Brush, default solid White The color of the unit measure text. 
Value double, default 0 The value to represent.
ValueBrush Brush, default solid White The color of the value text.
ValueStringFormat string, default ‘N0’ (integer values) The StringFormat to apply to the displayed value.

Each of these dependency properties has a PropertyMetadata with a default value (so you don’t have to provide one) and a PropertyChangedCallback (so any change at runtime is reflected immediately in the gauge’s looks and behavior).

There are three such callbacks:

  • OnValueChanged: called when the value changed – redraws the trail and rotates the needle
  • OnInteractivityChanged: called when IsInteractive changed (see further)
  • OnScaleChanged: called when the scale needs to be redrawn
  • OnFaceChanged: called when the needle and ticks need to be redrawn

The control is responsive: whenever a property value is changed, one (or more) of these callbacks is executed.

What’s new in Radial Gauge v1.1?

Height and Width of face elements are configurable.

In the previous versions of the Radial Gauge, the height and width of needle and ticks was fixed to give the control its ‘chubby’ Windows 8 look. To customize the gauge, you could only play with its colors:

compositiongauge_gallery

The new version allows you to go for a thinner look and feel (and more):

Gauge_Widths

Here’s part of the XAML for the first gauge from the previous screenshot. You can now specify the width of all constituents:

<controls:RadialGauge Unit="Things"
                        NeedleWidth="2"
                        TickWidth="2"
                        ScaleWidth="2" />

It’s interactive.

The previous versions of the Radial Gauge were rather passive: the control could only be used to display a Value. The new version comes with a property called ‘Isinteractive’ which make the control … interactive. Just like with a Slider control you can use touch, mouse, or pen to update the Value. And I admit: I didn’t try the XBox controller.

In interactive mode, you can place the Needle by tapping or dragging, and the control will adjust its Value. Here’s how this works: based on the value of the IsInteractive property the control registers or unregisters handlers for the Tapped and the ManipulationDelta events and it sets the corresponding ManipulationMode:

private static void OnInteractivityChanged(
	DependencyObject d, 
	DependencyPropertyChangedEventArgs e)
{
    RadialGauge radialGauge = (RadialGauge)d;

    if (radialGauge.IsInteractive)
    {
        radialGauge.Tapped += radialGauge.RadialGauge_Tapped;
        radialGauge.ManipulationDelta += 
		radialGauge.RadialGauge_ManipulationDelta;
        radialGauge.ManipulationMode = 
		ManipulationModes.TranslateX | 
		ManipulationModes.TranslateY;
    }
    else
    {
        radialGauge.Tapped -= radialGauge.RadialGauge_Tapped;
        radialGauge.ManipulationDelta -= 
		radialGauge.RadialGauge_ManipulationDelta;
        radialGauge.ManipulationMode = ManipulationModes.None;
    }
}

Both event handlers apply the same logic: the current position is translated to the corresponding needle angle, which is then translated to the gauge Value. You can not place the needle outside the scale – the triangle from MaxAngle to MinAngle) is happily ignored:

    private void RadialGauge_ManipulationDelta(
	object sender, 
	ManipulationDeltaRoutedEventArgs e)
        {
            SetGaugeValueFromPoint(e.Position);
        }

        private void RadialGauge_Tapped(
	object sender, 
	TappedRoutedEventArgs e)
        {
            SetGaugeValueFromPoint(e.GetPosition(this));
        }

        private void SetGaugeValueFromPoint(Point p)
        {
            var pt = new Point(
	p.X - (ActualWidth / 2), 
	-p.Y + (ActualHeight / 2));

            var angle = Math.Atan2(pt.X, pt.Y) * 180 / Math.PI;
            var value = Minimum + 
	((Maximum - Minimum) * (angle - MinAngle) / (MaxAngle - MinAngle));
            if (value < Minimum || value > Maximum)
            {
                // Ignore positions outside the scale angle.
                return;
            }

            Value = value;
        }

To prevent rounding issues when setting the Value through interaction, the control was extended with a StepSize dependency property. It determines the rounding interval for the Value. At the default StepSize value of zero, the control does no rounding at all. Set it to one if you only want integer values.

Each time the Value changes, the StepSize adjustment is applied:

if (radialGauge.StepSize != 0)
{
    radialGauge.Value = radialGauge.RoundToMultiple(
	radialGauge.Value, 
	radialGauge.StepSize);
}

I borrowed the rounding algorithm from the Rating Control. It’s unfortunate that there’s no such function in the System.Math class:

private double RoundToMultiple(double number, double multiple)
{
    double modulo = number % multiple;
    if ((multiple - modulo) <= modulo)
    {
        modulo = multiple - modulo;
    }
    else
    {
        modulo *= -1;
    }

    return number + modulo;
}

The begin and end angles of the scale are configurable.

In the previous iterations of the Radial Gauge, the arc of the scale was hardcoded and went clockwise from down left (-150°) to down right (150°). This gave the gauge a nice circular look – which is excellent in a responsive design context. It was also easy to find a place to display the value and unit information: centered at the bottom of the control. That 300° symmetrical look is still the default look, and here’s the corresponding style:

<Style TargetType="local:RadialGauge">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:RadialGauge">
                <Viewbox>
                    <Grid x:Name="PART_Container"
                          Height="200"
                          Width="200"
                          Background="Transparent">

                        <!-- Scale -->
                        <Path Name="PART_Scale"
                              Stroke="{TemplateBinding ScaleBrush}"
                              StrokeThickness="{TemplateBinding ScaleWidth}" />

                        <!-- Trail -->
                        <Path Name="PART_Trail"
                              Stroke="{TemplateBinding TrailBrush}"
                              StrokeThickness="{TemplateBinding ScaleWidth}" />

                        <!-- Value and Unit -->
                        <StackPanel VerticalAlignment="Bottom"
                                    HorizontalAlignment="Center">
                            <TextBlock Name="PART_ValueText"
                                       Foreground="{TemplateBinding ValueBrush}"
                                       FontSize="20"
                                       FontWeight="SemiBold"
                                       Text="{TemplateBinding Value}"
                                       TextAlignment="Center"
                                       Margin="0 0 0 2" />
                            <TextBlock Foreground="{TemplateBinding UnitBrush}"
                                       FontSize="16"
                                       TextAlignment="Center"
                                       Text="{TemplateBinding Unit}"
                                       Margin="0" />
                        </StackPanel>
                    </Grid>
                </Viewbox>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

It’s a simple style: you just have to tell the control where to draw the arcs and where to draw the value and unit texts. That’s the only XAML in the gauge, the rest (needle and ticks) is drawn by the Composition API.

The scale’s minimum and maximum angles are now Dependency Properties, so you can configure them. The proposed value ranges are -180° to 0° for MinAngle and 0° to 180° for MaxAngle.

Gauge_Angles

Feel free to deviate from the proposed values for MinAngle and MaxAngle. These were just the ranges that I had in mind when implementing all the internal calculations. Here are two examples of fully functional gauges that operate outside of the proposed ranges:

Gauge_SmallArcs

When you deviate from the default minimum or maximum angles, you probably need to retemplate the control to position the value and unit measure text. So I created a sample app that allows you to build and test your own templates. Here’s its main page with some sample templates:

Gauge1.1

My favorite template is the 270° gauge, based on RPM and speed indicators in cars. It’s three quarters of a circle (from -180° to +90°), the unit measure is neatly placed at the end of the scale, and the value appears in big in the bottom right quadrant.

Here’s how 270° gauges look like in a SquareOfsquares test container:
Template270

This is the corresponding template – it’s called it ‘Audi’ because it looks like the gauges I stare at when commuting:

    <Style x:Key="Audi"
           TargetType="controls:RadialGauge">
        <Setter Property="MinAngle"
                Value="-180" />
        <Setter Property="MaxAngle"
                Value="90" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="controls:RadialGauge">
                    <Viewbox>
                        <Grid x:Name="PART_Container"
                              Height="200"
                              Width="200"
                              Background="Transparent">
                            <Grid.RowDefinitions>
                                <RowDefinition />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>

                            <!-- Scale -->
                            <Path Name="PART_Scale"
                                  Stroke="{TemplateBinding ScaleBrush}"
                                  StrokeThickness="{TemplateBinding ScaleWidth}"
                                  Grid.ColumnSpan="2"
                                  Grid.RowSpan="2" />

                            <!-- Trail -->
                            <Path Name="PART_Trail"
                                  Stroke="{TemplateBinding TrailBrush}"
                                  StrokeThickness="{TemplateBinding ScaleWidth}"
                                  Grid.ColumnSpan="2"
                                  Grid.RowSpan="2" />

                            <!-- Value and Unit -->
                            <TextBlock Name="PART_ValueText"
                                       Foreground="{TemplateBinding ValueBrush}"
                                       FontSize="40"
                                       FontWeight="SemiBold"
                                       Text="{TemplateBinding Value}"
                                       TextAlignment="Center"
                                       VerticalAlignment="Center"
                                       Grid.Column="1"
                                       Grid.Row="1" />
                            <TextBlock Foreground="{TemplateBinding UnitBrush}"
                                       FontSize="16"
                                       FontWeight="Light"
                                       TextAlignment="Right"
                                       Text="{TemplateBinding Unit}"
                                       VerticalAlignment="Top"
                                       Margin="0 2 0 0"
                                       Grid.Column="1"
                                       Grid.Row="1" />
                        </Grid>
                    </Viewbox>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Here are some examples of other templates:

Template180

Template360

It has a NuGet package

The Radial Gauge is distributed through the Microsoft.Uwp.Toolkit.UI.Controls NuGet package:

NuGet

Make sure to install v1.1.0 or later.

As you see, the UWP Community Toolkit is a powerful and flexible control. If you want a playground to test some styles and templates, then checkout this sample app.

Enjoy!