Universal Windows Platform – Adaptive Card

Adaptive Card demonstrates how to use Adaptive Cards, an open card exchange format, to add to the Timeline in Task View of Windows 10

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in a Name and select a Location and then select Ok to create the Project
vs2017-new-project-window

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

From the Menu choose Tools, then NuGet Package Manager and Package Manager Console

vs2017-tools-nuget-package-manager-package-manager-console

Step 6

Then in the Package Manager Console Window which usually appears at the bottom of Visual Studio 2017 at the PM> Prompt type in following:

 
Install-Package AdaptiveCards

vs2017-package-manager-console-adaptive-card-adaptivecards

Followed by typing Enter to install AdaptiveCards from NuGet into the Application which should be Successfully installed

Step 7

While still in the Package Manager Console Window of Visual Studio 2017 at the PM> Prompt type in following:

 
Install-Package AdaptiveCards.Rendering.Uwp

vs2017-package-manager-console-adaptive-card-adaptivecards-rendering-uwp

Followed by typing Enter to install AdaptiveCards.Rendering.Uwp from NuGet into the Application which should be Successfully installed

Step 8

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 9

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project

vs2017-add-new-item-library

Step 10

Once in the Code View for Library.cs the following should be entered:

using AdaptiveCards;
using AdaptiveCards.Rendering.Uwp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.ApplicationModel.UserActivities;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI.Shell;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Cards = AdaptiveCards;
using RenderCards = AdaptiveCards.Rendering.Uwp;

public class AdaptiveItem
{
    public string Title { get; set; }
    public string Body { get; set; }
}

public class Card
{
    private const string auto = "Auto";
    private const string stretch = "Stretch";

    private readonly Random random = new Random((int)DateTime.Now.Ticks);
    private readonly AdaptiveCardRenderer renderer = new AdaptiveCardRenderer();
    private readonly Uri adaptive_card_image =
    new Uri("http://adaptivecards.io/content/adaptive-card-50.png");

    private Cards.AdaptiveCard GetCard(AdaptiveItem item)
    {
        Cards.AdaptiveCard card = new Cards.AdaptiveCard()
        {
            Id = random.Next(1, 100000000).ToString(),
            Body = new List<AdaptiveElement>()
            {
              new Cards.AdaptiveColumnSet()
              {
                    Columns = new List<Cards.AdaptiveColumn>()
                    {
                        new Cards.AdaptiveColumn()
                        {
                            Width = auto,
                            Items = new List<AdaptiveElement>()
                            {
                                new Cards.AdaptiveImage()
                                {
                                    Url = adaptive_card_image
                                }
                            }
                        },
                        new Cards.AdaptiveColumn()
                        {
                            Width = stretch,
                            Items = new List<AdaptiveElement>()
                            {
                                new Cards.AdaptiveTextBlock()
                                {
                                    Text = item.Title,
                                    Size = AdaptiveTextSize.ExtraLarge,
                                    Weight = AdaptiveTextWeight.Bolder,
                                },
                                new Cards.AdaptiveTextBlock()
                                {
                                    Text = item.Body,
                                    Size = AdaptiveTextSize.Medium,
                                    Weight = AdaptiveTextWeight.Lighter
                                }
                            },
                        }
                    }
                }
            }
        };
        return card;
    }

    private RenderCards.AdaptiveCard Convert(Cards.AdaptiveCard source, out string json)
    {
        try
        {
            json = source.ToJson();
            RenderCards.AdaptiveCardParseResult result = RenderCards.AdaptiveCard.FromJsonString(json);
            return result.AdaptiveCard;
        }
        catch (Exception)
        {
            json = null;
            return null;
        }
    }

    private Cards.AdaptiveCard Parse(string json)
    {
        try
        {
            Cards.AdaptiveCardParseResult result = Cards.AdaptiveCard.FromJson(json);
            return result.Card;
        }
        catch (Exception)
        {
            return null;
        }
    }

