WinUI 3 et “Project Reunion” : que vous promet Microsoft pour moderniser vos applications desktop ?

En mars 2021, Microsoft annonce la sortie de « Project Reunion 0.5 preview », un projet ambitieux. Avec « Project Reunion », Microsoft introduit WinUI 3.0 et le souhait d’unifier les plateformes de développement ciblant les devices Windows 10.

Pour cela, la volonté est de réunir deux mondes seulement similaires d’apparence. D’un côté, nous avons les développeurs d’applications desktop C++, au travers des applications Win32 C++ ou MFC et de l’autre, les développeurs d’applications C# avec les applications UWP, WPF et Winform. Par ailleurs, les développeurs auront l’opportunité de moderniser les applications natives Win32 C++ et UWP vers le système « fluent design » propre à Windows 10 et ainsi gagner en sobriété et en élégance de manière native et intuitive.

Avant tout, l’objectif est d’offrir aux développeurs la possibilité d’utiliser du C++ ou du C# pour des applications visant l’ensemble des plateformes compatibles Windows 10 (Desktop, tablette, écran tactile, Xbox, Hololens, Surface, …). Bien que les deux types d’applications soient dites « natives », avant ce « Project Reunion », de grandes disparités demeurent pour adresser les différentes plateformes. Par exemple, les applications Xbox sont réservées à UWP, tandis que les pilotes Windows le sont pour du Win32 C++. Jusqu’à présent, les applications Win32 C++ et UWP se complétaient dans l’écosystème Windows, chacune avec un rôle bien défini. Mais à présent, le « Project Reunion » et sa librairie WinUI engagent les développeurs à choisir leur solution de prédilection pour adresser les projets Windows 10 (figure 1)   

 

 

 

Figure 1 : https://docs.microsoft.com/fr-fr/windows/apps/winui/

WinUI 3 découle d’un projet sorti avec la version Windows 10 1903 en mai 2019 : Xaml Island. L’idée derrière ce projet est de proposer au développeur d’améliorer l’apparence et les fonctionnalités des applications Desktop WPF, WinForm ou Win32 C++ grâce au contrôle Xaml WinRt. Techniquement, cette volonté de Microsoft permet au développeur d’ajouter des contrôles UWP modernes à d’anciennes applications. Cela fonctionne parfaitement, que ce soit en utilisant le sdk avec les contrôles de Windows.UI.Xaml.UIElement ou le Windows Community Toolkit, les développeurs WPF ainsi que Winform peuvent ajouter des contrôles de type WebView ou InkCanvas (et bien d’autres contrôles en supplément). Comme vous avez pu le remarquer dans la figure 1, WinUI 3 incorpore directement les contrôles Xaml grâce à Microsoft.UI.Xaml.* issus du projet Xaml Island. WinUI est donc la librairie regroupant l’ensemble des contrôles modernes appliqués au Fluent Design. Nous pourrions conclure que WinUI est l’effort réalisé par Microsoft pour extraire l’ensemble des commandes et fonctionnalités propres au système d’exploitation Windows afin que les développeurs utilisent les composants visuels sans se soucier du système d’exploitation cible, tout en conservant le XAML (Evitant ainsi au développeur de réapprendre un nouveau langage). Et c’est tout à l’honneur de Microsoft !  

Mais le C# et le .Net 5 dans tout cela ?

Pour voir le rendu d’un premier projet, il est nécessaire d’installer la dernière version preview de Visual Studio. Pour l’heure, il s’agit de la version 16.10 Preview 1. Il est également nécessaire d’installer 2 extensions : Project Reunion et WinUI 3 Project Templates. Une fois installées, si nous nous concentrons sur la partie C#, deux choix s’offrent à nous pour créer un nouveau projet :

  • Un template Desktop à base de .Net 5
  • Un template Desktop ciblant la plateforme universelle Uwp

