De WinForms y WPF a Avalonia con la misma API de sgcUI

sgcUI incluye controles nativos para WinForms, WPF y Avalonia que comparten un modelo por producto. Las columnas del grid, la gráfica, los datos del planificador y el tema se escriben una sola vez y compilan sin cambios para cada host, de modo que llevar una pantalla a Avalonia, y con ella a macOS y Linux, significa cambiar el código de la ventana y los tipos de control, no la lógica de la pantalla. Este kit muestra cómo, con código de un ejemplo que compila y supera su autotest en los tres hosts.

Una aplicación de escritorio Windows que debe funcionar en macOS y Linux

Pantallas de gestión con grids, gráficas y planificadores, una capa de datos que quieres conservar y sin ganas de reescribir la lógica de pantalla en un nuevo framework de interfaz.

Desde WinForms

WinForms funciona solo en Windows. Con sgcUI, los controles TsgcWinForms* que usas hoy tienen gemelos TsgcAvalonia* con los mismos miembros. El código de configuración pasa a una biblioteca de clases compartida, el formulario se convierte en una ventana de Avalonia y DataSource pasa a ser ItemsSource.

Desde WPF

WPF también funciona solo en Windows. Sus controles sgcUI son los más parecidos a los de Avalonia: ambos se enlazan con ItemsSource, ambos se declaran en XAML con el mismo URI de espacio de nombres sgc, y la llamada al hilo de interfaz es el cambio de código principal.

La ruta habitual para una aplicación WPF existente es Avalonia XPF, que su fabricante describe como "a binary-compatible WPF runtime" y "a fork of WPF that maintains API and binary compatibility across the managed surface area". En el momento de escribir esto (2026-10-20) XPF se vende como licencias perpetuas por aplicación con doce meses de actualizaciones y soporte: Internal a 9.500 EUR por aplicación (despliegue solo a tus propios empleados), Business a 29.500 EUR por aplicación (despliegue comercial a macOS y Linux de escritorio, con Linux embebido como complemento de 21.250 EUR) y Enterprise desde 124.500 EUR por aplicación para móvil o el navegador. Las suites WPF de terceros pueden funcionar sobre XPF con condiciones: las notas de XPF sobre bibliotecas de terceros dicen que los shims de la API Win32 son "required for DevExpress controls", que algunos controles de DevExpress dependen de System.Drawing.Common (GDI+), "which is deprecated on non-Windows platforms", que los controles de DevExpress en Linux requieren libgdiplus, y que "Issues most commonly arise with controls that use GDI or WinForms components". Avalonia en sí es un framework de interfaz con licencia MIT y gratuito. sgcUI para .NET se licencia por desarrollador, desde €380 para la edición .NET Standard, con el código fuente C# completo para WinForms, WPF y Avalonia, redistribución binaria gratuita y sin cuota por aplicación. Los paquetes Community gratuitos están en NuGet.

Un modelo, tres hosts ligeros

Cada producto sgcUI tiene un modelo que no sabe nada del host. Los controles de WinForms, WPF y Avalonia son vistas ligeras sobre él.

Modelos compartidos

TsgcGridColumns, TsgcGridData, TsgcChart, TsgcPlannerData, TsgcPlannerSchedulerOptions, TsgcPivotEngine y TsgcDiagramDocument son los mismos tipos en los tres hosts. El código escrito contra ellos compila sin cambios para cada uno.

Los mismos nombres de miembro

Columns, Data, ShowFooter, GroupSummaries, GroupBy, SaveLayoutToString, LoadLayoutFromString, Chart, Options, View, Date, PrintSettings, CreatePrintSource, Print, ShowPrintPreview y ExportToPDF, además de los eventos del grid como OnCellClick, OnSelectionChange y OnSortChange.

El mismo diseño guardado

Un diseño de grid guardado con SaveLayoutToString es el mismo texto JSON en todos los hosts. En el ejemplo, los tres hosts escriben su diseño en un archivo y los tres archivos tienen un único SHA-256. Los temas (TsgcUITheme, sgcUIApplication.Theme, sgcUIApplication.AccentColor, alto contraste) también se comparten.

El control host de cada producto

La regla general: sustituye el prefijo TsgcWinForms o TsgcWpf por TsgcAvalonia. Las excepciones están marcadas en la tabla.

