Da WinForms e WPF ad Avalonia con la stessa API sgcUI

sgcUI offre controlli nativi per WinForms, WPF e Avalonia che condividono un modello per prodotto. Le colonne della griglia, il grafico, i dati del planner e il tema si scrivono una sola volta e si compilano senza modifiche per ogni host, quindi portare una schermata su Avalonia, e con essa su macOS e Linux, significa cambiare il codice della finestra e i tipi di controllo, non la logica della schermata. Questo kit mostra come, con il codice di un esempio che si compila e supera il proprio test automatico su tutti e tre gli host.

Un'app desktop Windows che deve funzionare su macOS e Linux

Schermate gestionali con griglie, grafici e scheduler, un livello dati che vuoi conservare e nessuna voglia di riscrivere la logica delle schermate in un nuovo framework UI.

Da WinForms

WinForms funziona solo su Windows. Con sgcUI i controlli TsgcWinForms* che usi oggi hanno equivalenti TsgcAvalonia* con gli stessi membri. Il codice di configurazione passa a una libreria di classi condivisa, il form diventa una finestra Avalonia e DataSource diventa ItemsSource.

Da WPF

Anche WPF funziona solo su Windows. I suoi controlli sgcUI sono i più vicini ad Avalonia: entrambi si collegano con ItemsSource, entrambi sono dichiarati in XAML con lo stesso URI di namespace sgc e la chiamata al thread UI è la principale modifica al codice.

Il percorso abituale per un'applicazione WPF esistente è Avalonia XPF, che il suo produttore descrive come "a binary-compatible WPF runtime" e "a fork of WPF that maintains API and binary compatibility across the managed surface area". Al momento della scrittura (2026-10-20) XPF è venduto con licenze perpetue per applicazione con dodici mesi di aggiornamenti e supporto: Internal a 9.500 EUR per app (distribuzione solo ai propri dipendenti), Business a 29.500 EUR per app (distribuzione commerciale su macOS e Linux desktop, Linux embedded come componente aggiuntivo da 21.250 EUR) ed Enterprise da 124.500 EUR per app per dispositivi mobili o browser. Le suite WPF di terze parti possono funzionare su XPF a determinate condizioni: le note di XPF sulle librerie di terze parti dicono che gli shim delle API Win32 sono "required for DevExpress controls", che alcuni controlli DevExpress dipendono da System.Drawing.Common (GDI+), "which is deprecated on non-Windows platforms", che i controlli DevExpress su Linux richiedono libgdiplus e che "Issues most commonly arise with controls that use GDI or WinForms components". Avalonia stesso è un framework UI gratuito con licenza MIT. sgcUI per .NET è concesso in licenza per sviluppatore, a partire da €380 per l'edizione .NET Standard, con il codice sorgente C# completo per WinForms, WPF e Avalonia, ridistribuzione binaria libera e nessun canone per applicazione. I pacchetti Community gratuiti sono su NuGet.

Un modello, tre host leggeri

Ogni prodotto sgcUI ha un modello che non sa nulla dell'host. I controlli WinForms, WPF e Avalonia sono viste leggere costruite su di esso.

Modelli condivisi

TsgcGridColumns, TsgcGridData, TsgcChart, TsgcPlannerData, TsgcPlannerSchedulerOptions, TsgcPivotEngine e TsgcDiagramDocument sono gli stessi tipi sui tre host. Il codice scritto su di essi si compila senza modifiche per ciascuno.

Gli stessi nomi di membri

Columns, Data, ShowFooter, GroupSummaries, GroupBy, SaveLayoutToString, LoadLayoutFromString, Chart, Options, View, Date, PrintSettings, CreatePrintSource, Print, ShowPrintPreview e ExportToPDF, più gli eventi della griglia come OnCellClick, OnSelectionChange e OnSortChange.

Lo stesso layout salvato

