Merge remote-tracking branch 'origin/master' into dev/split-controls

This commit is contained in:
michael-hawker 2021-01-26 13:16:21 -08:00
Родитель f55f101ca2 5553f45f68
Коммит d09c667a0b
23 изменённых файлов: 930 добавлений и 157 удалений

Просмотреть файл

@ -1,19 +1,21 @@
<Project>
<PropertyGroup>
<UseUWP Condition="'$(TargetFramework)' == 'uap10.0' or '$(TargetFramework)' == 'uap10.0.17763' or '$(TargetFramework)' == 'native' or '$(TargetFramework)' == 'net461'">true</UseUWP>
<UseUWP Condition="$(TargetFramework.Contains(`uap10.0`)) or '$(TargetFramework)' == 'net461'">true</UseUWP>
</PropertyGroup>
<Choose>
<When Condition="!$(TargetFramework.Contains(`uap10.0`)) and '$(TargetFramework)' != 'native' and '$(IsSampleProject)' != 'true'">
<!--We'll include signing the Notifications library since we need the DLL signature to match for interop from class libraries to main projects-->
<When Condition="(!$(TargetFramework.Contains(`uap10.0`)) and '$(TargetFramework)' != 'native' and '$(IsSampleProject)' != 'true') or $(MSBuildProjectName) == 'Microsoft.Toolkit.Uwp.Notifications'">
<PropertyGroup>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)toolkit.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
</When>
</Choose>
<Import Project="$(MSBuildThisFileDirectory)build\Windows.Toolkit.Uwp.Build.targets" Condition="'$(UseUWP)' == 'true'"/>
<!--Exclude Notifications project from this since it sets different min versions than what we want for notifications-->
<Import Project="$(MSBuildThisFileDirectory)build\Windows.Toolkit.Uwp.Build.targets" Condition="'$(UseUWP)' == 'true' and $(MSBuildProjectName) != 'Microsoft.Toolkit.Uwp.Notifications'"/>
<Target Name="AddCommitHashToAssemblyAttributes" BeforeTargets="GetAssemblyAttributes">
<ItemGroup>

Просмотреть файл

@ -1,22 +1,10 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<!--<TargetFrameworks>netstandard1.4;uap10.0;native;net461;netcoreapp3.1</TargetFrameworks>-->
<!-- Removed 'native' target to unblock CI on VS 16.8, tied to changes breaking workaround for https://github.com/NuGet/Home/issues/5154 -->
<TargetFrameworks>netstandard1.4;uap10.0;net461;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>netstandard1.4;uap10.0.19041;net461;netcoreapp3.1;net5.0;net5.0-windows10.0.17763.0;native</TargetFrameworks>
<DefineConstants>$(DefineConstants);NETFX_CORE</DefineConstants>
<Title>Windows Community Toolkit Notifications</Title>
<Description>
Generate tile, toast, and badge notifications for Windows 10 via code, with the help of IntelliSense.
Adds Support for adaptive tiles and adaptive/interactive toasts for Windows 10. It is part of the Windows Community Toolkit.
Supports C# and C++ UWP project types.
Also works with C# portable class libraries and non-UWP C# projects like server projects.
This project contains outputs for netstandard1.4, uap10.0 and native for WinRT.
</Description>
<PackageTags>notifications win10 windows 10 tile tiles toast toasts badge xml uwp c# csharp c++</PackageTags>
<ExtrasImplicitPlatformPackageIsPrivate Condition=" '$(TargetFramework)' == 'native' ">true</ExtrasImplicitPlatformPackageIsPrivate>
<DefaultTargetPlatformMinVersion>10240</DefaultTargetPlatformMinVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
<NuspecFile>Microsoft.Toolkit.Uwp.Notifications.nuspec</NuspecFile>
</PropertyGroup>
<Choose>
@ -27,16 +15,17 @@
<!--Reference Windows SDK NuGet of correct target platform version-->
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.1" />
</ItemGroup>
<PropertyGroup>
<!--Define the WINDOWS_UWP conditional symbol, since the Windows.Data.Xml and the Windows.UI.Notification namespaces are available-->
<DefineConstants>$(DefineConstants);WINDOWS_UWP;WIN32</DefineConstants>
</PropertyGroup>
</When>
</Choose>
<PropertyGroup Condition="'$(TargetFramework)'=='net461' or '$(TargetFramework)'=='netcoreapp3.1' or '$(TargetFramework)'=='net5.0-windows10.0.17763.0'">
<!--Define the WINDOWS_UWP conditional symbol, since the Windows.Data.Xml and the Windows.UI.Notification namespaces are available-->
<DefineConstants>$(DefineConstants);WINDOWS_UWP;WIN32</DefineConstants>
</PropertyGroup>
<!--NET Core desktop apps also need the Registry NuGet package and System.Reflection.Emit for generating COM class dynamically-->
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'">
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.1' or '$(TargetFramework)'=='net5.0-windows10.0.17763.0'">
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
@ -54,6 +43,12 @@
<ItemGroup Condition=" '$(TargetFramework)' != 'native' ">
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0.19041'">
<TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
<DefaultTargetPlatformMinVersion>16299</DefaultTargetPlatformMinVersion>
<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'native'">
<OutputType>winmdobj</OutputType>
@ -63,8 +58,9 @@
<NugetTargetMoniker Condition="'$(DesignTimeBuild)' == 'true'">native</NugetTargetMoniker>
<NugetTargetMoniker Condition="'$(DesignTimeBuild)' != 'true'">UAP,Version=v10.0</NugetTargetMoniker>
<PackageTargetFallback>uap10.0</PackageTargetFallback>
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == '' ">10.0.19041.0</TargetPlatformVersion>
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == '' ">10.0.10240.0</TargetPlatformMinVersion>
<TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
<DefaultTargetPlatformMinVersion>10240</DefaultTargetPlatformMinVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
<DefineConstants Condition="'$(DisableImplicitFrameworkDefines)' != 'true'">$(DefineConstants);NETFX_CORE;WINDOWS_UWP;WINRT</DefineConstants>
<CopyLocalLockFileAssemblies Condition="'$(CopyLocalLockFileAssemblies)' == ''">false</CopyLocalLockFileAssemblies>
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
@ -76,5 +72,12 @@
<ImplicitFrameworkDefine Condition="'$(DisableImplicitFrameworkDefines)' != 'true'">UAP$(TargetPlatformMinVersion.Replace('.', '_'))</ImplicitFrameworkDefine>
<DisableImplicitFrameworkDefines Condition="'$(DisableImplicitFrameworkDefines)' != 'true'">true</DisableImplicitFrameworkDefines>
</PropertyGroup>
<!--Set the nuspec properties. Dependent on version which isn't updated till after GetBuildVersion. Condition ensures we only set once since this runs multiple times for each target.-->
<Target Name="SetNuspecProperties" AfterTargets="GetBuildVersion">
<PropertyGroup Condition="'$(NuspecProperties)' == ''">
<NuspecProperties>buildOutput=bin\$(Configuration);version=$(Version)</NuspecProperties>
</PropertyGroup>
</Target>
</Project>