Producto WinForms WPF Avalonia
sgcGridTsgcWinFormsGridTsgcWpfGridTsgcAvaloniaGrid
Columna declarada de sgcGriden código, 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 es Radial, Linear, Digital, Compass, Clock, LED, LEDBar, ProgressRing, VUMeter o Annunciator.
sgcDashboardTsgcWinFormsDashboardTsgcWpfDashboardTsgcAvaloniaDashboard
Mosaicos de sgcDashboardTsgcWinFormsDashboardTile, ...ChartTile, ...GridTile, ...GaugeTile, ...PivotTile, TsgcWinFormsKPICard y TsgcWinFormsDashboardMapTileTsgcWpfDashboardTile, ...ChartTile, ...GridTile, ...GaugeTile, ...PivotTile, TsgcWpfKPICard. Sin mosaico de mapa.TsgcAvaloniaDashboardTile, ...ChartTile, ...GridTile, ...GaugeTile, ...PivotTile, TsgcAvaloniaKPICard. Sin mosaico de mapa.
sgcMapsSgcMapControl (espacio de nombres esegece.sgcMaps.WinForms)TsgcWpfMapTsgcAvaloniaMap
sgcEditorsTsgcWinForms<Editor>TsgcWpf<Editor>TsgcAvalonia<Editor>
Editor es 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
Visor de sgcPDFTsgcPDFViewerControlTsgcWpfPDFViewerTsgcAvaloniaPDFViewer
Diseñador de sgcReportsTsgcWinFormsReportDesignerTsgcWpfReportDesignerTsgcAvaloniaReportDesigner
Vista previa de sgcReportsTsgcReportPreviewVCL (un Panel de WinForms)TsgcWpfReportPreviewTsgcAvaloniaReportPreview
sgcUICoreTsgcWinFormsAIQueryBar, TsgcWinFormsValidationSummaryTsgcWpfAIQueryBar, TsgcWpfValidationSummaryTsgcAvaloniaAIQueryBar, TsgcAvaloniaValidationSummary

Hay un espacio de nombres por producto (esegece.sgcGrid, esegece.sgcCharts, ...) y es el mismo para los tres hosts, salvo el mapa de WinForms, que está en esegece.sgcMaps.WinForms. Los controles host vienen en los paquetes esegece.<Product>.WinForms, .Wpf y .Avalonia; el mosaico de mapa de WinForms del dashboard está en esegece.sgcDashboard.Maps.WinForms. En WPF y Avalonia todos los productos salvo sgcReports se declaran en XAML con un único URI de espacio de nombres, xmlns:sgc="https://www.esegece.com/sgcui"; para sgcReports usa una correspondencia clr-namespace.

Los miembros que cambian en una migración

Todo lo demás en los controles conserva su nombre. Estos son los puntos donde se nota el host.

Aspecto WinForms WPF Avalonia
Enlazar una lista al gridDataSource (y DataMember)ItemsSource (DependencyProperty)ItemsSource (StyledProperty)
Miembros de enlace del gridAutoGenerateColumns, KeyMember, SelectedItem, ItemsBinding y ItemsWriteBack (Registered), los mismos en los tres hosts
Sincronización del elemento actualningunaIsSynchronizedWithCurrentItemninguna
Columnas y datos del gridColumns (TsgcGridColumns), Data (TsgcGridData)
Modelo de gráficaChart (TsgcChart)
Modelo del planificadorData (TsgcPlannerData), Options (TsgcPlannerSchedulerOptions)
TemaTheme del control (TsgcUITheme; utDefault sigue a sgcUIApplication.Theme)lo mismo, más sgcUICore_Theme_Avalonia.sgcUIApplyThemeToWindow
Hilo de interfazControl.BeginInvokeDispatcher.BeginInvokeDispatcher.UIThread.Post
Inicio de la aplicaciónApplication.Run(form)new Application().Run(window)una clase Application con un tema, y StartWithClassicDesktopLifetime
Framework de destino de la aplicaciónnet8.0-windows (también net472)net8.0-windows (también net472)net8.0, net9.0, net10.0

Líneas HOST y líneas SAME

Cada fragmento está copiado del ejemplo MigrateToAvalonia, que compila en Release para las ediciones Registered y Community con 0 avisos y supera su autotest en WinForms, WPF y Avalonia. En los archivos de host, una línea marcada HOST difiere entre los tres hosts y una línea marcada SAME es idéntica en todos.

Escrito una vez: la capa compartida

La biblioteca de clases compartida referencia solo los paquetes de modelo esegece.sgcGrid, esegece.sgcCharts, esegece.sgcPlanner y esegece.sgcUICore, nunca un paquete de host. Las columnas del grid, con agrupación y resúmenes, se configuran contra 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();
    }
}

La gráfica es un modelo TsgcChart, con su serie circular alimentada en vivo desde una ObservableCollection mediante 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 tema para todos los controles sgcUI de la aplicación:

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

El único servicio del host: publicar en el hilo de interfaz

Las colecciones enlazadas cambian en el hilo de interfaz. La capa compartida no conoce el host, así que recibe la llamada de publicación como un delegado:

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

Los campos de control difieren solo en el tipo

