Counter App using C# Markup and MVVM
Download the complete C# Markup + MVVM sample
Note
Estimated time to complete: 10 minutes
Introduction
This tutorial will walk you through creating a simple counter application with Uno Platform. The application will have a Button
that increments the counter, a TextBox
where the user can enter the step size, and a TextBlock
that displays the current value of the counter.
In this tutorial you will learn how to:
- Create a new Project with Uno Platform using Visual Studio Template Wizard or the dotnet new command
- Add elements to the C# file, using C# Markup, to define the layout of the application
- Add code to the C# file to implement the application logic using the Model-View-ViewModel (MVVM) pattern
- Use data binding to connect the UI to the application logic
To complete this tutorial you don't need any prior knowledge of the Uno Platform or C#.
If you're a more experienced developer, you may want to skip this tutorial and jump straight to either the SimpleCalculator or the TubePlayer sample. Both are more complex and will give you a better idea of what Uno Platform can do.
Create the Application
To get started, we're going to use the Uno Platform solution template with the simplest set of options selected. The solution template can be accessed using either Visual Studio's New Project wizard or the command line.
Note
If you don't have the Uno Platform Extension for Visual Studio installed, follow these instructions.
Launch Visual Studio and click on Create new project on the Start Window. Alternatively, if you're already in Visual Studio, click New, Project from the File menu.
Type
Uno Platform
in the search boxClick Uno Platform App, then Next
Name the project
Counter
and click Create
At this point you'll enter the Uno Platform Template Wizard, giving you options to customize the generated application. For this tutorial, we're only going to configure the markup language and the presentation framework.
Select Blank in Presets selection
Select the Presentation tab and choose MVVM
Select the Markup tab and choose C# Markup
Before completing the wizard, take a look through each of the sections and see what other options are available. You can always come back and create a new project with different options later. For more information on all the template options, see Using the Uno Platform Template.
- Click Create to complete the wizard
The template will create a solution with a simple cross-platform project, named Counter
, ready to run.
At this point, the newly created application can be opened in Visual Studio or your preferred IDE. The image below displays the Counter
project, which includes files for the application's layout and business logic. Additionally, the project contains a Platforms
folder for platform-specific files, a Strings
folder for localization, and an Assets
folder for images, icons, and other assets.
Before proceeding, you should select a target platform and run the application. For more information on debugging an application, see for Visual Studio, Visual Studio Code, or Rider.
MainWindow and MainPage
The majority of an Uno Platform application is defined in a project, in this case, named Counter. This project contains files that define the layout of the application and files that implement the application logic.
The startup logic for the application is contained in the App.xaml.cs
file. In the OnLaunched
method, the MainWindow
of the application is initialized with a Frame
, used for navigation between pages, and the MainPage
is set as the initial page.
The layout for the MainPage
is defined in the MainPage.cs file. This file contains the C# Markup that defines the layout of the application.
public sealed partial class MainPage : Page
{
public MainPage()
{
this.Background(ThemeResource.Get<Brush>("ApplicationPageBackgroundThemeBrush"))
.Content(
new StackPanel()
.VerticalAlignment(VerticalAlignment.Center)
.HorizontalAlignment(HorizontalAlignment.Center)
.Children(
new TextBlock()
.Text("Hello Uno Platform!")
)
);
}
}
This defines a page with a background set to the theme resource ApplicationPageBackgroundThemeBrush, meaning it will adapt to the theme (Dark or Light) of the application.
The page contains a StackPanel
, which will lay out controls in a vertical stack and is aligned in the center of the page, both horizontally and vertically. The StackPanel
contains a single TextBlock
control, which displays the text Hello Uno Platform
and is aligned in the horizontal center of the StackPanel.
Add a Control
We're going to replace the existing TextBlock
with an Image
but before we can do this, we need to add the image file to the application. Download this SVG image and add it to the Assets folder. At this point, you should rebuild the application in order for the image to be included in the application package.
Note
If you're working in Visual Studio, select the newly added logo.svg file in the Solution Explorer, open the Properties tool window, and make sure the Build Action property is set to UnoImage
. For other IDEs, no further action is required as the template automatically sets the Build Action to UnoImage
for all files in the Assets folder.
Including SVG files with the UnoImage
build action will use Uno.Resizetizer to convert the SVG file to a PNG file for each platform. The generated PNG files will be included in the application package and used at runtime. For more information on using Uno.Resizetizer in Uno Platform, see Get Started with Uno.Resizetizer.
Now that we have the image file, we can replace the TextBlock
with an Image
.
Open the MainPage.cs file.
Replace the
TextBlock
with the followingImage
element.new Image() .Width(150) .Height(150) .Source("ms-appx:///Assets/logo.png")
- The
Width
andHeight
have been set on theImage
to ensure the image is displayed at the correct size. The Source property has been set to the path of the image file.
Run the application to see the updated MainPage
. You should see the image displayed in the center of the page. Keep the application running whilst completing the rest of this tutorial. Hot Reload is used to automatically update the running application as you make changes to the application. For more information on Hot Reload, see Hot Reload.
Change the Layout
The layout of the application uses a StackPanel
which allows multiple controls to be added as children and will layout them in a vertical stack. An alternative to the StackPanel
that is often used to control layout within an Uno Platform application is the Grid
. The Grid
allows controls to be laid out in rows and columns, and is often used to create more complex layouts.
A StackPanel
is a good choice for this application as we want the controls to be laid out vertically, one above the other. Let's go ahead and add the remaining controls for the counter.
Update the
StackPanel
to remove theHorizontalAlignment
property, as we'll be centering each of the nested elements individually.new StackPanel() .VerticalAlignment(VerticalAlignment.Center)
Update the
Image
element to center it horizontally and add a margin.new Image() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .Width(150) .Height(150) .Source("ms-appx:///Assets/logo.png"),
Add a
TextBox
to allow the user to enter the step size.new TextBox() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .TextAlignment(Microsoft.UI.Xaml.TextAlignment.Center) .PlaceholderText("Step Size") .Text("1"),
Add a
TextBlock
to display the current counter value.new TextBlock() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .TextAlignment(Microsoft.UI.Xaml.TextAlignment.Center) .Text("Counter: 1"),
Add a
Button
to increment the counter.new Button() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .Content("Increment Counter by Step Size")
ViewModel
So far all the elements we've added to the MainPage have had their content set directly. This is fine for static content, but for dynamic content, we need to use data binding. Data binding allows us to connect the UI to the application logic, so that when the application logic changes, the UI is automatically updated.
As part of creating the application, we selected MVVM as the presentation framework. This added a reference to the MVVM Toolkit package which provides a base class called ObservableObject
which implements the INotifyPropertyChanged
interface. This interface is used to notify the UI when a property has changed so that the UI can be updated.
Add a new class named
MainViewModel
.Update the MainViewModel class to be a
partial
class and inherit from ObservableObject.internal partial class MainViewModel : ObservableObject { }
Add the
_count
and_step
fields to the MainViewModel class. These fields both have theObservableProperty
attribute applied, which will generate matching properties,Count
andStep
, that will automatically raise thePropertyChanged
event when their value is changed.[ObservableProperty] private int _count = 0; [ObservableProperty] private int _step = 1;
Add a method called
Increment
to theMainViewModel
that will increment the counter by the step size. TheRelayCommand
attribute will generate a matchingICommand
property,IncrementCommand
, that will call theIncrement
method when theICommand.Execute
method is called.[RelayCommand] private void Increment() => Count += Step;
Note
If you see red squiggles under Count
or Step
, save the file to have Hot Reload apply your changes.
The final code for the MainViewModel
class should look like this:
internal partial class MainViewModel : ObservableObject
{
[ObservableProperty]
private int _count = 0;
[ObservableProperty]
private int _step = 1;
[RelayCommand]
private void Increment()
=> Count += Step;
}
Data Binding
Now that we have the MainViewModel
class, we can update the MainPage
to use data binding to connect the UI to the application logic.
Let's add the
DataContext
to our page. To do so, add.DataContext(new MainViewModel(), (page, vm) => page
before.Background(...)
. Remember to close theDataContext
expression with a)
at the end of the code. It should look similar to the code below:this.DataContext(new MainViewModel(), (page, vm) => page .Background(ThemeResource.Get<Brush>("ApplicationPageBackgroundThemeBrush")) .Content( ... ) );
Update the
TextBlock
by removing its current text content and replacing it with a binding expression for theCount
property of theMainViewModel
. Modify the existingText
property with() => vm.Count, txt => $"Counter: {txt}"
. The adjusted code is as follows:new TextBlock() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .TextAlignment(Microsoft.UI.Xaml.TextAlignment.Center) .Text(() => vm.Count, txt => $"Counter: {txt}")
Update the
TextBox
by binding theText
property to theStep
property of theMainViewModel
. TheMode
of the binding is set toTwoWay
so that theStep
property is updated when the user changes the value in theTextBox
.new TextBox() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .TextAlignment(Microsoft.UI.Xaml.TextAlignment.Center) .PlaceholderText("Step Size") .Text(x => x.Binding(() => vm.Step).TwoWay())
Update the
Button
to add aCommand
property that is bound to theIncrementCommand
property of theMainViewModel
.new Button() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .Command(() => vm.IncrementCommand) .Content("Increment Counter by Step Size")
The final code for MainPage.cs should look like this:
public sealed partial class MainPage : Page { public MainPage() { this.DataContext(new MainViewModel(), (page, vm) => page .Background(ThemeResource.Get<Brush>("ApplicationPageBackgroundThemeBrush")) .Content( new StackPanel() .VerticalAlignment(VerticalAlignment.Center) .Children( new Image() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .Width(150) .Height(150) .Source("ms-appx:///Counter/Assets/logo.png"), new TextBox() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .TextAlignment(Microsoft.UI.Xaml.TextAlignment.Center) .PlaceholderText("Step Size") .Text(x => x.Binding(() => vm.Step).TwoWay()), new TextBlock() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .TextAlignment(Microsoft.UI.Xaml.TextAlignment.Center) .Text(() => vm.Count, txt => $"Counter: {txt}"), new Button() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .Command(() => vm.IncrementCommand) .Content("Increment Counter by Step Size") ) ) ); } }
Wrap Up
At this point, you should have a working counter application. Try changing the step size and clicking the button to increment the counter.
If you want to see the completed application, you can download the source code from GitHub.