    private FrameworkElement Render(Cards.AdaptiveCard card, out string json)
    {
        try
        {
            RenderedAdaptiveCard rendered = renderer.RenderAdaptiveCard(Convert(card, out json));
            return rendered.FrameworkElement;
        }
        catch (Exception)
        {
            json = null;
            return null;
        }
    }

    public FrameworkElement Render(AdaptiveItem item, out string json)
    {
        Cards.AdaptiveCard card = GetCard(item);
        return Render(card, out json);
    }

    public FrameworkElement Render(string json)
    {
        Cards.AdaptiveCard card = Parse(json);
        return Render(card, out json);
    }
}

public class Timeline
{
    private const string uri = "https://comentsys.wordpress.com/uwp-adaptive-card";
    private readonly Random random = new Random((int)DateTime.Now.Ticks);
    private readonly UserActivityChannel channel = UserActivityChannel.GetDefault();

    public async void Create(string json, string text)
    {
        string id = random.Next(1, 100000000).ToString();
        UserActivity activity = await channel.GetOrCreateUserActivityAsync(id);
        activity.VisualElements.DisplayText = text;
        activity.VisualElements.Content =
        AdaptiveCardBuilder.CreateAdaptiveCardFromJson(json);
        activity.ActivationUri = new Uri(uri);
        activity.FallbackUri = new Uri(uri);
        await activity.SaveAsync();
        UserActivitySession session = activity.CreateSession();
        session?.Dispose();
    }
}

public class Library
{
    private const string app_title = "Adaptive Card";
    private const string extension_json = ".json";
    private const string extension_png = ".png";

    private static readonly Card card = new Card();
    private static readonly Timeline timeline = new Timeline();

    private async Task<string> OpenAsync()
    {
        try
        {
            FileOpenPicker picker = new FileOpenPicker()
            {
                SuggestedStartLocation = PickerLocationId.ComputerFolder
            };
            picker.FileTypeFilter.Add(extension_json);
            StorageFile open = await picker.PickSingleFileAsync();
            if (open != null)
            {
                return await FileIO.ReadTextAsync(open);
            }
        }
        finally
        {
        }
        return null;
    }

