De WinForms et WPF vers Avalonia avec la même API sgcUI

sgcUI livre des contrôles natifs pour WinForms, WPF et Avalonia qui partagent un modèle par produit. Les colonnes de la grille, le graphique, les données du planificateur et le thème s'écrivent une seule fois et se compilent sans changement pour chaque hôte, si bien que porter un écran vers Avalonia, et avec lui vers macOS et Linux, revient à changer le code de la fenêtre et les types de contrôle, pas la logique de l'écran. Ce kit montre comment, avec le code d'un exemple qui compile et réussit son autotest sur les trois hôtes.

Une application de bureau Windows qui doit tourner sur macOS et Linux

Des écrans métier avec grilles, graphiques et planificateurs, une couche de données que vous voulez conserver, et aucune envie de réécrire la logique des écrans dans un nouveau framework d'interface.

Depuis WinForms

WinForms ne fonctionne que sous Windows. Avec sgcUI, les contrôles TsgcWinForms* que vous utilisez aujourd'hui ont des équivalents TsgcAvalonia* avec les mêmes membres. Le code de configuration passe dans une bibliothèque de classes partagée, la fiche devient une fenêtre Avalonia, et DataSource devient ItemsSource.

Depuis WPF

WPF ne fonctionne lui aussi que sous Windows. Ses contrôles sgcUI sont les plus proches d'Avalonia : tous deux se lient avec ItemsSource, tous deux se déclarent en XAML avec le même URI d'espace de noms sgc, et l'appel au thread d'interface est le principal changement de code.

La voie habituelle pour une application WPF existante est Avalonia XPF, que son éditeur décrit comme « un runtime WPF compatible au niveau binaire » et « un fork de WPF qui maintient la compatibilité d'API et binaire sur toute la surface gérée ». Au moment de la rédaction (2026-10-20), XPF est vendu en licences perpétuelles par application avec douze mois de mises à jour et d'assistance : Internal à 9 500 EUR par application (déploiement à vos propres employés uniquement), Business à 29 500 EUR par application (déploiement commercial sur macOS et Linux de bureau, Linux embarqué en option à 21 250 EUR), et Enterprise à partir de 124 500 EUR par application pour le mobile ou le navigateur. Les suites WPF tierces peuvent tourner sur XPF sous conditions : les notes XPF sur les bibliothèques tierces indiquent que des shims d'API Win32 sont « nécessaires pour les contrôles DevExpress », que certains contrôles DevExpress dépendent de System.Drawing.Common (GDI+), « qui est obsolète sur les plateformes autres que Windows », que les contrôles DevExpress sous Linux nécessitent libgdiplus, et que « les problèmes surviennent le plus souvent avec les contrôles qui utilisent GDI ou des composants WinForms ». Avalonia lui-même est un framework d'interface sous licence MIT gratuit. sgcUI pour .NET est licencié par développeur, à partir de €380 pour l'édition .NET Standard, avec le code source C# complet pour WinForms, WPF et Avalonia, la redistribution binaire gratuite et aucuns frais par application. Des paquets Community gratuits sont disponibles sur NuGet.

Un modèle, trois hôtes légers

Chaque produit sgcUI a un modèle qui ne sait rien de l'hôte. Les contrôles WinForms, WPF et Avalonia en sont des vues légères.

Modèles partagés

TsgcGridColumns, TsgcGridData, TsgcChart, TsgcPlannerData, TsgcPlannerSchedulerOptions, TsgcPivotEngine et TsgcDiagramDocument sont les mêmes types sur les trois hôtes. Le code écrit contre eux se compile sans changement pour chacun.

Les mêmes noms de membres

Columns, Data, ShowFooter, GroupSummaries, GroupBy, SaveLayoutToString, LoadLayoutFromString, Chart, Options, View, Date, PrintSettings, CreatePrintSource, Print, ShowPrintPreview et ExportToPDF, ainsi que les événements de la grille comme OnCellClick, OnSelectionChange et OnSortChange.

La même mise en page enregistrée

Une mise en page de grille enregistrée avec SaveLayoutToString est le même texte JSON sur chaque hôte. Dans l'exemple, les trois hôtes écrivent leur mise en page dans un fichier et les trois fichiers ont un seul SHA-256. Les thèmes (TsgcUITheme, sgcUIApplication.Theme, sgcUIApplication.AccentColor, contraste élevé) sont eux aussi partagés.

