[WIP] Basic Notification Compat intergration

This commit is contained in:
Terry MacDonald 2020-12-20 15:21:53 +13:00
parent 33689931ca
commit 3a2b74b049
8 changed files with 12160 additions and 61 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
//using Microsoft.Toolkit.Uwp.Notifications;
using DesktopNotifications;
using static DesktopNotifications.NotificationActivator;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace DisplayMagician
{
// The GUID must be unique to your app. Create a new GUID if copying this code.
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(INotificationActivationCallback))]
[Guid("56F14154-6339-4B94-8B82-80F78D5BCEAF"), ComVisible(true)]
public class DesktopNotificationActivator : NotificationActivator
{
public override void OnActivated(string invokedArgs, NotificationUserInput userInput, string appUserModelId)
{
/*Application.Current.Dispatcher.Invoke(delegate
{
// Tapping on the top-level header launches with empty args
if (arguments.Length == 0)
{
// Perform a normal launch
OpenWindowIfNeeded();
return;
}
// Parse the query string (using NuGet package QueryString.NET)
QueryString args = QueryString.Parse(invokedArgs);
// See what action is being requested
switch (args["action"])
{
// Open the image
case "viewImage":
// The URL retrieved from the toast args
string imageUrl = args["imageUrl"];
// Make sure we have a window open and in foreground
OpenWindowIfNeeded();
// And then show the image
(App.Current.Windows[0] as MainWindow).ShowImage(imageUrl);
break;
// Background: Quick reply to the conversation
case "reply":
// Get the response the user typed
string msg = userInput["tbReply"];
// And send this message
SendMessage(msg);
// If there's no windows open, exit the app
if (App.Current.Windows.Count == 0)
{
Application.Current.Shutdown();
}
break;
}
});*/
}
private void OpenWindowIfNeeded()
{
/*// Make sure we have a window open (in case user clicked toast while app closed)
if (App.Current.Windows.Count == 0)
{
new MainWindow().Show();
}
// Activate the window, bringing it to focus
App.Current.Windows[0].Activate();
// And make sure to maximize the window too, in case it was currently minimized
App.Current.Windows[0].WindowState = WindowState.Normal;*/
}
}
}

View File