La grande nouveauté de Project Reunion est d’offrir au développeur la possibilité de créer des applications desktop WinUI en ciblant directement .Net 5. Pour réaliser ce tour de passe-passe et rendre compatible ce qui ne l’est pas au départ, WinUI 3 dépend directement du package Nuget C#/WinRt et de sa dll WinRT.Runtime.dll. C#/WinRt est ce que l’on appelle la projection de Windows Runtime (WinRT) pour le language C#. Cette projection permet d’utiliser toutes les API de WinRT de façon native et surtout naturelle, comme si elles étaient développées directement C#. C#/WinRt est par conséquent un adaptateur entre C# et WinRT pour fournir les bons mappages en équivalent .Net comme les chaînes, les URI, les collections génériques, etc…. Si nous prenons au hasard l’interface Ilist<T> du fichier Ilist.net5.cs issue de GitHub : https://github.com/microsoft/CsWinRT/blob/master/src/WinRT.Runtime/Projections/IList.net5.cs

private readonly global::Windows.Foundation.Collections.IVector<T> _vector; public FromAbiHelper(global::Windows.Foundation.Collections.IVector<T> vector) { _vector = vector; } public int Count { get { uint size = _vector.Size; if (((uint)int.MaxValue) < size) { throw new InvalidOperationException(ErrorStrings.InvalidOperation_CollectionBackingListTooLarge); } return (int)size; } }

On se rend compte directement que l’interface « IList » dépend elle-même d’une interface « IVector » issue de la collection Windows Foundation, l’appelation Vector étant propre au C++. Pour simplifier, nous avons donc une projection qui prend en entrée du C# que nous pouvons utiliser avec une liste générique Ilist et avec un IVector en C++ en sortie. La méthode « Count » permet de récupérer directement la taille de la liste tout à fait naturellement. Je vous invite à regarder les autres méthodes. Pour aller plus loin, C#/WinRt intègre un outil nommé cswinrt.exe dont l’objectif est de générer du code interopérable C# .Net 5 et .Net Standard 2.0 à partir des fichiers de metadonnées de type *.winmd. Par la suite, ce code utilisera directement la librairie WinRt.runtime.dll. Toute la subtilité réside dans le fait que cswinrt.exe a ingéré de base tous les fichiers *.winmd de WinUI et généré le code C# .Net 5 interopérable. Par conséquent, chaque développeur obtient la possibilité de créer sa propre bibliothèque .Net Desktop WinUI. Cela dit, elle soulève peu d’intérêt car Microsoft la fournit directement et elle est nommé Microsoft.WinUI.dll. C’est cette même dll qui est intégrée dans le nuget Win UI 3 et qui découle de la Windows.WinUI.dll de Xaml Islands. Par conséquent qu’il s’agisse du compilateur C# ou du Runtime .Net, aucune autre connaissance supplémentaire au-delà de ces librairies n’est impérative pour développer des applications performantes et élégantes de style fluent design.  

Notre premier projet C# .Net 5

Si nous en revenons à la création d’une application .Net 5 WinUI 3, vous devez donc choisir le template Blank App, Packaged (WinUI 3 in desktop) :  

 

 

 

Figure 2 template pour la création de l'application

Une fois créé, la première chose à remarquer est la mise en place de deux projets de base. Un projet avec le code de l’application .Net 5, facilement reconnaissable car le .csproj est directement lisible depuis Visual Studio. Le deuxième projet est destiné à la création du package de l’application pour générer un package MSIX classique. Pour réaliser un exemple, nous pourrions nous baser directement sur l’excellente application Xaml Control Gallery disponible dans le store Windows. Cette application reprend l’ensemble des controls disponibles dans Xaml Island avec une suite d’exemples pour chaque contrôle ainsi que le code xaml associé. Un lien vers GitHub est également disponible dans chaque page menant à l’exemple affiché.  

 

 

 

Figure 3 Aperçu de Xaml Controls Gallery

En prenant la partie TabView, il me suffit d’intégrer ce code dans ma page xaml windows :

