Z WinForms i WPF do Avalonia z tym samym API sgcUI

sgcUI dostarcza natywne kontrolki dla WinForms, WPF i Avalonia, które współdzielą jeden model na produkt. Kolumny siatki, wykres, dane harmonogramu i motyw pisze się raz i kompiluje bez zmian dla każdego hosta, więc przeniesienie ekranu do Avalonia, a wraz z nim na macOS i Linux, sprowadza się do zmiany kodu okna i typów kontrolek, a nie logiki ekranu. Ten zestaw pokazuje jak, na kodzie przykładu, który się kompiluje i przechodzi swój autotest na wszystkich trzech hostach.

Aplikacja desktopowa dla Windows, która ma działać na macOS i Linux

Ekrany biznesowe z siatkami, wykresami i harmonogramami, warstwa danych, którą chcesz zachować, i brak ochoty na przepisywanie logiki ekranów w nowym frameworku interfejsu.

Z WinForms

WinForms działa tylko w systemie Windows. W sgcUI kontrolki TsgcWinForms*, których używasz dzisiaj, mają odpowiedniki TsgcAvalonia* z tymi samymi członkami. Kod konfiguracji trafia do wspólnej biblioteki klas, formularz staje się oknem Avalonia, a DataSource zamienia się w ItemsSource.

Z WPF

WPF także działa tylko w systemie Windows. Jego kontrolki sgcUI są najbliższe Avalonia: obie wiążą dane przez ItemsSource, obie deklaruje się w XAML z tym samym URI przestrzeni nazw sgc, a główną zmianą w kodzie jest wywołanie wątku interfejsu.

Typową drogą dla istniejącej aplikacji WPF jest Avalonia XPF, który jego wydawca opisuje jako „binarnie zgodne środowisko uruchomieniowe WPF” i „fork WPF, który utrzymuje zgodność API i zgodność binarną na całej powierzchni zarządzanej”. W chwili pisania (2026-10-20) XPF jest sprzedawany w licencjach wieczystych na aplikację z dwunastoma miesiącami aktualizacji i wsparcia: Internal za 9500 EUR na aplikację (wdrożenie tylko dla własnych pracowników), Business za 29 500 EUR na aplikację (wdrożenie komercyjne na macOS i desktopowym Linuksie, wbudowany Linux opcjonalnie za 21 250 EUR) oraz Enterprise od 124 500 EUR na aplikację dla urządzeń mobilnych lub przeglądarki. Zestawy WPF firm trzecich mogą działać na XPF pod pewnymi warunkami: uwagi XPF o bibliotekach firm trzecich podają, że shimy API Win32 są „wymagane dla kontrolek DevExpress”, że niektóre kontrolki DevExpress zależą od System.Drawing.Common (GDI+), „które jest przestarzałe na platformach innych niż Windows”, że kontrolki DevExpress w Linuksie wymagają libgdiplus oraz że „problemy najczęściej występują przy kontrolkach, które używają GDI lub komponentów WinForms”. Sama Avalonia to darmowy framework interfejsu na licencji MIT. sgcUI dla .NET jest licencjonowany na programistę, od €380 za edycję .NET Standard, z pełnym kodem źródłowym C# dla WinForms, WPF i Avalonia, bezpłatną redystrybucją binarną i bez opłat za aplikację. Bezpłatne pakiety Community są dostępne w NuGet.

Jeden model, trzy lekkie hosty

Każdy produkt sgcUI ma model, który nic nie wie o hoście. Kontrolki WinForms, WPF i Avalonia to jego lekkie widoki.

Wspólne modele

TsgcGridColumns, TsgcGridData, TsgcChart, TsgcPlannerData, TsgcPlannerSchedulerOptions, TsgcPivotEngine i TsgcDiagramDocument to te same typy na wszystkich trzech hostach. Kod pisany względem nich kompiluje się bez zmian dla każdego z nich.

Te same nazwy członków

Columns, Data, ShowFooter, GroupSummaries, GroupBy, SaveLayoutToString, LoadLayoutFromString, Chart, Options, View, Date, PrintSettings, CreatePrintSource, Print, ShowPrintPreview i ExportToPDF, a także zdarzenia siatki, takie jak OnCellClick, OnSelectionChange i OnSortChange.

