diff --git a/samples/ControlCatalog/Pages/NotificationsPage.xaml b/samples/ControlCatalog/Pages/NotificationsPage.xaml
index 46c1fe52de..b4094d8a2e 100644
--- a/samples/ControlCatalog/Pages/NotificationsPage.xaml
+++ b/samples/ControlCatalog/Pages/NotificationsPage.xaml
@@ -1,11 +1,28 @@
-
-
-
-
+
+ TopLevel bound notification manager.
+
+
+
+
+ XAML only notification manager.
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs b/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs
index 6f83e5c366..fac1989976 100644
--- a/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs
@@ -1,5 +1,6 @@
using Avalonia;
using Avalonia.Controls;
+using Avalonia.Controls.Notifications;
using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
@@ -27,7 +28,12 @@ namespace ControlCatalog.Pages
{
base.OnAttachedToVisualTree(e);
- _viewModel.NotificationManager = new Avalonia.Controls.Notifications.WindowNotificationManager(TopLevel.GetTopLevel(this));
+ _viewModel.NotificationManager = new WindowNotificationManager(TopLevel.GetTopLevel(this)!);
+ }
+
+ public void NotificationOnClick()
+ {
+ this.Get("ControlNotifications").Show("Notification clicked");
}
}
}
diff --git a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs
index bcbcb345ef..40af6e033a 100644
--- a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs
@@ -11,7 +11,7 @@ namespace ControlCatalog.ViewModels
{
ShowCustomManagedNotificationCommand = MiniCommand.Create(() =>
{
- NotificationManager?.Show(new NotificationViewModel() { Title = "Hey There!", Message = "Did you know that Avalonia now supports Custom In-Window Notifications?" , NotificationManager = NotificationManager});
+ NotificationManager?.Show(new NotificationViewModel() { Title = "Hey There!", Message = "Did you know that Avalonia now supports Custom In-Window Notifications?" , NotificationManager = NotificationManager}, NotificationType.Warning);
});
ShowManagedNotificationCommand = MiniCommand.Create(() =>
@@ -19,11 +19,6 @@ namespace ControlCatalog.ViewModels
NotificationManager?.Show(new Avalonia.Controls.Notifications.Notification("Welcome", "Avalonia now supports Notifications.", NotificationType.Information));
});
- ShowNativeNotificationCommand = MiniCommand.Create(() =>
- {
- NotificationManager?.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error));
- });
-
YesCommand = MiniCommand.Create(() =>
{
NotificationManager?.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today."));
@@ -45,8 +40,5 @@ namespace ControlCatalog.ViewModels
public MiniCommand ShowCustomManagedNotificationCommand { get; }
public MiniCommand ShowManagedNotificationCommand { get; }
-
- public MiniCommand ShowNativeNotificationCommand { get; }
-
}
}
diff --git a/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs b/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs
index b2e6e9e80b..bd57f3a86f 100644
--- a/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs
+++ b/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs
@@ -1,4 +1,5 @@
-using Avalonia.Metadata;
+using System;
+using Avalonia.Metadata;
namespace Avalonia.Controls.Notifications
{
diff --git a/src/Avalonia.Controls/Notifications/Notification.cs b/src/Avalonia.Controls/Notifications/Notification.cs
index 376df175f3..ace8cba243 100644
--- a/src/Avalonia.Controls/Notifications/Notification.cs
+++ b/src/Avalonia.Controls/Notifications/Notification.cs
@@ -1,4 +1,7 @@
using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
namespace Avalonia.Controls.Notifications
{
@@ -9,8 +12,10 @@ namespace Avalonia.Controls.Notifications
/// This class represents a notification that can be displayed either in a window using
/// or by the host operating system (to be implemented).
///
- public class Notification : INotification
+ public class Notification : INotification, INotifyPropertyChanged
{
+ private string? _title, _message;
+
///
/// Initializes a new instance of the class.
///
@@ -35,23 +40,59 @@ namespace Avalonia.Controls.Notifications
OnClick = onClick;
OnClose = onClose;
}
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Notification() : this(null, null)
+ {
+ }
///
- public string? Title { get; private set; }
+ public string? Title
+ {
+ get => _title;
+ set
+ {
+ if (_title != value)
+ {
+ _title = value;
+ OnPropertyChanged();
+ }
+ }
+ }
///
- public string? Message { get; private set; }
+ public string? Message
+ {
+ get => _message;
+ set
+ {
+ if (_message != value)
+ {
+ _message = value;
+ OnPropertyChanged();
+ }
+ }
+ }
///
- public NotificationType Type { get; private set; }
+ public NotificationType Type { get; set; }
///
- public TimeSpan Expiration { get; private set; }
+ public TimeSpan Expiration { get; set; }
///
- public Action? OnClick { get; private set; }
+ public Action? OnClick { get; set; }
///
- public Action? OnClose { get; private set; }
+ public Action? OnClose { get; set; }
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
}
}
diff --git a/src/Avalonia.Controls/Notifications/NotificationCard.cs b/src/Avalonia.Controls/Notifications/NotificationCard.cs
index 7d5b6cc0ca..d233525056 100644
--- a/src/Avalonia.Controls/Notifications/NotificationCard.cs
+++ b/src/Avalonia.Controls/Notifications/NotificationCard.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using Avalonia.Reactive;
using Avalonia.Controls.Metadata;
@@ -25,42 +25,7 @@ namespace Avalonia.Controls.Notifications
///
public NotificationCard()
{
- this.GetObservable(IsClosedProperty)
- .Subscribe(x =>
- {
- if (!IsClosing && !IsClosed)
- {
- return;
- }
-
- RaiseEvent(new RoutedEventArgs(NotificationClosedEvent));
- });
-
- this.GetObservable(ContentProperty)
- .Subscribe(x =>
- {
- if (x is INotification notification)
- {
- switch (notification.Type)
- {
- case NotificationType.Error:
- PseudoClasses.Add(":error");
- break;
-
- case NotificationType.Information:
- PseudoClasses.Add(":information");
- break;
-
- case NotificationType.Success:
- PseudoClasses.Add(":success");
- break;
-
- case NotificationType.Warning:
- PseudoClasses.Add(":warning");
- break;
- }
- }
- });
+ UpdateNotificationType();
}
///
@@ -93,6 +58,21 @@ namespace Avalonia.Controls.Notifications
public static readonly StyledProperty IsClosedProperty =
AvaloniaProperty.Register(nameof(IsClosed));
+ ///
+ /// Gets or sets the type of the notification
+ ///
+ public NotificationType NotificationType
+ {
+ get { return GetValue(NotificationTypeProperty); }
+ set { SetValue(NotificationTypeProperty, value); }
+ }
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty NotificationTypeProperty =
+ AvaloniaProperty.Register(nameof(NotificationType));
+
///
/// Defines the event.
///
@@ -163,5 +143,52 @@ namespace Avalonia.Controls.Notifications
IsClosing = true;
}
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ base.OnPropertyChanged(e);
+
+ if (e.Property == ContentProperty && e.NewValue is INotification notification)
+ {
+ SetValue(NotificationTypeProperty, notification.Type);
+ }
+
+ if (e.Property == NotificationTypeProperty)
+ {
+ UpdateNotificationType();
+ }
+
+ if (e.Property == IsClosedProperty)
+ {
+ if (!IsClosing && !IsClosed)
+ {
+ return;
+ }
+
+ RaiseEvent(new RoutedEventArgs(NotificationClosedEvent));
+ }
+ }
+
+ private void UpdateNotificationType()
+ {
+ switch (NotificationType)
+ {
+ case NotificationType.Error:
+ PseudoClasses.Add(":error");
+ break;
+
+ case NotificationType.Information:
+ PseudoClasses.Add(":information");
+ break;
+
+ case NotificationType.Success:
+ PseudoClasses.Add(":success");
+ break;
+
+ case NotificationType.Warning:
+ PseudoClasses.Add(":warning");
+ break;
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
index b03099f750..6d9a030ead 100644
--- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
+++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
@@ -1,11 +1,12 @@
using System;
using System.Collections;
+using System.Collections.Generic;
using System.Linq;
-using Avalonia.Reactive;
using System.Threading.Tasks;
+using Avalonia.Collections;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
-using Avalonia.Rendering;
+using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Notifications
@@ -18,7 +19,6 @@ namespace Avalonia.Controls.Notifications
public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager
{
private IList? _items;
-
///
/// Defines the property.
///
@@ -49,18 +49,24 @@ namespace Avalonia.Controls.Notifications
get { return GetValue(MaxItemsProperty); }
set { SetValue(MaxItemsProperty, value); }
}
-
+
///
/// Initializes a new instance of the class.
///
- /// The window that will host the control.
- public WindowNotificationManager(TopLevel? host)
+ /// The TopLevel that will host the control.
+ public WindowNotificationManager(TopLevel? host) : this()
{
- if (host != null)
+ if (host is not null)
{
- Install(host);
+ InstallFromTopLevel(host);
}
+ }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public WindowNotificationManager()
+ {
UpdatePseudoClasses(Position);
}
@@ -73,6 +79,8 @@ namespace Avalonia.Controls.Notifications
///
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
+ base.OnApplyTemplate(e);
+
var itemsControl = e.NameScope.Find("PART_Items");
_items = itemsControl?.Children;
}
@@ -80,49 +88,85 @@ namespace Avalonia.Controls.Notifications
///
public void Show(INotification content)
{
- Show(content as object);
+ Show(content, content.Type, content.Expiration, content.OnClick, content.OnClose);
}
///
public async void Show(object content)
{
- var notification = content as INotification;
-
+ if (content is INotification notification)
+ {
+ Show(notification, notification.Type, notification.Expiration, notification.OnClick, notification.OnClose);
+ }
+ else
+ {
+ Show(content, NotificationType.Information);
+ }
+ }
+
+ ///
+ /// Shows a Notification
+ ///
+ /// the content of the notification
+ /// the type of the notification
+ /// the expiration time of the notification after which it will automatically close. If the value is Zero then the notification will remain open until the user closes it
+ /// an Action to be run when the notification is clicked
+ /// an Action to be run when the notification is closed
+ /// style classes to apply
+ public async void Show(object content,
+ NotificationType type,
+ TimeSpan? expiration = null,
+ Action? onClick = null,
+ Action? onClose = null,
+ string[]? classes = null)
+ {
+ Dispatcher.UIThread.VerifyAccess();
+
var notificationControl = new NotificationCard
{
- Content = content
+ Content = content,
+ NotificationType = type
};
+ // Add style classes if any
+ if (classes != null)
+ {
+ foreach (var @class in classes)
+ {
+ notificationControl.Classes.Add(@class);
+ }
+ }
+
notificationControl.NotificationClosed += (sender, args) =>
{
- notification?.OnClose?.Invoke();
+ onClose?.Invoke();
_items?.Remove(sender);
};
notificationControl.PointerPressed += (sender, args) =>
{
- if (notification != null && notification.OnClick != null)
- {
- notification.OnClick.Invoke();
- }
+ onClick?.Invoke();
(sender as NotificationCard)?.Close();
};
- _items?.Add(notificationControl);
-
- if (_items?.OfType().Count(i => !i.IsClosing) > MaxItems)
+ Dispatcher.UIThread.Post(() =>
{
- _items.OfType().First(i => !i.IsClosing).Close();
- }
+ _items?.Add(notificationControl);
- if (notification != null && notification.Expiration == TimeSpan.Zero)
+ if (_items?.OfType().Count(i => !i.IsClosing) > MaxItems)
+ {
+ _items.OfType().First(i => !i.IsClosing).Close();
+ }
+ });
+
+ if (expiration == TimeSpan.Zero)
{
return;
}
- await Task.Delay(notification?.Expiration ?? TimeSpan.FromSeconds(5));
+ await Task.Delay(expiration ?? TimeSpan.FromSeconds(5));
notificationControl.Close();
}
@@ -139,18 +183,30 @@ namespace Avalonia.Controls.Notifications
///
/// Installs the within the
- /// of the host .
///
- /// The that will be the host.
- private void Install(TemplatedControl host)
+ private void InstallFromTopLevel(TopLevel topLevel)
{
- var adornerLayer = host.FindDescendantOfType()?.AdornerLayer;
+ topLevel.TemplateApplied += TopLevelOnTemplateApplied;
+ var adorner = topLevel.FindDescendantOfType()?.AdornerLayer;
+ if (adorner is not null)
+ {
+ adorner.Children.Add(this);
+ AdornerLayer.SetAdornedElement(this, adorner);
+ }
+ }
- if (adornerLayer is not null)
+ private void TopLevelOnTemplateApplied(object? sender, TemplateAppliedEventArgs e)
+ {
+ if (Parent is AdornerLayer adornerLayer)
{
- adornerLayer.Children.Add(this);
- AdornerLayer.SetAdornedElement(this, adornerLayer);
+ adornerLayer.Children.Remove(this);
+ AdornerLayer.SetAdornedElement(this, null);
}
+
+ // Reinstall notification manager on template reapplied.
+ var topLevel = (TopLevel)sender!;
+ topLevel.TemplateApplied -= TopLevelOnTemplateApplied;
+ InstallFromTopLevel(topLevel);
}
private void UpdatePseudoClasses(NotificationPosition position)
diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
index 37c46e1e7d..f21b608667 100644
--- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs
+++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
@@ -61,7 +61,7 @@ namespace Avalonia.Controls.Primitives
return adorner.GetValue(AdornedElementProperty);
}
- public static void SetAdornedElement(Visual adorner, Visual adorned)
+ public static void SetAdornedElement(Visual adorner, Visual? adorned)
{
adorner.SetValue(AdornedElementProperty, adorned);
}