Просмотреть файл

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>Microsoft.Toolkit.Uwp.Notifications</id>
<version>$version$</version>
<title>Windows Community Toolkit Notifications</title>
<authors>Microsoft.Toolkit,dotnetfoundation</authors>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<licenseUrl>https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/master/license.md</licenseUrl>
<projectUrl>https://github.com/windows-toolkit/WindowsCommunityToolkit</projectUrl>
<iconUrl>https://raw.githubusercontent.com/windows-toolkit/WindowsCommunityToolkit/master/build/nuget.png</iconUrl>
<description>The official way to send toast notifications on Windows 10 via code rather than XML, with the help of IntelliSense. Supports all C# app types, including WPF, UWP, WinForms, and Console, even without packaging your app as MSIX. Also supports C++ UWP apps.
Additionally, generate notification payloads from your ASP.NET web server to send as push notifications, or generate notification payloads from class libraries.
For UWP/MSIX apps, you can also generate tile and badge notifications.</description>
<releaseNotes>https://github.com/windows-toolkit/WindowsCommunityToolkit/releases</releaseNotes>
<copyright>(c) .NET Foundation and Contributors. All rights reserved.</copyright>
<tags>notifications win10 windows 10 tile tiles toast toasts badge xml uwp c# csharp c++ wpf winforms</tags>
<repository type="git" url="https://github.com/windows-toolkit/WindowsCommunityToolkit.git" commit="72205c9add7c3fc1ed63bb77e6fc101e39f1ac33" />
<dependencies>
<group targetFramework=".NETStandard1.4">
<dependency id="NETStandard.Library" version="1.6.1" exclude="Build,Analyzers" />
<dependency id="System.ValueTuple" version="4.5.0" exclude="Build,Analyzers" />
</group>
<group targetFramework="UAP10.0.16299">
<dependency id="System.ValueTuple" version="4.5.0" exclude="Build,Analyzers" />
</group>
<group targetFramework=".NETCoreApp3.1">
<dependency id="Microsoft.Win32.Registry" version="4.7.0" exclude="Build,Analyzers" />
<dependency id="Microsoft.Windows.SDK.Contracts" version="10.0.19041.1" exclude="Build,Analyzers" />
<dependency id="System.Drawing.Common" version="4.7.0" exclude="Build,Analyzers" />
<dependency id="System.Reflection.Emit" version="4.7.0" exclude="Build,Analyzers" />
<dependency id="System.ValueTuple" version="4.5.0" exclude="Build,Analyzers" />
</group>
<group targetFramework="net5.0">
<dependency id="System.ValueTuple" version="4.5.0" exclude="Build,Analyzers" />
</group>
<group targetFramework="net5.0-windows10.0.17763">
<dependency id="Microsoft.Win32.Registry" version="4.7.0" exclude="Build,Analyzers" />
<dependency id="System.Drawing.Common" version="4.7.0" exclude="Build,Analyzers" />
<dependency id="System.Reflection.Emit" version="4.7.0" exclude="Build,Analyzers" />
<dependency id="System.ValueTuple" version="4.5.0" exclude="Build,Analyzers" />
</group>
<group targetFramework=".NETFramework4.6.1">
<dependency id="Microsoft.NETFramework.ReferenceAssemblies" version="1.0.0" exclude="Build,Analyzers" />
<dependency id="Microsoft.Windows.SDK.Contracts" version="10.0.19041.1" exclude="Build,Analyzers" />
<dependency id="System.ValueTuple" version="4.5.0" exclude="Build,Analyzers" />
</group>
<group targetFramework="native0.0" />
<group targetFramework="UAP10.0.10240" />
</dependencies>
</metadata>
<files>
<file src="Microsoft.Toolkit.Uwp.Notifications.targets" target="build\native\Microsoft.Toolkit.Uwp.Notifications.targets" />
<file src="$buildOutput$\net461\Microsoft.Toolkit.Uwp.Notifications.dll" target="lib\net461\Microsoft.Toolkit.Uwp.Notifications.dll" />
<file src="$buildOutput$\net461\Microsoft.Toolkit.Uwp.Notifications.pdb" target="lib\net461\Microsoft.Toolkit.Uwp.Notifications.pdb" />
<file src="$buildOutput$\net461\Microsoft.Toolkit.Uwp.Notifications.xml" target="lib\net461\Microsoft.Toolkit.Uwp.Notifications.xml" />
<file src="$buildOutput$\net5.0\Microsoft.Toolkit.Uwp.Notifications.dll" target="lib\net5.0\Microsoft.Toolkit.Uwp.Notifications.dll" />
<file src="$buildOutput$\net5.0\Microsoft.Toolkit.Uwp.Notifications.pdb" target="lib\net5.0\Microsoft.Toolkit.Uwp.Notifications.pdb" />
<file src="$buildOutput$\net5.0\Microsoft.Toolkit.Uwp.Notifications.xml" target="lib\net5.0\Microsoft.Toolkit.Uwp.Notifications.xml" />
<file src="$buildOutput$\net5.0-windows10.0.17763.0\Microsoft.Toolkit.Uwp.Notifications.dll" target="lib\net5.0-windows10.0.17763\Microsoft.Toolkit.Uwp.Notifications.dll" />
<file src="$buildOutput$\net5.0-windows10.0.17763.0\Microsoft.Toolkit.Uwp.Notifications.pdb" target="lib\net5.0-windows10.0.17763\Microsoft.Toolkit.Uwp.Notifications.pdb" />
<file src="$buildOutput$\net5.0-windows10.0.17763.0\Microsoft.Toolkit.Uwp.Notifications.xml" target="lib\net5.0-windows10.0.17763\Microsoft.Toolkit.Uwp.Notifications.xml" />
<file src="$buildOutput$\netcoreapp3.1\Microsoft.Toolkit.Uwp.Notifications.dll" target="lib\netcoreapp3.1\Microsoft.Toolkit.Uwp.Notifications.dll" />
<file src="$buildOutput$\netcoreapp3.1\Microsoft.Toolkit.Uwp.Notifications.pdb" target="lib\netcoreapp3.1\Microsoft.Toolkit.Uwp.Notifications.pdb" />
<file src="$buildOutput$\netcoreapp3.1\Microsoft.Toolkit.Uwp.Notifications.xml" target="lib\netcoreapp3.1\Microsoft.Toolkit.Uwp.Notifications.xml" />
<file src="$buildOutput$\netstandard1.4\Microsoft.Toolkit.Uwp.Notifications.dll" target="lib\netstandard1.4\Microsoft.Toolkit.Uwp.Notifications.dll" />
<file src="$buildOutput$\netstandard1.4\Microsoft.Toolkit.Uwp.Notifications.pdb" target="lib\netstandard1.4\Microsoft.Toolkit.Uwp.Notifications.pdb" />
<file src="$buildOutput$\netstandard1.4\Microsoft.Toolkit.Uwp.Notifications.xml" target="lib\netstandard1.4\Microsoft.Toolkit.Uwp.Notifications.xml" />
<file src="$buildOutput$\uap10.0.19041\Microsoft.Toolkit.Uwp.Notifications.dll" target="lib\uap10.0.16299\Microsoft.Toolkit.Uwp.Notifications.dll" />
<file src="$buildOutput$\uap10.0.19041\Microsoft.Toolkit.Uwp.Notifications.pdb" target="lib\uap10.0.16299\Microsoft.Toolkit.Uwp.Notifications.pdb" />
<file src="$buildOutput$\uap10.0.19041\Microsoft.Toolkit.Uwp.Notifications.pri" target="lib\uap10.0.16299\Microsoft.Toolkit.Uwp.Notifications.pri" />
<file src="$buildOutput$\uap10.0.19041\Microsoft.Toolkit.Uwp.Notifications.xml" target="lib\uap10.0.16299\Microsoft.Toolkit.Uwp.Notifications.xml" />
<file src="$buildOutput$\native\Microsoft.Toolkit.Uwp.Notifications.pdb" target="lib\native\Microsoft.Toolkit.Uwp.Notifications.pdb" />
<file src="$buildOutput$\native\Microsoft.Toolkit.Uwp.Notifications.pri" target="lib\native\Microsoft.Toolkit.Uwp.Notifications.pri" />
<file src="$buildOutput$\native\Microsoft.Toolkit.Uwp.Notifications.winmd" target="lib\native\Microsoft.Toolkit.Uwp.Notifications.winmd" />
<file src="$buildOutput$\native\Microsoft.Toolkit.Uwp.Notifications.xml" target="lib\native\Microsoft.Toolkit.Uwp.Notifications.xml" />
<file src="$buildOutput$\native\Microsoft.Toolkit.Uwp.Notifications.pdb" target="lib\uap10.0.10240\Microsoft.Toolkit.Uwp.Notifications.pdb" />
<file src="$buildOutput$\native\Microsoft.Toolkit.Uwp.Notifications.pri" target="lib\uap10.0.10240\Microsoft.Toolkit.Uwp.Notifications.pri" />
<file src="$buildOutput$\native\Microsoft.Toolkit.Uwp.Notifications.winmd" target="lib\uap10.0.10240\Microsoft.Toolkit.Uwp.Notifications.winmd" />
<file src="$buildOutput$\native\Microsoft.Toolkit.Uwp.Notifications.xml" target="lib\uap10.0.10240\Microsoft.Toolkit.Uwp.Notifications.xml" />
</files>
</package>

