Universal Windows Platform – Custom Rating

Custom Rating demonstrates how to create a custom Style for the RatingControl

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

Once the Project is created from the Solution Explorer select App.xaml

vs2017-app

Step 6

From the Menu choose View and then Designer

vs2017-view-designer

Step 7

Once in the Design View for App.xaml between the Application and /Application elements the following should be entered:

<Application.Resources>
	<RatingItemFontInfo x:Key="RatingControlFontInfo" Glyph=""/>
	<Style x:Key="CustomRating" TargetType="RatingControl">
		<Setter Property="Foreground" Value="{ThemeResource RatingControlCaptionForeground}"/>
		<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}"/>
		<Setter Property="FontFamily" Value="Segoe MDL2 Assets"/>
		<Setter Property="ItemInfo" Value="{StaticResource RatingControlFontInfo}"/>
		<Setter Property="Template">
			<Setter.Value>
				<ControlTemplate TargetType="RatingControl">
					<Grid x:Name="LayoutRoot">
						<VisualStateManager.VisualStateGroups>
							<VisualStateGroup x:Name="CommonStates">
								<VisualState x:Name="Disabled">
									<VisualState.Setters>
										<Setter Target="ForegroundContentPresenter.Foreground" Value="{ThemeResource RatingControlDisabledSelectedForeground}"/>
									</VisualState.Setters>
								</VisualState>
								<VisualState x:Name="Placeholder">
									<VisualState.Setters>
										<Setter Target="ForegroundContentPresenter.Foreground" Value="Green"/>
									</VisualState.Setters>
								</VisualState>
								<VisualState x:Name="PointerOverPlaceholder">
									<VisualState.Setters>
										<Setter Target="ForegroundContentPresenter.Foreground" Value="{ThemeResource RatingControlPointerOverPlaceholderForeground}"/>
									</VisualState.Setters>
								</VisualState>
								<VisualState x:Name="PointerOverUnselected">
									<VisualState.Setters>
										<Setter Target="ForegroundContentPresenter.Foreground" Value="{ThemeResource RatingControlPointerOverUnselectedForeground}"/>
									</VisualState.Setters>
								</VisualState>
								<VisualState x:Name="Set">
									<VisualState.Setters>
										<Setter Target="ForegroundContentPresenter.Foreground" Value="Salmon"/>
									</VisualState.Setters>
								</VisualState>
								<VisualState x:Name="PointerOverSet">
									<VisualState.Setters>
										<Setter Target="ForegroundContentPresenter.Foreground" Value="Gold"/>
									</VisualState.Setters>
								</VisualState>
							</VisualStateGroup>
						</VisualStateManager.VisualStateGroups>
						<StackPanel Margin="-20,-20,-20,-20" Orientation="Horizontal" Grid.Row="0">
							<StackPanel x:Name="RatingBackgroundStackPanel" Background="Transparent" Margin="20,20,0,20" Orientation="Horizontal"/>
							<TextBlock x:Name="Caption" AutomationProperties.AccessibilityView="Raw" Height="32" IsHitTestVisible="False" Margin="4,9,20,0" AutomationProperties.Name="RatingCaption" Style="{ThemeResource CaptionTextBlockStyle}" TextLineBounds="TrimToBaseline" Text="{TemplateBinding Caption}" VerticalAlignment="Center"/>
						</StackPanel>
						<ContentPresenter x:Name="ForegroundContentPresenter" IsHitTestVisible="False" Grid.Row="0">
							<StackPanel Margin="-40,-40,-40,-40" Orientation="Horizontal">
								<StackPanel x:Name="RatingForegroundStackPanel" IsHitTestVisible="False" Margin="40,40,40,40" Orientation="Horizontal"/>
							</StackPanel>
						</ContentPresenter>
					</Grid>
				</ControlTemplate>
			</Setter.Value>
		</Setter>
	</Style>
</Application.Resources>

Within the App.xaml is defined an Application.Resources with a Style of “CustomRating” and a TargetType of Rating – this example takes a copy of the style for the standard Rating and customises many of the properties including Glyph set to to the Unicode value of the Heart.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

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

<Viewbox Margin="50">
	<RatingControl Margin="50" Name="Rating" Value="4" Style="{StaticResource CustomRating}"/>
</Viewbox>

The block of XAML represents the RatingControl Control with the Style of the the CustomRating and is within a Viewbox.

Step 11

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 12

After the Application has started running you can then select the Rating to display a different number of Heart items selected

ran-custom-ratring

Step 13

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

vs2017-close

For this example Expression Blend was used to create a copy of the standard Style for the RatingControl Content which was then customised with different default colours and glyph used to represent the rating, more sophisticated customisation is possible – the example is just the start of the idea of creating a completely custom look to an application by modifying controls in this way.

Creative Commons License

Universal Windows Platform – Learning App

Learning App shows how to create a simple application that can use Machine Learning to recognise Numbers written in an InkCanvas and output it to a TextBlock.

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 the Name as LearningApp and select a Location and then select Ok to create the Project
vs2017-new-project-learning-app

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

Once done Download and Save this mnist.onnx File to a Location on your Computer

download-learning-app

Step 6

Once Downloaded in the Solution Explorer select the Assets Folder

vs2017-assets-learning-app

Step 7

Then select from the Menu, Project, then Add Existing Item…

vs2017-project-add-existing-item

Step 8

Then from the Add Existing Item dialog find and select the previously Downloaded mnist.onnx File then select Add to add the file to the Project

vs2017-project-add-existing-item-learning-app

Step 9

Then in the Solution Explorer open the Assets Folder with the Arrow next to it and select the mnist.onnx File

vs2017-mnist-learning-app

Step 10

Then in the Properties for mnist.onnx set the Build Action to Content and then you can close the Assets Folder with the Arrow next to it

vs2017-properties-learning-app

Step 11

In the Solution Explorer select the Project

vs2017-project-learning-app

Step 12

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

vs2017-project-add-new-item

Step 13

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-learning-app

Step 14

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

using System;
using System.Linq;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Graphics.Imaging;
using Windows.Media;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Input.Inking;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;

namespace LearningApp
{
    public class Library
    {
        private InkPresenter _presenter;
        private MNISTModel _model = new MNISTModel();
        private MNISTModelInput _input = new MNISTModelInput();
        private MNISTModelOutput _output = new MNISTModelOutput();

        private async Task<VideoFrame> Render(InkCanvas inkCanvas)
        {
            RenderTargetBitmap target = new RenderTargetBitmap();
            await target.RenderAsync(inkCanvas, 28, 28);
            IBuffer buffer = await target.GetPixelsAsync();
            SoftwareBitmap bitmap = SoftwareBitmap.CreateCopyFromBuffer(buffer,
            BitmapPixelFormat.Bgra8, target.PixelWidth, target.PixelHeight,
            BitmapAlphaMode.Ignore);
            buffer = null;
            target = null;
            return VideoFrame.CreateWithSoftwareBitmap(bitmap);
        }

        public async void Init(InkCanvas inkCanvas)
        {
            _presenter = inkCanvas.InkPresenter;
            _presenter.InputDeviceTypes =
            CoreInputDeviceTypes.Pen |
            CoreInputDeviceTypes.Mouse |
            CoreInputDeviceTypes.Touch;
            _presenter.UpdateDefaultDrawingAttributes(new InkDrawingAttributes()
            {
                Color = Colors.White,
                Size = new Size(22, 22),
                IgnorePressure = true,
                IgnoreTilt = true,
            });
            StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(
            new Uri($"ms-appx:///Assets/mnist.onnx"));
            _model = await MNISTModel.CreateMNISTModel(file);
        }

        public async void Recognise(InkCanvas inkCanvas, TextBlock display)
        {
            _input.Input3 = await Render(inkCanvas);
            _output = await _model.EvaluateAsync(_input);
            int result = _output.Plus214_Output_0.IndexOf(_output.Plus214_Output_0.Max());
            display.Text = result.ToString();
        }

        public void Clear(ref TextBlock display)
        {
            display.Text = string.Empty;
            _presenter.StrokeContainer.Clear();
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. The Library Class has Members for InkPresenter, MNISTModel, MNISTModelInput and MNISTModelOutput.

While in the Library Class there is a Render Method which takes an InkCanvas as a Parameter and responds with a Task of VideoFrame it will create a RenderTargetBitmap from the InkCanvas and then creates a SoftwareBitmap from this which is used to create and return a VideoFrame based from this.

Also in the Library Class there is an Init

Method which takes an InkCanvas as a Parameter and configures an InkPresenter including using UpdateDefaultDrawingAttributes to provide some configuration for drawing and then there is a StorageFile which is set to the mnist.onnx File. The Recognise Method has a InkCanvas and TextBlock passed in as Parameters, it sets the MNISTModelInput to the result of the Render Method and the MNISTModelOutput is set to the EvaluateAsync of this Input and an int is set to the a result of the Output. The Clear Method is used to reset the TextBlock and the InkPresenter.

Step 15

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-learning-app

Step 16

From the Menu choose View and then Designer

vs2017-view-designer

Step 17

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

<Grid Margin="50">
	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="50*"/>
		<ColumnDefinition Width="50*"/>
	</Grid.ColumnDefinitions>
	<Grid Grid.Column="0" Background="Black">
		<InkCanvas x:Name="InkCanvas"/>
	</Grid>
	<Grid Grid.Column="1" Background="WhiteSmoke">
		<TextBlock Name="Display" FontSize="200"
		HorizontalAlignment="Center" VerticalAlignment="Center"/>
	</Grid>
</Grid>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Scan" Label="Recognise" Click="Recognise_Click"/>
	<AppBarButton Icon="Clear" Label="Clear" Click="Clear_Click"/>
</CommandBar>

Within the main Grid Element, the first block of XAML is a Grid Control which has two Columns, in the first Column is an InkCanvas and is within a Grid which has a Background set to the Black and in the second Column is another Grid with a TextBlock and is within a Grid. The second block of XAML is a CommandBar with AppBarButton for Recognise which calls Recognise_Click and Clear which calls Clear_Click.

Step 18

From the Menu choose View and then Code

vs2017-view-code

Step 19

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

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(InkCanvas);
}

private void Recognise_Click(object sender, RoutedEventArgs e)
{
	library.Recognise(InkCanvas, Display);
}

private void Clear_Click(object sender, RoutedEventArgs e)
{
	library.Clear(ref Display);
}

There is an OnNavigatedTo Event Handler which calls the Init Method in the Library Class and Recognise_Click which calls the Recognise Method and Clear_Click which calls the Clear Method of the Library Class.

Step 20

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 21

Once the Application has started running you can then write a digit or number between 0 and 9 with a Pen, Mouse or Touch in the InkCanvas and then select Recognise to display the recognised digit in the TextBlock

uwp-ran-learning-app

Step 22

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

vs2017-close

This example shows how you can use Machine Learning to recognise numbers that have been written into an InkCanvas and output in a TextBlock using MNIST, an Open Neural Network Exchange or ONNX trained machine learning model to recognise drawn numeric digits. This example is based on the MNIST sample in the Windows Machine Learning on GitHub by Microsoft

Creative Commons License

Universal Windows Platform – Analysis App

Analysis App shows how to create a simple application that can recognise anything written as text in an InkCanvas and output it to a TextBlock.

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

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

vs2017-project-add-new-item

Step 6

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 7

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

using System;
using System.Collections.Generic;
using Windows.UI.Core;
using Windows.UI.Input.Inking;
using Windows.UI.Input.Inking.Analysis;
using Windows.UI.Xaml.Controls;

public class Library
{
    private InkPresenter _presenter;
    private InkAnalyzer _analyser;

    private void Presenter_StrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args)
    {
        _analyser.AddDataForStrokes(args.Strokes);
    }

    private void Presenter_StrokesErased(InkPresenter sender, InkStrokesErasedEventArgs args)
    {
        foreach (InkStroke stroke in args.Strokes)
        {
            _analyser.RemoveDataForStroke(stroke.Id);
        }
    }

    public void Init(ref InkCanvas inkCanvas)
    {
        _presenter = inkCanvas.InkPresenter;
        _presenter.StrokesCollected += Presenter_StrokesCollected;
        _presenter.StrokesErased += Presenter_StrokesErased;
        _presenter.InputDeviceTypes =
        CoreInputDeviceTypes.Pen |
        CoreInputDeviceTypes.Mouse |
        CoreInputDeviceTypes.Touch;
        _analyser = new InkAnalyzer();
    }

    public async void Analyse(TextBlock display)
    {
        IReadOnlyList<InkStroke> strokes = _presenter.StrokeContainer.GetStrokes();
        foreach (InkStroke stroke in strokes)
        {
            _analyser.SetStrokeDataKind(stroke.Id, InkAnalysisStrokeKind.Writing);
        }
        InkAnalysisResult result = await _analyser.AnalyzeAsync();
        if (result.Status == InkAnalysisStatus.Updated)
        {
            display.Text = _analyser.AnalysisRoot.RecognizedText;
        }
    }