Le contrôle hôte de chaque produit

La règle générale : remplacez le préfixe TsgcWinForms ou TsgcWpf par TsgcAvalonia. Les exceptions sont signalées dans le tableau.

Produit WinForms WPF Avalonia
sgcGridTsgcWinFormsGridTsgcWpfGridTsgcAvaloniaGrid
Colonne sgcGrid déclaréedans le code, Columns.AddTsgcWpfGridColumn (XAML)TsgcAvaloniaGridColumn (XAML)
sgcChartsTsgcWinFormsChart, TsgcWinFormsChartEditorTsgcWpfChart, TsgcWpfChartEditorTsgcAvaloniaChart, TsgcAvaloniaChartEditor
sgcPivotGridTsgcWinFormsPivotGrid, TsgcWinFormsPivotFieldListTsgcWpfPivotGrid, TsgcWpfPivotFieldListTsgcAvaloniaPivotGrid, TsgcAvaloniaPivotFieldList
sgcPlannerTsgcWinFormsScheduler, TsgcWinFormsGantt, TsgcWinFormsKanbanTsgcWpfScheduler, TsgcWpfGantt, TsgcWpfKanbanTsgcAvaloniaScheduler, TsgcAvaloniaGantt, TsgcAvaloniaKanban
sgcDiagramTsgcWinFormsDiagramView, TsgcWinFormsDiagramPalette, TsgcWinFormsDiagramMinimapTsgcWpfDiagramView, TsgcWpfDiagramPalette, TsgcWpfDiagramMinimapTsgcAvaloniaDiagramView, TsgcAvaloniaDiagramPalette, TsgcAvaloniaDiagramMinimap
sgcGaugesTsgcWinForms<Kind>GaugeTsgcWpf<Kind>GaugeTsgcAvalonia<Kind>Gauge
Kind est Radial, Linear, Digital, Compass, Clock, LED, LEDBar, ProgressRing, VUMeter ou Annunciator.
sgcDashboardTsgcWinFormsDashboardTsgcWpfDashboardTsgcAvaloniaDashboard
Tuiles sgcDashboardTsgcWinFormsDashboardTile, ...ChartTile, ...GridTile, ...GaugeTile, ...PivotTile, TsgcWinFormsKPICard et TsgcWinFormsDashboardMapTileTsgcWpfDashboardTile, ...ChartTile, ...GridTile, ...GaugeTile, ...PivotTile, TsgcWpfKPICard. Pas de tuile carte.TsgcAvaloniaDashboardTile, ...ChartTile, ...GridTile, ...GaugeTile, ...PivotTile, TsgcAvaloniaKPICard. Pas de tuile carte.
sgcMapsSgcMapControl (espace de noms esegece.sgcMaps.WinForms)TsgcWpfMapTsgcAvaloniaMap
sgcEditorsTsgcWinForms<Editor>TsgcWpf<Editor>TsgcAvalonia<Editor>
L'éditeur est ButtonEdit, CurrencyEdit, MaskEdit, SpinEdit, DatePicker, DateTimePicker, TimePicker, LookupEdit, SearchEdit, TokenEdit, Memo, ColorPicker, MonthCalendar, RadioGroup, CheckGroup, Rating ou RangeSlider.
sgcNavigationTsgcWinFormsNavigationBar, ...NavigationTabs, ...NavigationBreadcrumb, ...DockManagerTsgcWpfNavigationBar, ...NavigationTabs, ...NavigationBreadcrumb, ...DockManagerTsgcAvaloniaNavigationBar, ...NavigationTabs, ...NavigationBreadcrumb, ...DockManager
sgcToolbarTsgcWinFormsRibbon, TsgcWinFormsToolbar, TsgcWinFormsStatusBarTsgcWpfRibbon, TsgcWpfToolbar, TsgcWpfStatusBarTsgcAvaloniaRibbon, TsgcAvaloniaToolbar, TsgcAvaloniaStatusBar
sgcWizardTsgcWinFormsWizard, TsgcWinFormsStepperTsgcWpfWizard, TsgcWpfStepperTsgcAvaloniaWizard, TsgcAvaloniaStepper
sgcRichEditTsgcWinFormsRichEditTsgcWpfRichEditTsgcAvaloniaRichEdit
Visionneuse sgcPDFTsgcPDFViewerControlTsgcWpfPDFViewerTsgcAvaloniaPDFViewer
Concepteur sgcReportsTsgcWinFormsReportDesignerTsgcWpfReportDesignerTsgcAvaloniaReportDesigner
Aperçu sgcReportsTsgcReportPreviewVCL (un Panel WinForms)TsgcWpfReportPreviewTsgcAvaloniaReportPreview
sgcUICoreTsgcWinFormsAIQueryBar, TsgcWinFormsValidationSummaryTsgcWpfAIQueryBar, TsgcWpfValidationSummaryTsgcAvaloniaAIQueryBar, TsgcAvaloniaValidationSummary

