diff --git a/Chapter08/Complete/MyMediaCollection/App.xaml.cs b/Chapter08/Complete/MyMediaCollection/App.xaml.cs
index a7faf93..9a2ce58 100644
--- a/Chapter08/Complete/MyMediaCollection/App.xaml.cs
+++ b/Chapter08/Complete/MyMediaCollection/App.xaml.cs
@@ -3,13 +3,17 @@ using Microsoft.Extensions.Hosting;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
+using Microsoft.Windows.AppLifecycle;
+using Microsoft.Windows.AppNotifications;
+using MyMediaCollection.Helpers;
using MyMediaCollection.Interfaces;
using MyMediaCollection.Services;
using MyMediaCollection.ViewModels;
using MyMediaCollection.Views;
using System;
-using System.Net;
+using System.Runtime.InteropServices;
using System.Threading.Tasks;
+using WinRT.Interop;
namespace MyMediaCollection
{
@@ -18,6 +22,11 @@ namespace MyMediaCollection
///
public partial class App : Application
{
+ [DllImport("user32.dll", SetLastError = true)]
+ static extern void SwitchToThisWindow(IntPtr hWnd, bool turnOn);
+
+ private NotificationManager notificationManager;
+
public static IHost HostContainer { get; private set; }
///
@@ -27,13 +36,45 @@ namespace MyMediaCollection
public App()
{
this.InitializeComponent();
+ notificationManager = new NotificationManager();
+ notificationManager.Init();
+ AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
+ }
+
+ private void CurrentDomain_ProcessExit(object sender, EventArgs e)
+ {
+ notificationManager.Unregister();
+ }
+
+ public static void ToForeground()
+ {
+ if (m_window != null)
+ {
+ IntPtr handle = WindowNative.GetWindowHandle(m_window);
+ if (handle != IntPtr.Zero)
+ {
+ SwitchToThisWindow(handle, true);
+ }
+ }
+ }
+
+ public static string GetFullPathToExe()
+ {
+ var path = AppDomain.CurrentDomain.BaseDirectory;
+ var pos = path.LastIndexOf("\\");
+ return path.Substring(0, pos);
+ }
+
+ public static string GetFullPathToAsset(string assetName)
+ {
+ return $"{GetFullPathToExe()}\\Assets\\{assetName}";
}
///
/// Invoked when the application is launched.
///
/// Details about the launch request and process.
- protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
+ protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
m_window = new MainWindow();
var rootFrame = new Frame();
@@ -41,6 +82,22 @@ namespace MyMediaCollection
rootFrame.NavigationFailed += RootFrame_NavigationFailed;
rootFrame.Navigate(typeof(MainPage), args);
m_window.Content = rootFrame;
+
+ var currentInstance = AppInstance.GetCurrent();
+ if (currentInstance.IsCurrent)
+ {
+ AppActivationArguments activationArgs = currentInstance.GetActivatedEventArgs();
+ if (activationArgs != null)
+ {
+ ExtendedActivationKind extendedKind = activationArgs.Kind;
+ if (extendedKind == ExtendedActivationKind.AppNotification)
+ {
+ var notificationActivatedEventArgs = (AppNotificationActivatedEventArgs)activationArgs.Data;
+ notificationManager.ProcessLaunchActivationArgs(notificationActivatedEventArgs);
+ }
+ }
+ }
+
m_window.Activate();
}
@@ -49,7 +106,7 @@ namespace MyMediaCollection
throw new Exception($"Error loading page {e.SourcePageType.FullName}");
}
- private Window m_window;
+ private static Window m_window;
internal Window Window => m_window;
diff --git a/Chapter08/Complete/MyMediaCollection/Helpers/NotificationManager.cs b/Chapter08/Complete/MyMediaCollection/Helpers/NotificationManager.cs
new file mode 100644
index 0000000..a9fa398
--- /dev/null
+++ b/Chapter08/Complete/MyMediaCollection/Helpers/NotificationManager.cs
@@ -0,0 +1,88 @@
+using Microsoft.Windows.AppNotifications;
+using System;
+using System.Collections.Generic;
+
+namespace MyMediaCollection.Helpers
+{
+ internal class NotificationManager
+ {
+ private bool isRegistered;
+ private Dictionary> notificationHandlers;
+
+ public NotificationManager()
+ {
+ isRegistered = false;
+
+ // When adding new a scenario, be sure to add its notification handler here.
+ notificationHandlers = new Dictionary>
+ {
+ { ToastWithAvatar.ScenarioId, ToastWithAvatar.NotificationReceived },
+ { ToastWithText.ScenarioId, ToastWithText.NotificationReceived }
+ };
+ }
+
+ ~NotificationManager()
+ {
+ Unregister();
+ }
+
+ public void Unregister()
+ {
+ if (isRegistered)
+ {
+ AppNotificationManager.Default.Unregister();
+ isRegistered = false;
+ }
+ }
+
+ public void Init()
+ {
+ AppNotificationManager notificationManager = AppNotificationManager.Default;
+
+ // Add handler before calling Register.
+ notificationManager.NotificationInvoked += OnNotificationInvoked;
+ notificationManager.Register();
+
+ isRegistered = true;
+ }
+
+ public void ProcessLaunchActivationArgs(AppNotificationActivatedEventArgs notificationActivatedEventArgs)
+ {
+ DispatchNotification(notificationActivatedEventArgs);
+ NotificationShared.AppLaunchedFromNotification();
+ }
+
+ private bool DispatchNotification(AppNotificationActivatedEventArgs notificationActivatedEventArgs)
+ {
+ var scenarioId = notificationActivatedEventArgs.Arguments[NotificationShared.scenarioTag];
+ if (scenarioId.Length != 0)
+ {
+ try
+ {
+ notificationHandlers[int.Parse(scenarioId)](notificationActivatedEventArgs);
+ return true;
+ }
+ catch
+ {
+ // No matching NotificationHandler for scenarioId.
+ return false;
+ }
+ }
+ else
+ {
+ // No scenarioId provided
+ return false;
+ }
+ }
+
+ public void OnNotificationInvoked(object sender, AppNotificationActivatedEventArgs notificationActivatedEventArgs)
+ {
+ NotificationShared.NotificationReceived();
+
+ if (!DispatchNotification(notificationActivatedEventArgs))
+ {
+ NotificationShared.UnrecognizedToastOriginator();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter08/Complete/MyMediaCollection/Helpers/NotificationShared.cs b/Chapter08/Complete/MyMediaCollection/Helpers/NotificationShared.cs
new file mode 100644
index 0000000..8fad23a
--- /dev/null
+++ b/Chapter08/Complete/MyMediaCollection/Helpers/NotificationShared.cs
@@ -0,0 +1,43 @@
+using Microsoft.UI.Xaml.Controls;
+using MyMediaCollection.Views;
+
+namespace MyMediaCollection.Helpers
+{
+ public class NotificationShared
+ {
+ public const string scenarioTag = "scenarioId";
+
+ public struct Notification
+ {
+ public string Originator;
+ public string Action;
+ public bool HasInput;
+ public string Input;
+ };
+
+ public static void CouldNotSendToast()
+ {
+ MainPage.Current.NotifyUser("Could not send toast", InfoBarSeverity.Error);
+ }
+
+ public static void ToastSentSuccessfully()
+ {
+ MainPage.Current.NotifyUser("Toast sent successfully!", InfoBarSeverity.Success);
+ }
+
+ public static void AppLaunchedFromNotification()
+ {
+ MainPage.Current.NotifyUser("App launched from notifications", InfoBarSeverity.Informational);
+ }
+
+ public static void NotificationReceived()
+ {
+ MainPage.Current.NotifyUser("Notification received", InfoBarSeverity.Informational);
+ }
+
+ public static void UnrecognizedToastOriginator()
+ {
+ MainPage.Current.NotifyUser("Unrecognized Toast Originator or Unknown Error", InfoBarSeverity.Error);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter08/Complete/MyMediaCollection/Helpers/ToastWithAvatar.cs b/Chapter08/Complete/MyMediaCollection/Helpers/ToastWithAvatar.cs
new file mode 100644
index 0000000..049946b
--- /dev/null
+++ b/Chapter08/Complete/MyMediaCollection/Helpers/ToastWithAvatar.cs
@@ -0,0 +1,42 @@
+using Microsoft.Windows.AppNotifications.Builder;
+using Microsoft.Windows.AppNotifications;
+using MyMediaCollection.Views;
+
+namespace MyMediaCollection.Helpers
+{
+ public class ToastWithAvatar
+ {
+ public const int ScenarioId = 1;
+ public const string ScenarioName = "Local Toast with Image";
+
+ public static bool SendToast()
+ {
+ var appNotification = new AppNotificationBuilder()
+ .AddArgument("action", "ToastClick")
+ .AddArgument(NotificationShared.scenarioTag, ScenarioId.ToString())
+ .SetAppLogoOverride(new System.Uri($"file://{App.GetFullPathToAsset("Square150x150Logo.scale-200.png")}"), AppNotificationImageCrop.Circle)
+ .AddText(ScenarioName)
+ .AddText("This is a notification message.")
+ .AddButton(new AppNotificationButton("Open App")
+ .AddArgument("action", "OpenApp")
+ .AddArgument(NotificationShared.scenarioTag, ScenarioId.ToString()))
+ .BuildNotification();
+
+ AppNotificationManager.Default.Show(appNotification);
+
+ // If notification is sent, it will have an Id. Success.
+ return appNotification.Id != 0;
+ }
+
+ public static void NotificationReceived(AppNotificationActivatedEventArgs notificationActivatedEventArgs)
+ {
+ var notification = new NotificationShared.Notification
+ {
+ Originator = ScenarioName,
+ Action = notificationActivatedEventArgs.Arguments["action"]
+ };
+ MainPage.Current.NotificationReceived(notification);
+ App.ToForeground();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter08/Complete/MyMediaCollection/Helpers/ToastWithText.cs b/Chapter08/Complete/MyMediaCollection/Helpers/ToastWithText.cs
new file mode 100644
index 0000000..e383298
--- /dev/null
+++ b/Chapter08/Complete/MyMediaCollection/Helpers/ToastWithText.cs
@@ -0,0 +1,47 @@
+using Microsoft.Windows.AppNotifications.Builder;
+using Microsoft.Windows.AppNotifications;
+using MyMediaCollection.Views;
+
+namespace MyMediaCollection.Helpers
+{
+ public class ToastWithText
+ {
+ public const int ScenarioId = 2;
+ public const string ScenarioName = "Local Toast with Image and Text Entry";
+ const string textboxReplyId = "textboxReply";
+
+ public static bool SendToast()
+ {
+ var appNotification = new AppNotificationBuilder()
+ .AddArgument("action", "ToastClick")
+ .AddArgument(NotificationShared.scenarioTag, ScenarioId.ToString())
+ .SetAppLogoOverride(new System.Uri($"file://{App.GetFullPathToAsset("Square150x150Logo.scale-200.png")}"), AppNotificationImageCrop.Circle)
+ .AddText(ScenarioName)
+ .AddText("This is a notification message.")
+ .AddTextBox(textboxReplyId, "Enter a reply", "Reply box")
+ .AddButton(new AppNotificationButton("Reply")
+ .AddArgument("action", "Reply")
+ .AddArgument(NotificationShared.scenarioTag, ScenarioId.ToString())
+ .SetInputId(textboxReplyId))
+ .BuildNotification();
+
+ AppNotificationManager.Default.Show(appNotification);
+
+ // If notification is sent, it will have an Id. Success.
+ return appNotification.Id != 0;
+ }
+
+ public static void NotificationReceived(AppNotificationActivatedEventArgs notificationActivatedEventArgs)
+ {
+ var notification = new NotificationShared.Notification
+ {
+ Originator = ScenarioName,
+ Action = notificationActivatedEventArgs.Arguments["action"],
+ HasInput = true,
+ Input = notificationActivatedEventArgs.UserInput[textboxReplyId]
+ };
+ MainPage.Current.NotificationReceived(notification);
+ App.ToForeground();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter08/Complete/MyMediaCollection/MyMediaCollection.csproj b/Chapter08/Complete/MyMediaCollection/MyMediaCollection.csproj
index 476f29f..6d42fbf 100644
--- a/Chapter08/Complete/MyMediaCollection/MyMediaCollection.csproj
+++ b/Chapter08/Complete/MyMediaCollection/MyMediaCollection.csproj
@@ -28,13 +28,13 @@
-
+
-
+
-
-
+
+
diff --git a/Chapter08/Complete/MyMediaCollection/Package.appxmanifest b/Chapter08/Complete/MyMediaCollection/Package.appxmanifest
index f15a1ea..6faa5e1 100644
--- a/Chapter08/Complete/MyMediaCollection/Package.appxmanifest
+++ b/Chapter08/Complete/MyMediaCollection/Package.appxmanifest
@@ -4,6 +4,8 @@
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
+ xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
+ xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
IgnorableNamespaces="uap rescap">
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter08/Complete/MyMediaCollection/ViewModels/MainViewModel.cs b/Chapter08/Complete/MyMediaCollection/ViewModels/MainViewModel.cs
index 3def498..d718f18 100644
--- a/Chapter08/Complete/MyMediaCollection/ViewModels/MainViewModel.cs
+++ b/Chapter08/Complete/MyMediaCollection/ViewModels/MainViewModel.cs
@@ -1,6 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml.Input;
+using MyMediaCollection.Helpers;
using MyMediaCollection.Interfaces;
using MyMediaCollection.Model;
using System.Collections.ObjectModel;
@@ -98,5 +99,23 @@ namespace MyMediaCollection.ViewModels
}
private bool CanDeleteItem() => SelectedMediaItem != null;
+
+ [RelayCommand]
+ private void SendToast()
+ {
+ if (ToastWithAvatar.SendToast())
+ NotificationShared.ToastSentSuccessfully();
+ else
+ NotificationShared.CouldNotSendToast();
+ }
+
+ [RelayCommand]
+ private void SendToastWithText()
+ {
+ if (ToastWithText.SendToast())
+ NotificationShared.ToastSentSuccessfully();
+ else
+ NotificationShared.CouldNotSendToast();
+ }
}
}
\ No newline at end of file
diff --git a/Chapter08/Complete/MyMediaCollection/Views/MainPage.xaml b/Chapter08/Complete/MyMediaCollection/Views/MainPage.xaml
index ad422cf..8c79b8c 100644
--- a/Chapter08/Complete/MyMediaCollection/Views/MainPage.xaml
+++ b/Chapter08/Complete/MyMediaCollection/Views/MainPage.xaml
@@ -14,6 +14,7 @@
+
@@ -69,6 +70,12 @@
Margin="4,0">
+
+
@@ -77,5 +84,6 @@
Margin="8"/>
+
diff --git a/Chapter08/Complete/MyMediaCollection/Views/MainPage.xaml.cs b/Chapter08/Complete/MyMediaCollection/Views/MainPage.xaml.cs
index f63db82..b1786e8 100644
--- a/Chapter08/Complete/MyMediaCollection/Views/MainPage.xaml.cs
+++ b/Chapter08/Complete/MyMediaCollection/Views/MainPage.xaml.cs
@@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using MyMediaCollection.ViewModels;
+using MyMediaCollection.Helpers;
namespace MyMediaCollection.Views
{
@@ -10,10 +11,13 @@ namespace MyMediaCollection.Views
///
public sealed partial class MainPage : Page
{
+ public static MainPage Current;
+
public MainPage()
{
ViewModel = App.HostContainer.Services.GetService();
this.InitializeComponent();
+ Current = this;
Loaded += MainPage_Loaded;
}
@@ -27,5 +31,63 @@ namespace MyMediaCollection.Views
}
public MainViewModel ViewModel;
+
+ public void NotifyUser(string message, InfoBarSeverity severity, bool isOpen = true)
+ {
+ if (DispatcherQueue.HasThreadAccess)
+ {
+ UpdateStatus(message, severity, isOpen);
+ }
+ else
+ {
+ DispatcherQueue.TryEnqueue(() =>
+ {
+ UpdateStatus(message, severity, isOpen);
+ });
+ }
+ }
+
+ private void UpdateStatus(string message, InfoBarSeverity severity, bool isOpen)
+ {
+ notifyInfoBar.Message = message;
+ notifyInfoBar.IsOpen = isOpen;
+ notifyInfoBar.Severity = severity;
+ }
+
+ public void NotificationReceived(NotificationShared.Notification notification)
+ {
+ var text = $"{notification.Originator}; Action: {notification.Action}";
+
+ if (notification.HasInput)
+ {
+ if (string.IsNullOrWhiteSpace(notification.Input))
+ text += "; No input received";
+ else
+ text += $"; Input received: {notification.Input}";
+ }
+
+ if (DispatcherQueue.HasThreadAccess)
+ DisplayMessageDialog(text);
+ else
+ {
+ DispatcherQueue.TryEnqueue(() =>
+ {
+ DisplayMessageDialog(text);
+ });
+ }
+ }
+
+ private void DisplayMessageDialog(string message)
+ {
+ ContentDialog notifyDialog = new()
+ {
+ XamlRoot = this.XamlRoot,
+ Title = "Notification received",
+ Content = message,
+ CloseButtonText = "Ok"
+ };
+
+ notifyDialog.ShowAsync();
+ }
}
}
\ No newline at end of file