    public void Clear(ref TextBlock display)
    {
        display.Text = string.Empty;
        _presenter.StrokeContainer.Clear();
        _analyser.ClearDataForAllStrokes();
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There are Members for InkPresenter and InkAnalyzer. The Presenter_StrokesCollected and Presenter_StrokesErased Event Handlers are used to call the AddDataForStrokes and RemoveDataForStroke Methods of the InkAnalyzer respectively.

While in the Library Class there is an Init Method which is used to assign the InkPresenter from an InkCanvas and setup the Event Handlers and configure the InputDeviceTypes. The Analyse Method is used to get a IReadOnlyList of InkStroke from the InkPresenter and foreach of these call SetStrokeDataKind to InkAnalysisStrokeKind.Writing and then will call the AnalyzeAsync of the InkAnalyzer to recognise any written text and once the Status is InkAnalysisStatus.Updated the TextBlock passed in has its Text Property set to the RecognizedText of the InkAnalyzer. The Clear Method is used to reset the TextBlock and the InkPresenter and InkAnalyzer.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-library

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

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

<Grid Margin="50">
	<Grid.RowDefinitions>
		<RowDefinition Height="Auto"/>
		<RowDefinition Height="50*"/>
		<RowDefinition Height="50*"/>
	</Grid.RowDefinitions>
	<InkToolbar Grid.Row="0" TargetInkCanvas="{x:Bind InkCanvas}" InitialControls="None">
		<InkToolbarBallpointPenButton/>
		<InkToolbarPencilButton/>
		<InkToolbarEraserButton/>
	</InkToolbar>
	<InkCanvas Grid.Row="1" x:Name="InkCanvas"/>
	<Grid Grid.Row="2" Background="WhiteSmoke">
		<TextBlock Name="Display" FontSize="100"
		HorizontalAlignment="Center" VerticalAlignment="Center"/>
	</Grid>
</Grid>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Scan" Label="Analyse" Click="Analyse_Click"/>
	<AppBarButton Icon="Clear" Label="Clear" Click="Clear_Click"/>
</CommandBar>

Within the main Grid Element, the first block of XAML is a Grid Control which has three Rows, in the first Row is an InkToolbar, the third Row is an InkCanvas and in the third Row is a TextBlock. The second block of XAML is a CommandBar with AppBarButton for Analyse which calls Analyse_Click and Clear which calls Clear_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

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

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(ref InkCanvas);
}

private void Analyse_Click(object sender, RoutedEventArgs e)
{
	library.Analyse(Display);
}

private void Clear_Click(object sender, RoutedEventArgs e)
{
	library.Clear(ref Display);
}

There is an OnNavigatedTo Event Handler which calls the Init Method in the Library Class and Analyse_Click which calls the Analyse Method and Clear_Click which calls the Clear Method of the Library Class.

Step 13

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 14

Once the Application has started running you can then write something with a Pen, Mouse or Touch in the InkCanvas and then select Analyse to display the recognised text in the TextBlock

uwp-ran-analysis-app

Step 15

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

vs2017-close

This example shows how you can use the build-in functionality of an InkCanvas to not only draw easily with Pen, Mouse or Touch but also use Ink Analysis to recognise what has been written as Text. This example is based upon the Ink Analysis sample in the Windows universal samples on GitHub by Microsoft.

Creative Commons License

Universal Windows Platform – Dictates App

Dictates App shows how to create a simple application that can recognise Dictated Speech and input this into a TextBox.

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

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

vs2017-project-add-new-item

Step 6

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 7

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

using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Pickers;
using System;
using System.Collections.Generic;
using Windows.UI.Xaml.Controls;
using Windows.Foundation;
using Windows.UI.Popups;
using Windows.System;
using Windows.Media.SpeechRecognition;
using System.Text;
using Windows.UI.Core;
using Windows.Globalization;
using Windows.UI.Xaml.Media;

public class Library
{
    private const string app_title = "Dictates App";
    private const string extension_txt = ".txt";
    private const string label_dictate = "Dictate";
    private const string label_stop = "Stop";
    private const uint privacy_statement_declined = 0x80045509;

    private IAsyncOperation<IUICommand> _dialogCommand;
    private SpeechRecognizer _recogniser = new SpeechRecognizer();
    private StringBuilder _builder = new StringBuilder();
    private CoreDispatcher _dispatcher;
    private bool _listening;

    public delegate void ResultHandler(string value);
    public event ResultHandler Result;

    public delegate void CompletedHandler();
    public event CompletedHandler Completed;

    private async Task<bool> ShowDialogAsync(string content, string title = app_title)
    {
        try
        {
            if (_dialogCommand != null)
            {
                _dialogCommand.Cancel();
                _dialogCommand = null;
            }
            _dialogCommand = new MessageDialog(content, title).ShowAsync();
            await _dialogCommand;
            return true;
        }
        catch (TaskCanceledException)
        {
            return false;
        }
    }

    private async void ShowPrivacy()
    {
        await Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-speechtyping"));
    }

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

    private async void SaveAsync(string contents)
    {
        try
        {
            FileSavePicker picker = new FileSavePicker()
            {
                SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
                DefaultFileExtension = extension_txt,
                SuggestedFileName = "Document"
            };
            picker.FileTypeChoices.Add("Text File", new List<string>() { extension_txt });
            StorageFile save = await picker.PickSaveFileAsync();
            if (save != null) await FileIO.WriteTextAsync(save, contents);
        }
        finally
        {
        }
    }

    private async void Recogniser_Completed(
        SpeechContinuousRecognitionSession sender, 
        SpeechContinuousRecognitionCompletedEventArgs args)
    {
        if (args.Status != SpeechRecognitionResultStatus.Success)
        {
            if (args.Status == SpeechRecognitionResultStatus.TimeoutExceeded)
            {
                await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    Result?.Invoke(_builder.ToString());
                    Completed?.Invoke();
                    _listening = false;
                });
            }
            else
            {
                await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    Completed?.Invoke();
                    _listening = false;
                });
            }
        }
    }

    private async void Recogniser_ResultGenerated(
        SpeechContinuousRecognitionSession sender, 
        SpeechContinuousRecognitionResultGeneratedEventArgs args)
    {
        if (args.Result.Confidence == SpeechRecognitionConfidence.Medium ||
            args.Result.Confidence == SpeechRecognitionConfidence.High)
        {
            _builder.Append($"{args.Result.Text} ");
            await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                Result?.Invoke(_builder.ToString());
            });
        }
    }

    private async void SpeechRecognizer_HypothesisGenerated(
        SpeechRecognizer sender, 
        SpeechRecognitionHypothesisGeneratedEventArgs args)
    {
        string hypothesis = args.Hypothesis.Text;
        string content = $"{_builder.ToString()} {hypothesis} ...";
        await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            Result?.Invoke(content);
        });
    }

    private async void Setup(Language language)
    {
        if (_recogniser != null)
        {
            _recogniser.ContinuousRecognitionSession.Completed -= Recogniser_Completed;
            _recogniser.ContinuousRecognitionSession.ResultGenerated -= Recogniser_ResultGenerated;
            _recogniser.HypothesisGenerated -= SpeechRecognizer_HypothesisGenerated;
            _recogniser.Dispose();
            _recogniser = null;
        }
        _recogniser = new SpeechRecognizer(language);
        SpeechRecognitionTopicConstraint constraint = new SpeechRecognitionTopicConstraint(
        SpeechRecognitionScenario.Dictation, "dictation");
        _recogniser.Constraints.Add(constraint);
        SpeechRecognitionCompilationResult result = await _recogniser.CompileConstraintsAsync();
        if (result.Status != SpeechRecognitionResultStatus.Success)
        {
            await ShowDialogAsync($"Grammar Compilation Failed: {result.Status.ToString()}");
        }
        _recogniser.ContinuousRecognitionSession.Completed += Recogniser_Completed;
        _recogniser.ContinuousRecognitionSession.ResultGenerated += Recogniser_ResultGenerated;
        _recogniser.HypothesisGenerated += SpeechRecognizer_HypothesisGenerated;
    }

    public Dictionary<Language, string> Languages()
    {
        Dictionary<Language, string> results = new Dictionary<Language, string>();
        foreach (Language language in SpeechRecognizer.SupportedTopicLanguages)
        {
            results.Add(language, language.DisplayName);
        }
        return results;
    }

    public async void Language(object value)
    {
        if (_recogniser != null)
        {
            Language language = (Language)value;
            if (_recogniser.CurrentLanguage != language)
            {
                try
                {
                    Setup(language);
                }
                catch (Exception exception)
                {
                    await ShowDialogAsync(exception.Message);
                }
            }
        }
    }

    private void Content_TextChanged(object sender, TextChangedEventArgs e)
    {
        var grid = (Grid)VisualTreeHelper.GetChild((TextBox)sender, 0);
        for (var i = 0; i <= VisualTreeHelper.GetChildrenCount(grid) - 1; i++)
        {
            object obj = VisualTreeHelper.GetChild(grid, i);
            if (!(obj is ScrollViewer)) continue;
            ((ScrollViewer)obj).ChangeView(0.0f, ((ScrollViewer)obj).ExtentHeight, 1.0f);
            break;
        }
    }

    public void Init(AppBarButton microphone, ComboBox languages, TextBox content)
    {
        _dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
        Completed += () =>
        {
            microphone.Label = label_dictate;
            languages.IsEnabled = true;
        };
        Result += (string value) =>
        {
            content.Text = value;
        };
        content.TextChanged += Content_TextChanged;
    }

    public async void Dictate(AppBarButton dictate, ComboBox languages, TextBox content)
    {
        dictate.IsEnabled = false;
        if (_listening == false)
        {
            if (_recogniser.State == SpeechRecognizerState.Idle)
            {
                dictate.Label = label_stop;
                languages.IsEnabled = false;
                try
                {
                    _listening = true;
                    await _recogniser.ContinuousRecognitionSession.StartAsync();
                }
                catch (Exception ex)
                {
                    if ((uint)ex.HResult == privacy_statement_declined)
                    {
                        ShowPrivacy();
                    }
                    else
                    {
                        await ShowDialogAsync(ex.Message);
                    }
                    _listening = false;
                    dictate.Label = label_dictate;
                    languages.IsEnabled = true;
                }
            }
        }
        else
        {
            _listening = false;
            dictate.Label = label_dictate;
            languages.IsEnabled = true;
            if (_recogniser.State != SpeechRecognizerState.Idle)
            {
                try
                {
                    await _recogniser.ContinuousRecognitionSession.StopAsync();
                    content.Text = _builder.ToString();
                }
                catch (Exception ex)
                {
                    await ShowDialogAsync(ex.Message);
                }
            }
        }
        dictate.IsEnabled = true;
    }

    public async void New(TextBox text)
    {
        if (await ShowDialogAsync("Create New Document?"))
        {
            _builder.Clear();
            text.Text = string.Empty;
        }
    }

    public async void Open(TextBox text)
    {
        string content = await OpenAsync();
        if (content != null)
        {
            text.Text = content;
        }
    }

    public void Save(ref TextBox text)
    {
        SaveAsync(text.Text);
    }
}

In the Code File for Library there are using statements to include the necessary functionality. The Library Class has const Values along with Members such as IAsyncOperation of IUICommand for use with the ShowDialogAsync Method which will display a MessageDialog, SpeechRecognizer and CoreDispatcher It also has event for ResultHandler and CompletedHandler.

Also within the Library Class there is a ShowPrivacy to display a Speech Typing Privacy Settings Page. 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.

While still in the Library Class there is a Recogniser_Completed Event Handler which will be triggered when the SpeechRecognizer has finished Recognising a Dictation and will trigger the relevent event based on the SpeechRecognitionResultStatus. The Recogniser_ResultGenerated will be triggered when there is a result from the SpeechRecognizer and this will based on a SpeechRecognitionConfidence and will Append to a StringBuilder and Raise the Result Event Accordingly with its contents. The SpeechRecognizer_HypothesisGenerated responds to when a Hypothesis from the SpeechRecognizer is dected and this Raise the Result Event with this.

Again in the Library Class there is a Setup Method which takes a Language Parameter and will configure the relevant Event Handlers and set up the SpeechRecognitionTopicConstraint and Language of the SpeechRecognizer. The Languages Method will return all the supported Languages of the SpeechRecognizer as a Dictionary of Language and string. The Language Method will set the CurrentLanguage of the SpeechRecognizer. Content_TextChanged is an Event Handler that will help Scroll content as it is Dictated within a TextBox, the Init Method is used to initialise the Event Handlers and CoreDispatcher. The Dictate Method is used to begin with StartAsync or end a Dictation with StopAsync of the ContinuousRecognitionSession of SpeechRecognizer and will also show any messages should there be any Errors. The New Method will be used to clear a TextBox, Open is used with OpenAsync to read a Text File and Save is used to write a Text File from the content Dictated.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-library

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

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

<Grid Margin="50">
	<Grid.RowDefinitions>
		<RowDefinition Height="Auto"/>
		<RowDefinition Height="*"/>
	</Grid.RowDefinitions>
	<ComboBox Grid.Row="0" Name="Language" HorizontalAlignment="Stretch"
	SelectedValuePath="Key" DisplayMemberPath="Value"
	SelectionChanged="Language_SelectionChanged"/>
	<TextBox Grid.Row="1" Name="Display" AcceptsReturn="True" TextWrapping="Wrap"/>
</Grid>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Name="Dictate" Icon="Microphone" Label="Dictate" Click="Dictate_Click"/>
	<AppBarButton Icon="Page2" Label="New" Click="New_Click"/>
	<AppBarButton Icon="OpenFile" Label="Open" Click="Open_Click"/>
	<AppBarButton Icon="Save" Label="Save" Click="Save_Click"/>
</CommandBar>

Within the main Grid Element, the first block of XAML is a Grid Control which has two Rows, in the first Row is a ComboBox which will display the Language Options available and in the second Row is a TextBox Control. The second block of XAML is a CommandBar with AppBarButton for Dictate, New, Open and Save that calls Dictate_Click, New_Click, Open_Click and Save_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

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

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(Dictate, Language, Display);
	Language.ItemsSource = library.Languages();
	Language.SelectedIndex = 0;
}