L'espace de noms est un par produit (esegece.sgcGrid, esegece.sgcCharts, ...) et le même pour les trois hôtes, sauf la carte WinForms, qui est dans esegece.sgcMaps.WinForms. Les contrôles hôtes se trouvent dans les paquets esegece.<Product>.WinForms, .Wpf et .Avalonia ; la tuile carte WinForms du tableau de bord est dans esegece.sgcDashboard.Maps.WinForms. Sous WPF et Avalonia, chaque produit sauf sgcReports se déclare en XAML avec un seul URI d'espace de noms, xmlns:sgc="https://www.esegece.com/sgcui" ; pour sgcReports, utilisez une correspondance clr-namespace.

Les membres qui changent lors d'un portage

Tout le reste des contrôles garde son nom. Voici les endroits où l'hôte transparaît.

Sujet WinForms WPF Avalonia
Lier une liste à la grilleDataSource (et DataMember)ItemsSource (DependencyProperty)ItemsSource (StyledProperty)
Membres de liaison de la grilleAutoGenerateColumns, KeyMember, SelectedItem, ItemsBinding et ItemsWriteBack (Registered), identiques sur les trois hôtes
Synchronisation de l'élément courantaucuneIsSynchronizedWithCurrentItemaucune
Colonnes et données de la grilleColumns (TsgcGridColumns), Data (TsgcGridData)
Modèle du graphiqueChart (TsgcChart)
Modèle du planificateurData (TsgcPlannerData), Options (TsgcPlannerSchedulerOptions)
ThèmeTheme du contrôle (TsgcUITheme ; utDefault suit sgcUIApplication.Theme)identique, plus sgcUICore_Theme_Avalonia.sgcUIApplyThemeToWindow
Thread d'interfaceControl.BeginInvokeDispatcher.BeginInvokeDispatcher.UIThread.Post
Démarrage de l'applicationApplication.Run(form)new Application().Run(window)une classe Application avec un thème, et StartWithClassicDesktopLifetime
Framework cible de l'applicationnet8.0-windows (aussi net472)net8.0-windows (aussi net472)net8.0, net9.0, net10.0

Lignes HOST et lignes SAME

Chaque extrait est copié de l'exemple MigrateToAvalonia, qui compile en Release pour les éditions Registered et Community avec 0 avertissement et réussit son autotest sur WinForms, WPF et Avalonia. Dans les fichiers d'hôte, une ligne marquée HOST diffère entre les trois hôtes et une ligne marquée SAME est identique dans tous.

Écrit une seule fois : la couche partagée

La bibliothèque de classes partagée ne référence que les paquets de modèle esegece.sgcGrid, esegece.sgcCharts, esegece.sgcPlanner et esegece.sgcUICore, jamais un paquet d'hôte. Les colonnes de la grille, avec regroupement et totaux, se configurent contre TsgcGridColumns :

