diff --git a/e2e/Avalonia/PrismAvaloniaDemo/App.axaml b/e2e/Avalonia/PrismAvaloniaDemo/App.axaml index 3dca8cf60..136cd24b1 100644 --- a/e2e/Avalonia/PrismAvaloniaDemo/App.axaml +++ b/e2e/Avalonia/PrismAvaloniaDemo/App.axaml @@ -1,11 +1,13 @@  + + + \ No newline at end of file diff --git a/e2e/Avalonia/PrismAvaloniaDemo/App.axaml.cs b/e2e/Avalonia/PrismAvaloniaDemo/App.axaml.cs index cad82d3a7..19fec496f 100644 --- a/e2e/Avalonia/PrismAvaloniaDemo/App.axaml.cs +++ b/e2e/Avalonia/PrismAvaloniaDemo/App.axaml.cs @@ -5,6 +5,8 @@ using Avalonia.Markup.Xaml; using Prism.DryIoc; using Prism.Ioc; +using Prism.Navigation.Regions; +using SampleApp.Services; using SampleApp.ViewModels; using SampleApp.Views; @@ -27,6 +29,26 @@ protected override AvaloniaObject CreateShell() protected override void RegisterTypes(IContainerRegistry containerRegistry) { - // Register you Services, Views, Dialogs, etc. + // Register your Services, Views, Dialogs, etc. here + + // Services + containerRegistry.RegisterSingleton(); + + // Views - Region Navigation + containerRegistry.RegisterForNavigation(); + containerRegistry.RegisterForNavigation(); + containerRegistry.RegisterForNavigation(); + } + + /// Called after Initialize. + protected override void OnInitialized() + { + // Register Views to the Region it will appear in. Don't register them in the ViewModel. + var regionManager = Container.Resolve(); + + // WARNING: Prism v11.0.0 + // - DataTemplates MUST define a DataType or else an XAML error will be thrown + // - Error: DataTemplate inside of DataTemplates must have a DataType set + regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(DashboardView)); } } diff --git a/e2e/Avalonia/PrismAvaloniaDemo/PrismAvaloniaDemo.csproj b/e2e/Avalonia/PrismAvaloniaDemo/PrismAvaloniaDemo.csproj index f6a1629e2..81b1e5c48 100644 --- a/e2e/Avalonia/PrismAvaloniaDemo/PrismAvaloniaDemo.csproj +++ b/e2e/Avalonia/PrismAvaloniaDemo/PrismAvaloniaDemo.csproj @@ -2,6 +2,7 @@ WinExe net8.0 + latest enable true app.manifest diff --git a/e2e/Avalonia/PrismAvaloniaDemo/RegionNames.cs b/e2e/Avalonia/PrismAvaloniaDemo/RegionNames.cs new file mode 100644 index 000000000..7ebaa5ddd --- /dev/null +++ b/e2e/Avalonia/PrismAvaloniaDemo/RegionNames.cs @@ -0,0 +1,8 @@ +namespace SampleApp; + +public static class RegionNames +{ + public const string ContentRegion = "ContentRegion"; + public const string FooterRegion = "FooterRegion"; + public const string SidebarRegion = "SidebarRegion"; +} diff --git a/e2e/Avalonia/PrismAvaloniaDemo/Services/INotificationService.cs b/e2e/Avalonia/PrismAvaloniaDemo/Services/INotificationService.cs new file mode 100644 index 000000000..f3f7d5f52 --- /dev/null +++ b/e2e/Avalonia/PrismAvaloniaDemo/Services/INotificationService.cs @@ -0,0 +1,13 @@ +using System; +using Avalonia.Controls; + +namespace SampleApp.Services; + +public interface INotificationService +{ + int NotificationTimeout { get; set; } + + void SetHostWindow(TopLevel window); + + void Show(string title, string message, Action? onClick = null); +} diff --git a/e2e/Avalonia/PrismAvaloniaDemo/Services/NotificationService.cs b/e2e/Avalonia/PrismAvaloniaDemo/Services/NotificationService.cs new file mode 100644 index 000000000..5258fca09 --- /dev/null +++ b/e2e/Avalonia/PrismAvaloniaDemo/Services/NotificationService.cs @@ -0,0 +1,43 @@ +using Avalonia.Controls.Notifications; + +namespace SampleApp.Services; + +public class NotificationService : INotificationService +{ + private WindowNotificationManager? _notificationManager; + private int _notificationTimeout = 10; + + public int NotificationTimeout { get => _notificationTimeout; set => _notificationTimeout = (value < 0) ? 0 : value; } + + /// Set the host window. + /// Parent window. + public void SetHostWindow(TopLevel hostWindow) + { + var notificationManager = new WindowNotificationManager(hostWindow) + { + Position = NotificationPosition.BottomRight, + MaxItems = 4, + Margin = new Thickness(0, 0, 15, 40) + }; + + _notificationManager = notificationManager; + } + + /// Display the notification. + /// Title. + /// Message. + /// Optional OnClick action. + public void Show(string title, string message, Action? onClick = null) + { + if (_notificationManager is { } nm) + { + nm.Show( + new Notification( + title, + message, + NotificationType.Information, + TimeSpan.FromSeconds(_notificationTimeout), + onClick)); + } + } +} diff --git a/e2e/Avalonia/PrismAvaloniaDemo/Styles/Icons.axaml b/e2e/Avalonia/PrismAvaloniaDemo/Styles/Icons.axaml new file mode 100644 index 000000000..36a5d3b89 --- /dev/null +++ b/e2e/Avalonia/PrismAvaloniaDemo/Styles/Icons.axaml @@ -0,0 +1,56 @@ + + + + + + + + + + + diff --git a/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/DashboardViewModel.cs b/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/DashboardViewModel.cs new file mode 100644 index 000000000..b9ae3ad21 --- /dev/null +++ b/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/DashboardViewModel.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Avalonia; +using Avalonia.Styling; +using Prism.Commands; +using SampleApp.Services; + +namespace SampleApp.ViewModels; + +public class DashboardViewModel : ViewModelBase +{ + private readonly INotificationService _notification; + private int _counter = 0; + private ObservableCollection _listItems = new(); + private int _listItemSelected = -1; + private string _listItemText = string.Empty; + private ThemeVariant? _themeSelected; + + public DashboardViewModel(INotificationService notifyService) + { + _notification = notifyService; + + ThemeSelected = Application.Current!.RequestedThemeVariant; + } + + public DelegateCommand CmdAddItem => new(() => + { + _counter++; + ListItems.Add($"Item Number: {_counter}"); + + // Optionally use, `Insert(0, ..)` to insert items at the top + //ListItems.Insert(0, entry); + }); + + public DelegateCommand CmdClearItems => new(() => + { + ListItems.Clear(); + }); + + public DelegateCommand CmdNotification => new(() => + { + _notification.Show("Hello Prism!", "Notification Pop-up Message."); + + // Alternate OnClick action + ////_notification.Show("Hello Prism!", "Notification Pop-up Message.", () => + ////{ + //// // Action to perform + ////}); + }); + + public ObservableCollection ListItems { get => _listItems; set => SetProperty(ref _listItems, value); } + + public int ListItemSelected + { + get => _listItemSelected; + set + { + SetProperty(ref _listItemSelected, value); + + if (value == -1) + return; + + ListItemText = ListItems[ListItemSelected]; + } + } + + public string ListItemText { get => _listItemText; set => SetProperty(ref _listItemText, value); } + + public ThemeVariant? ThemeSelected + { + get => _themeSelected; + set + { + SetProperty(ref _themeSelected, value); + Application.Current!.RequestedThemeVariant = _themeSelected; + } + } + + public List ThemeStyles => new() + { + ThemeVariant.Default, + ThemeVariant.Dark, + ThemeVariant.Light, + }; +} diff --git a/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/MainWindowViewModel.cs b/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/MainWindowViewModel.cs index 4efdc1b73..203da5bf7 100644 --- a/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/MainWindowViewModel.cs +++ b/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/MainWindowViewModel.cs @@ -1,13 +1,41 @@ +using Prism.Commands; +using Prism.Navigation.Regions; + +using SampleApp.Views; + namespace SampleApp.ViewModels; public class MainWindowViewModel : ViewModelBase { - public MainWindowViewModel() + private readonly IRegionManager _regionManager; + private bool _isPaneOpened; + + public MainWindowViewModel(IRegionManager regionManager) { - Title = "Welcome to Prism.Avalonia!"; + // Since this is a basic ShellWindow, there's not much to do here. + // For enterprise apps, you could register up subscriptions + // or other startup background tasks so that they get triggered + // on startup, rather than putting them in the DashboardViewModel. + // + // For example, initiate the pulling of News Feeds, etc. + + _regionManager = regionManager; + Title = "Sample Prism.Avalonia SplitView!"; + IsPaneOpened = true; } -#pragma warning disable CA1822 // Mark members as static - public string Greeting => "Hello from, Prism.Avalonia!"; -#pragma warning restore CA1822 // Mark members as static + public DelegateCommand CmdDashboard => new(() => + { + // _journal.Clear(); + _regionManager.RequestNavigate(RegionNames.ContentRegion, nameof(DashboardView)); + }); + + public DelegateCommand CmdFlyoutMenu => new(() => + { + IsPaneOpened = !IsPaneOpened; + }); + + public DelegateCommand CmdSettings => new(() => _regionManager.RequestNavigate(RegionNames.ContentRegion, nameof(SettingsView))); + + public bool IsPaneOpened { get => _isPaneOpened; set => SetProperty(ref _isPaneOpened, value); } } diff --git a/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/SettingsViewModel.cs b/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/SettingsViewModel.cs new file mode 100644 index 000000000..4ede0fe9a --- /dev/null +++ b/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/SettingsViewModel.cs @@ -0,0 +1,35 @@ +using SampleApp.Views; +using Prism.Commands; +using Prism.Navigation; +using Prism.Navigation.Regions; + +namespace SampleApp.ViewModels +{ + public class SettingsViewModel : ViewModelBase + { + private readonly IRegionManager _regionManager; + + public SettingsViewModel(IRegionManager regionManager) + { + _regionManager = regionManager; + Title = "Settings"; + } + + public DelegateCommand CmdNavigateToChild => new DelegateCommand(() => + { + var navParams = new NavigationParameters(); + navParams.Add("key1", "Some text"); + navParams.Add("key2", 999); + + _regionManager.RequestNavigate( + RegionNames.ContentRegion, + nameof(SubSettingsView), + navParams); + }); + + public override void OnNavigatedFrom(NavigationContext navigationContext) + { + base.OnNavigatedFrom(navigationContext); + } + } +} diff --git a/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/SubSettingsViewModel.cs b/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/SubSettingsViewModel.cs new file mode 100644 index 000000000..939bbe6cf --- /dev/null +++ b/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/SubSettingsViewModel.cs @@ -0,0 +1,53 @@ +using Prism.Commands; +using Prism.Navigation.Regions; +using SampleApp.Views; + +namespace SampleApp.ViewModels; + +public class SubSettingsViewModel : ViewModelBase +{ + private readonly IRegionManager _regionManager; + private IRegionNavigationJournal? _journal; + private string _messageNumber = string.Empty; + private string _messageText = string.Empty; + + public SubSettingsViewModel(IRegionManager regionManager) + { + _regionManager = regionManager; + + Title = "Settings - SubView"; + } + + public DelegateCommand CmdNavigateBack => new DelegateCommand(() => + { + // Go back to the previous calling page, otherwise, Dashboard. + if (_journal != null && _journal.CanGoBack) + _journal.GoBack(); + else + _regionManager.RequestNavigate(RegionNames.ContentRegion, nameof(DashboardView)); + }); + + public string MessageNumber { get => _messageNumber; set => SetProperty(ref _messageNumber, value); } + + public string MessageText { get => _messageText; set => SetProperty(ref _messageText, value); } + + /// Navigation completed successfully. + /// Navigation context. + public override void OnNavigatedTo(NavigationContext navigationContext) + { + // Used to "Go Back" to parent + _journal = navigationContext.NavigationService.Journal; + + // Display our parameters + MessageText = navigationContext.Parameters["key1"].ToString() ?? ""; + MessageNumber = navigationContext.Parameters["key2"].ToString() ?? ""; + } + + public override bool OnNavigatingTo(NavigationContext navigationContext) + { + // Navigation permission sample: + // Don't allow navigation if our keys are missing + return navigationContext.Parameters.ContainsKey("key1") && + navigationContext.Parameters.ContainsKey("key2"); + } +} diff --git a/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/ViewModelBase.cs b/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/ViewModelBase.cs index 229084d99..048a65f40 100644 --- a/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/ViewModelBase.cs +++ b/e2e/Avalonia/PrismAvaloniaDemo/ViewModels/ViewModelBase.cs @@ -1,11 +1,45 @@ using Prism.Mvvm; +using Prism.Navigation.Regions; namespace SampleApp.ViewModels; -public class ViewModelBase : BindableBase +public class ViewModelBase : BindableBase, INavigationAware { private string _title = string.Empty; - /// Gets or sets the title of the view. + /// Gets or sets the title of the View. public string Title { get => _title; set => SetProperty(ref _title, value); } + + /// + /// Called to determine if this instance can handle the navigation request. + /// Don't call this directly, use . + /// + /// The navigation context. + /// if this instance accepts the navigation request; otherwise, . + public virtual bool IsNavigationTarget(NavigationContext navigationContext) + { + // Auto-allow navigation + return OnNavigatingTo(navigationContext); + } + + /// Called when the implementer is being navigated away from. + /// The navigation context. + public virtual void OnNavigatedFrom(NavigationContext navigationContext) + { + } + + /// Called when the implementer has been navigated to. + /// The navigation context. + public virtual void OnNavigatedTo(NavigationContext navigationContext) + { + } + + /// Navigation validation checker. + /// Override for Prism 7.2's IsNavigationTarget. + /// The navigation context. + /// if this instance accepts the navigation request; otherwise, . + public virtual bool OnNavigatingTo(NavigationContext navigationContext) + { + return true; + } } diff --git a/e2e/Avalonia/PrismAvaloniaDemo/Views/DashboardView.axaml b/e2e/Avalonia/PrismAvaloniaDemo/Views/DashboardView.axaml new file mode 100644 index 000000000..d9b8271ee --- /dev/null +++ b/e2e/Avalonia/PrismAvaloniaDemo/Views/DashboardView.axaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + diff --git a/e2e/Avalonia/PrismAvaloniaDemo/Views/SubSettingsView.axaml.cs b/e2e/Avalonia/PrismAvaloniaDemo/Views/SubSettingsView.axaml.cs new file mode 100644 index 000000000..aa5c7558d --- /dev/null +++ b/e2e/Avalonia/PrismAvaloniaDemo/Views/SubSettingsView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace SampleApp.Views; + +public partial class SubSettingsView : UserControl +{ + public SubSettingsView() + { + InitializeComponent(); + } +}