Просмотреть файл

@ -20,9 +20,6 @@ namespace Microsoft.Toolkit.Uwp.Notifications
{
private const long APPMODEL_ERROR_NO_PACKAGE = 15700L;
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder packageFullName);
private static bool? _hasIdentity;
public static bool HasIdentity()
@ -37,10 +34,10 @@ namespace Microsoft.Toolkit.Uwp.Notifications
{
int length = 0;
var sb = new StringBuilder(0);
GetCurrentPackageFullName(ref length, sb);
NativeMethods.GetCurrentPackageFullName(ref length, sb);
sb = new StringBuilder(length);
int error = GetCurrentPackageFullName(ref length, sb);
int error = NativeMethods.GetCurrentPackageFullName(ref length, sb);
_hasIdentity = error != APPMODEL_ERROR_NO_PACKAGE;
}

Просмотреть файл

@ -50,7 +50,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
}
// If running as Desktop Bridge
if (DesktopBridgeHelpers.IsRunningAsUwp())
if (DesktopBridgeHelpers.HasIdentity())
{
// 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.
@ -94,7 +94,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
// 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(
NativeMethods.CoRegisterClassObject(
uuid,
new NotificationActivatorClassFactory<T>(),
CLSCTX_LOCAL_SERVER,
@ -151,14 +151,6 @@ namespace Microsoft.Toolkit.Uwp.Notifications
}
}
[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>
@ -198,7 +190,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
if (!_registeredAumidAndComServer)
{
// Check if Desktop Bridge
if (DesktopBridgeHelpers.IsRunningAsUwp())
if (DesktopBridgeHelpers.HasIdentity())
{
// Implicitly registered, all good!
_registeredAumidAndComServer = true;
@ -223,55 +215,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// </summary>
public static bool CanUseHttpImages
{
get { return DesktopBridgeHelpers.IsRunningAsUwp(); }
}
/// <summary>
/// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/tree/master/DesktopBridge.Helpers/Helpers.cs
/// </summary>
private class DesktopBridgeHelpers
{
private const long APPMODEL_ERROR_NO_PACKAGE = 15700L;
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private 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;
var sb = new StringBuilder(0);
GetCurrentPackageFullName(ref length, sb);
sb = new StringBuilder(length);
int error = GetCurrentPackageFullName(ref length, sb);
_isRunningAsUwp = error != 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;
}
}
get { return DesktopBridgeHelpers.HasIdentity(); }
}
}
}

Просмотреть файл

@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if WIN32
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Microsoft.Toolkit.Uwp.Notifications
{
internal class NativeMethods
{
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeleteObject([In] IntPtr hObject);
[DllImport("ole32.dll")]
internal static extern int CoRegisterClassObject(
[MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnk,
uint dwClsContext,
uint flags,
out uint lpdwRegister);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder packageFullName);
}
}
#endif

Просмотреть файл

@ -5,10 +5,12 @@
#if WIN32
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
@ -18,6 +20,11 @@ namespace Microsoft.Toolkit.Uwp.Notifications
{
internal class Win32AppInfo
{
/// <summary>
/// If an AUMID is greater than 129 characters, scheduled toast notification APIs will throw an exception.
/// </summary>
private const int AUMID_MAX_LENGTH = 129;
public string Aumid { get; set; }
public string DisplayName { get; set; }
@ -32,6 +39,9 @@ namespace Microsoft.Toolkit.Uwp.Notifications
IApplicationResolver appResolver = (IApplicationResolver)new CAppResolver();
appResolver.GetAppIDForProcess(Convert.ToUInt32(process.Id), out string appId, out _, out _, out _);
// Use app ID (or hashed app ID) as AUMID
string aumid = appId.Length > AUMID_MAX_LENGTH ? HashAppId(appId) : appId;
// Then try to get the shortcut (for display name and icon)
IShellItem shortcutItem = null;
try
@ -74,7 +84,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
{
}
DeleteObject(nativeHBitmap);
NativeMethods.DeleteObject(nativeHBitmap);
}
}
catch
@ -98,12 +108,21 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return new Win32AppInfo()
{
Aumid = appId,
Aumid = aumid,
DisplayName = displayName,
IconPath = iconPath
};
}
private static string HashAppId(string appId)
{
using (SHA1 sha1 = SHA1.Create())
{
byte[] hashedBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(appId));
return string.Join(string.Empty, hashedBytes.Select(b => b.ToString("X2")));
}
}
private static string GetDisplayNameFromCurrentProcess(Process process)
{
// If AssemblyTitle is set, use that
@ -212,10 +231,6 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return false;
}
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteObject([In] IntPtr hObject);
/// <summary>
/// From https://stackoverflow.com/a/41622689/1454643
/// Generates Guid based on String. Key assumption for this algorithm is that name is unique (across where it it's being used)

Просмотреть файл

@ -53,14 +53,18 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <param name="tag">The tag label of the toast notification to be removed.</param>
public void Remove(string tag)
{
#if WIN32
if (_aumid != null)
{
_history.Remove(tag, string.Empty, _aumid);
_history.Remove(tag, ToastNotificationManagerCompat.DEFAULT_GROUP, _aumid);
}
else
{
_history.Remove(tag);
}
#else
_history.Remove(tag);
#endif
}
/// <summary>