private void Language_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
	library.Language(Language.SelectedValue);
}

private void Dictate_Click(object sender, RoutedEventArgs e)
{
	library.Dictate(Dictate, Language, Display);
}

private void New_Click(object sender, RoutedEventArgs e)
{
	library.New(Display);
}

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

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

There is an OnNavigatedTo Event Handler which calls the Init Method in the Library Class and sets the ItemsSource of the ComboBox. The Language_SelectionChanged Event Handler calls the Language Method in the Library Class, Dictate_Click calls Dictate, New_Click calls New, Open_Click calls Open and Save_Click Calls Save in the Library Class.

Step 13

In the Solution Explorer select Package.appxmanifest

vs2017-mainpage

Step 14

From the Menu choose View and then Designer

vs2017-view-designer

Step 15

Finally in the Package.appxmanifest select Capabilities and then make sure the Microphone option is checked

vs2017-manifest-audio-recorder

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

Once the Application has started running you can select the Dictate Button to Start or Stop Dictation, the first time you do this there will be a question displayed similar to Let DictatesApp access your Microphone and select Yes then you can then Speak and it will recognise and input into the TextBox what you are saying, you can then use Save to preserve what you said that you can Open later or start again with New, you may also need to allow the Application via the Privacy link to Dictate speech

uwp-ran-dictates-app

Step 18

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

vs2017-close

This example shows how to use the SpeechRecognizer along with any supported Language on your Local Maching to Dictate anything spoken which will be Transcribed and Input into a TextBox, this is a simple example that shows how you can build in Speech recognition into an Application to implement continuous dictation.

Creative Commons License

Universal Windows Platform – Pomodoro App

Pomodoro App shows how to create a simple application that can help with time management using a timer to help break down tasks with a 25 Minute Task Timer and then have a Short 5 Minute or Long 15 Minute Break inbetween.

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 the Name as PomodoroApp and select a Location and then select Ok to create the Project
vs2017-new-project-pomodoro-app

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

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

vs2017-project-add-new-item

Step 6

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-pomodoro-app

Step 7

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

using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.Popups;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Reflection;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using System.Text.RegularExpressions;
using Windows.UI.Xaml;
using Windows.UI;
using Windows.UI.Notifications;
using Windows.Data.Xml.Dom;
using System.Linq;

namespace PomodoroApp
{
    public enum PomodoroType
    {
        [Description("\U0001F345")]
        TaskTimer = 25,
        [Description("\U00002615")]
        ShortBreak = 1,
        [Description("\U0001F34F")]
        LongBreak = 15
    }

    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected bool SetProperty<T>(ref T field, T value,
        [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        public void OnPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

    public class PomodoroItem : BindableBase
    {
        private PomodoroType _type;
        private Color _light;
        private Color _dark;

        private string GetGlyph(PomodoroType type)
        {
            FieldInfo info = type.GetType().GetField(type.ToString());
            DescriptionAttribute[] attr =
            (DescriptionAttribute[])info.GetCustomAttributes
            (typeof(DescriptionAttribute), false);
            return attr[0].Description;
        }

        private string GetId(PomodoroType type)
        {
            return Enum.GetName(typeof(PomodoroType), type);
        }

        private IEnumerable<string> GetName(PomodoroType type)
        {
            string text = GetId(type);
            Regex regex = new Regex(@"\p{Lu}\p{Ll}*");
            foreach (Match match in regex.Matches(text))
            {
                yield return match.Value;
            }
        }

        public PomodoroType Type
        {
            get { return _type; }
            set { SetProperty(ref _type, value); }
        }

        public Color Dark
        {
            get { return _dark; }
            set { SetProperty(ref _dark, value); }
        }

        public Color Light
        {
            get { return _light; }
            set { SetProperty(ref _light, value); }
        }

        public string Id => GetId(_type);

        public string Glyph => GetGlyph(_type);

        public string Name => string.Join(" ", GetName(_type));

        public TimeSpan TimeSpan => TimeSpan.FromMinutes((double)_type);

        public PomodoroItem(PomodoroType type, Color dark, Color light)
        {
            Type = type;
            Dark = dark;
            Light = light;
        }
    }

    public class PomodoroSetup : BindableBase
    {
        private bool _started;
        private string _display;
        private DateTime _start;
        private DateTime _finish;
        private TimeSpan _current;
        private PomodoroItem _item;
        private List<PomodoroItem> _items;
        private DispatcherTimer _timer;
        private ToastNotifier _notifier = ToastNotificationManager.CreateToastNotifier();

        private ScheduledToastNotification GetToast()
        {
            return _notifier.GetScheduledToastNotifications().FirstOrDefault();
        }

        private void ClearToast()
        {
            foreach (ScheduledToastNotification toast in _notifier.GetScheduledToastNotifications())
            {
                _notifier.RemoveFromSchedule(toast);
            }
        }

        private void AddToast(string id, string name, string glyph, DateTime start, DateTime finish)
        {
            XmlDocument xml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);
            xml.GetElementsByTagName("text")[0].InnerText = $"{glyph} {name}";
            xml.GetElementsByTagName("text")[1].InnerText = $"Start {start:HH:mm} Finish {finish:HH:mm}";
            ScheduledToastNotification toast = new ScheduledToastNotification(xml, finish) { Id = id };
            _notifier.AddToSchedule(toast);
        }

        private string GetDisplay(TimeSpan timeSpan) => timeSpan.ToString(@"mm\:ss");

        private void Start()
        {
            _started = true;
            if (_timer == null)
            {
                _timer = new DispatcherTimer()
                {
                    Interval = TimeSpan.FromMilliseconds(250)
                };
                _timer.Tick += (object s, object args) =>
                {
                    TimeSpan difference = _start - DateTime.UtcNow;
                    string display = GetDisplay(_current + difference);
                    if (_started && display != GetDisplay(TimeSpan.Zero))
                    {
                        Display = display;
                    }
                    else
                    {
                        _current = TimeSpan.Zero;
                        Display = GetDisplay(_current);
                        _timer.Stop();
                        _started = false;
                    }
                };
            }
            _timer.Start();
        }

        private void Stop()
        {
            if (_timer != null) _timer.Stop();
           _started = false;
            Select(_item);
        }

        public PomodoroItem Item
        {
            get { return _item; }
            set { SetProperty(ref _item, value); }
        }

        public List<PomodoroItem> Items
        {
            get { return _items; }
            set { SetProperty(ref _items, value); }
        }

        public string Display
        {
            get { return _display; }
            set { SetProperty(ref _display, value); }
        }

        public bool Started
        {
            get { return _started; }
            set { SetProperty(ref _started, value); }
        }

        public void Select(PomodoroItem item)
        {
            Item = item;
            _current = Item.TimeSpan;
            Display = GetDisplay(_current);
        }

        public void Init()
        {
            if (Items == null)
            {
                Items = new List<PomodoroItem>
                {
                    new PomodoroItem(PomodoroType.TaskTimer, 
                    Color.FromArgb(255, 240, 58, 23), 
                    Color.FromArgb(255, 239, 105, 80)),
                    new PomodoroItem(PomodoroType.ShortBreak,
                    Color.FromArgb(255, 131, 190, 236), 
                    Color.FromArgb(255, 179, 219, 212)),
                    new PomodoroItem(PomodoroType.LongBreak,
                    Color.FromArgb(255, 186, 216, 10), 
                    Color.FromArgb(255, 228, 245, 119)),
                };
            }
            ScheduledToastNotification toast = GetToast();
            PomodoroItem item = Items[0];
            if (toast != null)
            {
                item = Items.FirstOrDefault(f => f.Id == toast.Id);
                _start = toast.DeliveryTime.UtcDateTime - item.TimeSpan;
                _finish = toast.DeliveryTime.UtcDateTime;
                Start();
            }
            Select(item);
        }

        public void Toggle()
        {
            _start = DateTime.UtcNow;
            _finish = _start.Add(TimeSpan.FromMinutes((double)Item.Type));
            if(_started)
            {
                ClearToast();
                Stop();
            }
            else
            {
                AddToast(_item.Id, _item.Name, _item.Glyph, _start, _finish);
                _current = Item.TimeSpan;
                Start();
            }
        }
    }

    public class Library
    {
        private const string app_title = "Pomodoro App";

        private IAsyncOperation<IUICommand> _dialogCommand;
        private PomodoroSetup _setup = new PomodoroSetup();

        private async Task<bool> ShowDialogAsync(string content, string title = app_title)
        {
            try
            {
                if (_dialogCommand != null)
                {
                    _dialogCommand.Cancel();
                    _dialogCommand = null;
                }
                _dialogCommand = new MessageDialog(content, title).ShowAsync();
                await _dialogCommand;
                return true;
            }
            catch (TaskCanceledException)
            {
                return false;
            }
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            AppBarButton button = (AppBarButton)sender;
            if(_setup.Started)
            {
                await ShowDialogAsync($"You will need to Stop {_setup.Item.Name} Timer to Switch");
            }
            else
            {
                _setup.Select((PomodoroItem)button.Tag);
            }
        }

        public void Init(ref CommandBar command, ref Grid display)
        {
            _setup.Init();
            if (command.PrimaryCommands.Count == 1)
            {
                foreach (PomodoroItem item in _setup.Items)
                {
                    AppBarButton button = new AppBarButton()
                    {
                        Tag = item,
                        Content = item.Name,
                        Icon = new FontIcon()
                        {
                            Glyph = item.Glyph,
                            Margin = new Thickness(0, -5, 0, 0),
                            FontFamily = new FontFamily("Segoe UI Emoji")
                        }
                    };
                    button.Click += Button_Click;
                    command.PrimaryCommands.Add(button);
                }
            }
            display.DataContext = _setup;
        }

        public void Toggle()
        {
            _setup.Toggle();
        }
    }      
}

In the Code File for Library there are using statements to include the necessary functionality. There is an enum for PomodoroApp which has values for use within the Application and also includes a Description Attribute. There is a BindableBase Class which implements INotifyPropertyChanged there are SetProperty and OnPropertyChanged Methods.

The PomodoroItem Class Inherits from BindableBase and has members for PomodoroType and Color. The GetGlyph Method is used to read the DescriptionAttribute value, GetId Method is used to get the Name of the PomodoroType Value the and GetName Method is used to return a friendly Name for the PomodoroType. There are Properties for the PomodoroType, for Color, and some string Properties and for TimeSpan.

The PomodoroSetup Class

also Inherits from BindableBase and has various Members including ones for DateTime, PomodoroItem, DispatcherTimer and ToastNotifier. There is a GetToast Method which will return the first ScheduledToastNotification or default, as null. The ClearToast Method is used to remove all ScheduledToastNotification from the ToastNotificationManager. The AddToast Method is used to create a ScheduledToastNotification with a given ToastTemplateType. The GetDisplay Method uses a Lambda to return a Formatted TimeSpan.

Also in the PomodoroSetup Class there is a Start Method which will create a DispatcherTimer which will be used to Display a Current value which involves Calculating a time difference to use as a Countdown. The Stop Method will Stop the DispatcherTimer and call a Select Method. The Select Method is used to set the PomodoroItem to use and get the Current TimeSpan for this. The Init Method is used to help create the look-and-feel of the Application and will Populate the List of PomodoroItem with the different PomodoroType Values, it will get any existing ScheduledToastNotification to retrieve an already running Timer or call the Select Method. The Toggle Method is used to either Start or Stop a Timer.

The Library Class has Members for IAsyncOperation of IUICommand for use with the ShowDialogAsync Method which will display a MessageDialog and for PomodoroSetup. There is a Button_Click Event Handler which will call the Select Method to pick a Timer or if one is Started it will display a message with ShowDialogAsync. The Init Method is used to create the Layout of the Application and add additional AppBarButton for each PomodoroItem and there is a Toggle Method which will call the Toggle Method in the PomodoroSetup Class.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-pomodoro-app

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

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

<Grid Name="Display" Margin="50" HorizontalAlignment="Center">
	<Grid.RowDefinitions>
		<RowDefinition Height="50*"/>
		<RowDefinition Height="50*"/>
	</Grid.RowDefinitions>
	<Grid Grid.Row="0">
		<Grid.Background>
			<SolidColorBrush Color="{Binding Path=Item.Light, Mode=OneWay}"/>
		</Grid.Background>
		<Viewbox>
			<TextBlock Text="{Binding Path=Item.Glyph, Mode=OneWay}" FontFamily="Segoe UI Emoji"/>
		</Viewbox>
	</Grid>
	<Grid Grid.Row="1">
		<Grid.Background>
			<SolidColorBrush Color="{Binding Path=Item.Dark, Mode=OneWay}"/>
		</Grid.Background>
		<Viewbox>
			<TextBlock Margin="5" Text="{Binding Path=Display, Mode=OneWay}" Foreground="WhiteSmoke"/>
		</Viewbox>
	</Grid>
</Grid>
<CommandBar Name="Command" VerticalAlignment="Bottom">
	<AppBarButton Name="Timer" Icon="Clock" Label="Timer" Click="Timer_Click"/>
</CommandBar>

Within the main Grid Element, the first block of XAML is a Grid Control which has two Rows, in the first Row is a TextBlock which will display the Glyph for a PomodoroItem and is within a Grid which has a Background set to the Light Property and in the second Row is another Grid with another TextBlock set to the Display Property and is within a Grid with its Background set to the Dark Property. The second block of XAML is a CommandBar with AppBarButton for Time which calls Timer_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

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

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(ref Command, ref Display);
}