Un layout di griglia salvato con SaveLayoutToString è lo stesso testo JSON su ogni host. Nell'esempio i tre host scrivono il proprio layout in un file e i tre file hanno un unico SHA-256. Anche i temi (TsgcUITheme, sgcUIApplication.Theme, sgcUIApplication.AccentColor, alto contrasto) sono condivisi.

Il controllo host di ogni prodotto

La regola pratica: sostituisci il prefisso TsgcWinForms o TsgcWpf con TsgcAvalonia. Le eccezioni sono indicate nella tabella.

Prodotto WinForms WPF Avalonia
sgcGridTsgcWinFormsGridTsgcWpfGridTsgcAvaloniaGrid
Colonna dichiarata di sgcGridnel codice, 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 è Radial, Linear, Digital, Compass, Clock, LED, LEDBar, ProgressRing, VUMeter o Annunciator.
sgcDashboardTsgcWinFormsDashboardTsgcWpfDashboardTsgcAvaloniaDashboard
Tile di sgcDashboardTsgcWinFormsDashboardTile, ...ChartTile, ...GridTile, ...GaugeTile, ...PivotTile, TsgcWinFormsKPICard e TsgcWinFormsDashboardMapTileTsgcWpfDashboardTile, ...ChartTile, ...GridTile, ...GaugeTile, ...PivotTile, TsgcWpfKPICard. Nessun tile mappa.TsgcAvaloniaDashboardTile, ...ChartTile, ...GridTile, ...GaugeTile, ...PivotTile, TsgcAvaloniaKPICard. Nessun tile mappa.
sgcMapsSgcMapControl (namespace esegece.sgcMaps.WinForms)TsgcWpfMapTsgcAvaloniaMap
sgcEditorsTsgcWinForms<Editor>TsgcWpf<Editor>TsgcAvalonia<Editor>
Editor è ButtonEdit, CurrencyEdit, MaskEdit, SpinEdit, DatePicker, DateTimePicker, TimePicker, LookupEdit, SearchEdit, TokenEdit, Memo, ColorPicker, MonthCalendar, RadioGroup, CheckGroup, Rating o RangeSlider.
sgcNavigationTsgcWinFormsNavigationBar, ...NavigationTabs, ...NavigationBreadcrumb, ...DockManagerTsgcWpfNavigationBar, ...NavigationTabs, ...NavigationBreadcrumb, ...DockManagerTsgcAvaloniaNavigationBar, ...NavigationTabs, ...NavigationBreadcrumb, ...DockManager
sgcToolbarTsgcWinFormsRibbon, TsgcWinFormsToolbar, TsgcWinFormsStatusBarTsgcWpfRibbon, TsgcWpfToolbar, TsgcWpfStatusBarTsgcAvaloniaRibbon, TsgcAvaloniaToolbar, TsgcAvaloniaStatusBar
sgcWizardTsgcWinFormsWizard, TsgcWinFormsStepperTsgcWpfWizard, TsgcWpfStepperTsgcAvaloniaWizard, TsgcAvaloniaStepper
sgcRichEditTsgcWinFormsRichEditTsgcWpfRichEditTsgcAvaloniaRichEdit
Visualizzatore PDF di sgcPDFTsgcPDFViewerControlTsgcWpfPDFViewerTsgcAvaloniaPDFViewer
Designer di sgcReportsTsgcWinFormsReportDesignerTsgcWpfReportDesignerTsgcAvaloniaReportDesigner
Anteprima di sgcReportsTsgcReportPreviewVCL (un Panel WinForms)TsgcWpfReportPreviewTsgcAvaloniaReportPreview
sgcUICoreTsgcWinFormsAIQueryBar, TsgcWinFormsValidationSummaryTsgcWpfAIQueryBar, TsgcWpfValidationSummaryTsgcAvaloniaAIQueryBar, TsgcAvaloniaValidationSummary