Shared\TMigrateModel.cs
// the grid columns, grouped by Region, with a sum of Amount in the
// footer and in every group row; TsgcGridColumns is the Columns of
// TsgcWinFormsGrid, TsgcWpfGrid and TsgcAvaloniaGrid
public static void ConfigureGrid(TsgcGridColumns aColumns)
{
    aColumns.BeginUpdate();
    try
    {
        aColumns.Clear();
        aColumns.Add(nameof(TOrder.Id), "Order", TsgcGridDataType.gdtInteger).Width = 80;
        aColumns.Add(nameof(TOrder.Customer), "Customer").Width = 180;
        aColumns.Add(nameof(TOrder.Region), "Region").GroupIndex = 0;
        TsgcGridColumn oDate = aColumns.Add(nameof(TOrder.Date), "Date", TsgcGridDataType.gdtDateTime);
        oDate.Width = 110;
        oDate.DisplayFormat = "yyyy-mm-dd";
        TsgcGridColumn oAmount = aColumns.Add(nameof(TOrder.Amount), "Amount", TsgcGridDataType.gdtFloat);
        oAmount.Width = 120;
        oAmount.DisplayFormat = "#,##0.00";
        oAmount.SummaryType = TsgcGridSummaryType.gsuSum;
        oAmount.SummaryFormat = "\"Total: \"#,##0.00";
        aColumns.Add(nameof(TOrder.Status), "Status").Width = 100;
    }
    finally
    {
        aColumns.EndUpdate();
    }
}

Le graphique est un modèle TsgcChart, sa série en secteurs alimentée en direct depuis une ObservableCollection via TsgcChartDataSource_Items :

Shared\TMigrateModel.cs
// the chart model: one pie series fed by RegionTotals through the
// items data source (live: a changed Amount updates its slice)
Chart = new TsgcChart();
Chart.Title.Text = "Amount by region";
Chart.Title.Visible = true;
Chart.Legend.Position = TsgcChartLegendPosition.clpTop;
Pie = new TsgcChartSeries_Pie();
Pie.Title = "Amount";
Pie.Chart = Chart;
FChartSource = new TsgcChartDataSource_Items();
FChartSource.Series = Pie;
FChartSource.LabelMember = nameof(TRegionTotal.Region);
FChartSource.YMember = nameof(TRegionTotal.Amount);
FChartSource.Items = RegionTotals;

Un seul thème pour chaque contrôle sgcUI de l'application :

Shared\TMigrateModel.cs
// the theme of every sgcUI control of the application (their Theme is
// utDefault, so they follow sgcUIApplication.Theme), and one accent
public static void ApplyTheme()
{
    sgcUIApplication.Theme = TsgcUITheme.utLight;
    sgcUIApplication.AccentColor = 0xFF2563EB;
}

Le seul service d'hôte : poster vers le thread d'interface

Les collections liées changent sur le thread d'interface. La couche partagée ne connaît pas l'hôte, elle reçoit donc l'appel de publication sous forme de délégué :

Shared\TMigrateModel.cs
// aPost runs an action on the UI thread; it is the only host specific
// service of the layer (WinForms Control.BeginInvoke, WPF
// Dispatcher.BeginInvoke, Avalonia Dispatcher.UIThread.Post)
public TMigrateModel(Action<Action> aPost)
Shared\TMigrateModel.cs
// a feed on a worker thread: the order is built there and posted to the
// UI thread with aPost, because bound collections change on the UI thread
public Task AddOrderFromWorker()
{
    return Task.Run(() =>
    {
        TOrder oOrder = DoNewOrder();
        FPost(() => DoAdd(oOrder));
    });
}

Les champs de contrôle ne diffèrent que par le type

Avant : les types WinForms ou WPF. Après : les types Avalonia.

WinForms\TMigrateForm.cs
// HOST: the WinForms control types
private readonly TsgcWinFormsGrid FGrid;
private readonly TsgcWinFormsChart FChart;
private readonly TsgcWinFormsScheduler FScheduler;
Wpf\TMigrateWindow.cs
// HOST: the WPF control types
private readonly TsgcWpfGrid FGrid;
private readonly TsgcWpfChart FChart;
private readonly TsgcWpfScheduler FScheduler;
Avalonia\TMigrateWindow.cs
// HOST: the Avalonia control types
private readonly TsgcAvaloniaGrid FGrid;
private readonly TsgcAvaloniaChart FChart;
private readonly TsgcAvaloniaScheduler FScheduler;

Lier l'écran : le même bloc sur chaque hôte

Deux lignes sont marquées HOST : l'appel au thread d'interface et la liaison de la liste. Le reste est le même texte dans les trois fichiers.

WinForms\TMigrateForm.cs
// HOST: WinForms reaches the UI thread with Control.BeginInvoke
FModel = new TMigrateModel(aAction => BeginInvoke(aAction));