Ten sam zapisany układ

Układ siatki zapisany przez SaveLayoutToString to ten sam tekst JSON na każdym hoście. W przykładzie wszystkie trzy hosty zapisują układ do pliku, a trzy pliki mają jeden SHA-256. Motywy (TsgcUITheme, sgcUIApplication.Theme, sgcUIApplication.AccentColor, wysoki kontrast) również są wspólne.

Kontrolka hosta każdego produktu

Zasada ogólna: zamień prefiks TsgcWinForms lub TsgcWpf na TsgcAvalonia. Wyjątki są zaznaczone w tabeli.

Produkt WinForms WPF Avalonia
sgcGridTsgcWinFormsGridTsgcWpfGridTsgcAvaloniaGrid
Kolumna sgcGrid zadeklarowanaw kodzie, 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 to Radial, Linear, Digital, Compass, Clock, LED, LEDBar, ProgressRing, VUMeter lub Annunciator.
sgcDashboardTsgcWinFormsDashboardTsgcWpfDashboardTsgcAvaloniaDashboard
Kafelki sgcDashboardTsgcWinFormsDashboardTile, ...ChartTile, ...GridTile, ...GaugeTile, ...PivotTile, TsgcWinFormsKPICard i TsgcWinFormsDashboardMapTileTsgcWpfDashboardTile, ...ChartTile, ...GridTile, ...GaugeTile, ...PivotTile, TsgcWpfKPICard. Brak kafelka mapy.TsgcAvaloniaDashboardTile, ...ChartTile, ...GridTile, ...GaugeTile, ...PivotTile, TsgcAvaloniaKPICard. Brak kafelka mapy.
sgcMapsSgcMapControl (przestrzeń nazw esegece.sgcMaps.WinForms)TsgcWpfMapTsgcAvaloniaMap
sgcEditorsTsgcWinForms<Editor>TsgcWpf<Editor>TsgcAvalonia<Editor>
Edytor to ButtonEdit, CurrencyEdit, MaskEdit, SpinEdit, DatePicker, DateTimePicker, TimePicker, LookupEdit, SearchEdit, TokenEdit, Memo, ColorPicker, MonthCalendar, RadioGroup, CheckGroup, Rating lub RangeSlider.
sgcNavigationTsgcWinFormsNavigationBar, ...NavigationTabs, ...NavigationBreadcrumb, ...DockManagerTsgcWpfNavigationBar, ...NavigationTabs, ...NavigationBreadcrumb, ...DockManagerTsgcAvaloniaNavigationBar, ...NavigationTabs, ...NavigationBreadcrumb, ...DockManager
sgcToolbarTsgcWinFormsRibbon, TsgcWinFormsToolbar, TsgcWinFormsStatusBarTsgcWpfRibbon, TsgcWpfToolbar, TsgcWpfStatusBarTsgcAvaloniaRibbon, TsgcAvaloniaToolbar, TsgcAvaloniaStatusBar
sgcWizardTsgcWinFormsWizard, TsgcWinFormsStepperTsgcWpfWizard, TsgcWpfStepperTsgcAvaloniaWizard, TsgcAvaloniaStepper
sgcRichEditTsgcWinFormsRichEditTsgcWpfRichEditTsgcAvaloniaRichEdit
Przeglądarka sgcPDFTsgcPDFViewerControlTsgcWpfPDFViewerTsgcAvaloniaPDFViewer
Projektant sgcReportsTsgcWinFormsReportDesignerTsgcWpfReportDesignerTsgcAvaloniaReportDesigner
Podgląd sgcReportsTsgcReportPreviewVCL (Panel WinForms)TsgcWpfReportPreviewTsgcAvaloniaReportPreview
sgcUICoreTsgcWinFormsAIQueryBar, TsgcWinFormsValidationSummaryTsgcWpfAIQueryBar, TsgcWpfValidationSummaryTsgcAvaloniaAIQueryBar, TsgcAvaloniaValidationSummary