Il namespace è uno per prodotto (esegece.sgcGrid, esegece.sgcCharts, ...) e uguale per i tre host, tranne la mappa WinForms, che è in esegece.sgcMaps.WinForms. I controlli host sono nei pacchetti esegece.<Product>.WinForms, .Wpf e .Avalonia; il tile mappa WinForms della dashboard è in esegece.sgcDashboard.Maps.WinForms. Su WPF e Avalonia ogni prodotto tranne sgcReports è dichiarato in XAML con un solo URI di namespace, xmlns:sgc="https://www.esegece.com/sgcui"; per sgcReports usa una mappatura clr-namespace.

I membri che cambiano in un porting

Tutto il resto dei controlli mantiene il proprio nome. Questi sono i punti in cui l'host emerge.

Aspetto WinForms WPF Avalonia
Collegare una lista alla grigliaDataSource (e DataMember)ItemsSource (DependencyProperty)ItemsSource (StyledProperty)
Membri di binding della grigliaAutoGenerateColumns, KeyMember, SelectedItem, ItemsBinding e ItemsWriteBack (Registered), uguali sui tre host
Sincronizzazione dell'elemento correntenessunaIsSynchronizedWithCurrentItemnessuna
Colonne e dati della grigliaColumns (TsgcGridColumns), Data (TsgcGridData)
Modello del graficoChart (TsgcChart)
Modello del plannerData (TsgcPlannerData), Options (TsgcPlannerSchedulerOptions)
TemaTheme del controllo (TsgcUITheme; utDefault segue sgcUIApplication.Theme)lo stesso, più sgcUICore_Theme_Avalonia.sgcUIApplyThemeToWindow
Thread UIControl.BeginInvokeDispatcher.BeginInvokeDispatcher.UIThread.Post
Avvio dell'applicazioneApplication.Run(form)new Application().Run(window)una classe Application con un tema e StartWithClassicDesktopLifetime
Target framework dell'appnet8.0-windows (anche net472)net8.0-windows (anche net472)net8.0, net9.0, net10.0

Righe HOST e righe SAME

Ogni estratto è copiato dall'esempio MigrateToAvalonia, che si compila in Release per le edizioni Registered e Community con 0 avvisi e supera il proprio test automatico su WinForms, WPF e Avalonia. Nei file host, una riga contrassegnata HOST differisce tra i tre host e una riga contrassegnata SAME è identica in tutti.

Scritto una sola volta: il livello condiviso

La libreria di classi condivisa fa riferimento solo ai pacchetti del modello esegece.sgcGrid, esegece.sgcCharts, esegece.sgcPlanner ed esegece.sgcUICore, mai a un pacchetto host. Le colonne della griglia, con raggruppamento e riepiloghi, si configurano su 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();
    }
}

Il grafico è un modello TsgcChart, con la serie a torta alimentata in tempo reale da una ObservableCollection tramite 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 solo tema per ogni controllo sgcUI dell'applicazione:

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;
}

L'unico servizio dell'host: pubblicare sul thread UI

Le collezioni collegate cambiano sul thread UI. Il livello condiviso non conosce l'host, quindi riceve la chiamata di pubblicazione come delegato:

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));
    });
}

I campi dei controlli differiscono solo per il tipo

Prima: i tipi WinForms o WPF. Dopo: i tipi 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;

Collegare la schermata: lo stesso blocco su ogni host

Due righe sono contrassegnate HOST: la chiamata al thread UI e il binding della lista. Il resto è lo stesso testo nei tre file.

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;

L'avvio dell'applicazione

Il codice di avvio è per natura codice dell'host. La chiamata al tema è la stessa sui tre host.

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();
    }
}

Otto passaggi per ogni schermata

L'app Windows continua a funzionare dopo ogni passaggio, quindi il passaggio può essere fatto una schermata alla volta.

1

Estrai il livello condiviso

Sposta i dati e la configurazione dei controlli in una libreria di classi che fa riferimento solo ai pacchetti del modello sgcUI (esegece.sgcGrid, esegece.sgcCharts, esegece.sgcPlanner, esegece.sgcUICore). Scrivila su TsgcGridColumns, TsgcChart, TsgcPlannerData e TsgcPlannerSchedulerOptions.

