Skip to content
Bruno Sonnino
Menu
  • Home
  • About
Menu

Modernizing your .NET app with WinUI3

Posted on 23 December 2020

In the last post I showed how to use Xaml Islands to modernize your .NET app. As you could see, there are a lot of steps and the procedure is a little clumsy. But now it’s getting better, Microsoft has introduced WinUI 3 and, with that, things are getting better: you don’t need special components to add your WinUI code, you can use the WinUI components directly in you app. But there is a pitfall – right now, when I’m writing this article (end of 2020), Win UI 3 is still in preview and you need the preview version of Visual Studio to use it. You can install the preview version side-by-side with the production version, there is no problem with that. You can download the preview version from here. Once you download and install the preview version of Visual Studio, you need to download the WinUI3 templates to create new apps. This is done by installing the VSIX package from here. Now we are ready to modernize our app from the last post with WinUI3. In Visual Studio Preview, create a new app. We’ll create a new blank, packaged desktop app:

When you create that app, Visual Studio will create a solution with two projects: a WPF app and a package app that will create a MSIX package that will allow your program to run in a sandbox and to send it to Windows Store:

If you run the application right now, the package will install the app, with a button in the center of the window, that will change its text when clicked. We’ll change the app to show our photos. The first step is to add the Newtonsoft.Json NuGet package. The MVVM Light package wasn’t ported to .NET 5.0, so we’ll use the MVVM framework from the Community toolkit (https://github.com/windows-toolkit/MVVM-Samples). In the NuGet package manager, install the Micosoft.Toolkit.MVVM package. Once you installed these packages, you can copy the Converters, Model, Photos and ViewModels paths from the WPFXamlIslands project from my Github. If you build the application, you will see some errors. Some of them are due to the conversion to WinUI3 and other ones are due to the use of the new MVVM Framework. Let’s begin converting the files for the framework. The MVVM Toolkit doesn’t have the ViewModelBase class, but has the similar ObservableObject. In MainViewModel.cs, changed the base class of the viewmodel to ObservableObject:

public class MainViewModel : ObservableObject

Then, we must change the IoC container that is used in MVVM Light to the one used in the MVVM toolkit. For that, you must add this code in App.xaml.cs:

/// <summary> 
/// Gets the current <see cref="App"/> instance in use 
/// </summary> 
public new static App Current => (App)Application.Current; 

/// <summary> 
/// Gets the <see cref="IServiceProvider"/> instance to resolve application services. 
/// </summary> 
public IServiceProvider Services { get; } 

/// <summary> 
/// Configures the services for the application. 
/// </summary> 
private static IServiceProvider ConfigureServices() 
{ 
  var services = new ServiceCollection(); 
  services.AddSingleton<MainViewModel>(); 
return services.BuildServiceProvider(); 
}
C#

This code will configure the service collection, adding the instantiation of MainViewModel as a singleton, when required and will configure the service provider. We should call the ConfigureServices method in the constructor, like this:

public App() 
{
  Services = ConfigureServices();
  this.InitializeComponent();
  this.Suspending += OnSuspending; 
}
C#

Now, we can get a reference to the ViewModel, when we need it, as the DataContext for the view. In MainWindow.xaml.cs, when you try to set the DataContext like this, you get an error, saying that DataContext is not defined:

public MainWindow()
{
    this.InitializeComponent();
    DataContext = App.Current.Services.GetService(typeof(MainViewModel));
}
C#

That’s because the Window in WinUI isn’t a UIElement and thus, doesn’t have a DataContext. There are two workarounds to this issue:

  • Use x:Bind, that doesn’t need a DataContext for data binding
  • Set the DataContext to the main element of the window

So, we’ll choose the second option and initialize the DataContext with this code:

public MainWindow()
{
    this.InitializeComponent();
    MainGrid.DataContext = App.Current.Services.GetService(typeof(MainViewModel));
}
C#

The next step is to remove the ViewModelLocator, as it won’t be needed. One other error is in the converter. The namespaces in the WinUI have changed, and we must change them to use the code. System.Windows.Data has changed to Microsoft.UI.Xaml.Data and System.Windows.Media.Imaging has changed to Microsoft.UI.Xaml.Media.Imaging (as you can see, there was a change from System.Windows to Microsoft.UI.Xaml).

Besides that, in WinUI, the interface IValueConverter is implemented, but the signatures of Convert and ConvertBack are different, so we have to change the signature of the methods (the code remains unchanged):

public object Convert(object value, Type targetType, object parameter, string language)
{
    string imagePath = $"{AppDomain.CurrentDomain.BaseDirectory}Photos\\{value}.jpg";
    BitmapImage bitmapImage = !string.IsNullOrWhiteSpace(value?.ToString()) &&
                              File.Exists(imagePath) ?
        new BitmapImage(new Uri(imagePath)) :
        null;
    return bitmapImage;
}

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

Now we can add the FlipView to the main window. In this case, we don’t need any component nor setup in the code behind – all the code is in MainWindow.xaml:

<Grid x:Name="MainGrid">
 <FlipView ItemsSource="{Binding Photos}">
   <FlipView.ItemTemplate>
     <DataTemplate>
       <Grid Margin="5">
         <Grid.RowDefinitions>
           <RowDefinition Height="\*" />
           <RowDefinition Height="40" />
         </Grid.RowDefinitions>
         <Image Source="{Binding PhotoUrl}" Grid.Row="0" Margin="5"
             Stretch="Uniform" />
         <TextBlock Text="{Binding UserName}" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1"/>
       </Grid>
     </DataTemplate>
   </FlipView.ItemTemplate>
 </FlipView>
</Grid>
XML

With that, we can run the program and we get another error:

We get a DirectoryNotFoundException because of the package. When we are using the package, the current directory has changed and it’s not the install folder anymore, so we have to point to the full path. This is done with a code like this:

public MainViewModel()
{
    var fileName = $"{AppDomain.CurrentDomain.BaseDirectory}Photos\\_credits.json";
    Photos = JsonConvert.DeserializeObject<Dictionary<string, PhotoData>>(
        File.ReadAllText(fileName),
        new JsonSerializerSettings
        {
            ContractResolver = new DefaultContractResolver
            {
                NamingStrategy = new SnakeCaseNamingStrategy()
            }
        }).Select(p => new PhotoData() { PhotoUrl = $".\\Photos\\{p.Key}.jpg", UserName = p.Value.UserName });
}
C#

Now, when you run the app, it should run ok, but I got a C++ exception:

In this case, I just unchecked the “Break when this exception is thrown” box and everything worked fine:

We now have our app modernized with WinUI3 components, there is no need to add a new component to interface, like XamlIslands and everything works as a single program. There were some things to update, like components that weren’t ported to .NET 5, new namespaces and some issues with the path, but the changes were pretty seamless. WinUI3 is still in preview and there will surely be changes before it goes to production, but you can have a glance of what is coming and how you can modernize your apps with WinUI 3.

All the code for this article is at https://github.com/bsonnino/WinUiViewer

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • May 2025
  • December 2024
  • October 2024
  • August 2024
  • July 2024
  • June 2024
  • November 2023
  • October 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • June 2022
  • April 2022
  • March 2022
  • February 2022
  • January 2022
  • July 2021
  • June 2021
  • May 2021
  • April 2021
  • March 2021
  • February 2021
  • January 2021
  • December 2020
  • October 2020
  • September 2020
  • April 2020
  • March 2020
  • January 2020
  • November 2019
  • September 2019
  • August 2019
  • July 2019
  • June 2019
  • April 2019
  • March 2019
  • February 2019
  • January 2019
  • December 2018
  • November 2018
  • October 2018
  • September 2018
  • August 2018
  • July 2018
  • June 2018
  • May 2018
  • November 2017
  • October 2017
  • September 2017
  • August 2017
  • June 2017
  • May 2017
  • March 2017
  • February 2017
  • January 2017
  • December 2016
  • November 2016
  • October 2016
  • September 2016
  • August 2016
  • July 2016
  • June 2016
  • May 2016
  • April 2016
  • March 2016
  • February 2016
  • October 2015
  • August 2013
  • May 2013
  • February 2012
  • January 2012
  • April 2011
  • March 2011
  • December 2010
  • November 2009
  • June 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • July 2008
  • March 2008
  • February 2008
  • January 2008
  • December 2007
  • November 2007
  • October 2007
  • September 2007
  • August 2007
  • July 2007
  • Development
  • English
  • Português
  • Uncategorized
  • Windows

.NET AI Algorithms asp.NET Backup C# Debugging Delphi Dependency Injection Desktop Bridge Desktop icons Entity Framework JSON Linq Mef Minimal API MVVM NTFS Open Source OpenXML OzCode PowerShell Sensors Silverlight Source Code Generators sql server Surface Dial Testing Tools TypeScript UI Unit Testing UWP Visual Studio VS Code WCF WebView2 WinAppSDK Windows Windows 10 Windows Forms Windows Phone WPF XAML Zip

  • Entries RSS
  • Comments RSS
©2025 Bruno Sonnino | Design: Newspaperly WordPress Theme
Menu
  • Home
  • About