    private async void Render(FrameworkElement element, StorageFile file)
    {
        using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
        {
            BitmapEncoder encoder = await BitmapEncoder.CreateAsync(
            BitmapEncoder.PngEncoderId, stream);
            RenderTargetBitmap target = new RenderTargetBitmap();
            await target.RenderAsync(element, 0, 0);
            IBuffer buffer = await target.GetPixelsAsync();
            encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied,
            (uint)target.PixelWidth, (uint)target.PixelHeight, 96.0, 96.0, buffer.ToArray());
            await encoder.FlushAsync();
            target = null;
            buffer = null;
            encoder = null;
        }
    }

    private async void SaveAsync(FrameworkElement element, string json)
    {
        try
        {
            FileSavePicker picker = new FileSavePicker()
            {
                SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
                DefaultFileExtension = extension_json,
                SuggestedFileName = "Template"
            };
            picker.FileTypeChoices.Add("Json File", new List<string>() { extension_json });
            picker.FileTypeChoices.Add("Image File", new List<string>() { extension_png });
            StorageFile save = await picker.PickSaveFileAsync();
            if (save != null)
            {
                if (save.FileType == extension_json)
                {
                    await FileIO.WriteTextAsync(save, json);
                }
                else if (save.FileType == extension_png)
                {
                    Render(element, save);
                }
            }
        }
        finally
        {
        }
    }

    public void View(ref TextBox title, ref TextBox body, ref TextBox input, ref Canvas display)
    {
        if (!string.IsNullOrEmpty(title.Text) && !string.IsNullOrEmpty(body.Text))
        {
            display.Children.Clear();
            AdaptiveItem item = new AdaptiveItem() { Title = title.Text, Body = body.Text };
            FrameworkElement element = card.Render(item, out string json);
            if (element != null && json != null)
            {
                input.Text = json;
                display.Children.Add(element);
            }
        }
    }

    public void View(ref TextBox input, ref Canvas display)
    {
        if (!string.IsNullOrEmpty(input.Text))
        {
            string json = input.Text;
            display.Children.Clear();
            FrameworkElement element = card.Render(json);
            if (element != null && json != null)
            {
                input.Text = json;
                display.Children.Add(element);
            }
        }
    }

    public async void Open(TextBox input, Canvas display)
    {
        string json = await OpenAsync();
        if (json != null)
        {
            FrameworkElement element = card.Render(json);
            if (element != null && json != null)
            {
                input.Text = json;
                display.Children.Add(element);
            }
        }
    }

    public void Save(ref TextBox input, ref Canvas display)
    {
        if (!string.IsNullOrEmpty(input.Text) && display.Children.Any())
        {
            string json = input.Text;
            FrameworkElement element = display.Children.FirstOrDefault() as FrameworkElement;
            SaveAsync(element, json);
        }
    }

    public void Add(ref TextBox input, ref Canvas display)
    {
        if (!string.IsNullOrEmpty(input.Text) && display.Children.Any())
        {
            string json = input.Text;
            timeline.Create(json, app_title);
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There is also a AdaptiveItem Class with Properties for Title and Body.

There is a CardClass which has various const and readonly Values including Random to produce randomised numbers and for AdaptiveCardRenderer to help produce rendered versions of the AdaptiveCard. There is a GetCard Method which will return an AdaptiveCard from the AdaptiveCards Package with a given layout which includes a Uri to an Image and some AdaptiveTextBlock to produce output from the passed in AdaptiveItem. There is a Convert Method to convert from AdaptiveCard from the AdaptiveCards.Rendering.Uwp Package to one from the AdaptiveCards Package. Then there are Render Methods to produce the FrameworkElement and JSON string based on an AdaptiveCard from AdaptiveCards Package, an AdaptiveItem or from JSON.

There is a Timeline Class which has some const and readonly Members including for UserActivityChannel. The Create Method is used to generate a UserActivity from the passed in JSON string and uses the UserActivityChannel and UserActivitySession to add an item to the Timeline in Windows 10 with the content from CreateAdaptiveCardFromJson of AdaptiveCardBuilder.

The Library Class has various const and readonly Values including Card for the AdaptiveCard Functionality and Timeline for the UserActivity Functionality. OpenAsync is used with a FileOpenPicker and the ReadTextAsync of FileIO to read a Text File. SaveAsync is used with FileSavePicker and WriteTextAsync of FileIO.

Also in the Library Class is a SaveAsync Method which is which takes a FrameworkElement and StorageFile – with an IRandomAccessStream from this it will use a BitmapEncoder with RenderTargetBitmap to create the Image to be Rendered from the passed in FrameworkElement. SaveAsync is used with a FileSavePicker and the WriteTextAsync of FileIO for the .json File Extension or Json File and Render is used for the .png File Extension or Image File.

Finally in the Library Class there are View Method which will both get a FrameworkElement from the Render Method of the Card Class with one using the AdaptiveItem and the other just json. Then there is an Open Method which will use OpenAsync to get json and use this with the Render Method of the Card Class for json. The Save Method will use SaveAsync to output either content of a TextBox as json or the Children of a Canvas as FrameworkElement to then be optionally output as an Image. Thre Add Merhod is used to call the Create Method of the Timeline Class to insert an item into the Timeline of Windows 10 in Task View.

Step 11

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-library

Step 12

From the Menu choose View and then Designer

vs2017-view-designer

Step 13

The Design View will be displayed along with the XAML View and in this between the Grid and /Grid elements, enter the following XAML:

<Grid Margin="50">
	<Grid.RowDefinitions>
		<RowDefinition Height="Auto"/>
		<RowDefinition Height="*"/>
	</Grid.RowDefinitions>
	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="50*"/>
		<ColumnDefinition Width="50*"/>
	</Grid.ColumnDefinitions>
	<TextBox Name="Title" Grid.Row="0" Grid.Column="0" PlaceholderText="Title" Margin="20"/>
	<TextBox Name="Body" Grid.Row="0" Grid.Column="1" PlaceholderText="Body" Margin="20"/>
	<TextBox Name="Input" Grid.Row="1" Grid.Column="0" AcceptsReturn="True" TextWrapping="Wrap" Margin="20"/>
	<Canvas Name="Display" Grid.Row="1" Grid.Column="1" Margin="20"/>
</Grid>
<CommandBar Name="Command" VerticalAlignment="Bottom">
	<AppBarButton Icon="View" Label="View">
		<AppBarButton.Flyout>
			<MenuFlyout>
				<MenuFlyoutItem Icon="PreviewLink" Text="Item" Click="Item_Click"/>
				<MenuFlyoutItem Icon="Document" Text="Json" Click="Json_Click"/>
			</MenuFlyout>
		</AppBarButton.Flyout>
	</AppBarButton>
	<AppBarButton Icon="OpenFile" Label="Open" Click="Open_Click"/>
	<AppBarButton Icon="Save" Label="Save" Click="Save_Click"/>
	<AppBarButton Icon="Add" Label="Add" Click="Add_Click"/>
</CommandBar>

Within the main Grid Element, the first block of XAML is a Grid Control which has two Rows and two Columns, in the first Row are two TextBox Controls in a Column each for Title and Body and in first Column of the second Row is a TextBox for Input of JSON and in the second Column of the second Row is a Canvas for Display. The second block of XAML is a CommandBar with AppBarButton for View which contains a MenuFlyout with MenuFlyoutItem for Item which calls Item_Click and Json which calls Json_Click. There are AppBarButton for Open which calls Open_Click, for Save which calls Save_Click and Add which calls Add_Click.

Step 14

From the Menu choose View and then Code

vs2017-view-code

Step 15

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:

Library library = new Library();

private void Item_Click(object sender, RoutedEventArgs e)
{
	library.View(ref Title, ref Body, ref Input, ref Display);
}

private void Json_Click(object sender, RoutedEventArgs e)
{
	library.View(ref Input, ref Display);
}

private void Open_Click(object sender, RoutedEventArgs e)
{
	library.Open(Input, Display);
}

private void Save_Click(object sender, RoutedEventArgs e)
{
	library.Save(ref Input, ref Display);
}

private void Add_Click(object sender, RoutedEventArgs e)
{
	library.Add(ref Input, ref Display);
}

Below the MainPage() Method an instance of the Library Class is created, then there is an Item_Click Event Handler which calls the View Method in the Library Class and a Json_Click Event Handler which calls the other View Method of the Library Class. There is also Open_Click Event Handler which calls the Open Method of the Library Class and a Save_Click Event Handler which calls the Save Method of the Library Class, finally there is an Add_Click Event Handler which calls the Add Method in the Library Class.

Step 16

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 17

After the Application has started running you can then type into the TextBox for Title and Body then select View to display the JSON and Display for the Adaptive Card or you can use Open to select a JSON Template to use instead, you can then use Add to add this to the Timeline of Task View in Windows 10

run-adaptive-card

Step 18

An Activity will be displayed using the Adaptive Card in Windows 10 in the Task View in the Timeline created in the Application.

ran-adaptive-card

Step 19

To Exit the Application select the Close button in the top right of the Application

vs2017-close

Adaptive Card shows how to use the Packages for AdaptiveCards and AdaptiveCards.Rendering.Uwp from NuGet to create an Adaptive Card, it can also preview the output and can take JSON as input and can then use other templates, to get these and to find out more information about Adaptive Cards go to http://adaptivecards.io which is also the source of the Image used in the Example which is the Logo created for them by Microsoft.

Creative Commons License

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s