Click here to Skip to main content
15,867,488 members
Articles / Programming Languages / Visual Basic

The Missing Avalonia Templates for VB

Rate me:
Please Sign up or sign in to vote.
3.81/5 (8 votes)
26 Mar 2023CPOL9 min read 14K   142   14   4
Add the missing project templates for the App and Control Library projects that were not included
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:

Image 1

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:

  1. 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
  1. Mobile
  • iOS
  • Android
  1. IOT
  • Raspberry Pi
  1. 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:

  • Avalonia Control Library

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

Image 2

MacOS Monterey (v12.6.3)

Image 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.

XML
<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" />
    <!--Condition below is needed to remove Avalonia.Diagnostics 
        package from build output in Release configuration.-->
    <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:

XML
<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:

XML
<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:

XML
<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" />
    <!--Condition below is needed to remove Avalonia.Diagnostics 
        package from build output in Release configuration.-->
    <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

VB
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

XML
<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:

VB
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

XML
<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:

VB
Imports Avalonia.Controls
Imports Avalonia.Markup.Xaml

Partial Public Class MainWindow : Inherits Window

    Private Window As Window

    Sub New()

        ' This call is required by the designer.
        InitializeComponent()

    End Sub

    ' Auto-wiring does not work for VB, so do it manually
    ' Wires up the controls and optionally loads XAML markup and attaches dev tools
    ' (if Avalonia.Diagnostics package is referenced)
    Private Sub InitializeComponent(Optional loadXaml As Boolean = True)

        If loadXaml Then
            AvaloniaXamlLoader.Load(Me)
        End If

        ' An example of manually getting the named Window
        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:

C#
// <auto-generated />

using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace AvaloniaApplication
{
    partial class MainWindow
    {
        internal global::Avalonia.Controls.Window MyWindow;

        /// <summary>
        /// Wires up the controls and optionally loads XAML markup 
        /// and attaches dev tools
        /// (if Avalonia.Diagnostics package is referenced).
        /// </summary>
        /// <param name="loadXaml">Should the XAML be loaded into the component.
        /// </param>
        /// <param name="attachDevTools">Should the dev tools be attached.</param>

        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:

VB
Dim Control_Reference = FindNameScope().Find("Control_Name")

For example, if we have the following in the AXAML file:

XML
<TextBlock x:Name="MyTextBlock"/>

In the code-behind, we would do the following:

VB
Dim MyTextBlock = FindNameScope().Find("MyTextBlock")

' Then we can set properties, call the control's methods, or wire up event handlers
' For example:
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:

  1. Community Toolkit
  2. ReativeUI

Image 4

Project File

It is the same as the previous version except for the library references.

Community Toolkit
XML
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
ReativeUI
XML
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />

Program.VB file (Community Toolkit)

This is the same as the previous project type.

Program.VB File (ReativeUI)

For ReativeUI, we need to wire in the support:

VB
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.

VB
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:

XML
<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:

VB
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

            ' Line below is needed to remove Avalonia data validation.
            ' Without this line you will get duplicate validations 
            ' from both Avalonia and CT
            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:

XML
<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>
        <!-- This only sets the DataContext for the previewer in an IDE,
             to set the actual DataContext for runtime, 
             set the DataContext property in code (look at App.axaml.cs) -->
        <vm:MainWindowViewModel/>
    </Design.DataContext>

    <TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" 
     VerticalAlignment="Center"/>

</Window>

And the code-behind:

VB
Imports Avalonia.Controls
Imports Avalonia.Markup.Xaml
Imports AvaloniaMvvmVB.ViewModels

Partial Public Class MainWindow : Inherits Window

    Private VM As MainWindowViewModel

    Sub New()

        ' This call is required by the designer.
        InitializeComponent()

    End Sub

    ' Auto-wiring does not work for VB, so do it manually
    ' Wires up the controls and optionally loads XAML markup 
    ' and attaches dev tools (if Avalonia.Diagnostics package is referenced)
    Private Sub InitializeComponent(Optional loadXaml As Boolean = True)

        If loadXaml Then
            AvaloniaXamlLoader.Load(Me)
        End If

    End Sub

    ' An example of capturing the ViewModel if required in the code-behind
    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:

Community Toolkit
VB
Imports CommunityToolkit.Mvvm.ComponentModel

Namespace ViewModels

    Public Class ViewModelBase : Inherits ObservableObject

    End Class

End Namespace 
ReativeUI
VB
Imports ReactiveUI

Namespace ViewModels

    Public Class ViewModelBase : Inherits ReactiveObject

    End Class

End Namespace 

MainWindowViewModel

This file is identical for both Community Toolkit and ReativeUI:

VB
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:

XML
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <RootNamespace>AvaloniaControlLibraryVB</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Avalonia" Version="0.10.18" />
    <!--Condition below is needed to remove Avalonia.Diagnostics 
        package from build output in Release configuration.-->
    <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.

XML
<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:

VB
Partial Public Class UserControl1 : Inherits UserControl

    Sub New()
        InitializeComponent()
    End Sub

    ' Auto-wiring does not work for VB, so do it manually
    ' Wires up the controls and optionally loads XAML markup and 
    ' attaches dev tools (if Avalonia.Diagnostics package is referenced)
    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:

XML
<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

Image 5

  1. Select the "Project Template" option, then the project to export:

    Image 6

  2. 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:

    Image 7

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.

Nuget Packages

History

  • 21st March, 2023 - v1.00 - Initial release
  • 26th March, 2023 - v1.01 - Removed remnants of temp files used to build this article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMissing ConsoleApp Pin
Cicciocap26-Mar-23 9:10
Cicciocap26-Mar-23 9:10 
AnswerRe: Missing ConsoleApp Pin
Graeme_Grant26-Mar-23 13:36
mvaGraeme_Grant26-Mar-23 13:36 
GeneralMy vote of 5 Pin
LightTempler21-Mar-23 9:27
LightTempler21-Mar-23 9:27 
VERY cool!
No, it 's not possible. But here we go, making it work anyway. I like this attitude. Smile | :)
LiTe
GeneralRe: My vote of 5 Pin
Graeme_Grant21-Mar-23 12:03
mvaGraeme_Grant21-Mar-23 12:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.