@ -0,0 +1,506 @@
// ******************************************************************
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
// ******************************************************************
/*
* License for the RegisterActivator portion of code from FrecherxDachs
The MIT License (MIT)
Copyright (c) 2020 Michael Dietrich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
* */
using Microsoft.Win32;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using Windows.UI.Notifications;
using static DesktopNotifications.NotificationActivator;
namespace DesktopNotifications
{
public class DesktopNotificationManagerCompat
{
public const string TOAST_ACTIVATED_LAUNCH_ARG = "-ToastActivated";
private static bool _registeredAumidAndComServer;
private static string _aumid;
private static bool _registeredActivator;
/// <summary>
/// If you're not using MSIX or sparse packages, you must call this method to register your AUMID with the Compat library and to
/// register your COM CLSID and EXE in LocalServer32 registry. Feel free to call this regardless, and we will no-op if running
/// under Desktop Bridge. Call this upon application startup, before calling any other APIs.
/// </summary>
/// <param name="aumid">An AUMID that uniquely identifies your application.</param>
public static void RegisterAumidAndComServer<T>(string aumid)
where T : NotificationActivator
{
if (string.IsNullOrWhiteSpace(aumid))
{
throw new ArgumentException("You must provide an AUMID.", nameof(aumid));
}
// If running as Desktop Bridge
if (DesktopBridgeHelpers.IsRunningAsUwp())
{
// Clear the AUMID since Desktop Bridge doesn't use it, and then we're done.
// Desktop Bridge apps are registered with platform through their manifest.
// Their LocalServer32 key is also registered through their manifest.
_aumid = null;
_registeredAumidAndComServer = true;
return;
}
_aumid = aumid;
String exePath = Process.GetCurrentProcess().MainModule.FileName;
RegisterComServer<T>(exePath);
_registeredAumidAndComServer = true;
}
private static void RegisterComServer<T>(String exePath)
where T : NotificationActivator
{
// We register the EXE to start up when the notification is activated
string regString = String.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}", typeof(T).GUID);
using (var key = Registry.CurrentUser.CreateSubKey(regString))
{
// Include a flag so we know this was a toast activation and should wait for COM to process
// We also wrap EXE path in quotes for extra security
key.SetValue("LocalServer32", '"' + exePath + '"' + " " + TOAST_ACTIVATED_LAUNCH_ARG);
}
if (IsElevated)
{
// For elevated apps, we need to ensure they'll activate in existing running process by adding
// some values in local machine
using (var key = Registry.LocalMachine.CreateSubKey(regString))
{
// Same as above, except also including AppId to link to our AppId entry below
key.SetValue("LocalServer32", '"' + exePath + '"' + " " + TOAST_ACTIVATED_LAUNCH_ARG);
key.SetValue("AppId", "{" + typeof(T).GUID + "}");
}
// This tells COM to match any client, so Action Center will activate our elevated process.
// More info: https://docs.microsoft.com/windows/win32/com/runas
using (var key = Registry.LocalMachine.CreateSubKey(String.Format("SOFTWARE\\Classes\\AppID\\{{{0}}}", typeof(T).GUID)))
{
key.SetValue("RunAs", "Interactive User");
}
}
}
/// <summary>
/// Registers the activator type as a COM server client so that Windows can launch your activator.
/// </summary>
/// <typeparam name="T">Your implementation of NotificationActivator. Must have GUID and ComVisible attributes on class.</typeparam>
public static void RegisterActivator<T>()
where T : NotificationActivator, new()
{
// Big thanks to FrecherxDachs for figuring out the following code which works in .NET Core 3: https://github.com/FrecherxDachs/UwpNotificationNetCoreTest
var uuid = typeof(T).GUID;
uint _cookie;
CoRegisterClassObject(uuid, new NotificationActivatorClassFactory<T>(), CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE, out _cookie);
_registeredActivator = true;
}
[ComImport]
[Guid("00000001-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IClassFactory
{
[PreserveSig]
int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
[PreserveSig]
int LockServer(bool fLock);
}
private const int CLASS_E_NOAGGREGATION = -2147221232;
private const int E_NOINTERFACE = -2147467262;
private const int CLSCTX_LOCAL_SERVER = 4;
private const int REGCLS_MULTIPLEUSE = 1;
private const int S_OK = 0;
private static readonly Guid IUnknownGuid = new Guid("00000000-0000-0000-C000-000000000046");
private class NotificationActivatorClassFactory<T> : IClassFactory where T : NotificationActivator, new()
{
public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
{
ppvObject = IntPtr.Zero;
if (pUnkOuter != IntPtr.Zero)
Marshal.ThrowExceptionForHR(CLASS_E_NOAGGREGATION);
if (riid == typeof(T).GUID || riid == IUnknownGuid)
// Create the instance of the .NET object
ppvObject = Marshal.GetComInterfaceForObject(new T(),
typeof(INotificationActivationCallback));
else
// The object that ppvObject points to does not support the
// interface identified by riid.
Marshal.ThrowExceptionForHR(E_NOINTERFACE);
return S_OK;
}
public int LockServer(bool fLock)
{
return S_OK;
}
}
[DllImport("ole32.dll")]
private static extern int CoRegisterClassObject(
[MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnk,
uint dwClsContext,
uint flags,
out uint lpdwRegister);
/// <summary>
/// Creates a toast notifier. You must have called <see cref="RegisterActivator{T}"/> first (and also <see cref="RegisterAumidAndComServer(string)"/> if you're a classic Win32 app), or this will throw an exception.
/// </summary>
/// <returns></returns>
public static ToastNotifier CreateToastNotifier()
{
EnsureRegistered();
if (_aumid != null)
{
// Non-Desktop Bridge
return ToastNotificationManager.CreateToastNotifier(_aumid);
}
else
{
// Desktop Bridge
return ToastNotificationManager.CreateToastNotifier();
}
}
/// <summary>
/// Gets the <see cref="DesktopNotificationHistoryCompat"/> object. You must have called <see cref="RegisterActivator{T}"/> first (and also <see cref="RegisterAumidAndComServer(string)"/> if you're a classic Win32 app), or this will throw an exception.
/// </summary>
public static DesktopNotificationHistoryCompat History
{
get
{
EnsureRegistered();
return new DesktopNotificationHistoryCompat(_aumid);
}
}
private static void EnsureRegistered()
{
// If not registered AUMID yet
if (!_registeredAumidAndComServer)
{
// Check if Desktop Bridge
if (DesktopBridgeHelpers.IsRunningAsUwp())
{
// Implicitly registered, all good!
_registeredAumidAndComServer = true;
}
else
{
// Otherwise, incorrect usage
throw new Exception("You must call RegisterAumidAndComServer first.");
}
}
// If not registered activator yet
if (!_registeredActivator)
{
// Incorrect usage
throw new Exception("You must call RegisterActivator first.");
}
}
/// <summary>
/// Gets a boolean representing whether http images can be used within toasts. This is true if running with package identity (MSIX or sparse package).
/// </summary>
public static bool CanUseHttpImages { get { return DesktopBridgeHelpers.IsRunningAsUwp(); } }
private static bool IsElevated
{
get
{
return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
}
}
/// <summary>
/// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/edit/master/DesktopBridge.Helpers/Helpers.cs
/// </summary>
private class DesktopBridgeHelpers
{
const long APPMODEL_ERROR_NO_PACKAGE = 15700L;
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder packageFullName);
private static bool? _isRunningAsUwp;
public static bool IsRunningAsUwp()
{
if (_isRunningAsUwp == null)
{
if (IsWindows7OrLower)
{
_isRunningAsUwp = false;
}
else
{
int length = 0;
StringBuilder sb = new StringBuilder(0);
int result = GetCurrentPackageFullName(ref length, sb);
sb = new StringBuilder(length);
result = GetCurrentPackageFullName(ref length, sb);
_isRunningAsUwp = result != APPMODEL_ERROR_NO_PACKAGE;
}
}
return _isRunningAsUwp.Value;
}
private static bool IsWindows7OrLower
{
get
{
int versionMajor = Environment.OSVersion.Version.Major;
int versionMinor = Environment.OSVersion.Version.Minor;
double version = versionMajor + (double)versionMinor / 10;
return version <= 6.1;
}
}
}
}
/// <summary>
/// Manages the toast notifications for an app including the ability the clear all toast history and removing individual toasts.
/// </summary>
public sealed class DesktopNotificationHistoryCompat
{
private string _aumid;
private ToastNotificationHistory _history;
/// <summary>
/// Do not call this. Instead, call <see cref="DesktopNotificationManagerCompat.History"/> to obtain an instance.
/// </summary>
/// <param name="aumid"></param>
internal DesktopNotificationHistoryCompat(string aumid)
{
_aumid = aumid;
_history = ToastNotificationManager.History;
}
/// <summary>
/// Removes all notifications sent by this app from action center.
/// </summary>
public void Clear()
{
if (_aumid != null)
{
_history.Clear(_aumid);
}
else
{
_history.Clear();
}
}
/// <summary>
/// Gets all notifications sent by this app that are currently still in Action Center.
/// </summary>
/// <returns>A collection of toasts.</returns>
public IReadOnlyList<ToastNotification> GetHistory()
{
return _aumid != null ? _history.GetHistory(_aumid) : _history.GetHistory();
}
/// <summary>
/// Removes an individual toast, with the specified tag label, from action center.
/// </summary>
/// <param name="tag">The tag label of the toast notification to be removed.</param>
public void Remove(string tag)
{
if (_aumid != null)
{
_history.Remove(tag, string.Empty, _aumid);
}
else
{
_history.Remove(tag);
}
}
/// <summary>
/// Removes a toast notification from the action using the notification's tag and group labels.
/// </summary>
/// <param name="tag">The tag label of the toast notification to be removed.</param>
/// <param name="group">The group label of the toast notification to be removed.</param>
public void Remove(string tag, string group)
{
if (_aumid != null)
{
_history.Remove(tag, group, _aumid);
}
else
{
_history.Remove(tag, group);
}
}
/// <summary>
/// Removes a group of toast notifications, identified by the specified group label, from action center.
/// </summary>
/// <param name="group">The group label of the toast notifications to be removed.</param>
public void RemoveGroup(string group)
{
if (_aumid != null)
{
_history.RemoveGroup(group, _aumid);
}
else
{
_history.RemoveGroup(group);
}
}
}
/// <summary>
/// Apps must implement this activator to handle notification activation.
/// </summary>
public abstract class NotificationActivator : NotificationActivator.INotificationActivationCallback
{
public void Activate(string appUserModelId, string invokedArgs, NOTIFICATION_USER_INPUT_DATA[] data, uint dataCount)
{
OnActivated(invokedArgs, new NotificationUserInput(data), appUserModelId);
}
/// <summary>
/// This method will be called when the user clicks on a foreground or background activation on a toast. Parent app must implement this method.
/// </summary>
/// <param name="arguments">The arguments from the original notification. This is either the launch argument if the user clicked the body of your toast, or the arguments from a button on your toast.</param>
/// <param name="userInput">Text and selection values that the user entered in your toast.</param>
/// <param name="appUserModelId">Your AUMID.</param>
public abstract void OnActivated(string arguments, NotificationUserInput userInput, string appUserModelId);
// These are the new APIs for Windows 10
#region NewAPIs
[StructLayout(LayoutKind.Sequential), Serializable]
public struct NOTIFICATION_USER_INPUT_DATA
{
[MarshalAs(UnmanagedType.LPWStr)]
public string Key;
[MarshalAs(UnmanagedType.LPWStr)]
public string Value;
}
[ComImport,
Guid("53E31837-6600-4A81-9395-75CFFE746F94"), ComVisible(true),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface INotificationActivationCallback
{
void Activate(
[In, MarshalAs(UnmanagedType.LPWStr)]
string appUserModelId,
[In, MarshalAs(UnmanagedType.LPWStr)]
string invokedArgs,
[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]
NOTIFICATION_USER_INPUT_DATA[] data,
[In, MarshalAs(UnmanagedType.U4)]
uint dataCount);
}
#endregion
}
/// <summary>
/// Text and selection values that the user entered on your notification. The Key is the ID of the input, and the Value is what the user entered.
/// </summary>
public class NotificationUserInput : IReadOnlyDictionary<string, string>
{
private NotificationActivator.NOTIFICATION_USER_INPUT_DATA[] _data;
internal NotificationUserInput(NotificationActivator.NOTIFICATION_USER_INPUT_DATA[] data)
{
_data = data;
}
public string this[string key] => _data.First(i => i.Key == key).Value;
public IEnumerable<string> Keys => _data.Select(i => i.Key);
public IEnumerable<string> Values => _data.Select(i => i.Value);
public int Count => _data.Length;
public bool ContainsKey(string key)
{
return _data.Any(i => i.Key == key);
}
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return _data.Select(i => new KeyValuePair<string, string>(i.Key, i.Value)).GetEnumerator();
}
public bool TryGetValue(string key, out string value)
{
foreach (var item in _data)
{
if (item.Key == key)
{
value = item.Value;
return true;
}
}
value = null;
return false;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -86,6 +86,8 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="DesktopNotificationActivator.cs" />
<Compile Include="DesktopNotificationManagerCompat.cs" />
<Compile Include="GameLibraries\Game.cs" />
<Compile Include="GameLibraries\SteamAppInfoParser\AppInfo.cs" />
<Compile Include="GameLibraries\SteamAppInfoParser\EUniverse.cs" />
@ -255,6 +257,9 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications">
<Version>6.1.1</Version>
</PackageReference>
<PackageReference Include="MintPlayer.IconUtils">
<Version>1.0.4</Version>
</PackageReference>
@ -264,6 +269,9 @@
<PackageReference Include="NLog">
<Version>4.7.6</Version>
</PackageReference>
<PackageReference Include="QueryString.NET">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="ValveKeyValue">
<Version>0.3.1.152</Version>
</PackageReference>

View File

@ -13,6 +13,9 @@ using DisplayMagician.Shared;
using DisplayMagician.UIForms;
using System.Text.RegularExpressions;
using System.Drawing;
//using Microsoft.Toolkit.Uwp.Notifications;
using DesktopNotifications;
//using System.Runtime.InteropServices;
namespace DisplayMagician {
@ -53,7 +56,11 @@ namespace DisplayMagician {
// taskbar pinning and similar.
// This is a helper function that sets the AUMID to a static default defined
// within ShellUtils under the DisplayMagician.Shared project.
ShellUtils.SetDefaultProcessExplicitAppUserModelID();
//ShellUtils.SetDefaultProcessExplicitAppUserModelID();
// Register AUMID, COM server, and activator
DesktopNotificationManagerCompat.RegisterAumidAndComServer<DesktopNotificationActivator>(ShellUtils.AUMID);
DesktopNotificationManagerCompat.RegisterActivator<DesktopNotificationActivator>();
// Prepare NLog for logging
var config = new NLog.Config.LoggingConfiguration();

View File

@ -31,6 +31,7 @@
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
this.btn_toast = new System.Windows.Forms.Button();
this.btn_settings = new System.Windows.Forms.Button();
this.lbl_create_profile = new System.Windows.Forms.Label();
this.btn_setup_display_profiles = new System.Windows.Forms.Button();
@ -71,6 +72,7 @@
//
// splitContainer1.Panel1
//
this.splitContainer1.Panel1.Controls.Add(this.btn_toast);
this.splitContainer1.Panel1.Controls.Add(this.btn_settings);
this.splitContainer1.Panel1.Controls.Add(this.lbl_create_profile);
this.splitContainer1.Panel1.Controls.Add(this.btn_setup_display_profiles);
@ -86,6 +88,16 @@
this.splitContainer1.Panel2.Controls.Add(this.pb_game_shortcut);
this.splitContainer1.TabStop = false;
//
// btn_toast
//
this.btn_toast.FlatAppearance.MouseDownBackColor = System.Drawing.Color.IndianRed;
this.btn_toast.FlatAppearance.MouseOverBackColor = System.Drawing.Color.Brown;
resources.ApplyResources(this.btn_toast, "btn_toast");
this.btn_toast.ForeColor = System.Drawing.Color.White;
this.btn_toast.Name = "btn_toast";
this.btn_toast.UseVisualStyleBackColor = true;
this.btn_toast.Click += new System.EventHandler(this.btn_toast_Click);
//
// btn_settings
//
this.btn_settings.FlatAppearance.MouseDownBackColor = System.Drawing.Color.IndianRed;
@ -303,5 +315,6 @@
private System.Windows.Forms.Label lbl_create_profile;
private System.Windows.Forms.Label lbl_create_shortcut;
private System.Windows.Forms.Button btn_settings;
private System.Windows.Forms.Button btn_toast;
}
}

View File

@ -12,10 +12,16 @@ using System.Threading;
using System.Reflection;
using DisplayMagician.Shared;
using System.Runtime.InteropServices;
//using Microsoft.Toolkit.Uwp.Notifications;
using DesktopNotifications;
using Microsoft.QueryStringDotNET;
using Windows.UI.Notifications;
using System.IO;
using AutoUpdaterDotNET;
using Newtonsoft.Json;
using System.Net;
using Windows.Data.Xml.Dom;
using Microsoft.Toolkit.Uwp.Notifications;
namespace DisplayMagician.UIForms
{
@ -390,5 +396,78 @@ namespace DisplayMagician.UIForms
else if (!mySettings.MinimiseOnStart && cb_minimise_notification_area.Checked)
cb_minimise_notification_area.Checked = false;
}
private void btn_toast_Click(object sender, EventArgs e)
{
// Construct the toast content
ToastContent toastContent = new ToastContent()
{
// Arguments when the user taps body of toast
Launch = "DisplayMagician_Notification",
Visual = new ToastVisual()
{
BindingGeneric = new ToastBindingGeneric()
{
Children =
{
new AdaptiveText()
{
Text = "Hello from DisplayMagician!"
},
},
}
},
/*
Actions = new ToastActionsCustom()
{
Inputs =
{
new ToastTextBox("tbReply")
{
PlaceholderContent = "Type a response"
}
},
Buttons =
{
// Note that there's no reason to specify background activation, since our COM
// activator decides whether to process in background or launch foreground window
new ToastButton("Reply", new QueryString()
{
{ "action", "reply" },
{ "conversationId", conversationId.ToString() }
}.ToString()),
new ToastButton("Like", new QueryString()
{
{ "action", "like" },
{ "conversationId", conversationId.ToString() }
}.ToString()),
new ToastButton("View", new QueryString()
{
{ "action", "viewImage" },
{ "imageUrl", image }
}.ToString())
}
}*/
};
// Make sure to use Windows.Data.Xml.Dom
var doc = new XmlDocument();
doc.LoadXml(toastContent.GetContent());
// And create the toast notification
var toast = new ToastNotification(doc);
// And then show it
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
}
}
}

View File

@ -132,6 +132,36 @@
<data name="splitContainer1.Orientation" type="System.Windows.Forms.Orientation, System.Windows.Forms">
<value>Horizontal</value>
</data>
<data name="btn_toast.FlatStyle" type="System.Windows.Forms.FlatStyle, System.Windows.Forms">
<value>Flat</value>
</data>
<data name="btn_toast.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<data name="btn_toast.Location" type="System.Drawing.Point, System.Drawing">
<value>366, 64</value>
</data>
<data name="btn_toast.Size" type="System.Drawing.Size, System.Drawing">
<value>75, 23</value>
</data>
<data name="btn_toast.TabIndex" type="System.Int32, mscorlib">
<value>8</value>
</data>
<data name="btn_toast.Text" xml:space="preserve">
<value>Show Toast</value>
</data>
<data name="&gt;&gt;btn_toast.Name" xml:space="preserve">
<value>btn_toast</value>
</data>
<data name="&gt;&gt;btn_toast.Type" xml:space="preserve">
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;btn_toast.Parent" xml:space="preserve">
<value>splitContainer1.Panel1</value>
</data>
<data name="&gt;&gt;btn_toast.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="btn_settings.FlatStyle" type="System.Windows.Forms.FlatStyle, System.Windows.Forms">
<value>Flat</value>
</data>
@ -157,7 +187,7 @@
<value>splitContainer1.Panel1</value>
</data>
<data name="&gt;&gt;btn_settings.ZOrder" xml:space="preserve">
<value>0</value>
<value>1</value>
</data>
<data name="lbl_create_profile.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>None</value>
@ -190,7 +220,7 @@
<value>splitContainer1.Panel1</value>
</data>
<data name="&gt;&gt;lbl_create_profile.ZOrder" xml:space="preserve">
<value>1</value>
<value>2</value>
</data>
<data name="btn_setup_display_profiles.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>None</value>
@ -223,7 +253,7 @@
<value>splitContainer1.Panel1</value>
</data>
<data name="&gt;&gt;btn_setup_display_profiles.ZOrder" xml:space="preserve">
<value>2</value>
<value>3</value>
</data>
<data name="pb_display_profile.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
@ -10771,7 +10801,7 @@
<value>splitContainer1.Panel1</value>
</data>
<data name="&gt;&gt;pb_display_profile.ZOrder" xml:space="preserve">
<value>3</value>
<value>4</value>
</data>
<data name="&gt;&gt;splitContainer1.Panel1.Name" xml:space="preserve">
<value>splitContainer1.Panel1</value>
@ -10798,7 +10828,7 @@
<value>NoControl</value>
</data>
<data name="lbl_create_shortcut.Location" type="System.Drawing.Point, System.Drawing">
<value>220, 253</value>
<value>220, 257</value>
</data>
<data name="lbl_create_shortcut.Size" type="System.Drawing.Size, System.Drawing">
<value>343, 22</value>
@ -10837,7 +10867,7 @@
<value>NoControl</value>
</data>
<data name="cb_minimise_notification_area.Location" type="System.Drawing.Point, System.Drawing">
<value>234, 357</value>
<value>234, 363</value>
</data>
<data name="cb_minimise_notification_area.Size" type="System.Drawing.Size, System.Drawing">
<value>332, 20</value>
@ -10870,7 +10900,7 @@
<value>Microsoft Sans Serif, 9.75pt</value>
</data>
<data name="lbl_version.Location" type="System.Drawing.Point, System.Drawing">
<value>12, 358</value>
<value>12, 364</value>
</data>
<data name="lbl_version.Size" type="System.Drawing.Size, System.Drawing">
<value>25, 16</value>
@ -10906,7 +10936,7 @@
<value>Microsoft Sans Serif, 21.75pt</value>
</data>
<data name="btn_setup_game_shortcuts.Location" type="System.Drawing.Point, System.Drawing">
<value>212, 180</value>
<value>212, 184</value>
</data>
<data name="btn_setup_game_shortcuts.Size" type="System.Drawing.Size, System.Drawing">
<value>360, 50</value>
@ -10939,7 +10969,7 @@
<value>NoControl</value>
</data>
<data name="btn_exit.Location" type="System.Drawing.Point, System.Drawing">
<value>700, 354</value>
<value>700, 360</value>
</data>
<data name="btn_exit.Size" type="System.Drawing.Size, System.Drawing">
<value>75, 23</value>