Просмотреть файл

@ -26,6 +26,8 @@ namespace Microsoft.Toolkit.Uwp.Notifications
{
#if WIN32
private const string TOAST_ACTIVATED_LAUNCH_ARG = "-ToastActivated";
private const string REG_HAS_SENT_NOTIFICATION = "HasSentNotification";
internal const string DEFAULT_GROUP = "toolkitGroupNull";
private const int CLASS_E_NOAGGREGATION = -2147221232;
private const int E_NOINTERFACE = -2147467262;
@ -37,6 +39,8 @@ namespace Microsoft.Toolkit.Uwp.Notifications
private static bool _registeredOnActivated;
private static List<OnActivated> _onActivated = new List<OnActivated>();
private static bool _hasSentNotification;
/// <summary>
/// Event that is triggered when a notification or notification button is clicked. Subscribe to this event in your app's initial startup code.
/// </summary>
@ -55,7 +59,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
}
catch (Exception ex)
{
_initializeEx = ex;
_initializeEx = new InvalidOperationException("Failed to register notification activator", ex);
}
}
@ -104,7 +108,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
private static string _win32Aumid;
private static string _clsid;
private static Exception _initializeEx;
private static InvalidOperationException _initializeEx;
static ToastNotificationManagerCompat()
{
@ -115,7 +119,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
catch (Exception ex)
{
// We catch the exception so that things like subscribing to the event handler doesn't crash app
_initializeEx = ex;
_initializeEx = new InvalidOperationException("Failed initializing notifications", ex);
}
}
@ -145,7 +149,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
var activatorType = CreateAndRegisterActivator();
// Register via registry
using (var rootKey = Registry.CurrentUser.CreateSubKey(@"Software\Classes\AppUserModelId\" + _win32Aumid))
using (var rootKey = Registry.CurrentUser.CreateSubKey(GetRegistrySubKey()))
{
// If they don't have identity, we need to specify the display assets
if (!DesktopBridgeHelpers.HasIdentity())
@ -168,12 +172,20 @@ namespace Microsoft.Toolkit.Uwp.Notifications
// Background color only appears in the settings page, format is
// hex without leading #, like "FFDDDDDD"
rootKey.SetValue("IconBackgroundColor", "FFDDDDDD");
// Additionally, we need to read whether they've sent a notification before
_hasSentNotification = rootKey.GetValue(REG_HAS_SENT_NOTIFICATION) != null;
}
rootKey.SetValue("CustomActivator", string.Format("{{{0}}}", activatorType.GUID));
}
}
private static string GetRegistrySubKey()
{
return @"Software\Classes\AppUserModelId\" + _win32Aumid;
}
private static Type CreateActivatorType()
{
// https://stackoverflow.com/questions/24069352/c-sharp-typebuilder-generate-class-with-function-dynamically
@ -239,7 +251,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
// Big thanks to FrecherxDachs for figuring out the following code which works in .NET Core 3: https://github.com/FrecherxDachs/UwpNotificationNetCoreTest
var uuid = activatorType.GUID;
CoRegisterClassObject(
NativeMethods.CoRegisterClassObject(
uuid,
new NotificationActivatorClassFactory(activatorType),
CLSCTX_LOCAL_SERVER,
@ -250,12 +262,36 @@ namespace Microsoft.Toolkit.Uwp.Notifications
private static void RegisterComServer(Type activatorType, string exePath)
{
// We register the EXE to start up when the notification is activated
string regString = string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}\\LocalServer32", activatorType.GUID);
var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(regString);
string regString = string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}", activatorType.GUID);
using (var key = Registry.CurrentUser.CreateSubKey(regString + "\\LocalServer32"))
{
// 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(null, '"' + exePath + '"' + " " + TOAST_ACTIVATED_LAUNCH_ARG);
}
// 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(null, '"' + 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
using (var localServer32 = key.CreateSubKey("LocalServer32"))
{
localServer32.SetValue(null, '"' + exePath + '"' + " " + TOAST_ACTIVATED_LAUNCH_ARG);
}
key.SetValue("AppId", "{" + activatorType.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}}}", activatorType.GUID)))
{
key.SetValue("RunAs", "Interactive User");
}
}
}
/// <summary>
@ -320,20 +356,20 @@ namespace Microsoft.Toolkit.Uwp.Notifications
}
}
[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);
private static bool IsElevated
{
get
{
return new System.Security.Principal.WindowsPrincipal(System.Security.Principal.WindowsIdentity.GetCurrent()).IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
}
}
#endif
/// <summary>
/// Creates a toast notifier.
/// </summary>
/// <returns><see cref="ToastNotifier"/></returns>
public static ToastNotifier CreateToastNotifier()
/// <returns><see cref="ToastNotifierCompat"/>An instance of the toast notifier.</returns>
public static ToastNotifierCompat CreateToastNotifier()
{
#if WIN32
if (_initializeEx != null)
@ -343,14 +379,14 @@ namespace Microsoft.Toolkit.Uwp.Notifications
if (DesktopBridgeHelpers.HasIdentity())
{
return ToastNotificationManager.CreateToastNotifier();
return new ToastNotifierCompat(ToastNotificationManager.CreateToastNotifier());
}
else
{
return ToastNotificationManager.CreateToastNotifier(_win32Aumid);
return new ToastNotifierCompat(ToastNotificationManager.CreateToastNotifier(_win32Aumid));
}
#else
return ToastNotificationManager.CreateToastNotifier();
return new ToastNotifierCompat(ToastNotificationManager.CreateToastNotifier());
#endif
}
@ -437,7 +473,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
// Remove registry key
if (_win32Aumid != null)
{
Registry.CurrentUser.DeleteSubKey(@"Software\Classes\AppUserModelId\" + _win32Aumid);
Registry.CurrentUser.DeleteSubKey(GetRegistrySubKey());
}
}
catch
@ -448,7 +484,32 @@ namespace Microsoft.Toolkit.Uwp.Notifications
{
if (_clsid != null)
{
Registry.CurrentUser.DeleteSubKey(string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}\\LocalServer32", _clsid));
try
{
Registry.CurrentUser.DeleteSubKeyTree(string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}", _clsid));
}
catch
{
}
if (IsElevated)
{
try
{
Registry.LocalMachine.DeleteSubKeyTree(string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}", _clsid));
}
catch
{
}
try
{
Registry.LocalMachine.DeleteSubKeyTree(string.Format("SOFTWARE\\Classes\\AppID\\{{{0}}}", _clsid));
}
catch
{
}
}
}
}
catch
@ -472,6 +533,51 @@ namespace Microsoft.Toolkit.Uwp.Notifications
}
}
#endif
#if WIN32
internal static void SetHasSentToastNotification()
{
// For plain Win32 apps, record that we've sent a notification
if (!_hasSentNotification && !DesktopBridgeHelpers.HasIdentity())
{
_hasSentNotification = true;
try
{
using (var rootKey = Registry.CurrentUser.CreateSubKey(GetRegistrySubKey()))
{
rootKey.SetValue(REG_HAS_SENT_NOTIFICATION, 1);
}
}
catch
{
}
}
}
internal static void PreRegisterIdentityLessApp()
{
// For plain Win32 apps, we first have to have send a toast notification once before using scheduled toasts.
if (!_hasSentNotification && !DesktopBridgeHelpers.HasIdentity())
{
const string tag = "toolkit1stNotif";
// Show the toast
new ToastContentBuilder()
.AddText("New notification")
.Show(toast =>
{
// We'll hide the popup and set the toast to expire in case removing doesn't work
toast.SuppressPopup = true;
toast.Tag = tag;
toast.ExpirationTime = DateTime.Now.AddSeconds(15);
});
// And then remove it
ToastNotificationManagerCompat.History.Remove(tag);
}
}
#endif
}
}

