From d164acea210281c4d6840a30e95176356bdca9d9 Mon Sep 17 00:00:00 2001 From: Zhang Dian <54255897+zdpcdt@users.noreply.github.com> Date: Fri, 24 Apr 2026 19:58:27 +0800 Subject: [PATCH] feat: enhance navigation and UI structure in MainView and Application. (#811) * feat: enhance navigation and UI structure in MainView and Application. * feat: implement singleton pattern for PaletteDemoViewModel and add initialization check * feat: set FontWeight to Normal for navigation button in MainView --------- Co-authored-by: Dong Bin --- .../Pages/PaletteDemo.axaml.cs | 10 +- .../ViewModels/MainViewModel.cs | 353 +++++++++++++++ .../ViewModels/PaletteDemoViewModel.cs | 5 +- demo/Semi.Avalonia.Demo/Views/MainView.axaml | 403 ++++++------------ .../Views/MainView.axaml.cs | 238 +---------- 5 files changed, 499 insertions(+), 510 deletions(-) create mode 100644 demo/Semi.Avalonia.Demo/ViewModels/MainViewModel.cs diff --git a/demo/Semi.Avalonia.Demo/Pages/PaletteDemo.axaml.cs b/demo/Semi.Avalonia.Demo/Pages/PaletteDemo.axaml.cs index 33b7283..f5476e1 100644 --- a/demo/Semi.Avalonia.Demo/Pages/PaletteDemo.axaml.cs +++ b/demo/Semi.Avalonia.Demo/Pages/PaletteDemo.axaml.cs @@ -12,14 +12,16 @@ public partial class PaletteDemo : UserControl public PaletteDemo() { InitializeComponent(); - this.DataContext = new PaletteDemoViewModel(); + this.DataContext = PaletteDemoViewModel.Instance.Value; } protected override async void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - PaletteDemoViewModel? vm = this.DataContext as PaletteDemoViewModel; - await Dispatcher.UIThread.InvokeAsync(() => { vm?.InitializeResources(); }); + if (this.DataContext is PaletteDemoViewModel vm && !vm.IsInitialized) + { + await Dispatcher.UIThread.InvokeAsync(() => { vm?.InitializeResources(); }); + } } public async Task Copy(object? o) @@ -31,4 +33,4 @@ public partial class PaletteDemo : UserControl await c.SetTextAsync(o.ToString()); } } -} \ No newline at end of file +} diff --git a/demo/Semi.Avalonia.Demo/ViewModels/MainViewModel.cs b/demo/Semi.Avalonia.Demo/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..24bd7cd --- /dev/null +++ b/demo/Semi.Avalonia.Demo/ViewModels/MainViewModel.cs @@ -0,0 +1,353 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Layout; +using Avalonia.Styling; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Semi.Avalonia.Demo.Pages; + +namespace Semi.Avalonia.Demo.ViewModels; + +public partial class MainViewModel : ObservableObject +{ + private readonly Dictionary _itemsByTitle = new(StringComparer.Ordinal); + private readonly IReadOnlyList _allSections; + + [ObservableProperty] public partial string? SearchText { get; set; } + + public string DocumentationUrl => "https://docs.irihi.tech/semi"; + public string RepoUrl => "https://github.com/irihitech/Semi.Avalonia"; + public IReadOnlyList MenuItems { get; } + public IReadOnlyList Sections { get; } + public ObservableCollection FilteredSections { get; } = []; + public bool ShowEmptySearchState => FilteredSections.Count == 0 && !string.IsNullOrWhiteSpace(SearchText); + public ContentPage? CurrentPage => SelectedItem?.Page; + public string SelectedPageTitle => SelectedItem?.Title ?? "Overview"; + + public NavigationItemViewModel? SelectedItem + { + get; + private set + { + if (ReferenceEquals(field, value)) + { + return; + } + + var previous = field; + if (SetProperty(ref field, value)) + { + previous?.IsSelected = false; + value?.IsSelected = true; + OnPropertyChanged(nameof(CurrentPage)); + OnPropertyChanged(nameof(SelectedPageTitle)); + } + } + } + + public MainViewModel() + { + MenuItems = + [ + new MenuItemViewModel + { + Header = "Theme", + Items = + [ + new MenuItemViewModel { Header = "Auto", Command = FollowSystemThemeCommand }, + new MenuItemViewModel { Header = "Aquatic", Command = SelectThemeCommand, CommandParameter = SemiTheme.Aquatic }, + new MenuItemViewModel { Header = "Desert", Command = SelectThemeCommand, CommandParameter = SemiTheme.Desert }, + new MenuItemViewModel { Header = "Dusk", Command = SelectThemeCommand, CommandParameter = SemiTheme.Dusk }, + new MenuItemViewModel { Header = "NightSky", Command = SelectThemeCommand, CommandParameter = SemiTheme.NightSky }, + ] + }, + new MenuItemViewModel + { + Header = "Locale", + Items = + [ + new MenuItemViewModel { Header = "简体中文", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("zh-CN") }, + new MenuItemViewModel { Header = "English", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("en-US") }, + new MenuItemViewModel { Header = "日本語", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("ja-JP") }, + new MenuItemViewModel { Header = "한국어", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("ko-KR") }, + new MenuItemViewModel { Header = "English (UK)", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("en-GB") }, + new MenuItemViewModel { Header = "Italiano", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("it-IT") }, + new MenuItemViewModel { Header = "Italiano (Switzerland)", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("it-CH") }, + new MenuItemViewModel { Header = "Nederlands", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("nl-NL") }, + new MenuItemViewModel { Header = "Nederlands (Belgium)", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("nl-BE") }, + new MenuItemViewModel { Header = "Українська", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("uk-UA") }, + new MenuItemViewModel { Header = "Русский", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("ru-RU") }, + new MenuItemViewModel { Header = "繁體中文", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("zh-TW") }, + new MenuItemViewModel { Header = "Deutsch", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("de-DE") }, + new MenuItemViewModel { Header = "Español", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("es-ES") }, + new MenuItemViewModel { Header = "Polski", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("pl-PL") }, + new MenuItemViewModel { Header = "Français", Command = SelectLocaleCommand, CommandParameter = new CultureInfo("fr-FR") }, + ] + } + ]; + + Sections = _allSections = + [ + new NavigationSectionViewModel("Overview", + [ + CreateItem("Overview", static () => new Overview()), + CreateItem("About Us", static () => new AboutUs()), + ]), + new NavigationSectionViewModel("Resource Browser", + [ + CreateItem("Palette", static () => new PaletteDemo()), + CreateItem("HighContrastTheme", static () => new HighContrastDemo()), + CreateItem("Variables", static () => new VariablesDemo()), + CreateItem("Icon", static () => new IconDemo()), + ]), + new NavigationSectionViewModel("Separate Pack", + [ + CreateItem("ColorPicker", static () => new ColorPickerDemo()), + CreateItem("DataGrid", static () => new DataGridDemo()), + ]), + new NavigationSectionViewModel("Basic", + [ + CreateItem("TextBlock", static () => new TextBlockDemo()), + CreateItem("SelectableTextBlock", static () => new SelectableTextBlockDemo()), + CreateItem("Border", static () => new BorderDemo()), + CreateItem("PathIcon", static () => new PathIconDemo()), + ]), + new NavigationSectionViewModel("Button", + [ + CreateItem("Button", static () => new ButtonDemo()), + CreateItem("HyperlinkButton", static () => new HyperlinkButtonDemo()), + CreateItem("CheckBox", static () => new CheckBoxDemo()), + CreateItem("RadioButton", static () => new RadioButtonDemo()), + CreateItem("ToggleSwitch", static () => new ToggleSwitchDemo()), + ]), + new NavigationSectionViewModel("Input", + [ + CreateItem("TextBox", static () => new TextBoxDemo()), + CreateItem("AutoCompleteBox", static () => new AutoCompleteBoxDemo()), + CreateItem("ComboBox", static () => new ComboBoxDemo()), + CreateItem("ButtonSpinner", static () => new ButtonSpinnerDemo()), + CreateItem("NumericUpDown", static () => new NumericUpDownDemo()), + CreateItem("Slider", static () => new SliderDemo()), + CreateItem("ManagedFileChooser", static () => new ManagedFileChooserDemo()), + ]), + new NavigationSectionViewModel("Date/Time", + [ + CreateItem("Calendar", static () => new CalendarDemo()), + CreateItem("CalendarDatePicker", static () => new CalendarDatePickerDemo()), + CreateItem("DatePicker", static () => new DatePickerDemo()), + CreateItem("TimePicker", static () => new TimePickerDemo()), + ]), + new NavigationSectionViewModel("Navigation", + [ + CreateItem("ContentPage", static () => new ContentPageDemo()), + CreateItem("CarouselPage", static () => new CarouselPageDemo()), + CreateItem("DrawerPage", static () => new DrawerPageDemo()), + CreateItem("NavigationPage", static () => new NavigationPageDemo()), + CreateItem("TabbedPage", static () => new TabbedPageDemo()), + CreateItem("TabControl", static () => new TabControlDemo()), + CreateItem("TabStrip", static () => new TabStripDemo()), + CreateItem("TreeView", static () => new TreeViewDemo()), + ]), + new NavigationSectionViewModel("Show", + [ + CreateItem("Carousel", static () => new CarouselDemo()), + CreateItem("PipsPager", static () => new PipsPagerDemo()), + CreateItem("Expander", static () => new ExpanderDemo()), + CreateItem("Flyout", static () => new FlyoutDemo()), + CreateItem("HeaderedContentControl", static () => new HeaderedContentControlDemo()), + CreateItem("Label", static () => new LabelDemo()), + CreateItem("ListBox", static () => new ListBoxDemo()), + CreateItem("SplitView", static () => new SplitViewDemo()), + CreateItem("ToolTip", static () => new ToolTipDemo()), + ]), + new NavigationSectionViewModel("Feedback", + [ + CreateItem("DataValidationErrors", static () => new DataValidationErrorsDemo()), + CreateItem("Notification", static () => new NotificationDemo()), + CreateItem("ProgressBar", static () => new ProgressBarDemo()), + CreateItem("RefreshContainer", static () => new RefreshContainerDemo()), + ]), + new NavigationSectionViewModel("Other", + [ + CreateItem("CommandBar", static () => new CommandBarDemo()), + CreateItem("GridSplitter", static () => new GridSplitterDemo()), + CreateItem("Menu", static () => new MenuDemo()), + CreateItem("ScrollViewer", static () => new ScrollViewerDemo()), + CreateItem("ThemeVariantScope", static () => new ThemeVariantDemo()), + CreateItem("WindowCustomizationsPage", static () => new WindowCustomizationsPage()), + ]), + ]; + + SelectedItem = Sections[0].Items[0]; + RefreshFilteredSections(); + } + + public bool TryNavigateTo(string title) + { + if (_itemsByTitle.TryGetValue(title, out var item)) + { + SelectedItem = item; + return true; + } + + return false; + } + + partial void OnSearchTextChanged(string? value) + { + RefreshFilteredSections(); + } + + [RelayCommand] + private void NavigateTo(object? parameter) + { + if (parameter is NavigationItemViewModel item) + { + SelectedItem = item; + } + } + + [RelayCommand] + private void FollowSystemTheme() + { + Application.Current?.RegisterFollowSystemTheme(); + } + + [RelayCommand] + private void ToggleTheme() + { + var app = Application.Current; + if (app is null) return; + var theme = app.ActualThemeVariant; + app.RequestedThemeVariant = theme == ThemeVariant.Dark ? ThemeVariant.Light : ThemeVariant.Dark; + app.UnregisterFollowSystemTheme(); + } + + [RelayCommand] + private void SelectTheme(object? obj) + { + var app = Application.Current; + if (app is null) return; + app.RequestedThemeVariant = obj as ThemeVariant; + app.UnregisterFollowSystemTheme(); + } + + [RelayCommand] + private void SelectLocale(object? obj) + { + var app = Application.Current; + if (app is null) return; + SemiTheme.OverrideLocaleResources(app, obj as CultureInfo); + } + + [RelayCommand] + private static async Task OpenUrl(string url) + { + var launcher = ResolveDefaultTopLevel()?.Launcher; + if (launcher is not null) + { + await launcher.LaunchUriAsync(new Uri(url)); + } + } + + private NavigationItemViewModel CreateItem(string title, Func contentFactory) + { + var item = new NavigationItemViewModel(title, NavigateToCommand, contentFactory); + _itemsByTitle.Add(title, item); + return item; + } + + private void RefreshFilteredSections() + { + var search = string.IsNullOrWhiteSpace(SearchText) ? string.Empty : SearchText.Trim(); + + FilteredSections.Clear(); + + foreach (var section in _allSections) + { + if (search.Length == 0 || + section.Header.Contains(search, StringComparison.InvariantCultureIgnoreCase)) + { + FilteredSections.Add(section); + continue; + } + + var matchedItems = section.Items + .Where(item => item.Title.Contains(search, StringComparison.InvariantCultureIgnoreCase)) + .ToArray(); + + if (matchedItems.Length > 0) + { + FilteredSections.Add(new NavigationSectionViewModel(section.Header, matchedItems)); + } + } + + OnPropertyChanged(nameof(ShowEmptySearchState)); + } + + private static TopLevel? ResolveDefaultTopLevel() + { + return Application.Current?.ApplicationLifetime switch + { + IClassicDesktopStyleApplicationLifetime desktopLifetime => desktopLifetime.MainWindow, + ISingleViewApplicationLifetime singleView => TopLevel.GetTopLevel(singleView.MainView), + _ => null + }; + } +} + +public class NavigationSectionViewModel +{ + public NavigationSectionViewModel(string header, IReadOnlyList items) + { + Header = header; + Items = items; + } + + public string Header { get; } + + public IReadOnlyList Items { get; } +} + +public partial class NavigationItemViewModel : ObservableObject +{ + private readonly Func _contentFactory; + + public NavigationItemViewModel(string title, ICommand navigateCommand, Func contentFactory) + { + Title = title; + NavigateCommand = navigateCommand; + _contentFactory = contentFactory; + } + + public string Title { get; } + + public ICommand NavigateCommand { get; } + + public ContentPage Page => field ??= new ContentPage + { + Header = Title, + Background = null, + HorizontalContentAlignment = HorizontalAlignment.Stretch, + VerticalContentAlignment = VerticalAlignment.Stretch, + Content = _contentFactory() + }; + + [ObservableProperty] public partial bool IsSelected { get; set; } +} + +public class MenuItemViewModel +{ + public string? Header { get; set; } + public ICommand? Command { get; set; } + public object? CommandParameter { get; set; } + public IList? Items { get; set; } +} diff --git a/demo/Semi.Avalonia.Demo/ViewModels/PaletteDemoViewModel.cs b/demo/Semi.Avalonia.Demo/ViewModels/PaletteDemoViewModel.cs index 363c608..e3aeab8 100644 --- a/demo/Semi.Avalonia.Demo/ViewModels/PaletteDemoViewModel.cs +++ b/demo/Semi.Avalonia.Demo/ViewModels/PaletteDemoViewModel.cs @@ -14,6 +14,8 @@ namespace Semi.Avalonia.Demo.ViewModels; public partial class PaletteDemoViewModel : ObservableObject { + public static Lazy Instance { get; } = new(() => new PaletteDemoViewModel()); + public bool IsInitialized { get; private set; } private readonly string[] _predefinedColorNames = [ "Red", "Pink", "Purple", "Violet", "Indigo", @@ -44,6 +46,7 @@ public partial class PaletteDemoViewModel : ObservableObject InitializePalette(); InitializeFunctionalColors(); InitializeShadows(); + IsInitialized = true; } private void InitializePalette() @@ -289,4 +292,4 @@ public partial class ShadowGroupViewModel : ObservableObject } } } -} \ No newline at end of file +} diff --git a/demo/Semi.Avalonia.Demo/Views/MainView.axaml b/demo/Semi.Avalonia.Demo/Views/MainView.axaml index b33d608..1cf74f4 100644 --- a/demo/Semi.Avalonia.Demo/Views/MainView.axaml +++ b/demo/Semi.Avalonia.Demo/Views/MainView.axaml @@ -4,283 +4,142 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:pages="using:Semi.Avalonia.Demo.Pages" - xmlns:views="clr-namespace:Semi.Avalonia.Demo.Views" + xmlns:vm="clr-namespace:Semi.Avalonia.Demo.ViewModels" d:DesignHeight="450" d:DesignWidth="800" - x:DataType="views:MainViewModel" + x:DataType="vm:MainViewModel" mc:Ignorable="d"> - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + diff --git a/demo/Semi.Avalonia.Demo/Views/MainView.axaml.cs b/demo/Semi.Avalonia.Demo/Views/MainView.axaml.cs index 418830b..809ee5e 100644 --- a/demo/Semi.Avalonia.Demo/Views/MainView.axaml.cs +++ b/demo/Semi.Avalonia.Demo/Views/MainView.axaml.cs @@ -1,250 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Threading.Tasks; -using System.Windows.Input; -using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Styling; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; +using Semi.Avalonia.Demo.ViewModels; namespace Semi.Avalonia.Demo.Views; public partial class MainView : UserControl { + private readonly MainViewModel _viewModel; + public MainView() { InitializeComponent(); - this.DataContext = new MainViewModel(); + DataContext = _viewModel = new MainViewModel(); WeakReferenceMessenger.Default.Register(this, "JumpTo", MessageHandler); } private void MessageHandler(object _, string message) { - foreach (var item in tab.ItemsView) - { - if (item is TabItem tabItem && tabItem.Header is not null && tabItem.Header.Equals(message)) - { - tab.SelectedItem = tabItem; - break; - } - } + _viewModel.TryNavigateTo(message); } } - -public partial class MainViewModel : ObservableObject -{ - public string DocumentationUrl => "https://docs.irihi.tech/semi"; - public string RepoUrl => "https://github.com/irihitech/Semi.Avalonia"; - public IReadOnlyList MenuItems { get; } - - public MainViewModel() - { - MenuItems = - [ - new MenuItemViewModel - { - Header = "Theme", - Items = - [ - new MenuItemViewModel - { - Header = "Auto", - Command = FollowSystemThemeCommand - }, - new MenuItemViewModel - { - Header = "Aquatic", - Command = SelectThemeCommand, - CommandParameter = SemiTheme.Aquatic - }, - new MenuItemViewModel - { - Header = "Desert", - Command = SelectThemeCommand, - CommandParameter = SemiTheme.Desert - }, - new MenuItemViewModel - { - Header = "Dusk", - Command = SelectThemeCommand, - CommandParameter = SemiTheme.Dusk - }, - new MenuItemViewModel - { - Header = "NightSky", - Command = SelectThemeCommand, - CommandParameter = SemiTheme.NightSky - }, - ] - }, - new MenuItemViewModel - { - Header = "Locale", - Items = - [ - new MenuItemViewModel - { - Header = "简体中文", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("zh-CN") - }, - new MenuItemViewModel - { - Header = "English", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("en-US") - }, - new MenuItemViewModel - { - Header = "日本語", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("ja-JP") - }, - new MenuItemViewModel - { - Header = "한국어", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("ko-KR") - }, - new MenuItemViewModel - { - Header = "English (UK)", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("en-GB") - }, - new MenuItemViewModel - { - Header = "Italiano", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("it-IT") - }, - new MenuItemViewModel - { - Header = "Italiano (Switzerland)", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("it-CH") - }, - new MenuItemViewModel - { - Header = "Nederlands", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("nl-NL") - }, - new MenuItemViewModel - { - Header = "Nederlands (Belgium)", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("nl-BE") - }, - new MenuItemViewModel - { - Header = "Українська", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("uk-UA") - }, - new MenuItemViewModel - { - Header = "Русский", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("ru-RU") - }, - new MenuItemViewModel - { - Header = "繁體中文", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("zh-TW") - }, - new MenuItemViewModel - { - Header = "Deutsch", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("de-DE") - }, - new MenuItemViewModel - { - Header = "Español", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("es-ES") - }, - new MenuItemViewModel - { - Header = "Polski", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("pl-PL") - }, - new MenuItemViewModel - { - Header = "Français", - Command = SelectLocaleCommand, - CommandParameter = new CultureInfo("fr-FR") - }, - ] - } - ]; - } - - [RelayCommand] - private void FollowSystemTheme() - { - Application.Current?.RegisterFollowSystemTheme(); - } - - [RelayCommand] - private void ToggleTheme() - { - var app = Application.Current; - if (app is null) return; - var theme = app.ActualThemeVariant; - app.RequestedThemeVariant = theme == ThemeVariant.Dark ? ThemeVariant.Light : ThemeVariant.Dark; - app.UnregisterFollowSystemTheme(); - } - - [RelayCommand] - private void SelectTheme(object? obj) - { - var app = Application.Current; - if (app is null) return; - app.RequestedThemeVariant = obj as ThemeVariant; - app.UnregisterFollowSystemTheme(); - } - - [RelayCommand] - private void SelectLocale(object? obj) - { - var app = Application.Current; - if (app is null) return; - SemiTheme.OverrideLocaleResources(app, obj as CultureInfo); - } - - [RelayCommand] - private static async Task OpenUrl(string url) - { - var launcher = ResolveDefaultTopLevel()?.Launcher; - if (launcher is not null) - { - await launcher.LaunchUriAsync(new Uri(url)); - } - } - - private static TopLevel? ResolveDefaultTopLevel() - { - return Application.Current?.ApplicationLifetime switch - { - IClassicDesktopStyleApplicationLifetime desktopLifetime => desktopLifetime.MainWindow, - ISingleViewApplicationLifetime singleView => TopLevel.GetTopLevel(singleView.MainView), - _ => null - }; - } -} - -public class MenuItemViewModel -{ - public string? Header { get; set; } - public ICommand? Command { get; set; } - public object? CommandParameter { get; set; } - public IList? Items { get; set; } -} \ No newline at end of file