Counter App using C# Markup and MVUX
Download the complete C# Markup + MVUX 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-Update-eXtended (MVUX) 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, command lines or any of the suggested IDEs below.
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 Platformin the search boxClick Uno Platform App, then Next
Name the project
Counterand 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 MVUX
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 single 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 a 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
TextBlockwith the followingImageelement.new Image() .Width(150) .Height(150) .Source("ms-appx:///Assets/logo.png")
- The
WidthandHeighthave been set on theImageto 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
StackPanelto remove theHorizontalAlignmentproperty, as we'll be centering each of the nested elements individually.new StackPanel() .VerticalAlignment(VerticalAlignment.Center)Update the
Imageelement 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
TextBoxto 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
TextBlockto display the current counter value.new TextBlock() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .TextAlignment(Microsoft.UI.Xaml.TextAlignment.Center) .Text("Counter: 1"),Add a
Buttonto increment the counter.new Button() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .Content("Increment Counter by Step Size")
MainModel
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 MVUX as the presentation framework. This added a reference to MVUX which is responsible for managing our Models and generating the necessary bindings.
Add a new class named
MainModel.Update the
MainModelclass to be apartial record.internal partial record MainModel { }Add a new
partial recordabove namedCountable. This record will be responsible for updating our counter's properties all while ensuring immutability. Learn more about immutable records here.internal partial record Countable { }Add the
CountandStepproperties to theCountable's primary constructor.internal partial record Countable(int Count, int Step) { }Add an
Incrementmethod to theCountablerecord. Thewithoperator allows us to create a new instance of the object.public Countable Increment() => this with { Count = Count + Step };Add the newly created
Countableas a property in theMainModelclass. The type must beIState<Countable>and we use=> State.Value(...)to initialize it.public IState<Countable> Countable => State.Value(this, () => new Countable(0, 1));Add a method named
IncrementCounterto theMainModelthat will in turn call theCountable'sIncrementmethod and therefore update the counter. You can find more information on commands in MVUX here.public ValueTask IncrementCounter() => Countable.UpdateAsync(c => c?.Increment());
The final code for the MainModel class should look like this:
internal partial record Countable(int Count, int Step)
{
public Countable Increment() => this with
{
Count = Count + Step
};
}
internal partial record MainModel
{
public IState<Countable> Countable => State.Value(this, () => new Countable(0, 1));
public ValueTask IncrementCounter()
=> Countable.UpdateAsync(c => c?.Increment());
}
Data Binding
As the application uses MVUX, the MainModel class is used to generate a bindable ViewModel, MainViewModel. We can update the MainPage to use data binding to connect the UI to the application logic.
Let's add the
DataContextto our page. To do so, add.DataContext(new MainViewModel(), (page, vm) => pagebefore.Background(...). Remember to close theDataContextexpression 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
TextBlockby removing its current text content and replacing it with a binding expression for theCountable.Countproperty of theMainViewModel. Modify the existingTextproperty with() => vm.Countable.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.Countable.Count, txt => $"Counter: {txt}")Update the
TextBoxby binding theTextproperty to theCountable.Stepproperty of the MainViewModel. TheModeof the binding is set toTwoWayso that theCountable.Stepproperty 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.Countable.Step).TwoWay())Update the
Buttonto add aCommandproperty that is bound to theIncrementCountertask of theMainViewModel.new Button() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .Command(() => vm.IncrementCounter) .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:///Assets/logo.png"), new TextBox() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .TextAlignment(Microsoft.UI.Xaml.TextAlignment.Center) .PlaceholderText("Step Size") .Text(x => x.Binding(() => vm.Countable.Step).TwoWay()), new TextBlock() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .TextAlignment(Microsoft.UI.Xaml.TextAlignment.Center) .Text(() => vm.Countable.Count, txt => $"Counter: {txt}"), new Button() .Margin(12) .HorizontalAlignment(HorizontalAlignment.Center) .Command(() => vm.IncrementCounter) .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.