Просмотреть файл

@ -0,0 +1,172 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if WINDOWS_UWP
using System.Collections.Generic;
using Windows.UI.Notifications;
namespace Microsoft.Toolkit.Uwp.Notifications
{
/// <summary>
/// Allows you to show and schedule toast notifications.
/// </summary>
public sealed class ToastNotifierCompat
{
private ToastNotifier _notifier;
internal ToastNotifierCompat(ToastNotifier notifier)
{
_notifier = notifier;
}
/// <summary>
/// Displays the specified toast notification.
/// </summary>
/// <param name="notification">The object that contains the content of the toast notification to display.</param>
public void Show(ToastNotification notification)
{
#if WIN32
PreprocessToast(notification);
#endif
_notifier.Show(notification);
#if WIN32
ToastNotificationManagerCompat.SetHasSentToastNotification();
#endif
}
/// <summary>
/// Hides the specified toast notification from the screen (moves it into Action Center).
/// </summary>
/// <param name="notification">The object that specifies the toast to hide.</param>
public void Hide(ToastNotification notification)
{
#if WIN32
PreprocessToast(notification);
#endif
_notifier.Hide(notification);
}
/// <summary>
/// Adds a ScheduledToastNotification for later display by Windows.
/// </summary>
/// <param name="scheduledToast">The scheduled toast notification, which includes its content and timing instructions.</param>
public void AddToSchedule(ScheduledToastNotification scheduledToast)
{
#if WIN32
ToastNotificationManagerCompat.PreRegisterIdentityLessApp();
PreprocessScheduledToast(scheduledToast);
#endif
_notifier.AddToSchedule(scheduledToast);
}
/// <summary>
/// Cancels the scheduled display of a specified ScheduledToastNotification.
/// </summary>
/// <param name="scheduledToast">The notification to remove from the schedule.</param>
public void RemoveFromSchedule(ScheduledToastNotification scheduledToast)
{
#if WIN32
PreprocessScheduledToast(scheduledToast);
#endif
_notifier.RemoveFromSchedule(scheduledToast);
}
/// <summary>
/// Gets the collection of ScheduledToastNotification objects that this app has scheduled for display.
/// </summary>
/// <returns>The collection of scheduled toast notifications that the app bound to this notifier has scheduled for timed display.</returns>
public IReadOnlyList<ScheduledToastNotification> GetScheduledToastNotifications()
{
return _notifier.GetScheduledToastNotifications();
}
/// <summary>
/// Updates the existing toast notification that has the specified tag and belongs to the specified notification group.
/// </summary>
/// <param name="data">An object that contains the updated info.</param>
/// <param name="tag">The identifier of the toast notification to update.</param>
/// <param name="group">The ID of the ToastCollection that contains the notification.</param>
/// <returns>A value that indicates the result of the update (failure, success, etc).</returns>
public NotificationUpdateResult Update(NotificationData data, string tag, string group)
{
return _notifier.Update(data, tag, group);
}
/// <summary>
/// Updates the existing toast notification that has the specified tag.
/// </summary>
/// <param name="data">An object that contains the updated info.</param>
/// <param name="tag">The identifier of the toast notification to update.</param>
/// <returns>A value that indicates the result of the update (failure, success, etc).</returns>
public NotificationUpdateResult Update(NotificationData data, string tag)
{
#if WIN32
// For apps that don't have identity...
if (!DesktopBridgeHelpers.HasIdentity())
{
// If group isn't specified, we have to add a group since otherwise can't remove without a group
return Update(data, tag, ToastNotificationManagerCompat.DEFAULT_GROUP);
}
#endif
return _notifier.Update(data, tag);
}
/// <summary>
/// Gets a value that tells you whether there is an app, user, or system block that prevents the display of a toast notification.
/// </summary>
public NotificationSetting Setting
{
get
{
#if WIN32
// Just like scheduled notifications, apps need to have sent a notification
// before checking the setting value works
ToastNotificationManagerCompat.PreRegisterIdentityLessApp();
#endif
return _notifier.Setting;
}
}
#if WIN32
private void PreprocessToast(ToastNotification notification)
{
// For apps that don't have identity...
if (!DesktopBridgeHelpers.HasIdentity())
{
// If tag is specified
if (!string.IsNullOrEmpty(notification.Tag))
{
// If group isn't specified, we have to add a group since otherwise can't remove without a group
notification.Group = ToastNotificationManagerCompat.DEFAULT_GROUP;
}
}
}
private void PreprocessScheduledToast(ScheduledToastNotification notification)
{
// For apps that don't have identity...
if (!DesktopBridgeHelpers.HasIdentity())
{
// If tag is specified
if (!string.IsNullOrEmpty(notification.Tag))
{
// If group isn't specified, we have to add a group since otherwise can't remove without a group
notification.Group = ToastNotificationManagerCompat.DEFAULT_GROUP;
}
}
}
#endif
}
}
#endif

Просмотреть файл

@ -28,8 +28,7 @@
<TextBlock HorizontalAlignment="Left"
VerticalAlignment="Top"
Foreground="OrangeRed"
IsHitTestVisible="False"
Text="Please scroll down to see the effect." />
IsHitTestVisible="False"><Run Text="Please scroll down to see the effect." /><LineBreak /><Run Text="The default threshold for triggering lazy loading is 300 px." /></TextBlock>
<Button Width="48"
Height="48"
HorizontalAlignment="Right"

Просмотреть файл

@ -6,6 +6,7 @@
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{ThemeResource ApplicationForegroundThemeBrush}" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="LazyLoadingThreshold" Value="300" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ImageEx">

Просмотреть файл

@ -48,7 +48,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// <summary>
/// Identifies the <see cref="EnableLazyLoading"/> dependency property.
/// </summary>
public static readonly DependencyProperty EnableLazyLoadingProperty = DependencyProperty.Register(nameof(EnableLazyLoading), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false));
public static readonly DependencyProperty EnableLazyLoadingProperty = DependencyProperty.Register(nameof(EnableLazyLoading), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false, EnableLazyLoadingChanged));
/// <summary>
/// Identifies the <see cref="LazyLoadingThreshold"/> dependency property.
/// </summary>
public static readonly DependencyProperty LazyLoadingThresholdProperty = DependencyProperty.Register(nameof(LazyLoadingThreshold), typeof(double), typeof(ImageExBase), new PropertyMetadata(default(double), LazyLoadingThresholdChanged));
/// <summary>
/// Returns a mask that represents the alpha channel of an image as a <see cref="CompositionBrush"/>
@ -139,5 +144,40 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
get { return (bool)GetValue(EnableLazyLoadingProperty); }
set { SetValue(EnableLazyLoadingProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating the threshold for triggering lazy loading.
/// </summary>
public double LazyLoadingThreshold
{
get { return (double)GetValue(LazyLoadingThresholdProperty); }
set { SetValue(LazyLoadingThresholdProperty, value); }
}
private static void EnableLazyLoadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageExBase control)
{
var value = (bool)e.NewValue;
if (value)
{
control.LayoutUpdated += control.ImageExBase_LayoutUpdated;
control.InvalidateLazyLoading();
}
else
{
control.LayoutUpdated -= control.ImageExBase_LayoutUpdated;
}
}
}
private static void LazyLoadingThresholdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageExBase control && control.EnableLazyLoading)
{
control.InvalidateLazyLoading();
}
}
}
}