2

Chiamala da Windows

Fai in modo che la schermata WinForms o WPF esistente chiami quella libreria. Continua a funzionare su Windows come prima.

3

Crea l'app Avalonia

Un progetto net8.0 con Avalonia.Desktop, Avalonia.Themes.Fluent e i pacchetti esegece.<Product>.Avalonia di cui hai bisogno.

4

Sostituisci i tipi di controllo

TsgcWinForms* e TsgcWpf* diventano TsgcAvalonia*, con le eccezioni elencate nella tabella di mappatura.

5

Porta la finestra

Un Form con un TableLayoutPanel, o una Window WPF con una Grid, diventa una Window Avalonia con una Grid. Il XAML di WPF passa a .axaml e mantiene l'URI di namespace sgc.

6

Solo WinForms: il binding

Cambia DataSource in ItemsSource. Il codice WPF usa già ItemsSource.

7

La chiamata al thread UI

BeginInvoke o Dispatcher.BeginInvoke diventa Dispatcher.UIThread.Post.

8

Verifica ogni host

Esegui gli stessi controlli su ogni host, come fa il /selftest dell'esempio, e confronta il JSON del layout della griglia salvato: deve essere lo stesso testo.

Cosa non si associa uno a uno

Leggi questa sezione prima di pianificare il passaggio.

MigrateToAvalonia

Una schermata con una griglia di 40 ordini raggruppati per regione, un grafico a torta dell'importo per regione e uno scheduler della settimana lavorativa, mostrata da tre host a partire da un unico livello condiviso.

Dove si trova

Nelle demo .NET di sgcUI, cartella Suite\MigrateToAvalonia della soluzione sgcUI_Demos.sln: Shared, WinForms, Wpf, Avalonia e un readme.html. Nel download della demo i progetti fanno riferimento ai pacchetti NuGet di sgcUI.

Come eseguirla

dotnet build Avalonia\sgcUIMigrate.Avalonia.csproj -c Release, e lo stesso per WinForms\sgcUIMigrate.WinForms.csproj e Wpf\sgcUIMigrate.Wpf.csproj. Aggiungi -p:SgcCommunity=true per l'edizione Community. L'host Avalonia funziona anche su macOS e Linux.

Test automatico

sgcUIMigrate.<Host>.exe /selftest [folder] verifica le righe, il raggruppamento, la torta, lo scheduler, un ordine aggiunto su un thread di lavoro e un ordine rimosso, scrive migrate_selftest_<host>.txt e migrate_layout_<host>.json, e termina con il numero di controlli falliti. Tutti i 13 controlli sono superati sui tre host e i tre file di layout sono identici byte per byte.

Per essere onesti con entrambe le strade: XPF punta a eseguire un'applicazione WPF esistente su macOS e Linux con codice e controlli in gran parte invariati, e sgcUI non lo fa. Con sgcUI porti su Avalonia il codice della finestra di ogni schermata, e le parti sgcUI della schermata passano senza modifiche. Le schermate costruite con controlli di altri produttori richiedono che quei controlli vengano sostituiti per primi, su Windows, e solo dopo si porta la schermata. sgcUI è la strada giusta quando le tue schermate usano, o possono passare a, la griglia, i grafici, il planner, il pivot, il diagramma e gli altri prodotti di sgcUI elencati sopra, e vuoi controlli Avalonia nativi con il codice sorgente completo e nessun livello di runtime intermedio. Per i prodotti e le edizioni vedi la pagina del prodotto sgcUI.

La scelta più conveniente: All-AccessTutti i prodotti eSeGeCe, con Supporto Premium incluso, a partire da €1,059/anno.
Vedi i prezzi All-Access

Porta oggi la tua prima schermata

Aggiungi i pacchetti Community gratuiti da NuGet, oppure i pacchetti Registered per ogni funzionalità e il codice sorgente completo, e porta la tua prima schermata con l'esempio MigrateToAvalonia accanto.