// SAME on every host: the shared columns, footer and group sums
TMigrateModel.ConfigureGrid(FGrid.Columns);
FGrid.ShowFooter = true;
FGrid.GroupSummaries = true;
// HOST: WinForms binds with DataSource (WPF and Avalonia: ItemsSource)
FGrid.DataSource = FModel.Orders;
// SAME on every host: the shared chart model and planner data
FChart.Chart = FModel.Chart;
TMigrateModel.ConfigureScheduler(FScheduler.Options);
FScheduler.Data = FModel.Planner;
FScheduler.View = TsgcPlannerSchedulerViewKind.psvWorkWeek;
FScheduler.Date = TMigrateModel.CS_WEEK;
Wpf\TMigrateWindow.cs
// HOST: WPF reaches the UI thread with Dispatcher.BeginInvoke
FModel = new TMigrateModel(aAction => Dispatcher.BeginInvoke(aAction));

// SAME on every host: the shared columns, footer and group sums
TMigrateModel.ConfigureGrid(FGrid.Columns);
FGrid.ShowFooter = true;
FGrid.GroupSummaries = true;
// HOST: WPF binds with ItemsSource (WinForms: DataSource)
FGrid.ItemsSource = FModel.Orders;
// SAME on every host: the shared chart model and planner data
FChart.Chart = FModel.Chart;
TMigrateModel.ConfigureScheduler(FScheduler.Options);
FScheduler.Data = FModel.Planner;
FScheduler.View = TsgcPlannerSchedulerViewKind.psvWorkWeek;
FScheduler.Date = TMigrateModel.CS_WEEK;
Avalonia\TMigrateWindow.cs
// HOST: Avalonia reaches the UI thread with Dispatcher.UIThread.Post
FModel = new TMigrateModel(aAction => Dispatcher.UIThread.Post(aAction));

// SAME on every host: the shared columns, footer and group sums
TMigrateModel.ConfigureGrid(FGrid.Columns);
FGrid.ShowFooter = true;
FGrid.GroupSummaries = true;
// HOST: Avalonia binds with ItemsSource, as WPF (WinForms: DataSource)
FGrid.ItemsSource = FModel.Orders;
// SAME on every host: the shared chart model and planner data
FChart.Chart = FModel.Chart;
TMigrateModel.ConfigureScheduler(FScheduler.Options);
FScheduler.Data = FModel.Planner;
FScheduler.View = TsgcPlannerSchedulerViewKind.psvWorkWeek;
FScheduler.Date = TMigrateModel.CS_WEEK;

Le démarrage de l'application

Le code de démarrage est par nature du code d'hôte. L'appel du thème est le même sur les trois hôtes.

WinForms\Program.cs
// HOST: the WinForms application start
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// SAME on every host: one theme for every sgcUI control
TMigrateModel.ApplyTheme();
TMigrateForm oForm = new TMigrateForm(vTest ? vFolder : null);
Application.Run(oForm);
return oForm.ExitCode;
Wpf\Program.cs
// HOST: the WPF application start
Application oApp = new Application { ShutdownMode = ShutdownMode.OnMainWindowClose };
// SAME on every host: one theme for every sgcUI control
TMigrateModel.ApplyTheme();
TMigrateWindow oWindow = new TMigrateWindow(vTest ? vFolder : null);
oApp.Run(oWindow);
return oWindow.ExitCode;
Avalonia\Program.cs
// HOST: Avalonia needs an Application class with a theme
internal sealed class TMigrateApp : Application
{
    public static string TestFolder;

    public override void Initialize()
    {
        Styles.Add(new FluentTheme());
        RequestedThemeVariant = ThemeVariant.Light;
    }

    public override void OnFrameworkInitializationCompleted()
    {
        // SAME on every host: one theme for every sgcUI control
        TMigrateModel.ApplyTheme();
        IClassicDesktopStyleApplicationLifetime oDesktop =
            ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
        if (oDesktop != null)
            oDesktop.MainWindow = new TMigrateWindow(TestFolder);
        base.OnFrameworkInitializationCompleted();
    }
}

Huit étapes par écran

L'application Windows continue de fonctionner après chaque étape, la migration peut donc se faire écran par écran.

1

Extraire la couche partagée