Просмотреть файл

@ -3,6 +3,9 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using Microsoft.Toolkit.Uwp.Extensions;
using Microsoft.Toolkit.Uwp.UI.Extensions;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -79,8 +82,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
public ImageExBase()
{
LockObj = new object();
EffectiveViewportChanged += ImageExBase_EffectiveViewportChanged;
}
/// <summary>
@ -215,15 +216,47 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
VisualStateManager.GoToState(this, FailedState, true);
}
private void ImageExBase_EffectiveViewportChanged(FrameworkElement sender, EffectiveViewportChangedEventArgs args)
private void ImageExBase_LayoutUpdated(object sender, object e)
{
var bringIntoViewDistanceX = args.BringIntoViewDistanceX;
var bringIntoViewDistanceY = args.BringIntoViewDistanceY;
InvalidateLazyLoading();
}
var width = ActualWidth;
var height = ActualHeight;
private void InvalidateLazyLoading()
{
if (!IsLoaded)
{
_isInViewport = false;
return;
}
if (bringIntoViewDistanceX <= width && bringIntoViewDistanceY <= height)
// Find the first ascendant ScrollViewer, if not found, use the root element.
FrameworkElement hostElement = null;
var ascendants = this.FindAscendants().OfType<FrameworkElement>();
foreach (var ascendant in ascendants)
{
hostElement = ascendant;
if (hostElement is ScrollViewer)
{
break;
}
}
if (hostElement == null)
{
_isInViewport = false;
return;
}
var controlRect = TransformToVisual(hostElement)
.TransformBounds(new Rect(0, 0, ActualWidth, ActualHeight));
var lazyLoadingThreshold = LazyLoadingThreshold;
var hostRect = new Rect(
0 - lazyLoadingThreshold,
0 - lazyLoadingThreshold,
hostElement.ActualWidth + (2 * lazyLoadingThreshold),
hostElement.ActualHeight + (2 * lazyLoadingThreshold));
if (controlRect.IntersectsWith(hostRect))
{
_isInViewport = true;

Просмотреть файл

@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Rect = Windows.Foundation.Rect;
namespace Microsoft.Toolkit.Uwp.Extensions
{
/// <summary>
/// Extensions for the <see cref="Rect"/> type.
/// </summary>
public static class RectExtensions
{
/// <summary>
/// Determines if a rectangle intersects with another rectangle.
/// </summary>
/// <param name="rect1">The first rectangle to test.</param>
/// <param name="rect2">The second rectangle to test.</param>
/// <returns>This method returns <see langword="true"/> if there is any intersection, otherwise <see langword="false"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IntersectsWith(this Rect rect1, Rect rect2)
{
if (rect1.IsEmpty || rect2.IsEmpty)
{
return false;
}
return (rect1.Left <= rect2.Right) &&
(rect1.Right >= rect2.Left) &&
(rect1.Top <= rect2.Bottom) &&
(rect1.Bottom >= rect2.Top);
}
}
}

Просмотреть файл

@ -8,27 +8,48 @@
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition Height="AUto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button
x:Name="ButtonPopToast"
Content="Pop toast"
Click="ButtonPopToast_Click"
Margin="0,0,6,0"/>
<Button
Grid.Column="1"
x:Name="ButtonClearToasts"
Content="Clear toasts"
Click="ButtonClearToasts_Click"
Margin="6,0,0,0"/>
</Grid>
<StackPanel Margin="20">
<Grid Height="36">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button
x:Name="ButtonPopToast"
Content="Pop toast"
Click="ButtonPopToast_Click"
Margin="0,0,6,0"/>
<Button
Grid.Column="1"
x:Name="ButtonScheduleToast"
Content="Schedule toast"
Click="ButtonScheduleToast_Click"
Margin="6,0,6,0"/>
<Button
Grid.Column="2"
x:Name="ButtonClearToasts"
Content="Clear toasts"
Click="ButtonClearToasts_Click"
Margin="6,0,0,0"/>
</Grid>
<Grid Margin="0,12,0,0" Height="36">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button
x:Name="ButtonProgressToast"
Content="Progress bar toast"
Click="ButtonProgressToast_Click"
Margin="0,0,6,0"/>
</Grid>
</StackPanel>
<ContentControl
x:Name="ContentBody"

Просмотреть файл

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
@ -10,6 +11,8 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using Microsoft.Toolkit.Uwp.Notifications;
using Windows.Services.Maps;
using Windows.UI.Notifications;
namespace Microsoft.Toolkit.Win32.WpfCore.SampleApp
{
@ -27,6 +30,12 @@ namespace Microsoft.Toolkit.Win32.WpfCore.SampleApp
private async void ButtonPopToast_Click(object sender, RoutedEventArgs e)
{
if (ToastNotificationManagerCompat.CreateToastNotifier().Setting != NotificationSetting.Enabled)
{
MessageBox.Show("Notifications are disabled from the system settings.");
return;
}
string title = "Andrew sent you a picture";
string content = "Check this out, The Enchantments!";
string image = "https://picsum.photos/364/202?image=883";
@ -144,5 +153,84 @@ namespace Microsoft.Toolkit.Win32.WpfCore.SampleApp
{
ToastNotificationManagerCompat.History.Clear();
}
private async void ButtonScheduleToast_Click(object sender, RoutedEventArgs e)
{
// Schedule a toast to appear in 5 seconds
new ToastContentBuilder()
// Arguments that are returned when the user clicks the toast or a button
.AddArgument("action", MyToastActions.ViewConversation)
.AddArgument("conversationId", 7764)
.AddText("Scheduled toast notification")
.Schedule(DateTime.Now.AddSeconds(5));
// Inform the user
var tb = new TextBlock()
{
Text = "Toast scheduled to appear in 5 seconds",
FontWeight = FontWeights.Bold
};
ContentBody.Content = tb;
// And after 5 seconds, clear the informational message
await Task.Delay(5000);
if (ContentBody.Content == tb)
{
ContentBody.Content = null;
}
}
private async void ButtonProgressToast_Click(object sender, RoutedEventArgs e)
{
const string tag = "progressToast";
new ToastContentBuilder()
.AddArgument("action", MyToastActions.ViewConversation)
.AddArgument("conversationId", 423)
.AddText("Sending image to conversation...")
.AddVisualChild(new AdaptiveProgressBar()
{
Value = new BindableProgressBarValue("progress"),
Status = "Sending..."
})
.Show(toast =>
{
toast.Tag = tag;
toast.Data = new NotificationData(new Dictionary<string, string>()
{
{ "progress", "0" }
});
});
double progress = 0;
while (progress < 1)
{
await Task.Delay(new Random().Next(1000, 3000));
progress += (new Random().NextDouble() * 0.15) + 0.1;
ToastNotificationManagerCompat.CreateToastNotifier().Update(
new NotificationData(new Dictionary<string, string>()
{
{ "progress", progress.ToString() }
}), tag);
}
new ToastContentBuilder()
.AddArgument("action", MyToastActions.ViewConversation)
.AddArgument("conversationId", 423)
.AddText("Sent image to conversation!")
.Show(toast =>
{
toast.Tag = tag;
});
}
}
}

Просмотреть файл

@ -8,6 +8,10 @@
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Toolkit.Uwp.Notifications\Microsoft.Toolkit.Uwp.Notifications.csproj" />
</ItemGroup>

Просмотреть файл

@ -1,14 +1,34 @@
$ErrorActionPreference = "Stop"
$ProgressPreference="SilentlyContinue"
$global:ErrorActionPreference = "Stop"
$global:ProgressPreference="SilentlyContinue"
Write-Host "Running Smoke Test Package Analyis"
Write-Host "----------------------------------"
#####
# This script analyzes the final packaged outputs of
# There are two things analyzed:
# - The MSIX UPLOAD package has the raw dependencies before .NET Native compilation,
# this can help determine the maximum impact of the package if everything were to be included.
# - The MSIX BUNDLE package is the final .NET Native compiled app,
# this can be used to help determine the minimal impact of the package if only a few things are used.
# This is also what would end up on a user's machine and be the footprint of the app itself.
#
# Note: The 'minimum impact' can be larger than the 'maximum impact' as there are many
# system dependencies which don't get included by default and are then referenced by a package.
# This may seem counter-intuitive at first, but it is important to remember that when combining
# package use along with the use of other platform APIs smooths these sizes out as they will
# all be shared across all use cases.
#####
# Our script is at our SmokeTest root, we want to look for the AppPackages folder
$PackagePath = $PSScriptRoot + "\AppPackages\"
$FilePattern = "SmokeTest_{0}_x86_bundle.msixupload"
$DirPattern = "SmokeTest_{0}_Test\SmokeTest_{0}_x86.msixbundle"
$BaselineName = $FilePattern -f "UWPBaseline"
$BaselineBundleName = $DirPattern -f "UWPBaseline"
$TempFolder = "ExplodedArchive"
$TempFolder2 = "ExplodedBundle"
function Expand-MsixUploadPackage {
param (
@ -47,6 +67,34 @@ function Expand-MsixUploadPackage {
Pop-Location
}
function Expand-MsixBundlePackage {
param (
[string]$PackageFile,
[string]$Destination
)
$ZipBundle = $PackageFile.Replace("msixbundle", "zip")
Move-Item $PackageFile -Destination $ZipBundle
Expand-Archive $ZipBundle -DestinationPath $Destination
Move-Item $ZipBundle -Destination $PackageFile
Push-Location $Destination
$msix = (Get-ChildItem "*.msix").Name
$ZipMSIX = $msix.Replace("msix", "zip")
Move-Item $msix -Destination $ZipMSIX
Expand-Archive $ZipMSIX -DestinationPath . -Force # Force here as we have some duplicate file names we don't really care about from parent archives
Remove-Item $ZipMSIX
Pop-Location
}
if (Test-Path $PackagePath)
{
Push-Location $PackagePath
@ -55,13 +103,18 @@ if (Test-Path $PackagePath)
# TODO: Theoretically we could grab bits from the bin directory instead of having to expand each package, not sure about what we ignore though
Expand-MsixUploadPackage $BaselineName -Destination $TempFolder
Expand-MsixBundlePackage $BaselineBundleName -Destination $TempFolder2
# Get all the base file info only (grab stuff in directories but not the directories themselves)
$BaselineFiles = Get-ChildItem $TempFolder -Recurse -Attributes !Directory -Exclude "SmokeTest*"
$BaselineFiles2 = Get-ChildItem $TempFolder2 -Recurse -Attributes !Directory -Exclude "SmokeTest*"
$SmokeTestFiles = Get-ChildItem $TempFolder -Recurse -Attributes !Directory -Include "SmokeTest*"
$SmokeTestFiles2 = Get-ChildItem $TempFolder2 -Recurse -Attributes !Directory -Include "SmokeTest*"
$BaselineFootprint = ($BaselineFiles | Measure-Object -Property Length -sum).Sum + ($SmokeTestFiles | Measure-Object -Property Length -sum).Sum
Write-Host ("Baseline Footprint: {0:n0} bytes" -f $BaselineFootprint)
$BaselineFootprint2 = ($BaselineFiles2 | Measure-Object -Property Length -sum).Sum + ($SmokeTestFiles2 | Measure-Object -Property Length -sum).Sum
Write-Host ("Baseline Max Footprint: {0:n0} bytes" -f $BaselineFootprint)
Write-Host ("Baseline Min Footprint: {0:n0} bytes" -f $BaselineFootprint2)
Write-Host "-----------------------------------------"
$PackageList = Get-ChildItem "$PackagePath*.msixupload" -Exclude $BaselineName
@ -69,21 +122,29 @@ if (Test-Path $PackagePath)
#$i = 0
foreach ($Package in $PackageList)
{
# Extract the root package name between the initial '_'
$PackageShortName = ($Package.Name -split '_')[1]
#Write-Progress -Id 0 -Activity "Comparing Against Baseline..." -Status "Prepping Package" -PercentComplete (($i++ / $PackageList.count)*100) -CurrentOperation $Package.Name
# Make sure we've cleaned-up the last archive
Remove-Item $TempFolder -Recurse -Force
Remove-Item $TempFolder2 -Recurse -Force
#$ProgressPreference="SilentlyContinue"
Expand-MsixUploadPackage $Package.Name -Destination $TempFolder
# Also expand the final bundle based on the namespace name within the package.
Expand-MsixBundlePackage ($DirPattern -f $PackageShortName) -Destination $TempFolder2
#$ProgressPreference="Continue"
[System.Collections.ArrayList]$PackageFiles = Get-ChildItem $TempFolder -Recurse -Attributes !Directory -Exclude "SmokeTest*"
$PackageSmokeTestFiles = Get-ChildItem $TempFolder -Recurse -Attributes !Directory -Include "SmokeTest*"
[System.Collections.ArrayList]$PackageFiles2 = Get-ChildItem $TempFolder2 -Recurse -Attributes !Directory -Exclude "SmokeTest*"
$PackageSmokeTestFiles2 = Get-ChildItem $TempFolder2 -Recurse -Attributes !Directory -Include "SmokeTest*"
# TODO: Make function or regex better to extra package name more easily based on a template string at the top or something...
$PackageShortName = $Package.Name.substring(10, $Package.Name.Length - 32)
Write-Host ("{0} Additional Footprint: {1:n0} bytes" -f $PackageShortName, (($PackageFiles | Measure-Object -Property Length -sum).Sum + ($PackageSmokeTestFiles | Measure-Object -Property Length -sum).Sum - $BaselineFootprint))
Write-Host ("{0} Additional Max Footprint: {1:n0} bytes" -f $PackageShortName, (($PackageFiles | Measure-Object -Property Length -sum).Sum + ($PackageSmokeTestFiles | Measure-Object -Property Length -sum).Sum - $BaselineFootprint))
Write-Host ("{0} Additional Min Footprint: {1:n0} bytes" -f $PackageShortName, (($PackageFiles2 | Measure-Object -Property Length -sum).Sum + ($PackageSmokeTestFiles2 | Measure-Object -Property Length -sum).Sum - $BaselineFootprint2))
# Quick check on the base exe file/symbols differences
foreach ($file in $SmokeTestFiles)
@ -132,6 +193,52 @@ if (Test-Path $PackagePath)
# TODO: Especially if we add comparison to the main branch, we should format as an actual table and colorize via VT: https://stackoverflow.com/a/49038815/8798708
#Write-Progress -Id 1 -ParentId 0 -Activity "Comparing Against Baseline..." -Completed
Write-Host "-----------------COMPILED----------------"
# Quick check on the base exe file/symbols differences
foreach ($file in $SmokeTestFiles2)
{
$match = $null
$match = $PackageSmokeTestFiles2 | Where-Object {$_.Extension -eq $file.Extension}
if ($null -ne $match)
{
Write-Host (" App Diff: ({0}) = {1:n0}" -f $file.Extension, ($match.Length - $file.Length)) -ForegroundColor DarkCyan
}
}
#$j = 0
foreach ($file in $BaselineFiles2)
{
#Write-Progress -Id 1 -ParentId 0 -Activity "Comparing Against Baseline..." -Status "Comparing Package" -PercentComplete (($j++ / $BaselineFiles.count)*100) -CurrentOperation $file.Name
$match = $null
$match = $PackageFiles2 | Where-Object {$_.Name -eq $file.Name}
if ($null -ne $match)
{
# File was in baseline, but has a different size
if ($match.Length -ne $file.Length)
{
Write-Host (" Size Diff: {0} = {1:n0}" -f $file.Name, ($match.Length - $file.Length)) -ForegroundColor Magenta
}
# Remove checked files (performance) and also remaining are new
$PackageFiles2.Remove($match)
}
}
# List remaining (new) files to this package
foreach ($file in $PackageFiles2)
{
if ($file.Name -match $PackageShortName)
{
Write-Host (" Lib (self): {0} = {1:n0}" -f $file.Name, $file.Length) -ForegroundColor White
}
else
{
Write-Host (" Additional: {0} = {1:n0}" -f $file.Name, $file.Length) -ForegroundColor Yellow
}
}
Write-Host "-----------------------------------------"
Write-Host
}
@ -140,6 +247,7 @@ if (Test-Path $PackagePath)
# Clean-up
Remove-Item $TempFolder -Recurse -Force
Remove-Item $TempFolder2 -Recurse -Force
Pop-Location
}

Просмотреть файл

@ -0,0 +1,42 @@
#####
# This script downloads the specified version of NuGet packages for
# the required smoke tests from NuGet.
# This is useful for performing current analysis on prior builds of the Toolkit.
#
# Pass it a Version Number of the Package you'd like to download, otherwise it'll download the LKG.
# Defaults download to ../bin/nupkg/ directory
####
param (
[string] $Version = '',
[string] $DownloadPath = '../bin/nupkg/'
)
$global:ProgressPreference="SilentlyContinue"
Write-Host "Downloading Project NuGets for Version: $Version"
$ProjectFilePath = $PSScriptRoot + "\SmokeTests.proj"
$NuGetDownloadUrl = "https://www.nuget.org/api/v2/package/{0}/{1}" # PackageName, Version
[xml]$ProjXml = Get-Content -Path $ProjectFilePath
$PackageList = $ProjXml.Project.PropertyGroup.ToolkitPackages -split ';'
Push-Location $DownloadPath
# Download each package
foreach ($Package in $PackageList)
{
$PackageName = $Package.Trim() # Remove extra whitespace depending on proj file format
$PackageUrl = ($NuGetDownloadUrl -f $PackageName, $Version)
Write-Host "Downloading $PackageUrl"
Invoke-WebRequest $PackageUrl -OutFile ("{0}.{1}.nupkg" -f $PackageName, $Version)
}
Pop-Location
Write-Host "Done"

Просмотреть файл

@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.Toolkit.Uwp.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Windows.Foundation;
namespace UnitTests.Extensions
{
[TestClass]
public class Test_RectExtensions
{
[TestCategory("RectExtensions")]
[TestMethod]
[DataRow(0, 0, 2, 2, 0, 0, 2, 2, true)]// Full intersection.
[DataRow(0, 0, 2, 2, 1, 1, 2, 2, true)]// Partial intersection.
[DataRow(0, 0, 2, 2, -2, 0, 2, 2, true)]// Left edge intersection.
[DataRow(0, 0, 2, 2, 0, -2, 2, 2, true)]// Top edge intersection.
[DataRow(0, 0, 2, 2, 2, 0, 2, 2, true)]// Right edge intersection.
[DataRow(0, 0, 2, 2, 0, 2, 2, 2, true)]// Bottom edge intersection.
[DataRow(0, 0, 2, 2, -2, -2, 2, 2, true)]// Left top corner(0, 0) intersection.
[DataRow(0, 0, 2, 2, 2, -2, 2, 2, true)]// Right top corner(2, 0) intersection.
[DataRow(0, 0, 2, 2, 2, 2, 2, 2, true)]// Right bottom corner(2, 2) intersection.
[DataRow(0, 0, 2, 2, -2, 2, 2, 2, true)]// Left bottom corner(0, 2) intersection.
[DataRow(0, 0, 2, 2, 3, 0, 2, 2, false)]// No intersection.
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1117:Parameters should be on same line or separate lines", Justification = "Put the parameters of the same rectangle on the same line is clearer.")]
public static void Test_RectExtensions_IntersectsWith(
double rect1X, double rect1Y, double rect1Width, double rect1Height,
double rect2X, double rect2Y, double rect2Width, double rect2Height,
bool shouldIntersectsWith)
{
var rect1 = new Rect(rect1X, rect1Y, rect1Width, rect1Height);
var rect2 = new Rect(rect2X, rect2Y, rect2Width, rect2Height);
var isIntersectsWith = rect1.IntersectsWith(rect2);
Assert.IsTrue(isIntersectsWith == shouldIntersectsWith);
}
}
}

Просмотреть файл

@ -133,6 +133,7 @@
<Compile Include="Converters\Test_TypeToObjectConverter.cs" />
<Compile Include="Extensions\Helpers\ObjectWithNullableBoolProperty.cs" />
<Compile Include="Extensions\Test_PointExtensions.cs" />
<Compile Include="Extensions\Test_RectExtensions.cs" />
<Compile Include="Extensions\Test_SizeExtensions.cs" />
<Compile Include="Extensions\Test_BitmapIconExtensionMarkupExtension.cs" />
<Compile Include="Extensions\Test_FontIconSourceExtensionMarkupExtension.cs" />

Просмотреть файл

@ -1,5 +1,5 @@
{
"msbuild-sdks": {
"MSBuild.Sdk.Extras": "2.0.54"
"MSBuild.Sdk.Extras": "3.0.22"
}
}