Antes: los tipos de WinForms o de WPF. Después: los tipos de 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;

Enlazar la pantalla: el mismo bloque en cada host

Dos líneas están marcadas HOST: la llamada al hilo de interfaz y el enlace de la lista. El resto es el mismo texto en los tres archivos.

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;

El inicio de la aplicación

El código de inicio es código de host por naturaleza. La llamada al tema es la misma en los tres hosts.

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

Ocho pasos por pantalla

La aplicación de Windows sigue funcionando tras cada paso, así que el cambio puede hacerse pantalla a pantalla.

1

Extrae la capa compartida

Mueve los datos y la configuración de los controles a una biblioteca de clases que referencie solo los paquetes de modelo de sgcUI (esegece.sgcGrid, esegece.sgcCharts, esegece.sgcPlanner, esegece.sgcUICore). Escríbela contra TsgcGridColumns, TsgcChart, TsgcPlannerData y TsgcPlannerSchedulerOptions.

2

Llámala desde Windows

Haz que la pantalla WinForms o WPF existente llame a esa biblioteca. Sigue funcionando en Windows como antes.

3

Crea la aplicación Avalonia

Un proyecto net8.0 con Avalonia.Desktop, Avalonia.Themes.Fluent y los paquetes esegece.<Product>.Avalonia que necesites.

4

Cambia los tipos de control

TsgcWinForms* y TsgcWpf* pasan a TsgcAvalonia*, con las excepciones indicadas en la tabla de correspondencia.

5

Migra la ventana

Un Form con un TableLayoutPanel, o una Window de WPF con un Grid, pasa a ser una Window de Avalonia con un Grid. El XAML de WPF pasa a .axaml y conserva el URI de espacio de nombres sgc.

6

Solo WinForms: el enlace

Cambia DataSource por ItemsSource. El código WPF ya usa ItemsSource.

7

La llamada al hilo de interfaz

BeginInvoke o Dispatcher.BeginInvoke pasa a Dispatcher.UIThread.Post.

8

Comprueba cada host

Ejecuta las mismas comprobaciones en cada host, como hace el /selftest del ejemplo, y compara el JSON del diseño de grid guardado: debe ser el mismo texto.

Lo que no se corresponde uno a uno

Léelo antes de planificar el cambio.

MigrateToAvalonia

Una pantalla con un grid de 40 pedidos agrupados por región, una gráfica circular del importe por región y un planificador de semana laboral, mostrada por tres hosts a partir de una capa compartida.

Dónde está

En las demos .NET de sgcUI, carpeta Suite\MigrateToAvalonia de la solución sgcUI_Demos.sln: Shared, WinForms, Wpf, Avalonia y un readme.html. En la descarga de demos los proyectos referencian los paquetes NuGet de sgcUI.

Cómo ejecutarla

dotnet build Avalonia\sgcUIMigrate.Avalonia.csproj -c Release, y lo mismo para WinForms\sgcUIMigrate.WinForms.csproj y Wpf\sgcUIMigrate.Wpf.csproj. Añade -p:SgcCommunity=true para la edición Community. El host Avalonia también funciona en macOS y Linux.

Autotest

sgcUIMigrate.<Host>.exe /selftest [folder] comprueba las filas, la agrupación, el gráfico circular, el planificador, un pedido añadido desde un hilo de trabajo y un pedido eliminado, escribe migrate_selftest_<host>.txt y migrate_layout_<host>.json, y termina con el número de comprobaciones fallidas. Las 13 comprobaciones pasan en los tres hosts, y los tres archivos de diseño son idénticos byte a byte.

Para ser justos con ambas rutas: XPF pretende ejecutar una aplicación WPF existente en macOS y Linux con su código y sus controles en gran parte tal como están, y sgcUI no hace eso. Con sgcUI migras a Avalonia el código de ventana de cada pantalla, y las partes de sgcUI de la pantalla pasan sin cambios. Las pantallas construidas con controles de otros fabricantes necesitan que esos controles se sustituyan primero, en Windows, antes del cambio. sgcUI es el camino cuando tus pantallas usan, o pueden pasar a usar, el grid, las gráficas, el planificador, el pivot, el diagrama y los demás productos de sgcUI mencionados arriba, y quieres controles nativos de Avalonia con el código fuente completo y ninguna capa de ejecución intermedia. Para los productos y las ediciones consulta la página del producto sgcUI.

La mejor opción: All-AccessTodos los productos de eSeGeCe, con Premium Support incluido, desde €1,059 al año.
Ver precios de All-Access

Migra hoy tu primera pantalla

Añade los paquetes Community gratuitos de NuGet, o los paquetes Registered con todas las funciones y el código fuente completo, y migra tu primera pantalla con el ejemplo MigrateToAvalonia a tu lado.