Déplacez les données et la configuration des contrôles dans une bibliothèque de classes qui ne référence que les paquets de modèle sgcUI (esegece.sgcGrid, esegece.sgcCharts, esegece.sgcPlanner, esegece.sgcUICore). Écrivez-la contre TsgcGridColumns, TsgcChart, TsgcPlannerData et TsgcPlannerSchedulerOptions.

2

L'appeler depuis Windows

Faites en sorte que l'écran WinForms ou WPF existant appelle cette bibliothèque. Il continue de fonctionner sous Windows comme avant.

3

Créer l'application Avalonia

Un projet net8.0 avec Avalonia.Desktop, Avalonia.Themes.Fluent et les paquets esegece.<Product>.Avalonia dont vous avez besoin.

4

Échanger les types de contrôle

TsgcWinForms* et TsgcWpf* deviennent TsgcAvalonia*, avec les exceptions listées dans le tableau de correspondance.

5

Porter la fenêtre

Une Form avec un TableLayoutPanel, ou une Window WPF avec une Grid, devient une Window Avalonia avec une Grid. Le XAML WPF passe en .axaml et conserve l'URI d'espace de noms sgc.

6

WinForms uniquement : la liaison

Remplacez DataSource par ItemsSource. Le code WPF utilise déjà ItemsSource.

7

L'appel au thread d'interface

BeginInvoke ou Dispatcher.BeginInvoke devient Dispatcher.UIThread.Post.

8

Vérifier chaque hôte

Exécutez les mêmes vérifications sur chaque hôte, comme le fait le /selftest de l'exemple, et comparez le JSON de la mise en page enregistrée de la grille : ce doit être le même texte.

Ce qui ne correspond pas un pour un

Lisez ceci avant de planifier la migration.

MigrateToAvalonia

Un écran avec une grille de 40 commandes groupées par région, un graphique en secteurs du montant par région et un planificateur de semaine de travail, affichés par trois hôtes à partir d'une couche partagée.

Où la trouver

Dans les démos .NET de sgcUI, dossier Suite\MigrateToAvalonia de la solution sgcUI_Demos.sln : Shared, WinForms, Wpf, Avalonia et un readme.html. Dans le téléchargement des démos, les projets référencent les paquets NuGet sgcUI.

Comment l'exécuter

dotnet build Avalonia\sgcUIMigrate.Avalonia.csproj -c Release, et de même pour WinForms\sgcUIMigrate.WinForms.csproj et Wpf\sgcUIMigrate.Wpf.csproj. Ajoutez -p:SgcCommunity=true pour l'édition Community. L'hôte Avalonia fonctionne aussi sous macOS et Linux.

Autotest

sgcUIMigrate.<Host>.exe /selftest [folder] vérifie les lignes, le regroupement, le graphique en secteurs, le planificateur, une commande ajoutée sur un thread de travail et une commande supprimée, écrit migrate_selftest_<host>.txt et migrate_layout_<host>.json, et se termine avec le nombre de vérifications échouées. Les 13 vérifications réussissent sur les trois hôtes, et les trois fichiers de mise en page sont identiques octet pour octet.

Pour être juste envers les deux voies : XPF vise à faire tourner une application WPF existante sur macOS et Linux avec son code et ses contrôles en grande partie tels quels, et sgcUI ne fait pas cela. Avec sgcUI, vous portez le code de fenêtre de chaque écran vers Avalonia, et les parties sgcUI de l'écran passent sans changement. Les écrans construits sur des contrôles d'autres éditeurs demandent que ces contrôles soient d'abord remplacés, sous Windows, avant la migration. sgcUI est la voie à suivre lorsque vos écrans utilisent, ou peuvent passer à, la grille, les graphiques, le planificateur, le tableau croisé dynamique, le diagramme et les autres produits sgcUI ci-dessus, et que vous voulez des contrôles Avalonia natifs avec le code source complet et sans couche d'exécution intermédiaire. Pour les produits et les éditions, consultez la page produit sgcUI.

Meilleur rapport qualité-prix : All-AccessTous les produits eSeGeCe, Support Premium inclus, à partir de €1,059/an.
Voir les tarifs All-Access

Portez votre premier écran dès aujourd'hui

Ajoutez les paquets Community gratuits depuis NuGet, ou les paquets Registered pour toutes les fonctions et le code source complet, et portez votre premier écran avec l'exemple MigrateToAvalonia à côté.