private void Timer_Click(object sender, RoutedEventArgs e)
{
	library.Toggle();
}

There is an OnNavigatedTo Event Handler which calls the Init Method in the Library Class and Timer_Click which calls the Toggle Method of the Library Class.

Step 13

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 14

Once the Application has started running you can select the Timer Button to Start or Stop the Task Timer and then do some work or select the Short Break or Long Break to rest – at the end of the time a Toast Notification will appear

uwp-ran-pomodoro-app

Step 15

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

vs2017-close

This example shows how to implement the Pomodoro Technique created by Francesco Cirillo which involves deciding on a Task to perform then setting a Timer to 25 Minutes, then once done have a Short Break of 5 Minutes, then every Four Tasks performed then take a Long Break of 15 Minutes and then repeat the process again. This is a useful working technique and also is a great way on how to use ScheduledToastNotification and other features to create an application like this.

Creative Commons License

Universal Windows Platform – ZuneView App

ZuneView App shows how to create a simple application that can display any Microsoft Zune with the Devices and Colours that were available and Save an Image of the selected Device and Colour.

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 the Name as ZuneViewApp and select a Location and then select Ok to create the Project
vs2017-new-project-zuneview-app

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

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

vs2017-project-add-new-item

Step 6

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-zuneview-app

Step 7

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;

namespace ZuneViewApp
{
    public enum ZuneViewType
    {
        Keel, Draco, Scorpius, Pavo
    }

    public class ZuneViewStyle
    {
        public string Name { get; set; }
        public Color Colour { get; set; }
        public ZuneViewType[] Types { get; set; }

        public ZuneViewStyle(string name, Color colour, ZuneViewType[] types)
        {
            Name = name;
            Types = types;
            Colour = colour;
        }
    }

    public class ZuneViewDevice
    {
        public string Name { get; set; }
        public ZuneViewType Type { get; set; }
        public Brush Fill { get; set; } // Change to Style

        public ZuneViewDevice(ZuneViewType type, Brush fill)
        {
            Type = type;
            Fill = fill;
        }

        public ZuneViewDevice(string name, ZuneViewType type, Brush fill)
        {
            Name = name;
            Type = type;
            Fill = fill;
        }
    }

    public class ZuneViewDeviceTemplateSelector : DataTemplateSelector
    {
        public DataTemplate Keel { get; set; }
        public DataTemplate Draco { get; set; }
        public DataTemplate Scorpius { get; set; }
        public DataTemplate Pavo { get; set; }

        protected override DataTemplate SelectTemplateCore(object value, DependencyObject container)
        {
            if (value is ZuneViewDevice item)
            {
                switch (item.Type)
                {
                    case ZuneViewType.Keel:
                        return Keel;
                    case ZuneViewType.Draco:
                        return Draco;
                    case ZuneViewType.Scorpius:
                        return Scorpius;
                    case ZuneViewType.Pavo:
                        return Pavo;
                }
            }
            return null;
        }
    }

    public class Library
    {
        private const int scale_size = 1000;
        private const double image_dpi = 96.0;
        private const string file_extension = ".png";
        private readonly Color brown = Color.FromArgb(255, 68, 48, 22);
        private readonly Color black = Color.FromArgb(255, 28, 28, 28);
        private readonly Color red = Color.FromArgb(255, 132, 20, 44);
        private readonly Color platinum = Color.FromArgb(255, 220, 220, 220);

        private List<ZuneViewStyle> _styles = new List<ZuneViewStyle>();
        private List<ZuneViewDevice> _devices = new List<ZuneViewDevice>();

        private void AddStyle(string name, Color colour, params ZuneViewType[] types)
        {
            _styles.Add(new ZuneViewStyle(name, colour, types));
        }

        private void SetStyles()
        {
            AddStyle("Brown", brown, ZuneViewType.Keel);
            AddStyle("Halo", Color.FromArgb(255, 70, 90, 90), ZuneViewType.Keel);
            AddStyle("Red", Color.FromArgb(255, 234, 48, 104), ZuneViewType.Keel);
            AddStyle("Pink", Color.FromArgb(255, 228, 182, 194), ZuneViewType.Keel);
            AddStyle("Magenta", Color.FromArgb(255, 230, 62, 100), ZuneViewType.Keel);
            AddStyle("Blue", Color.FromArgb(255, 14, 84, 150), ZuneViewType.Keel);
            AddStyle("Orange", Color.FromArgb(255, 246, 116, 0), ZuneViewType.Keel);
            AddStyle("Black", Color.FromArgb(255, 16, 24, 22), ZuneViewType.Keel);
            AddStyle("White", Color.FromArgb(255, 235, 235, 235),
            ZuneViewType.Keel, ZuneViewType.Draco, ZuneViewType.Scorpius);
            AddStyle("Black", black, ZuneViewType.Draco, ZuneViewType.Scorpius);
            AddStyle("Red", red, ZuneViewType.Draco, ZuneViewType.Scorpius);
            AddStyle("Blue", Color.FromArgb(255, 2, 70, 130),
            ZuneViewType.Draco, ZuneViewType.Scorpius);
            AddStyle("Green", Color.FromArgb(255, 140, 132, 66), ZuneViewType.Scorpius);
            AddStyle("Pink", Color.FromArgb(255, 246, 164, 196), ZuneViewType.Scorpius);
            AddStyle("Citron", Color.FromArgb(255, 210, 198, 6), ZuneViewType.Scorpius);
            AddStyle("Platinum", platinum, ZuneViewType.Pavo);
            AddStyle("Onyx", Color.FromArgb(255, 24, 42, 44), ZuneViewType.Pavo);
            AddStyle("Red", Color.FromArgb(255, 124, 30, 54), ZuneViewType.Pavo);
            AddStyle("Blue", Color.FromArgb(255, 30, 104, 146), ZuneViewType.Pavo);
            AddStyle("Green", Color.FromArgb(255, 166, 172, 92), ZuneViewType.Pavo);
            AddStyle("Pink", Color.FromArgb(255, 192, 162, 182), ZuneViewType.Pavo);
            AddStyle("Magenta", Color.FromArgb(255, 140, 88, 140), ZuneViewType.Pavo);
            AddStyle("Purple", Color.FromArgb(255, 94, 90, 120), ZuneViewType.Pavo);
            AddStyle("Atomic", Color.FromArgb(255, 186, 24, 0), ZuneViewType.Pavo);
        }

        private void AddDevice(string name, ZuneViewType type, Color colour)
        {
            _devices.Add(new ZuneViewDevice(name, type, new SolidColorBrush(colour)));
        }

        private void SetDevices()
        {
            AddDevice("Zune 30", ZuneViewType.Keel, brown);
            AddDevice("Zune 80 120", ZuneViewType.Draco, black);
            AddDevice("Zune 4 8 16", ZuneViewType.Scorpius, red);
            AddDevice("Zune HD", ZuneViewType.Pavo, platinum);
        }

        private void SetItem(ref ItemsControl display, ref ComboBox devices, ref ComboBox styles)
        {
            ZuneViewDevice device = (ZuneViewDevice)devices.SelectedItem ?? _devices.First();
            ZuneViewStyle style = (ZuneViewStyle)styles.SelectedItem ?? _styles.First();
            display.ItemsSource = new List<ZuneViewDevice>()
            {
                new ZuneViewDevice(device.Type, new SolidColorBrush(style.Colour))
            };
        }

        private async void Render(UIElement 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, scale_size, 0);
                IBuffer buffer = await target.GetPixelsAsync();
                encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied,
                (uint)target.PixelWidth, (uint)target.PixelHeight, image_dpi, image_dpi, buffer.ToArray());
                await encoder.FlushAsync();
                target = null;
                buffer = null;
                encoder = null;
            }
        }

        public Library()
        {
            SetDevices();
            SetStyles();
        }

        public void Init(ref ItemsControl display, ref ComboBox devices, ref ComboBox styles)
        {
            devices.ItemsSource = _devices;
            styles.ItemsSource = _styles;
            devices.SelectedIndex = 0;
            styles.SelectedIndex = 0;
            SetItem(ref display, ref devices, ref styles);
        }

        public void Style(ref ItemsControl display, ref ComboBox devices, ref ComboBox styles)
        {
            SetItem(ref display, ref devices, ref styles);
        }

        public void Device(ref ItemsControl display, ref ComboBox devices, ref ComboBox styles)
        {
            ZuneViewDevice selected = (ZuneViewDevice)devices.SelectedItem;
            IEnumerable<ZuneViewStyle> list = _styles.Where(w => w.Types.Contains(selected.Type));
            styles.ItemsSource = list;
            styles.SelectedIndex = 0;
            SetItem(ref display, ref devices, ref styles);
        }

        public async void Save(UIElement element, ComboBox devices)
        {
            try
            {
                ZuneViewDevice selected = (ZuneViewDevice)devices.SelectedItem;
                FileSavePicker picker = new FileSavePicker
                {
                    DefaultFileExtension = file_extension,
                    SuggestedFileName = selected.Name,
                    SuggestedStartLocation = PickerLocationId.PicturesLibrary
                };
                picker.FileTypeChoices.Add("Image", new List<string>() { file_extension });
                StorageFile file = await picker.PickSaveFileAsync();
                if (file != null)
                {
                    Render(element, file);
                }
            }
            finally
            {
                // Ignore Exceptions
            }
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There is an enum for ZuneViewType for the different types of Zune Device.

The ZuneViewStyle Class has Properties for the Name of the Style, Color and an Array of ZuneViewType. Thee ZuneViewDevice Class has Properties for the Name of the Device, the ZuneViewType and Brush. The ZuneViewDeviceTemplateSelector is used to return the appropriate DataTemplate based on ZuneViewDevice.

The Library Class has various const and readonly Values including Color for examples for each Type of Device. There are List of ZuneViewStyle and List if ZuneViewDevice Members. There is a SetStyles Method which uses the AddStyle Method to add the Names, Colours and Devices for each Style and there is a SetDevices Method which uses the AddDevice Method to add the Names, Types and Example Colours of each Device. There is a Render Method which takes a UIElement and StorageFile – with an IRandomAccessStream from this it will use a BitmapEncoder with RenderTargetBitmap to create the Image to be Rendered.

Also in the Library Class there is a Constructor which calls the SetDevices and SetStyles Methods. The Init Method helps set up the look-and-feel of the Application, Style and Device will help set up the required items for the given Device that has been selected. The Save Method is used with a FileSavePicker to then call the Render Method with the selected file from PickSaveFileAsync.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-zuneview-app

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in this above the Grid element, enter the following XAML:

<Page.Resources>
	<LinearGradientBrush x:Key="ZuneKeel">
		<GradientStop Offset="0" Color="#020202"/>
		<GradientStop Offset="1" Color="#424242"/>
	</LinearGradientBrush>
	<LinearGradientBrush x:Key="ZunePad">
		<GradientStop Offset="0" Color="#66000000"/>
		<GradientStop Offset="1" Color="#22000000"/>
	</LinearGradientBrush>
	<LinearGradientBrush x:Key="ZunePadOuter">
		<GradientStop Offset="0" Color="#66FFFFFF"/>
		<GradientStop Offset="1" Color="#22FFFFFF"/>
	</LinearGradientBrush>
	<LinearGradientBrush x:Key="ZuneScreen">
		<GradientStop Offset="1" Color="#231F20"/>
		<GradientStop Offset="0" Color="#524F4F"/>
		<LinearGradientBrush.Transform>
			<RotateTransform Angle="68" CenterX="0.5" CenterY="0.5"/>
		</LinearGradientBrush.Transform>
	</LinearGradientBrush>
	<DataTemplate x:Key="KeelTemplate" x:DataType="local:ZuneViewDevice">
		<Canvas Width="21" Height="36">
			<Rectangle Height="36" Width="21" Fill="{x:Bind Path=Fill, Mode=OneWay}" 
			Canvas.Left="1" Canvas.Top="0" RadiusX="1" RadiusY="1">
			</Rectangle>
			<Rectangle Canvas.Left="2" Canvas.Top="1" Fill="{StaticResource ZuneScreen}" 
			Height="24" Stroke="#5D5D5D" Width="19" />
			<Ellipse Canvas.Left="6" Canvas.Top="24" Fill="{StaticResource ZuneKeel}" 
			Stroke="#5D5D5D" StrokeThickness="1.5" Height="11" Width="11"/>
		</Canvas>
	</DataTemplate>
	<DataTemplate x:Key="DracoTemplate" x:DataType="local:ZuneViewDevice">
		<Canvas Width="21" Height="36">
			<Rectangle Height="36" Width="20" Fill="{x:Bind Path=Fill, Mode=OneWay}" 
			Canvas.Left="1" Canvas.Top="0" RadiusX="1" RadiusY="1">
			</Rectangle>
			<Rectangle Fill="{StaticResource ZuneScreen}" Canvas.Left="2" Canvas.Top="1" 
			Height="24" Stroke="#191616" Width="18"/>
			<Rectangle Canvas.Left="6.5" Canvas.Top="25" Height="9" Width="9" RadiusX="3" RadiusY="3" 
			Fill="{StaticResource ZunePad}" Stroke="{StaticResource ZunePadOuter}"/>
		</Canvas>
	</DataTemplate>
	<DataTemplate x:Key="ScorpiusTemplate" x:DataType="local:ZuneViewDevice">
		<Canvas Width="21" Height="36">
			<Rectangle Height="35" Width="16" Fill="{x:Bind Path=Fill, Mode=OneWay}" 
			Canvas.Left="4" Canvas.Top="0"/>
			<Rectangle Canvas.Left="5" Canvas.Top="1" Fill="{StaticResource ZuneScreen}" 
			Height="18" Stroke="#191616" Width="14" />
			<Rectangle Canvas.Left="7" Canvas.Top="22" Fill="{StaticResource ZunePad}" 
			Stroke="{StaticResource ZunePadOuter}" Height="10" Width="10" RadiusX="3" RadiusY="3"/>
		</Canvas>
	</DataTemplate>
	<DataTemplate x:Key="PavoTemplate" x:DataType="local:ZuneViewDevice">
		<Canvas Width="21" Height="36">
			<Rectangle Height="32" Width="16" Fill="{x:Bind Path=Fill, Mode=OneWay}" Canvas.Left="4" Canvas.Top="2" 
			RadiusX="1.5" RadiusY="1.5">
			</Rectangle>
			<Rectangle Fill="{StaticResource ZuneScreen}" Canvas.Left="5" Canvas.Top="3.25" Height="24.5" 
			Width="14" Stroke="Black" StrokeThickness="1" StrokeMiterLimit="1"/>
			<Rectangle Fill="Black" Canvas.Left="5" Canvas.Top="27" Height="2" Width="14"/>
			<Polygon Points="0,0 8,0 6,1.5 2,1.5 0,0" Canvas.Left="8" Canvas.Top="29" 
			Height="1.5" Width="8">
				<Polygon.Fill>
					<LinearGradientBrush>
						<GradientStop Offset="0" Color="#FF162025"/>
						<GradientStop Offset="1" Color="#FF0C1417"/>
					</LinearGradientBrush>
				</Polygon.Fill>
			</Polygon>
		</Canvas>
	</DataTemplate>
	<DataTemplate x:Key="DevicesTemplate" x:DataType="local:ZuneViewDevice">
		<StackPanel Orientation="Horizontal">
			<Viewbox Margin="2" Width="{StaticResource ControlContentThemeFontSize}"
			Height="{StaticResource ControlContentThemeFontSize}">
				<ContentControl Content="{Binding}"
				ContentTemplateSelector="{StaticResource ZuneViewDeviceTemplateSelector}"/>
			</Viewbox>
			<TextBlock Text="{x:Bind Path=Name, Mode=OneWay}"/>
		</StackPanel>
	</DataTemplate>
	<DataTemplate x:Key="StylesTemplate" x:DataType="local:ZuneViewStyle">
		<StackPanel Orientation="Horizontal">
			<Grid Margin="2" Width="{StaticResource ControlContentThemeFontSize}" 
			Height="{StaticResource ControlContentThemeFontSize}">
				<Grid.Background>
					<SolidColorBrush Color="{x:Bind Path=Colour, Mode=OneWay}"/>
				</Grid.Background>
			</Grid>
			<TextBlock Text="{x:Bind Path=Name, Mode=OneWay}"/>
		</StackPanel>
	</DataTemplate>
	<DataTemplate x:Key="ContentTemplate">
		<ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource ZuneViewDeviceTemplateSelector}"/>
	</DataTemplate>
	<local:ZuneViewDeviceTemplateSelector x:Key="ZuneViewDeviceTemplateSelector" 
	Keel="{StaticResource KeelTemplate}" Draco="{StaticResource DracoTemplate}" 
	Scorpius="{StaticResource ScorpiusTemplate}" Pavo="{StaticResource PavoTemplate}"/>
