This article discusses processes of converting projects from C# to VB, and how to create your own Project Templates for Avalonia and any other project where you have reusable boiler code that you wish to use for any new project.
Contents
Introduction
I was working on a new article and both C# and VB code samples are included. If you want to see VB using Avalonia in action, then check out the next article, LogViewer Control for WinForms, WPF, and Avalonia in C# & VB[^].
I have never worked with Avalonia before and was surprised to find only C# and F# were officially supported with application templates. I went looking on Visual Studio Marketplace and there were not templates for VB. So I did a quick search and found a partially completed VB template: avalonia-vb-template-app | Github. We need to implement our own.
There are three templates added when you add the Avalonia for Visual Studio 2022 extension:
What is Avalonia?
From the offical Avalonia - How it works page:
Quote:
Avalonia UI enables .NET developers to create pixel-perfect apps for desktop, mobile and web from a single codebase. Avalonia UI takes inspiration from WPF and WinUI, the aim has never been to simply copy WPF or WinUI APIs.
Operating systems supported:
- Desktop
- Windows 7 (32 & 64bit), 10 (32 & 64bit), 11 (64bit)
- MacOS/OSX (Sierra 10.13 and higher)
- Linux Ubuntu 16.04+, Debian 9+, Fedora 30+ (Arm, Arm64, x64)
- Unix
- Mobile
- IOT
- Browser via WASM
Supported IDEs:
- Visual Studio 2017
- JetBrains Rider 2020.3
See Avalonia UI Documentation (official website) for more information.
Overview
This article is for Desktop Development only. All code is included in the download link at the beginning of this article. The sample projects included in the download link cover both the official default C# templates and our new minimal VB project templates.
The focus will be on filling the lack of VB support with fully implemented base projects for:
- Avalonia Application
- Avalonia MVVM Community Toolkit Application
- Avalonia MVVM ReativeUI Application
And as a bonus, not found in the default templates:
I will cover how I converted the C# templates to VB projects. If you want to add to your templates for creating new projects, there is a section below labeled Visual Studio How-To: Make Sample Projects as Reusable Templates that shows you how.
Sample Screenshots
Before we get into the article, let us see what we are going to achieve, a minimal template for VB. Below is the same project compiled and running on two different operating systems.
Windows 11
MacOS Monterey (v12.6.3)
What Does a C# Avalonia Project Template Look Like?
We are going to look at the first template from the screenshot above, Avalonia .NetCore App (AvaloniaUI), as it is common to all Avalonia project templates.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<PackageReference Condition="'$(Configuration)' == 'Debug'"
Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.6.1" />
</ItemGroup>
</Project>
This looks like a normal console app project file with PackageReference
for the required library files and the OutputType
is set to WinExe
. WinExe
simply means, hide the console window.
As a comparison, here is the default Console App project template *.csproj file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
The last two elements, ImplicitUsings
and Nullable
are C# specific and not required for VB.
How Do We Create an Avalonia Application Project for VB?
We need to manually build the project file and associated files to bootstrap the Avalonia framework.
Project File
Let us look at a default console app *.vbproj file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>ConsoleApp2</RootNamespace>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
</Project>
The key difference between the C# and VB project files is that the VB project file has an RootNamespace
element.
So, for Avalonia, we can add the missing references:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<RootNamespace>AvaloniaApplicationVB</RootNamespace>
</PropertyGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<PackageReference Condition="'$(Configuration)' == 'Debug'"
Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.6.1" />
</ItemGroup>
</Project>
Now we have a basic Avalonia project. Next, we need to wire up the bootstrap code.
Known Issues
Before we convert the C# code, we need to quickly look at the known issues at the time of writing this article (v0.10.18)
Avalonia was designed with C# and F# in mind, not VB. This does not stop us from creating apps in VB for Avalonia, however, there is more work with VB:
- File nesting does not work
- There are no Item Templates for Windows and User Controls
- Whilst the designer works visually, there is no *.g.vb
code-gen
to wire up controls in the code-behind
- Adding events via the designer will create methods in the code-behind
- Giving control names will not auto-generate the *.g.vb files for linking controls to the code-behind. How to work around this is covered below.
Converting the App Startup Code
The minimal required start-up is two (3 with code-behind) files: Program
(.vb) and App
(.axaml & *.axaml.vb). For brevity, I will only focus on the converted VB code, the default C# code generated is included in the download.
Program.VB File
Imports Avalonia
Module Program
<STAThread>
Sub Main(args As String())
BuildAvaloniaApp() _
.StartWithClassicDesktopLifetime(args)
End Sub
Public Function BuildAvaloniaApp() As AppBuilder
Return AppBuilder.Configure(Of App) _
.UsePlatformDetect() _
.LogToTrace()
End Function
End Module
App (.axaml & .axaml.vb) Files
<Application x:Class="AvaloniaApplicationVB.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Styles>
<FluentTheme Mode="Light"/>
</Application.Styles>
</Application>
And the code-behind:
Imports Avalonia
Imports Avalonia.Controls.ApplicationLifetimes
Imports Avalonia.Markup.Xaml
Public Partial Class App
Inherits Application
Public Overrides Sub Initialize()
AvaloniaXamlLoader.Load(Me)
End Sub
Public Overrides Sub OnFrameworkInitializationCompleted()
Dim desktop As IClassicDesktopStyleApplicationLifetime = Nothing
desktop = TryCast(ApplicationLifetime, IClassicDesktopStyleApplicationLifetime)
if desktop IsNot Nothing Then
desktop.MainWindow = New MainWindow()
End If
MyBase.OnFrameworkInitializationCompleted()
End Sub
End Class
MainWindow (.axaml & .axaml.vb) Files
<Window x:Class="AvaloniaApplicationVB.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="AvaloniaApplicationVB"
Width="800" Height="450">
Welcome to Avalonia VB!
</Window>
And the code-behind:
Imports Avalonia.Controls
Imports Avalonia.Markup.Xaml
Partial Public Class MainWindow : Inherits Window
Private Window As Window
Sub New()
InitializeComponent()
End Sub
Private Sub InitializeComponent(Optional loadXaml As Boolean = True)
If loadXaml Then
AvaloniaXamlLoader.Load(Me)
End If
Window = FindNameScope().Find("Window")
End Sub
End Class
How Did We Know to Use the FindNameScope Method?
By turning on the <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
flag in the *.csproj project file, we can view the generated AvaloniaApplication.MainWindow.g.cs file in the obj\Debug\net7.0\generated\Avalonia.NameGenerator\Avalonia.NameGenerator.AvaloniaNameSourceGenerator path:
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace AvaloniaApplication
{
partial class MainWindow
{
internal global::Avalonia.Controls.Window MyWindow;
public void InitializeComponent
(bool loadXaml = true, bool attachDevTools = true)
{
if (loadXaml)
{
AvaloniaXamlLoader.Load(this);
}
#if DEBUG
if (attachDevTools)
{
this.AttachDevTools();
}
#endif
MyWindow = this.FindNameScope()?.Find
<global::Avalonia.Controls.Window>("MyWindow");
}
}
}
Manually Wiring in Control References in AXAML Code-Behind
If we give a control a name, and we need to reference the control in the code-behind, we need to do it manually:
Dim Control_Reference = FindNameScope().Find("Control_Name")
For example, if we have the following in the AXAML file:
<TextBlock x:Name="MyTextBlock"/>
In the code-behind, we would do the following:
Dim MyTextBlock = FindNameScope().Find("MyTextBlock")
MyTextBlock.Text = "Hello World from the Code-Behind!"
MVVM App Project
Out of the box, for C# and F#, Avalonia Templates has two (2) types of MVVM Project types:
- Community Toolkit
- ReativeUI
Project File
It is the same as the previous version except for the library references.
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
ReativeUI
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
This is the same as the previous project type.
Program.VB File (ReativeUI)
For ReativeUI, we need to wire in the support:
Imports Avalonia
Imports Avalonia.ReactiveUI
Module Program
<STAThread>
Sub Main(args As String())
BuildAvaloniaApp() _
.StartWithClassicDesktopLifetime(args)
End Sub
Public Function BuildAvaloniaApp() As AppBuilder
Return AppBuilder.Configure(Of App) _
.UsePlatformDetect() _
.LogToTrace() _
.UseReactiveUI()
End Function
End Module
ViewLocator
By default, Avalonia implements support for the ViewLocator
design pattern for Data Templates. The sample projects that come with this article have this implemented.
Imports Avalonia.Controls
Imports Avalonia.Controls.Templates
Imports AvaloniaMvvmApplicationReactiveUIVB.ViewModels
Public Class ViewLocator : Implements IDataTemplate
Public Function Build(data As Object) As IControl _
Implements ITemplate(Of Object, IControl).Build
Dim name As String = data.GetType().FullName.Replace("ViewModel", "View")
Dim type As Type = Type.GetType(name)
If type IsNot Nothing Then
Return DirectCast(Activator.CreateInstance(type), Control)
End If
Return New TextBox With {.Text = "Not Found: " + name}
End Function
Public Function Match(data As Object) As Boolean Implements IDataTemplate.Match
Return TypeOf data Is ViewModelBase
End Function
End Class
App (.axaml & .axaml.vb) files
These files are identical for both Community Toolkit and ReativeUI. We need to add a reference to the ViewLocator
:
<Application x:Class="AvaloniaMvvmApplicationCommunityToolkitVB.App"
xmlns:local="using:AvaloniaMvvmApplicationCommunityToolkitVB"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
</Application.Styles>
</Application>
And the code-behind:
Imports Avalonia
Imports Avalonia.Controls.ApplicationLifetimes
Imports Avalonia.Data.Core
Imports Avalonia.Data.Core.Plugins
Imports Avalonia.Markup.Xaml
Imports AvaloniaMvvmApplicationCommunityToolkitVB.ViewModels
Partial Public Class App
Inherits Application
Public Overrides Sub Initialize()
AvaloniaXamlLoader.Load(Me)
End Sub
Public Overrides Sub OnFrameworkInitializationCompleted()
Dim desktop As IClassicDesktopStyleApplicationLifetime = Nothing
desktop = TryCast(ApplicationLifetime, IClassicDesktopStyleApplicationLifetime)
If desktop IsNot Nothing Then
ExpressionObserver.DataValidators _
.RemoveAll(Function(x) TypeOf x Is DataAnnotationsValidationPlugin)
desktop.MainWindow = New MainWindow() With
{
.DataContext = New MainWindowViewModel()
}
End If
MyBase.OnFrameworkInitializationCompleted()
End Sub
End Class
MainWindow (.axaml & .axaml.vb) Files
These files are identical for both Community Toolkit and ReativeUI:
<Window x:Class="AvaloniaMvvmVB.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:AvaloniaMvvmVB.ViewModels"
x:DataType="vm:MainWindowViewModel"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="AvaloniaMvvmVB"
Icon="/Assets/avalonia-logo.ico"
Width="800" Height="450">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Window>
And the code-behind:
Imports Avalonia.Controls
Imports Avalonia.Markup.Xaml
Imports AvaloniaMvvmVB.ViewModels
Partial Public Class MainWindow : Inherits Window
Private VM As MainWindowViewModel
Sub New()
InitializeComponent()
End Sub
Private Sub InitializeComponent(Optional loadXaml As Boolean = True)
If loadXaml Then
AvaloniaXamlLoader.Load(Me)
End If
End Sub
Protected Overrides Sub OnDataContextChanged(e As EventArgs)
VM = DataContext
MyBase.OnDataContextChanged(e)
End Sub
End Class
ViewModelBase
The Community Toolkit and ReativeUI use different methodologies to implement support for Data Binding, so the base implementation is slightly different:
Imports CommunityToolkit.Mvvm.ComponentModel
Namespace ViewModels
Public Class ViewModelBase : Inherits ObservableObject
End Class
End Namespace
ReativeUI
Imports ReactiveUI
Namespace ViewModels
Public Class ViewModelBase : Inherits ReactiveObject
End Class
End Namespace
MainWindowViewModel
This file is identical for both Community Toolkit and ReativeUI:
Namespace ViewModels
Public Class MainWindowViewModel : Inherits ViewModelBase
Public ReadOnly Property Greeting As String _
= "Welcome to Avalonia MVVM using VB!"
End Class
End Namespace
How Do We Create an Avalonia Control Library for VB?
There are no UserControl or Custom Control Library Templates for C# or F# that are installed when adding the Avalonia extension, nor in the official documentation. If you have used WPF equivalents, then you will know it is not that difficult. Below, I will walk you through creating a minimal implementation.
*.vbproj Project File
For a UserControl
or Custom Control Library, the project file is very similar to the application project:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>AvaloniaControlLibraryVB</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Condition="'$(Configuration)' == 'Debug'"
Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.6.1" />
</ItemGroup>
</Project>
UserControl (.axaml & .axaml.vb) Files
Like any WPF User Control, Avalonia implementation is very similar. However, if you need to reference controls in the AXAML from the code-behind, you need to manually reference the control using the FindNameScope
method, just like we did in MainWindow
above.
<UserControl x:Class="AvaloniaControlLibraryVB.UserControl1"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
Welcome to Avalonia Control Library VB!
</UserControl>
And the code-behind:
Partial Public Class UserControl1 : Inherits UserControl
Sub New()
InitializeComponent()
End Sub
Private Sub InitializeComponent(Optional loadXaml As Boolean = True)
If loadXaml Then
AvaloniaXamlLoader.Load(Me)
End If
End Sub
End Class
Using the Control in the control library is exactly the same as WPF - create a namespace, then use the control with the namespace reference:
<Window x:Class="SampleControlUseAppVB.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:AvaloniaControlLibraryVB;
assembly=AvaloniaControlLibraryVB"
Title="SampleControlUseAppVB"
Width="800" Height="450">
<controls:UserControl1 />
</Window>
Visual Studio How-To: Make Sample Projects as Reusable Templates
Visual Studio makes it very easy to create your own Templates from projects. You can read about it here: How to: Create project templates | Microsoft Learn
To access the Export Template Wizard in Visual Studio, load the solution with the project that you want to create a template from, then select the Export Template Wizard from the menu: Project > Export Template
- Select the "Project Template" option, then the project to export:
- Fill out the Template details, if you want to automatically import the template generated, and whether you want an explorer window to open to the generated template folder:
And we are done! Now you can use your project as a template in Visual Studio.
The optimal method is to do the process manually, then you can correctly tag the project for the New Project filtering. Here is a video that will walk you through the process: How to create your own project templates in .NET | YouTube
Summary
Even though VB is not officially supported, we have learned that you can make Applications and Libraries specific to Avalonia using VB that are cross-platform enabled. I have also walked you through the processes of converting projects from C# to VB, and how to create your own Project Templates for Avalonia and any other project where you have reusable boiler code that you wish to use for any new project.
References
Documentation, Articles, etc.
- Avalonia UI
- Templates
- Articles
Nuget Packages
- Dot Net (Core) 7.0 Framework
- Avalonia
History
- 21st March, 2023 - v1.00 - Initial release
- 26th March, 2023 - v1.01 - Removed remnants of temp files used to build this article