<Window x:Class="TestWinUi3Desktop.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:TestWinUi3Desktop" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" mc:Ignorable="d"> <Grid> <muxc:TabView AddTabButtonClick="TabView_AddButtonClick" TabCloseRequested="TabView_TabCloseRequested" Loaded="TabView_Loaded" /> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> </StackPanel> </Grid> </Window>

Le résultat est sans appel :

 

 

 

Figure 4 vue onglet .net 5

En appuyant sur les différents onglets, la page change et je peux même ajouter un nouvel onglet avec l’option +. De plus, il me suffit de coder le code behind C# pour avoir une gestion d’onglets pleinement opérationnelle :

using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; using System.Collections.ObjectModel; using Microsoft.UI.Xaml.Controls; using System; using System.Diagnostics; using Windows.Foundation; using Windows.Foundation.Metadata; using Windows.UI.Xaml; using System.Linq; using Windows.System; using Windows.UI.ViewManagement; using Windows.ApplicationModel.Core; using Windows.UI.Core; using WinRT.Interop; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. namespace TestWinUi3Desktop { public class MyData { public string DataHeader { get; set; } public Microsoft.UI.Xaml.Controls.IconSource DataIconSource { get; set; } public object DataContent { get; set; } } /// <summary> /// An empty window that can be used on its own or navigated to within a Frame. /// </summary> public sealed partial class MainWindow : Window { ObservableCollection<MyData> myDatas; public MainWindow() { this.InitializeComponent(); InitializeDataBindingSampleData(); } private void TabView_Loaded(object sender, RoutedEventArgs e) { for (int i = 0; i < 3; i++) { (sender as TabView).TabItems.Add(CreateNewTab(i)); } } private void TabView_AddButtonClick(TabView sender, object args) { sender.TabItems.Add(CreateNewTab(sender.TabItems.Count)); } private void TabView_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args) { sender.TabItems.Remove(args.Tab); } private TabViewItem CreateNewTab(int index) { TabViewItem newItem = new TabViewItem(); newItem.Header = $"Document {index}"; newItem.IconSource = new Microsoft.UI.Xaml.Controls.SymbolIconSource() { Symbol = Symbol.Document }; Frame frame = new Frame(); switch (index % 3) { case 0: frame.Navigate(typeof(SamplePage1)); break; case 1: frame.Navigate(typeof(SamplePage2)); break; case 2: frame.Navigate(typeof(SamplePage3)); break; } newItem.Content = frame; return newItem; } private void InitializeDataBindingSampleData() { myDatas = new ObservableCollection<MyData>(); for (int index = 0; index < 3; index++) { myDatas.Add(CreateNewMyData(index)); } } private MyData CreateNewMyData(int index) { var newData = new MyData { DataHeader = $"MyData Doc {index}", DataIconSource = new Microsoft.UI.Xaml.Controls.SymbolIconSource() { Symbol = Symbol.Placeholder } }; Frame frame = new Frame(); switch (index % 3) { case 0: frame.Navigate(typeof(SamplePage1)); break; case 1: frame.Navigate(typeof(SamplePage2)); break; case 2: frame.Navigate(typeof(SamplePage3)); break; } newData.DataContent = frame; return newData; } private void TabViewItemsSourceSample_AddTabButtonClick(TabView sender, object args) { myDatas.Add(CreateNewMyData(myDatas.Count)); } private void TabViewItemsSourceSample_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args) { myDatas.Remove(args.Item as MyData); } } }

A ce stade, je n’ai pris que le code fourni par Microsoft dans son application Xaml Controls Galery.  

Conclusion

Avec Project Reunion et WinUI 3, Microsoft lance une invitation à toute la communauté pour récupérer des feedbacks et ainsi améliorer de manière continue son projet. Concernant la partie C#, WinUI 3 semble prometteur et l’incorporation de l’ensemble des contrôles de WinRT permettra à terme d’améliorer grandement la productivité des développements sur les plateformes Windows. Vous pouvez retrouver mon code source sur mon GitHub.    

Article initialement publié dans le magazine Inside<C#>