</Page.Resources>

Then while still in the XAML View 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>
	<ComboBox Name="Devices" Grid.Row="0" Grid.Column="0" Margin="5"
	HorizontalAlignment="Stretch" SelectionChanged="Devices_SelectionChanged" 
	ItemTemplate="{StaticResource DevicesTemplate}"/>
	<ComboBox Name="Styles" Grid.Row="0" Grid.Column="1" Margin="5"
	HorizontalAlignment="Stretch" SelectionChanged="Styles_SelectionChanged"
	ItemTemplate="{StaticResource StylesTemplate}"/>
	<Grid Margin="50" Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Center">
		<Viewbox>
			<ItemsControl Name="Display" ItemTemplate="{StaticResource ContentTemplate}"/>
		</Viewbox>
	</Grid>
</Grid>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Save" Label="Save" Click="Save_Click"/>
</CommandBar>

Within the Page.Resources block of XAML above the Grid Element contains various Resources including LinearGradientBrush to help create the look-and-feel of the Devices and a DataTemplate for each ZuneViewDevice which is made up of Canvas, Rectangle and Ellipse Elements. There is also the ZuneViewDeviceTemplateSelector to help display the correct ZuneViewDevice Template.

Within the main Grid Element, the first block of XAML is a Grid Control which has two Rows and Columns, in the first Row are two ComboBox Controls in a Column each for Devices and Styles and in the second Row is a ItemsControl within a Viewbox which will be used to show the selected Device. The second block of XAML is a CommandBar with AppBarButton for Save which calls Save_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

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

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(ref Display, ref Devices, ref Styles);
}

private void Devices_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
	library.Device(ref Display, ref Devices, ref Styles);
}

private void Styles_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
	library.Style(ref Display, ref Devices, ref Styles);
}

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

There is an OnNavigatedTo Event Handler which calls the Init Method in the Library Class, there is a Devices_SelectionChanged and Styles_SelectionChanged Event Handlers to call the Device and Style Methods in the Library Class respectively and Save_Click which calls the Save Method of the Library Class.

Step 13

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 14

Once the Application has started running you can tap on the first ComboBox to select a Microsoft Zune and the second ComboBox to select a Colour for it and then can Save an Image of the selected Device in the selected Style

uwp-ran-zuneview-app

Step 15

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

vs2017-close

This example allows you to view Microsoft Zune of the Devices and Colours that were available to show how to use RenderTargetBitmap to Save an Image of a UIElement. The Assets used were originally created for ZuneCardr which was a Zune Social application that could read and export the Zune Card with other images and originally also allowed a Zune to be displayed also in an older version and make a great, nostalgic and hopefully fun way of learning how to create Images from XAML.

Keel was the code name of the original Zune, Draco was the code name of the follow-up the Zune 80 which was also later available in a 120GB Version, Scorpius was the code name of the smaller Zune and Pavo the code name for the Zune HD. Zune and all associated image designs and names are the trademarks and/or property of Microsoft and are shared here for educational purposes only.

Creative Commons License

Universal Windows Platform – FileView App

FileView App shows how to create a simple application that uses the FolderPicker and TreeView Control

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 the Name as FileViewApp and select a Location and then select Ok to create the Project
vs2017-new-project-fileview-app

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

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

vs2017-project-add-new-item

Step 6

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-fileview-app

Step 7

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

using System;
using System.Collections.Generic;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Xaml.Controls;

namespace FileViewApp
{
    public class FileViewItem
    {
        public string Name { get; set; }
        public string Path { get; set; }
    }

    public class Library
    {
        private async void FillNode(TreeViewNode node)
        {
            StorageFolder folder = null;
            if (node.Content is StorageFolder && node.HasUnrealizedChildren)
            {
                folder = (StorageFolder)node.Content;
            }
            else
            {
                return;
            }
            IReadOnlyList<IStorageItem> list = await folder.GetItemsAsync();
            if (list.Count == 0) return;
            foreach (IStorageItem item in list)
            {
                TreeViewNode itemNode = new TreeViewNode
                {
                    Content = item
                };
                if (item is StorageFolder)
                {
                    itemNode.HasUnrealizedChildren = true;
                }
                node.Children.Add(itemNode);
            }
            node.HasUnrealizedChildren = false;
        }

        public void Expanding(TreeViewNode node)
        {
            if (node.HasChildren) FillNode(node);
        }

        public void Collapsed(TreeViewNode node)
        {
            node.Children.Clear();
            node.HasUnrealizedChildren = true;
        }

        public FileViewItem Invoked(TreeViewNode node)
        {
            FileViewItem item = null;
            if (node.Content is IStorageItem storageItem)
            {
                item = new FileViewItem()
                {
                    Name = storageItem.Name,
                    Path = storageItem.Path
                };
                if (node.Content is StorageFolder)
                {
                    node.IsExpanded = !node.IsExpanded;
                }
            }
            return item;
        }