Przestrzeń nazw jest jedna na produkt (esegece.sgcGrid, esegece.sgcCharts, ...) i taka sama dla wszystkich trzech hostów, z wyjątkiem mapy WinForms, która jest w esegece.sgcMaps.WinForms. Kontrolki hostów znajdują się w pakietach esegece.<Product>.WinForms, .Wpf i .Avalonia; kafelek mapy WinForms w pulpicie jest w esegece.sgcDashboard.Maps.WinForms. W WPF i Avalonia każdy produkt poza sgcReports deklaruje się w XAML z jednym URI przestrzeni nazw, xmlns:sgc="https://www.esegece.com/sgcui"; dla sgcReports użyj mapowania clr-namespace.

Członkowie, które zmieniają się przy przenoszeniu

Cała reszta kontrolek zachowuje swoje nazwy. Oto miejsca, w których widać hosta.

Temat WinForms WPF Avalonia
Powiązanie listy z siatkąDataSource (i DataMember)ItemsSource (DependencyProperty)ItemsSource (StyledProperty)
Członkowie wiązania siatkiAutoGenerateColumns, KeyMember, SelectedItem, ItemsBinding i ItemsWriteBack (Registered), takie same na wszystkich trzech hostach
Synchronizacja bieżącego elementubrakIsSynchronizedWithCurrentItembrak
Kolumny i dane siatkiColumns (TsgcGridColumns), Data (TsgcGridData)
Model wykresuChart (TsgcChart)
Model harmonogramuData (TsgcPlannerData), Options (TsgcPlannerSchedulerOptions)
MotywTheme kontrolki (TsgcUITheme; utDefault podąża za sgcUIApplication.Theme)tak samo, plus sgcUICore_Theme_Avalonia.sgcUIApplyThemeToWindow
Wątek interfejsuControl.BeginInvokeDispatcher.BeginInvokeDispatcher.UIThread.Post
Uruchomienie aplikacjiApplication.Run(form)new Application().Run(window)klasa Application z motywem oraz StartWithClassicDesktopLifetime
Docelowy framework aplikacjinet8.0-windows (także net472)net8.0-windows (także net472)net8.0, net9.0, net10.0

Wiersze HOST i wiersze SAME

Każdy fragment jest skopiowany z przykładu MigrateToAvalonia, który kompiluje się w Release dla edycji Registered i Community z 0 ostrzeżeń i przechodzi swój autotest na WinForms, WPF i Avalonia. W plikach hostów wiersz oznaczony HOST różni się między trzema hostami, a wiersz oznaczony SAME jest identyczny we wszystkich.

Napisane raz: warstwa wspólna

Wspólna biblioteka klas odwołuje się tylko do pakietów modelu esegece.sgcGrid, esegece.sgcCharts, esegece.sgcPlanner i esegece.sgcUICore, nigdy do pakietu hosta. Kolumny siatki, z grupowaniem i sumami, konfiguruje się względem 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();
    }
}

Wykres to model TsgcChart, a jego seria kołowa jest zasilana na żywo z ObservableCollection przez 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;

Jeden motyw dla każdej kontrolki sgcUI w aplikacji:

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

Jedyna usługa hosta: przekazanie do wątku interfejsu

Powiązane kolekcje zmieniają się w wątku interfejsu. Warstwa wspólna nie zna hosta, więc wywołanie publikacji otrzymuje jako delegat:

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

Pola kontrolek różnią się tylko typem

Przed: typy WinForms lub WPF. Po: typy 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;

Powiązanie ekranu: ten sam blok na każdym hoście

Dwa wiersze są oznaczone HOST: wywołanie wątku interfejsu i powiązanie listy. Reszta to ten sam tekst we wszystkich trzech plikach.

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;

Uruchomienie aplikacji

Kod uruchomienia jest z natury kodem hosta. Wywołanie motywu jest takie samo na wszystkich trzech hostach.

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

Osiem kroków na ekran

Aplikacja Windows działa po każdym kroku, więc migrację można prowadzić ekran po ekranie.

1

Wyodrębnij warstwę wspólną

Przenieś dane i konfigurację kontrolek do biblioteki klas, która odwołuje się tylko do pakietów modelu sgcUI (esegece.sgcGrid, esegece.sgcCharts, esegece.sgcPlanner, esegece.sgcUICore). Pisz ją względem TsgcGridColumns, TsgcChart, TsgcPlannerData i TsgcPlannerSchedulerOptions.