        public async void Folder(TreeView tree)
        {
            try
            {
                FolderPicker picker = new FolderPicker()
                {
                    SuggestedStartLocation = PickerLocationId.PicturesLibrary
                };
                picker.FileTypeFilter.Add("*");
                IStorageFolder folder = await picker.PickSingleFolderAsync();
                if (folder != null)
                {
                    tree.RootNodes.Clear();
                    TreeViewNode node = new TreeViewNode
                    {
                        Content = folder,
                        IsExpanded = true,
                        HasUnrealizedChildren = true
                    };
                    tree.RootNodes.Add(node);
                    FillNode(node);
                }
            }
            finally
            {
                // Ignore Exceptions
            }
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There is a FileViewItem with Properties for Name and Path

The Library Class has a FillNode Method which will help populate the TreeView Control by going through any Folder Childen as StorageFolder to find more of them until all the IStorageItem for all StorageFolder have been found. The Expanding Method is used to call the FillNode Method and Collapsed will be used to remove Children from a TreeViewNode. The Invoked Method is used to set a FileViewItem based on a TreeViewNode and the Folder Method is used with a FolderPicker to select an IStorageFolder with PickSingleFolderAsync and then it will set a Root TreeViewNode as the starting point for the TreeView and then call the FillNode Method.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-fileview-app

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in this above the Grid element, enter the following XAML:

<Page.Resources>
	<DataTemplate x:Key="TreeViewItemTemplate">
		<TextBlock Text="{Binding Content.DisplayName}"
		HorizontalAlignment="Left" VerticalAlignment="Center"
		Style="{ThemeResource BodyTextBlockStyle}"/>
	</DataTemplate>
	<Style TargetType="TreeView">
		<Setter Property="Template">
			<Setter.Value>
				<ControlTemplate TargetType="TreeView">
					<TreeViewList x:Name="ListControl"
					ItemTemplate="{StaticResource TreeViewItemTemplate}"
					ItemContainerStyle="{StaticResource TreeViewItemStyle}"
					CanDragItems="True" AllowDrop="True" CanReorderItems="False">
					</TreeViewList>
				</ControlTemplate>
			</Setter.Value>
		</Setter>
	</Style>
</Page.Resources>

Then while still in the XAML View between the Grid and /Grid elements, enter the following XAML:

<SplitView IsPaneOpen="True" DisplayMode="Inline">
	<SplitView.Pane>
		<TreeView x:Name="Display" SelectionMode="Single"
		Expanding="TreeView_Expanding" Collapsed="TreeView_Collapsed"
		ItemInvoked="TreeView_ItemInvoked"/>
	</SplitView.Pane>
	<StackPanel Margin="10">
		<TextBlock Text="Name:"/>
		<TextBlock Name="FileName" FontWeight="SemiBold"/>
		<TextBlock Text="Path:"/>
		<TextBlock Name="FilePath" FontWeight="SemiBold"/>
	</StackPanel>
</SplitView>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="OpenLocal" Label="Folder" Click="Folder_Click"/>
</CommandBar>

Within the Page.Resources block of XAML above the Grid Element contains DataTemplate for the TreeView which contains a TextBlock and a Style for the TreeView which contains a ControlTemplate to set the look-and-feel of the TreeView accordingly.

Within the main Grid Element, the first block of XAML is a SplitView Control which contains an TreeView within the Pane annd a StackPanel in the main content which contains TextBlock Controls to display information about a selected File or Folder. The second block of XAML is a CommandBar with AppBarButton for Folder which calls Folder_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

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

Library library = new Library();

private void TreeView_Expanding(TreeView sender, TreeViewExpandingEventArgs args)
{
	library.Expanding(args.Node);
}

private void TreeView_Collapsed(TreeView sender, TreeViewCollapsedEventArgs args)
{
	library.Collapsed(args.Node);
}

private void TreeView_ItemInvoked(TreeView sender, TreeViewItemInvokedEventArgs args)
{
	FileViewItem item = library.Invoked((TreeViewNode)args.InvokedItem);
	FileName.Text = item.Name;
	FilePath.Text = item.Path;
}

private void Folder_Click(object sender, RoutedEventArgs e)
{
	library.Folder(Display);
}

There is an TreeView_Expanding, TreeView_Collapsed and TreeView_ItemInvoked Event Handler which will handle different Events from the TreeView Control along with TreeView_ItemInvoked which will get the FileViewItem to be displayed. There is a Folder_Click Event Handler which calls the Folder Method in the Library Class.

Step 13

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 14

Once the Application has started running you can tap on the Open Button to to select any Folder which itself and any Child Folders will be displayed in the TreeView along with some basic information about any selected Files or Folders of the TreeView will be displayed also

uwp-ran-fileview-app

Step 15

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

vs2017-close

This example was inspired from the TreeView example from docs.microsoft.com which has been expanded to allow the selection of any Folder using a FolderPicker

Creative Commons License

Universal Windows Platform – 360View App

360View App shows to create an application which will allow you to Open and Play 360 Video also known as Spherical Video Files

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

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

vs2017-project-add-new-item

Step 6

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 7

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

using System;
using System.Numerics;
using Windows.Media.Core;
using Windows.Media.MediaProperties;
using Windows.Media.Playback;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;

public class Library
{
    private const int scroll_step = 2;
    private const int mouse_wheel = 120;
    private const double change_increment = 0.5;

    private MediaPlaybackSphericalVideoProjection _projection;
    private MediaPlayerElement _element = null;
    private MediaPlayer _player;
    private Grid _grid = null;
    private bool _press = false;
    private double _delta = 1.8f;
    private double _centerX = 0;
    private double _centerY = 0;

    private Quaternion GetPitchRoll(double heading, double pitch, double roll)
    {
        double ToRadians(double degree)
        {
            return degree * Math.PI / 180.0;
        }
        Quaternion result;
        double headingPart = ToRadians(heading) * change_increment;
        double sin1 = Math.Sin(headingPart);
        double cos1 = Math.Cos(headingPart);
        double pitchPart = ToRadians(-pitch) * change_increment;
        double sin2 = Math.Sin(pitchPart);
        double cos2 = Math.Cos(pitchPart);
        double rollPart = ToRadians(roll) * change_increment;
        double sin3 = Math.Sin(rollPart);
        double cos3 = Math.Cos(rollPart);
        result.W = (float)(cos1 * cos2 * cos3 - sin1 * sin2 * sin3);
        result.X = (float)(cos1 * cos2 * sin3 + sin1 * sin2 * cos3);
        result.Y = (float)(sin1 * cos2 * cos3 + cos1 * sin2 * sin3);
        result.Z = (float)(cos1 * sin2 * cos3 - sin1 * cos2 * sin3);
        return result;
    }

    private bool IsOpened()
    {
        if (_player != null && _projection != null &&
        _player.PlaybackSession.PlaybackState != MediaPlaybackState.Opening &&
        _player.PlaybackSession.PlaybackState != MediaPlaybackState.None) return true;
        return false;
    }

    private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
    {
        if (e.OriginalSource != _grid && _press)
        {
            double changeX = e.GetCurrentPoint(_element).Position.X - _centerX;
            double changeY = _centerY - e.GetCurrentPoint(_element).Position.Y;
            _projection.ViewOrientation = GetPitchRoll(changeX, changeY, 0);
        }
        e.Handled = true;
    }

    private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
    {
        _press = false;
        e.Handled = true;
    }

    private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
    {
        if (e.OriginalSource != _grid && IsOpened()) _press = true;
        e.Handled = true;
    }

    private void OnPointerWheelChanged(object sender, PointerRoutedEventArgs e)
    {
        if (e.OriginalSource != _grid)
        {
            double value = _projection.HorizontalFieldOfViewInDegrees +
                (scroll_step * e.GetCurrentPoint(_element).Properties.MouseWheelDelta / mouse_wheel);
            if (value > 0 && value <= 180)
            {
                _projection.HorizontalFieldOfViewInDegrees = value;
            }
        }
        e.Handled = true;
    }

    private void PlaybackSession_PlaybackStateChanged(MediaPlaybackSession sender, object args)
    {
        _delta = (_player.PlaybackSession.PlaybackState == 
        MediaPlaybackState.Playing) ? 1.8f : 0.25f;
    }

    private void Player_MediaOpened(MediaPlayer sender, object args)
    {
        _projection = _player.PlaybackSession.SphericalVideoProjection;
        SphericalVideoFrameFormat videoFormat = _projection.FrameFormat;
        if (videoFormat != SphericalVideoFrameFormat.Equirectangular)
        {
            _projection.FrameFormat = SphericalVideoFrameFormat.Equirectangular;
        }
        _projection.IsEnabled = true;
        _projection.HorizontalFieldOfViewInDegrees = 120;
        _player.PlaybackSession.PlaybackStateChanged += PlaybackSession_PlaybackStateChanged;
    }

    public void Layout(ref MediaPlayerElement display)
    {
        if(_element == null) _element = display;
        _centerX = _element.ActualWidth / 2;
        _centerY = _element.ActualHeight / 2;
        if (_grid == null)
        {
            FrameworkElement _root = (FrameworkElement)VisualTreeHelper.GetChild(_element.TransportControls, 0);
            if (_root != null)
            {
                _grid = (Grid)_root.FindName("ControlPanelGrid");
                _grid.PointerPressed += OnPointerPressed;
                _grid.PointerReleased += OnPointerReleased;
                _grid.PointerWheelChanged += OnPointerWheelChanged;
                _element.PointerPressed += OnPointerPressed;
                _element.PointerReleased += OnPointerReleased;
                _element.PointerMoved += OnPointerMoved;
                _element.PointerWheelChanged += OnPointerWheelChanged;
            }
        }
    }

    public async void Open(MediaPlayerElement display)
    {
        try
        {
            FileOpenPicker picker = new FileOpenPicker()
            {
                SuggestedStartLocation = PickerLocationId.PicturesLibrary
            };
            picker.FileTypeFilter.Add(".mp4");
            StorageFile open = await picker.PickSingleFileAsync();
            if (open != null)
            {
                if (_element == null) _element = display;
                _player = new MediaPlayer
                {
                    Source = MediaSource.CreateFromStorageFile(open)
                };
                _player.MediaOpened += Player_MediaOpened;
                _element.SetMediaPlayer(_player);
            }
        }
        finally
        {
            // Ignore Exceptions
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality and in the Library Class has some const values which will help with displaying the Spherical Video and private Members for MediaPlaybackSphericalVideoProjection, MediaPlayerElement and MediaPlayer along with other values which are used as part of navigating the Spherical Video during playback.

In the Library Class there is a GetPitchRoll Method this will be used to calculate the Quaternion Vector based on a passed in heading, pitch and roll. The IsOpened Method is used to determine the MediaPlaybackState of the MediaPlayer as being Opened or Not. The OnPointerMoved Event Handler will be used to set the ViewOrientation of the MediaPlaybackSphericalVideoProjection using the GetPitchRoll Method.

Also in the Library Class there are Event Handlers for OnPointerMoved – which reacts to Mouse Movements or Touch Movements on the Grid, OnPointerReleased which will be when nothing is being Pressed and OnPointerReleased which is when an input is being Pressed and sets the _press Value accordingly. The PointerWheelChanged Event Handler reacts to Mouse Scroll Wheel Events to set the HorizontalFieldOfViewInDegrees of the MediaPlaybackSphericalVideoProjection, the PlaybackSession_PlaybackStateChanged Event Handler will set the relevant _delta Value and Player_MediaOpened will bv triggered when the MediaPlayer Video has been Opened, this will configure the look-and-feel of the Playback Session.

Still in the Library Class there is a Layout Method which is used to get the Grid from the MediaPlayerElement to allow interactions on top of it and will set all the Event Handlers to this and there is an Open Method which has a MediaPlayerElement Parameter passed in, within a try and catch Block to help handle any Exception there is a FileOpenPicker which is used to select a mp4 File it the sets the Source of the MediaPlayer using CreateFromStorageFile and sets up the MediaOpened Event Handler and the calls the SetMediaPlayer Method on the MediaPlayerElement of the MediaPlayer.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-library

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

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

<MediaPlayerElement Name="Display" AreTransportControlsEnabled="True" 
AutoPlay="True" LayoutUpdated="Display_LayoutUpdated"/>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="OpenFile" Label="Open" Click="Open_Click"/>
</CommandBar>

Within the main Grid Element, the first block of XAML is an MediaPlayerElement Control. The second block of XAML is a CommandBar with AppBarButton for Open which calls Open_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

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

Library library = new Library();

private void Display_LayoutUpdated(object sender, object e)
{
	library.Layout(ref Display);
}

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

There is a Display_LayoutUpdated Event Handler which will be triggered when the Layout of the Window is Updated and an Open_Click Event Handler which will will call the Open Method of the Library Class.

Step 13

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 14

Once the Application has started running you can tap on the Open Button then you need to select an mp4 Video File that is a Spherical Video Format which will then be displayed within the MediaPlayerElement Control and can be looked-around in with Mouse, Pen or Touch input

uwp-ran-360view-app

You can use the following sample Spherical Video File

Download

Step 15

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

vs2017-close

This example was inspired from the Microsoft Universal Windows Platform (UWP) app samples on GitHub and in particular the 360 Video Playback Example and the goal of this was to create a simpler implementation of the example available there. The example displayed is by Goutham Gandhi Nadendla from a set examples on GitHub

Creative Commons License

Universal Windows Platform – SvgView App

SvgView App shows how to create an application which will allow you to Open and Display Svg Files

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

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

vs2017-project-add-new-item

Step 6

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 7

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

using System;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;

public class Library
{
    public async void Open(Image display)
    {
        try
        {
            FileOpenPicker picker = new FileOpenPicker()
            {
                SuggestedStartLocation = PickerLocationId.PicturesLibrary
            };
            picker.FileTypeFilter.Add(".svg");
            StorageFile open = await picker.PickSingleFileAsync();
            if (open != null)
            {
                SvgImageSource source = new SvgImageSource()
                {
                    RasterizePixelHeight = display.ActualHeight,
                    RasterizePixelWidth = display.ActualWidth
                };
                if(await source.SetSourceAsync(await open.OpenReadAsync())
                    == SvgImageSourceLoadStatus.Success)
                {              
                    display.Source = source;
                }               
            }
        }
        finally
        {
            // Ignore Exceptions
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality and in the Library Class has an Open Method which has an Image Parameter passed in, within a try and catch Block to help handle any Exception there is a FileOpenPicker which is used to select a png File it then uses SvgImageSource and SetSourceAsync to a file selected with the FileOpenPicker with OpenReadAsync and sets the Source of the passed in Image Control, the RasterizePixelHeight and RasterizePixelWidth Methods are used to set the size of the resulting Image.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-library

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

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

<Image Margin="50" Name="Display" Width="400" Height="400" 
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="OpenFile" Label="Open" Click="Open_Click"/>
</CommandBar>

Within the main Grid Element, the first block of XAML is an Image Control. The second block of XAML is a CommandBar with AppBarButton for Open which calls Open_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

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

Library library = new Library();

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

There is an Open_Click Event Handler which will will call the Open Method of the Library Class.

Step 13

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 14

Once the Application has started running you can tap on the Open Button then you need to select a svg file which will then be displayed within the Image Control

uwp-ran-svgview-app

You can use the following sample Scalable Vector Graphics File

Download

Step 15

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

vs2017-close

SvgView App is a simple application that shows how easy it is to display a svg Image in your application and scale the Image appropriately.

Creative Commons License

Universal Windows Platform – Yatzy Game

Yatzy Game demonstrates how to create a Dice Game based on Yacht or Yahtzee

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 the Name as YatzyGame and select a Location and then select Ok to create the Project
vs2017-new-project-yatzy-game

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

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

vs2017-project-add-new-item

Step 6

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-yatzy-game

Step 7

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

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;

namespace YatzyGame
{
    public enum YatzyScoreType
    {
        AcesScore, TwosScore, ThreesScore, FoursScore, FivesScore, SixesScore,
        UpperTotalScore, UpperTotalBonusScore, ThreeOfAKindScore, FourOfAKindScore,
        FullHouseScore, SmallStraightScore, LargeStraightScore, YahtzeeScore,
        ChanceScore, YahtzeeBonusScore, LowerTotalScore, TotalScore
    }

    public class CommandHandler : ICommand
    {
        public event EventHandler CanExecuteChanged = null;
        private readonly Action<object> _action;

        public CommandHandler(Action<object> action) { _action = action; }

        public bool CanExecute(object parameter) { return true; }

        public void Execute(object parameter) { _action(parameter); }
    }

    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected bool SetProperty<T>(ref T field, T value,
        [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        public void OnPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

    public class YatzyDice : BindableBase
    {
        private ICommand _command;
        private Random _random;
        private int _index;
        private int _value;
        private bool _hold;

        public ICommand Command
        {
            get { return _command; }
            set { SetProperty(ref _command, value); }
        }

        public int Index
        {
            get { return _index; }
            set { SetProperty(ref _index, value); }
        }

        public int Value
        {
            get { return _value; }
            set { SetProperty(ref _value, value); }
        }

        public bool Hold
        {
            get { return _hold; }
            set { SetProperty(ref _hold, value); }
        }

        public YatzyDice(Random random)
        {
            _random = random;
        }

        public void Roll()
        {
            if (!Hold) Value = _random.Next(1, 7);
        }
    }

    public class YatzyCalculate : BindableBase
    {
        private const int diceTotal = 5;

        private int _total;
        private int _upperTotal;
        private int _lowerTotal;
        private bool _upperBonus;

        public void ResetScores()
        {
            _total = 0;
            _upperTotal = 0;
            _lowerTotal = 0;
            _upperBonus = false;
        }

        public void UpdateTotals(int score, bool upperScore)
        {
            if (upperScore)
            {
                _upperTotal += score;
                if (_upperTotal >= 63) _upperBonus = true;
            }
            else
            {
                _lowerTotal += score;
            }
            _total = 0;
            _total += _upperTotal;
            if (_upperBonus == true) _total += 35;
            _total += _lowerTotal;
        }

        public int GetAddUp(ref YatzyDice[] dice, int value)
        {
            int sum = 0;
            for (int i = 0; i < dice.Length; i++)
            {
                if (dice[i].Value == value)
                {
                    sum += value;
                }
            }
            return sum;
        }

        public int GetOfAKind(ref YatzyDice[] dice, int value)
        {
            int sum = 0;
            bool result = false;
            for (int i = 1; i <= 6; i++)
            {
                int count = 0;
                for (int j = 0; j < 5; j++)
                {
                    if (dice[j].Value == i) count++;
                    if (count > value) result = true;
                }
            }
            if (result)
            {
                for (int i = 0; i < dice.Length; i++)
                {
                    sum += dice[i].Value;
                }
            }
            return sum;
        }

        public int GetFullHouse(ref YatzyDice[] dice)
        {
            int sum = 0;
            int[] item = dice.Select(s => s.Value).ToArray();
            Array.Sort(item);
            if ((((item[0] == item[1]) && (item[1] == item[2])) && // Three of a Kind
               (item[3] == item[4]) && // Two of a Kind
               (item[2] != item[3])) ||
               ((item[0] == item[1]) && // Two of a Kind
               ((item[2] == item[3]) && (item[3] == item[4])) && // Three of a Kind
               (item[1] != item[2])))
            {
                sum = 25;
            }
            return sum;
        }

        public int GetSmallStraight(ref YatzyDice[] dice)
        {
            int sort = 0;
            int[] item = dice.Select(s => s.Value).ToArray();
            Array.Sort(item);
            for (int j = 0; j < 4; j++)
            {
                int value = 0;
                if (item[j] == item[j + 1])
                {
                    value = item[j];
                    for (int k = j; k < 4; k++)
                    {
                        item[k] = item[k + 1];
                    }
                    item[4] = value;
                }
            }
            if (((item[0] == 1) && (item[1] == 2) && (item[2] == 3) && (item[3] == 4)) ||
                ((item[0] == 2) && (item[1] == 3) && (item[2] == 4) && (item[3] == 5)) ||
                ((item[0] == 3) && (item[1] == 4) && (item[2] == 5) && (item[3] == 6)) ||
                ((item[1] == 1) && (item[2] == 2) && (item[3] == 3) && (item[4] == 4)) ||
                ((item[1] == 2) && (item[2] == 3) && (item[3] == 4) && (item[4] == 5)) ||
                ((item[1] == 3) && (item[2] == 4) && (item[3] == 5) && (item[4] == 6)))
            {
                sort = 30;
            }
            return sort;
        }

        public int GetLargeStraight(ref YatzyDice[] dice)
        {
            int sum = 0;
            int[] i = dice.Select(s => s.Value).ToArray();
            Array.Sort(i);
            if (((i[0] == 1) && (i[1] == 2) && (i[2] == 3) && (i[3] == 4) && (i[4] == 5)) ||
                ((i[0] == 2) && (i[1] == 3) && (i[2] == 4) && (i[3] == 5) && (i[4] == 6)))
            {
                sum = 40;
            }
            return sum;
        }

        public int GetYahtzee(ref YatzyDice[] dice)
        {
            int sum = 0;
            for (int i = 1; i <= 6; i++)
            {
                int Count = 0;
                for (int j = 0; j < 5; j++)
                {
                    if (dice[j].Value == i) Count++;
                    if (Count > 4) sum = 50;
                }
            }
            return sum;
        }

        public int GetChance(ref YatzyDice[] dice)
        {
            int sum = 0;
            for (int i = 0; i < 5; i++)
            {
                sum += dice[i].Value;
            }
            return sum;
        }

        public int TotalScore
        {
            get { return _total; }
            set { SetProperty(ref _total, value); }
        }

        public int UpperTotalScore
        {
            get { return _upperTotal; }
            set { SetProperty(ref _upperTotal, value); }
        }

        public int LowerTotalScore
        {
            get { return _lowerTotal; }
            set { SetProperty(ref _lowerTotal, value); }
        }

        public bool UpperBonus
        {
            get { return _upperBonus; }
            set { SetProperty(ref _upperBonus, value); }
        }
    }

    public class IntegerToGlyphConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is int face)
            {
                string[] faces = { null, "\u2680", "\u2681", "\u2682", "\u2683", "\u2684", "\u2685" };
                return faces[face];
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

    public class YatzyItem : BindableBase
    {
        private int _score;
        private ICommand _command = null;
        private YatzyScoreType _type;

        public int Score
        {
            get { return _score; }
            set { SetProperty(ref _score, value); }
        }

        public ICommand Command
        {
            get { return _command; }
            set { SetProperty(ref _command, value); }
        }

        public YatzyScoreType Type
        {
            get { return _type; }
            set { SetProperty(ref _type, value); }
        }

        public string Content
        {
            get
            {
                IEnumerable<string> GetContent(YatzyScoreType type)
                {
                    string text = Enum.GetName(typeof(YatzyScoreType), type);
                    Regex regex = new Regex(@"\p{Lu}\p{Ll}*");
                    foreach (Match match in regex.Matches(text))
                    {
                        yield return match.Value;
                    }
                }
                return string.Join(" ", GetContent(_type));
            }
        }
    }

    public class YatzyBoard : BindableBase
    {
        private const int total_dice = 5;
        private const string accept = "Do you wish to Accept?";
        private readonly Random random = new Random((int)DateTime.Now.Ticks);

        private YatzyDice[] _dice = new YatzyDice[total_dice]
        {
            null, null, null, null, null
        };
        private YatzyCalculate _calculate = new YatzyCalculate();
        private List<YatzyItem> _items = new List<YatzyItem>();
        private Func<string, Task<bool>> _confirm = null;
        private int _rollCount = 0;
        private int _scoreCount = 0;

        private YatzyItem GetItemByType(YatzyScoreType type)
        {
            return _items.FirstOrDefault(t => t.Type == type);
        }

        private void SetItemScoreByType(YatzyScoreType type, int score)
        {
            YatzyItem item = GetItemByType(type);
            if (item != null)
            {
                item.Score = score;
            }
        }

        private int GetItemScoreByType(YatzyScoreType type)
        {
            YatzyItem item = GetItemByType(type);
            if (item != null)
            {
                return item.Score;
            }
            return 0;
        }

        private async void AddUpDice(YatzyScoreType type, int value)
        {
            int score = GetItemScoreByType(type);
            if (_rollCount > 0 && score == 0)
            {
                int total = _calculate.GetAddUp(ref _dice, value);
                bool result = await _confirm($"Total is {total}. {accept}");
                if (result)
                {
                    SetItemScoreByType(type, total);
                    _calculate.UpdateTotals(total, true);
                    Reset();
                }
            }
        }

        private async void ValueOfAKind(YatzyScoreType type, int value, string name)
        {
            int score = GetItemScoreByType(type);
            if (_rollCount > 0 && score == 0)
            {
                int total = _calculate.GetOfAKind(ref _dice, value - 1);
                if (total != 0)
                {
                    bool result = await _confirm($"Total is {total}. {accept}");
                    if (result)
                    {
                        SetItemScoreByType(type, total);
                        _calculate.UpdateTotals(total, false);
                        Reset();
                    }
                }
                else
                {
                    bool result = await _confirm($"No {name} of a Kind. {accept}");
                    if (result)
                    {
                        SetItemScoreByType(type, 0);
                        _calculate.UpdateTotals(total, false);
                        Reset();
                    }
                }
            }
        }

        private async void ItemScore(YatzyScoreType type, int value, string name)
        {
            int score = GetItemScoreByType(type);
            if ((_rollCount > 0) && (score == 0))
            {
                int total = 0;
                if (type == YatzyScoreType.FullHouseScore)
                {
                    total = _calculate.GetFullHouse(ref _dice);
                }
                else if(type == YatzyScoreType.SmallStraightScore)
                {
                    total = _calculate.GetSmallStraight(ref _dice);
                }
                else if (type == YatzyScoreType.LargeStraightScore)
                {
                    total = _calculate.GetLargeStraight(ref _dice);
                }
                if (total == value)
                {
                    SetItemScoreByType(type, total);
                    _calculate.UpdateTotals(total, false);
                    Reset();
                }
                else
                {
                    bool result = await _confirm($"No {name}. {accept}");
                    if (result)
                    {
                        SetItemScoreByType(type, 0);
                        _calculate.UpdateTotals(total, false);
                        Reset();
                    }
                }
            }
        }

        private async void Yahtzee()
        {
            int score = GetItemScoreByType(YatzyScoreType.YahtzeeScore);
            if ((_rollCount > 0) && (score == 0))
            {
                int total = _calculate.GetYahtzee(ref _dice);
                if (total == 50)
                {
                    SetItemScoreByType(YatzyScoreType.YahtzeeScore, total);
                    _calculate.UpdateTotals(total, false);
                    Reset();
                }
                else
                {
                    bool result = await _confirm($"No Yahtzee. {accept}");
                    if (result)
                    {
                        SetItemScoreByType(YatzyScoreType.YahtzeeScore, 0);
                        SetItemScoreByType(YatzyScoreType.YahtzeeBonusScore, 0);
                        _scoreCount++;
                        _calculate.UpdateTotals(total, true);
                        Reset();
                    }
                }
            }
        }

        private async void Chance()
        {
            int score = GetItemScoreByType(YatzyScoreType.ChanceScore);
            if ((_rollCount > 0) && (score == 0))
            {
                int total = _calculate.GetChance(ref _dice);
                bool result = await _confirm($"Total is {total}. {accept}");
                if (result)
                {
                    SetItemScoreByType(YatzyScoreType.ChanceScore, total);
                    _calculate.UpdateTotals(total, false);
                    Reset();
                }
            }
        }

        private void YahtzeeBonus()
        {
            int yahtzeeScore = GetItemScoreByType(YatzyScoreType.YahtzeeScore);
            int yahtzeeBonusScore = GetItemScoreByType(YatzyScoreType.YahtzeeBonusScore);
            if ((_rollCount > 0) && (yahtzeeScore == 0) && (yahtzeeBonusScore != 0))
            {
                int total = _calculate.GetYahtzee(ref _dice);
                if (total == 50)
                {
                    SetItemScoreByType(YatzyScoreType.YahtzeeBonusScore, 100);
                    _calculate.UpdateTotals(100, false);
                    Reset();
                }
                else
                {
                    SetItemScoreByType(YatzyScoreType.YahtzeeBonusScore, 0);
                    _calculate.UpdateTotals(0, true);
                    Reset();
                }
            }
        }

        private void Hold(int index)
        {
            if (_rollCount != 0)
            {
                _dice[index].Hold = !_dice[index].Hold;
            }
        }

        public YatzyBoard()
        {
            for (int i = 0; i < total_dice; i++)
            {
                _dice[i] = new YatzyDice(random)
                {
                    Index = i,
                    Command = new CommandHandler((param) => Hold((int)param))
                };
            }
            _items.Clear();
            foreach (YatzyScoreType type in Enum.GetValues(typeof(YatzyScoreType)))
            {
                YatzyItem item = new YatzyItem() { Type = type };
                switch (item.Type)
                {
                    case YatzyScoreType.AcesScore:
                        item.Command = new CommandHandler((p) => AddUpDice(item.Type, 1));
                        break;
                    case YatzyScoreType.TwosScore:
                        item.Command = new CommandHandler((p) => AddUpDice(item.Type, 2));
                        break;
                    case YatzyScoreType.ThreesScore:
                        item.Command = new CommandHandler((p) => AddUpDice(item.Type, 3));
                        break;
                    case YatzyScoreType.FoursScore:
                        item.Command = new CommandHandler((p) => AddUpDice(item.Type, 4));
                        break;
                    case YatzyScoreType.FivesScore:
                        item.Command = new CommandHandler((p) => AddUpDice(item.Type, 5));
                        break;
                    case YatzyScoreType.SixesScore:
                        item.Command = new CommandHandler((p) => AddUpDice(item.Type, 6));
                        break;
                    case YatzyScoreType.ThreeOfAKindScore:
                        item.Command = new CommandHandler((p) => ValueOfAKind(item.Type, 3, "Three"));
                        break;
                    case YatzyScoreType.FourOfAKindScore:
                        item.Command = new CommandHandler((p) => ValueOfAKind(item.Type, 4, "Four"));
                        break;
                    case YatzyScoreType.FullHouseScore:
                        item.Command = new CommandHandler((p) => ItemScore(item.Type, 25, "Full House"));
                        break;
                    case YatzyScoreType.SmallStraightScore:
                        item.Command = new CommandHandler((p) => ItemScore(item.Type, 30, "Small Straight"));
                        break;
                    case YatzyScoreType.LargeStraightScore:
                        item.Command = new CommandHandler((p) => ItemScore(item.Type, 40, "Large Straight"));
                        break;
                    case YatzyScoreType.YahtzeeScore:
                        item.Command = new CommandHandler((p) => Yahtzee());
                        break;
                    case YatzyScoreType.ChanceScore:
                        item.Command = new CommandHandler((p) => Chance());
                        break;
                    case YatzyScoreType.YahtzeeBonusScore:
                        item.Command = new CommandHandler((p) => YahtzeeBonus());
                        break;
                }
                _items.Add(item);
            }
        }

        public YatzyBoard(Func<string, Task<bool>> dialog) : this()
        {
            _confirm = dialog;
        }

        public YatzyDice[] Dice
        {
            get { return _dice; }
            set { SetProperty(ref _dice, value); }
        }

        public int RollCount
        {
            get { return _rollCount; }
            set { SetProperty(ref _rollCount, value); }
        }

        public int ScoreCount
        {
            get { return _scoreCount; }
            set { SetProperty(ref _scoreCount, value); }
        }

        public List<YatzyItem> Items
        {
            get { return _items; }
            set { SetProperty(ref _items, value); }
        }

        public void Clear()
        {
            _calculate.ResetScores();
            _rollCount = 0;
            _scoreCount = 0;
            SetItemScoreByType(YatzyScoreType.UpperTotalScore, _calculate.UpperTotalScore);
            SetItemScoreByType(YatzyScoreType.UpperTotalBonusScore, 0);
            SetItemScoreByType(YatzyScoreType.LowerTotalScore, _calculate.LowerTotalScore);
            SetItemScoreByType(YatzyScoreType.TotalScore, _calculate.TotalScore);
            SetItemScoreByType(YatzyScoreType.AcesScore, 0);
            SetItemScoreByType(YatzyScoreType.TwosScore, 0);
            SetItemScoreByType(YatzyScoreType.ThreesScore, 0);
            SetItemScoreByType(YatzyScoreType.FoursScore, 0);
            SetItemScoreByType(YatzyScoreType.FivesScore, 0);
            SetItemScoreByType(YatzyScoreType.SixesScore, 0);
            SetItemScoreByType(YatzyScoreType.ThreeOfAKindScore, 0);
            SetItemScoreByType(YatzyScoreType.FourOfAKindScore, 0);
            SetItemScoreByType(YatzyScoreType.FullHouseScore, 0);
            SetItemScoreByType(YatzyScoreType.SmallStraightScore, 0);
            SetItemScoreByType(YatzyScoreType.LargeStraightScore, 0);
            SetItemScoreByType(YatzyScoreType.YahtzeeScore, 0);
            SetItemScoreByType(YatzyScoreType.ChanceScore, 0);
            SetItemScoreByType(YatzyScoreType.YahtzeeScore, 0);
            int value = 1;
            foreach (YatzyDice die in _dice)
            {
                die.Hold = false;
                die.Value = value++;
            }
        }

        public async void Reset()
        {
            _rollCount = 0;
            _scoreCount++;
            foreach (YatzyDice die in _dice)
            {
                die.Hold = false;
            }
            SetItemScoreByType(YatzyScoreType.UpperTotalScore, _calculate.UpperTotalScore);
            if (_calculate.UpperBonus)
            {
                SetItemScoreByType(YatzyScoreType.UpperTotalBonusScore, 35);
            }
            else
            {
                SetItemScoreByType(YatzyScoreType.UpperTotalBonusScore, 0);
            }
            SetItemScoreByType(YatzyScoreType.LowerTotalScore, _calculate.LowerTotalScore);
            SetItemScoreByType(YatzyScoreType.TotalScore, _calculate.TotalScore);
            if (_scoreCount == 14)
            {
                int total = GetItemScoreByType(YatzyScoreType.TotalScore);
                bool result = await _confirm($"Game Over, Score {total}. Play again?");
                if (result)
                {
                    Clear();
                }
            }
        }

        public void Roll()
        {
            if (_rollCount < 3)
            {
                if (_rollCount == 0)
                {
                    foreach (YatzyDice die in _dice)
                    {
                        die.Hold = false;
                    }
                }
                foreach (YatzyDice die in _dice)
                {
                    die.Roll();
                }
                _rollCount++;
                if (_rollCount == 3)
                {
                    foreach (YatzyDice die in _dice)
                    {
                        die.Hold = true;
                    }
                }
            }
        }
    }

    public class YatzyItemTemplateSelector : DataTemplateSelector
    {
        public DataTemplate ScoreItem { get; set; }
        public DataTemplate TotalItem { get; set; }

        protected override DataTemplate SelectTemplateCore(object value, DependencyObject container)
        {
            if (value is YatzyItem item)
            {
                return item.Command != null ? ScoreItem : TotalItem;
            }
            return null;
        }
    }

    public class Library
    {
        private const string app_title = "Yatzy Game";

        private YatzyBoard _board = null;
        private IAsyncOperation<ContentDialogResult> _dialogResult = null;

        private async Task<bool> ShowDialogAsync(string content)
        {
            return await ShowDialogAsync(content, "Yes", "No");
        }

        private async Task<bool> ShowDialogAsync(string content, string primary = "Ok",
            string close = "Close", string title = app_title)
        {
            try
            {
                if (_dialogResult != null)
                {
                    _dialogResult.Cancel();
                    _dialogResult = null;
                }
                _dialogResult = new ContentDialog
                {
                    Title = title,
                    Content = content,
                    CloseButtonText = close,
                    PrimaryButtonText = primary,
                    DefaultButton = ContentDialogButton.Primary,
                }.ShowAsync();
                return await _dialogResult == ContentDialogResult.Primary;
            }
            catch (TaskCanceledException)
            {
                return false;
            }
        }

        public void Init(ref ItemsControl dice, ref ItemsControl scores)
        {
            _board = new YatzyBoard(ShowDialogAsync);
            dice.ItemsSource = _board.Dice;
            scores.ItemsSource = _board.Items;
            _board.Clear();
        }

        public void Roll()
        {
            _board.Roll();
        }

        public async void New()
        {
            bool result = await ShowDialogAsync("Start a New Game?");
            if (result)
            {
                _board.Clear();
            }
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There are enum values for the Game such as YatzyScoreType which contains all the possible Score Types for the Game. There is a CommandHandler Class which Implements ICommand including the CanExecute and Execute Methods.

There is a BindableBase Class which Implements INotifyPropertyChanged there are SetProperty and OnPropertyChanged Methods. The YatzyDice has Members and Properties for the ICommand, and Index, Value and Hold. The Constructor for this sets the Random Member and there is a Roll whcih will select randomised numbers for the YatzyDice.

The YatzyCalculate Class is used to Calculate Scores for the Game, it has int Members for the Totals, it has a ResetScores Method to reset these values and an UpdateTotals Method Method to set them. The GetAddUp Method will add up the values for the YatzyDice, GetOfAKind will add up values for all the Dice of a given kind, passed in as value. The GetFullHouse Method will see if the YatzyDice makes up a Full House, the GetSmallStraight and GetLargeStraight Methods will Calculate those values for the Game and return the Score for those. GetYahtzee will work out if the result of the YatzyDice is a Yahtzee and GetChance will work out the Chance Score. There a Properties for TotalScore, UpperTotalScore, LowerTotalScore and UpperBonus.

There is an IntegerToGlyphConverter Class which will convert an int to the faces of a single Dice or Die, the YatzyItem Class has Properties for int for Score and for an ICommand and YatzyScoreType and there is a Property to return the Content for a YatzyScoreType.

The YatzyBoard Class has const and readonly including for Random to produce randomised values and has Members including a YatzyDice Array, plus YatzyCalculate and List of YatzyItem and a Func of string and Task of bool. There is a Method to GetItemByType which will get a YatzyItem by YatzyScoreType and SetItemScoreByType will set a Score by YatzyScoreType and GetItemScoreByType will get a Score by YatzyScoreType.

Also in the YatzyBoard Class there is a AddUpDice Method which will add up Scores based on YatzyScoreType and will call the YatzyCalculate Method of GetAddUp. The ValueOfAKind will call the Method of GetOfAKind from YatzyCalculate and update the relevant Scores and functionality. The ItemScore Method will work out the FullHouseScore, SmallStraightScore and LargeStraightScore . The Yahtzee Method will be used when there is a Yahtzee in the Game and Chance if there is a Chance in the Game and YahtzeeBonus for a Bonus Score. The Hold Method will set the Hold Property for the YatzyDice.

The YatzyBoard Constructor will set up the YatzyDice and will setup the List of YatzyItem with CommandHandler to bind to Methods for each of the YatzyScoreType. There a Properties for the Array of YatzyDice and ones for RollCount and ScoreCount and List of YatzyItem. The Clear Method is used to reset all the Scores for the Game, the Reset Method is used between rounds of the Game and will also determine when the Game is over and the Roll Method is used to produce the values for the Dice. There is a YatzyItemTemplateSelector Class for selecting the DataTemplate which will be based on the YatzyItem having the Command Property set or Not.

The Library Class has a ShowDialogAsync Method to display a MessageDialog and a Property for the YatzyBoard. There is an Init Method which is used to help with the look-and-feel of the Game, the Roll Method which calls the Roll Method of the YatzyBoard Class and New will begin a Game.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-yatzy-game

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in this above the Grid element, enter the following XAML:

<Page.Resources>
	<DataTemplate x:Key="DiceTemplate" x:DataType="local:YatzyDice">
		<StackPanel>
			<Grid Margin="2" Height="50" Width="50" CornerRadius="2" 
				Background="{ThemeResource AccentButtonBackground}">
				<FontIcon FontSize="60" Margin="0,-8,0,0" FontFamily="Segoe UI Emoji" 
				Foreground="{ThemeResource AccentButtonForeground}"
				Glyph="{x:Bind Value, Mode=OneWay, Converter={StaticResource IntegerToGlyphConverter}}"/>
			</Grid>
			<ToggleButton Margin="2" HorizontalAlignment="Center" Content="Hold" 
			IsChecked="{x:Bind Hold, Mode=OneWay}" Command="{x:Bind Command, Mode=OneWay}" 
			CommandParameter="{x:Bind Index, Mode=OneWay}"/>
		</StackPanel>
	</DataTemplate>
	<DataTemplate x:Key="ScoreTemplate" x:DataType="local:YatzyItem">
		<StackPanel>
			<Grid Margin="2">
				<Grid.ColumnDefinitions>
					<ColumnDefinition Width="*"/>
					<ColumnDefinition Width="Auto"/>
				</Grid.ColumnDefinitions>
				<Button Width="200" Grid.Column="0" HorizontalContentAlignment="Left" 
				Content="{x:Bind Content}" Command="{x:Bind Command}"/>
				<Grid Grid.Column="1" Background="{ThemeResource AccentButtonBackground}">
					<TextBlock Width="75" Text="{x:Bind Score, Mode=TwoWay}"
					TextAlignment="Center" VerticalAlignment="Center"
					Foreground="{ThemeResource AccentButtonForeground}"/>
				</Grid>
			</Grid>
		</StackPanel>
	</DataTemplate>
	<DataTemplate x:Key="TotalTemplate" x:DataType="local:YatzyItem">
		<StackPanel>
			<Grid Margin="2">
				<Grid.ColumnDefinitions>
					<ColumnDefinition Width="*"/>
					<ColumnDefinition Width="Auto"/>
				</Grid.ColumnDefinitions>
				<TextBlock Width="200" Grid.Column="0" TextAlignment="Right" 
				Text="{x:Bind Content}" FontWeight="SemiBold"/>
				<Grid Grid.Column="1" Background="{ThemeResource AccentButtonForeground}">
					<TextBlock Width="75" Text="{x:Bind Score, Mode=TwoWay}" 
					TextAlignment="Center" VerticalAlignment="Center" 
					Foreground="{ThemeResource AccentButtonBackground}"/>
				</Grid>
			</Grid>
		</StackPanel>
	</DataTemplate>
	<local:YatzyItemTemplateSelector x:Key="YatzyItemTemplateSelector" 
		ScoreItem="{StaticResource ScoreTemplate}" TotalItem="{StaticResource TotalTemplate}" />
	<local:IntegerToGlyphConverter x:Key="IntegerToGlyphConverter"/>
</Page.Resources>

Then while still in the XAML View between the Grid and /Grid elements, enter the following XAML:

<Viewbox>
	<StackPanel Margin="50">
		<ItemsControl Name="Dice" HorizontalAlignment="Center" ItemTemplate="{StaticResource DiceTemplate}">
			<ItemsControl.ItemsPanel>
				<ItemsPanelTemplate>
					<StackPanel Orientation="Horizontal"/>
				</ItemsPanelTemplate>
			</ItemsControl.ItemsPanel>
		</ItemsControl>
		<ItemsControl Name="Display" HorizontalAlignment="Center">
			<ItemsControl.ItemTemplate>
				<DataTemplate>
					<ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource YatzyItemTemplateSelector}"/>
				</DataTemplate>
			</ItemsControl.ItemTemplate>
		</ItemsControl>
	</StackPanel>
</Viewbox>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Page2" Label="New" Click="New_Click"/>
	<AppBarButton Icon="Shuffle" Label="Roll" Click="Roll_Click"/>
</CommandBar>

Within the Page.Resources block of XAML above the Grid Element contains DataTemplate for YatzyDice and for the YatzyItem of ScoreTemplate and TotalTemplate which are the the look-and-feel for the Dice and the Button Controls to be selected for the Scores and TextBlock Controls to display them. There is a YatzyItemTemplateSelector and IntegerToGlyphConverter to help display the correct DataTemplate for the look-and-feel of the Score Board of the Game and to display the Dice.

Within the main Grid Element, the first block of XAML is a Viewbox Control which contains an StackPanel which is where the Board will be displayed with two ItemsControl Controls. The second block of XAML is a CommandBar with AppBarButton for New which calls New_Click and Roll which calls Roll_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

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

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(ref Dice, ref Display);
}

private void New_Click(object sender, RoutedEventArgs e)
{
	library.New();
}

private void Roll_Click(object sender, RoutedEventArgs e)
{
	library.Roll();
}

There is an OnNavigatedTo Event Handler which will call the Init Method from the Library Class, and New_Click which will call the New Method of the Library Class and Roll_Click will call the Roll Method of the Library Class.

Step 13

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 14

Once the Application has started running you can tap on the Roll Button to randomly select the values for the Dice and you can use the other Buttons to total up your Score based on the values of the Dice

uwp-ran-yatzy-game

Step 15

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

vs2017-close

Yatzy Game is a Dice Game based on Yacht or Yahtzee and uses the Calculation Logic created by Mike Kitchen from their Example A Simple Yahtzee Game, this hopefully and interesting Game not only to create but to play as well.

Creative Commons License