2

Wywołaj ją z Windows

Niech istniejący ekran WinForms lub WPF wywołuje tę bibliotekę. W Windows nadal działa jak dotąd.

3

Utwórz aplikację Avalonia

Projekt net8.0 z Avalonia.Desktop, Avalonia.Themes.Fluent i potrzebnymi pakietami esegece.<Product>.Avalonia.

4

Wymień typy kontrolek

TsgcWinForms* i TsgcWpf* zamieniają się w TsgcAvalonia*, z wyjątkami wymienionymi w tabeli mapowania.

5

Przenieś okno

Form z TableLayoutPanel albo Window WPF z Grid staje się Window Avalonia z Grid. XAML WPF trafia do pliku .axaml i zachowuje URI przestrzeni nazw sgc.

6

Tylko WinForms: wiązanie

Zamień DataSource na ItemsSource. Kod WPF już używa ItemsSource.

7

Wywołanie wątku interfejsu

BeginInvoke lub Dispatcher.BeginInvoke zamienia się w Dispatcher.UIThread.Post.

8

Sprawdź każdy host

Uruchom te same testy na każdym hoście, tak jak robi to /selftest w przykładzie, i porównaj JSON zapisanego układu siatki: powinien to być ten sam tekst.

Co nie odpowiada sobie jeden do jednego

Przeczytaj to przed zaplanowaniem migracji.

MigrateToAvalonia

Jeden ekran z siatką 40 zamówień zgrupowanych według regionu, wykresem kołowym kwoty według regionu i harmonogramem tygodnia pracy, wyświetlany przez trzy hosty ze wspólnej warstwy.

Gdzie je znaleźć

W demach .NET sgcUI, w folderze Suite\MigrateToAvalonia rozwiązania sgcUI_Demos.sln: Shared, WinForms, Wpf, Avalonia i readme.html. W pobraniu dem projekty odwołują się do pakietów NuGet sgcUI.

Jak je uruchomić

dotnet build Avalonia\sgcUIMigrate.Avalonia.csproj -c Release, a także WinForms\sgcUIMigrate.WinForms.csproj i Wpf\sgcUIMigrate.Wpf.csproj. Dodaj -p:SgcCommunity=true dla edycji Community. Host Avalonia działa także na macOS i Linuksie.

Autotest

sgcUIMigrate.<Host>.exe /selftest [folder] sprawdza wiersze, grupowanie, wykres kołowy, harmonogram, zamówienie dodane w wątku roboczym i usunięte zamówienie, zapisuje migrate_selftest_<host>.txt i migrate_layout_<host>.json i kończy działanie, zwracając liczbę nieudanych testów. Wszystkie 13 testów przechodzi na wszystkich trzech hostach, a trzy pliki układu są identyczne bajt po bajcie.

Uczciwie wobec obu dróg: XPF ma uruchamiać istniejącą aplikację WPF na macOS i Linuksie z jej kodem i kontrolkami w większości bez zmian, a sgcUI tego nie robi. Z sgcUI przenosisz kod okna każdego ekranu do Avalonia, a części ekranu oparte na sgcUI przechodzą bez zmian. Ekrany zbudowane na kontrolkach innych dostawców wymagają najpierw wymiany tych kontrolek, jeszcze w Windows, przed migracją. sgcUI to dobra droga, gdy Twoje ekrany używają, albo mogą przejść na, siatkę, wykresy, harmonogram, tabelę przestawną, diagram i pozostałe produkty sgcUI wymienione wyżej, a chcesz mieć natywne kontrolki Avalonia z pełnym kodem źródłowym i bez pośredniej warstwy uruchomieniowej. Informacje o produktach i edycjach znajdziesz na stronie produktu sgcUI.

Najkorzystniejsza oferta: All-AccessWszystkie produkty eSeGeCe, ze wsparciem Premium w cenie, już od €1,059 rocznie.
Zobacz cennik All-Access

Przenieś swój pierwszy ekran już dziś

Dodaj bezpłatne pakiety Community z NuGet albo pakiety Registered z wszystkimi funkcjami i pełnym kodem źródłowym, i przenieś swój pierwszy ekran, mając obok przykład MigrateToAvalonia.