Merge branch 'master' into improvement/dispatcher-queue-helper
|
@ -78,7 +78,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
ThrowInvalidCastExceptionForGetFrom();
|
||||
}
|
||||
|
||||
return Unsafe.As<Box<T>>(obj);
|
||||
return Unsafe.As<Box<T>>(obj)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -94,7 +94,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Box<T> DangerousGetFrom(object obj)
|
||||
{
|
||||
return Unsafe.As<Box<T>>(obj);
|
||||
return Unsafe.As<Box<T>>(obj)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -108,7 +108,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
{
|
||||
if (obj.GetType() == typeof(T))
|
||||
{
|
||||
box = Unsafe.As<Box<T>>(obj);
|
||||
box = Unsafe.As<Box<T>>(obj)!;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
// manually be implemented in the Box<T> type. For instance, boxing a float
|
||||
// and calling ToString() on it directly, on its boxed object or on a Box<T>
|
||||
// reference retrieved from it will produce the same result in all cases.
|
||||
return Unsafe.As<Box<T>>(value);
|
||||
return Unsafe.As<Box<T>>(value)!;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
@ -422,11 +422,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <param name="value">The input <see cref="string"/> instance to cache.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void Add(string value, int hashcode)
|
||||
public void Add(string value, int hashcode)
|
||||
{
|
||||
ref string target = ref TryGet(value.AsSpan(), hashcode);
|
||||
|
||||
if (Unsafe.AreSame(ref target, ref Unsafe.AsRef<string>(null)))
|
||||
if (Unsafe.IsNullRef(ref target))
|
||||
{
|
||||
Insert(value, hashcode);
|
||||
}
|
||||
|
@ -443,11 +443,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
|
||||
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="value"/>.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe string GetOrAdd(string value, int hashcode)
|
||||
public string GetOrAdd(string value, int hashcode)
|
||||
{
|
||||
ref string result = ref TryGet(value.AsSpan(), hashcode);
|
||||
|
||||
if (!Unsafe.AreSame(ref result, ref Unsafe.AsRef<string>(null)))
|
||||
if (!Unsafe.IsNullRef(ref result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
@ -464,11 +464,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <param name="hashcode">The precomputed hashcode for <paramref name="span"/>.</param>
|
||||
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="span"/>, cached if possible.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe string GetOrAdd(ReadOnlySpan<char> span, int hashcode)
|
||||
public string GetOrAdd(ReadOnlySpan<char> span, int hashcode)
|
||||
{
|
||||
ref string result = ref TryGet(span, hashcode);
|
||||
|
||||
if (!Unsafe.AreSame(ref result, ref Unsafe.AsRef<string>(null)))
|
||||
if (!Unsafe.IsNullRef(ref result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
@ -488,11 +488,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <param name="value">The resulting cached <see cref="string"/> instance, if present</param>
|
||||
/// <returns>Whether or not the target <see cref="string"/> instance was found.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe bool TryGet(ReadOnlySpan<char> span, int hashcode, [NotNullWhen(true)] out string? value)
|
||||
public bool TryGet(ReadOnlySpan<char> span, int hashcode, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
ref string result = ref TryGet(span, hashcode);
|
||||
|
||||
if (!Unsafe.AreSame(ref result, ref Unsafe.AsRef<string>(null)))
|
||||
if (!Unsafe.IsNullRef(ref result))
|
||||
{
|
||||
value = result;
|
||||
|
||||
|
@ -527,7 +527,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
private unsafe ref string TryGet(ReadOnlySpan<char> span, int hashcode)
|
||||
{
|
||||
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
|
||||
ref MapEntry entry = ref Unsafe.AsRef<MapEntry>(null);
|
||||
ref MapEntry entry = ref Unsafe.NullRef<MapEntry>();
|
||||
int
|
||||
length = this.buckets.Length,
|
||||
bucketIndex = hashcode & (length - 1);
|
||||
|
@ -547,7 +547,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
}
|
||||
}
|
||||
|
||||
return ref Unsafe.AsRef<string>(null);
|
||||
return ref Unsafe.NullRef<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static ref T DangerousGetReference<T>(this T[] array)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArrayData>(array);
|
||||
var arrayData = Unsafe.As<RawArrayData>(array)!;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
|
||||
return ref r0;
|
||||
|
@ -55,7 +55,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static ref T DangerousGetReferenceAt<T>(this T[] array, int i)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArrayData>(array);
|
||||
var arrayData = Unsafe.As<RawArrayData>(array)!;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static ref T DangerousGetReference<T>(this T[,] array)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray2DData>(array);
|
||||
var arrayData = Unsafe.As<RawArray2DData>(array)!;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
|
||||
return ref r0;
|
||||
|
@ -63,7 +63,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray2DData>(array);
|
||||
var arrayData = Unsafe.As<RawArray2DData>(array)!;
|
||||
nint offset = ((nint)(uint)i * (nint)(uint)arrayData.Width) + (nint)(uint)j;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static ref T DangerousGetReference<T>(this T[,,] array)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray3DData>(array);
|
||||
var arrayData = Unsafe.As<RawArray3DData>(array)!;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
|
||||
return ref r0;
|
||||
|
@ -63,7 +63,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static ref T DangerousGetReferenceAt<T>(this T[,,] array, int i, int j, int k)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray3DData>(array);
|
||||
var arrayData = Unsafe.As<RawArray3DData>(array)!;
|
||||
nint offset =
|
||||
((nint)(uint)i * (nint)(uint)arrayData.Height * (nint)(uint)arrayData.Width) +
|
||||
((nint)(uint)j * (nint)(uint)arrayData.Width) + (nint)(uint)k;
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IntPtr DangerousGetObjectDataByteOffset<T>(this object obj, ref T data)
|
||||
{
|
||||
var rawObj = Unsafe.As<RawObjectData>(obj);
|
||||
var rawObj = Unsafe.As<RawObjectData>(obj)!;
|
||||
ref byte r0 = ref rawObj.Data;
|
||||
ref byte r1 = ref Unsafe.As<T, byte>(ref data);
|
||||
|
||||
|
@ -55,7 +55,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetObjectDataReferenceAt<T>(this object obj, IntPtr offset)
|
||||
{
|
||||
var rawObj = Unsafe.As<RawObjectData>(obj);
|
||||
var rawObj = Unsafe.As<RawObjectData>(obj)!;
|
||||
ref byte r0 = ref rawObj.Data;
|
||||
ref byte r1 = ref Unsafe.AddByteOffset(ref r0, offset);
|
||||
ref T r2 = ref Unsafe.As<byte, T>(ref r1);
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
#if NETCOREAPP3_1
|
||||
return ref Unsafe.AsRef(text.GetPinnableReference());
|
||||
#elif NETCOREAPP2_1
|
||||
var stringData = Unsafe.As<RawStringData>(text);
|
||||
var stringData = Unsafe.As<RawStringData>(text)!;
|
||||
|
||||
return ref stringData.Data;
|
||||
#else
|
||||
|
@ -53,7 +53,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
#if NETCOREAPP3_1
|
||||
ref char r0 = ref Unsafe.AsRef(text.GetPinnableReference());
|
||||
#elif NETCOREAPP2_1
|
||||
ref char r0 = ref Unsafe.As<RawStringData>(text).Data;
|
||||
ref char r0 = ref Unsafe.As<RawStringData>(text)!.Data;
|
||||
#else
|
||||
ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan());
|
||||
#endif
|
||||
|
|
|
@ -772,7 +772,7 @@ namespace Microsoft.Toolkit.HighPerformance.Memory
|
|||
}
|
||||
else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string))
|
||||
{
|
||||
string text = Unsafe.As<string>(this.instance);
|
||||
string text = Unsafe.As<string>(this.instance)!;
|
||||
int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt<char>(this.offset));
|
||||
ReadOnlyMemory<char> temp = text.AsMemory(index, (int)Length);
|
||||
|
||||
|
@ -786,16 +786,13 @@ namespace Microsoft.Toolkit.HighPerformance.Memory
|
|||
}
|
||||
else if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
// If the object is a MemoryManager<T>, just slice it as needed
|
||||
memory = memoryManager.Memory.Slice((int)(void*)this.offset, this.height * this.width);
|
||||
}
|
||||
// If the object is a MemoryManager<T>, just slice it as needed
|
||||
memory = memoryManager.Memory.Slice((int)(nint)this.offset, this.height * this.width);
|
||||
}
|
||||
else if (this.instance.GetType() == typeof(T[]))
|
||||
{
|
||||
// If it's a T[] array, also handle the initial offset
|
||||
T[] array = Unsafe.As<T[]>(this.instance);
|
||||
T[] array = Unsafe.As<T[]>(this.instance)!;
|
||||
int index = array.AsSpan().IndexOf(ref array.DangerousGetObjectDataReferenceAt<T>(this.offset));
|
||||
|
||||
memory = array.AsMemory(index, this.height * this.width);
|
||||
|
|
|
@ -794,7 +794,7 @@ namespace Microsoft.Toolkit.HighPerformance.Memory
|
|||
// difference between the start of the Span<char> (which directly wraps just the actual character data
|
||||
// within the string), and the input reference, which we can get from the byte offset in use. The result
|
||||
// is the character index which we can use to create the final Memory<char> instance.
|
||||
string text = Unsafe.As<string>(this.instance);
|
||||
string text = Unsafe.As<string>(this.instance)!;
|
||||
int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt<char>(this.offset));
|
||||
ReadOnlyMemory<char> temp = text.AsMemory(index, (int)Length);
|
||||
|
||||
|
@ -802,16 +802,13 @@ namespace Microsoft.Toolkit.HighPerformance.Memory
|
|||
}
|
||||
else if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
// If the object is a MemoryManager<T>, just slice it as needed
|
||||
memory = memoryManager.Memory.Slice((int)(void*)this.offset, this.height * this.width);
|
||||
}
|
||||
// If the object is a MemoryManager<T>, just slice it as needed
|
||||
memory = memoryManager.Memory.Slice((int)(nint)this.offset, this.height * this.width);
|
||||
}
|
||||
else if (this.instance.GetType() == typeof(T[]))
|
||||
{
|
||||
// If it's a T[] array, also handle the initial offset
|
||||
T[] array = Unsafe.As<T[]>(this.instance);
|
||||
T[] array = Unsafe.As<T[]>(this.instance)!;
|
||||
int index = array.AsSpan().IndexOf(ref array.DangerousGetObjectDataReferenceAt<T>(this.offset));
|
||||
|
||||
memory = array.AsMemory(index, this.height * this.width);
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<When Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
|
@ -51,14 +51,14 @@
|
|||
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<When Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
|
||||
<ItemGroup>
|
||||
|
||||
<!-- .NET Standard 2.1 doesn't have the Unsafe type -->
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
|
||||
|
@ -76,6 +76,9 @@
|
|||
</PropertyGroup>
|
||||
</When>
|
||||
<When Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
|
||||
<!-- NETCORE_RUNTIME: to avoid issues with APIs that assume a specific memory layout, we define a
|
||||
|
@ -89,7 +92,7 @@
|
|||
</When>
|
||||
<When Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<DefineConstants>SPAN_RUNTIME_SUPPORT;NETCORE_RUNTIME</DefineConstants>
|
||||
|
|
|
@ -11,6 +11,11 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// </summary>
|
||||
public sealed class AdaptiveProgressBarValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the property name to bind to.
|
||||
/// </summary>
|
||||
public string BindingName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value (0-1) representing the percent complete.
|
||||
/// </summary>
|
||||
|
@ -35,6 +40,11 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
return "indeterminate";
|
||||
}
|
||||
|
||||
if (BindingName != null)
|
||||
{
|
||||
return "{" + BindingName + "}";
|
||||
}
|
||||
|
||||
return Value.ToString();
|
||||
}
|
||||
|
||||
|
@ -69,5 +79,18 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
Value = d
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a progress bar value using the specified binding name.
|
||||
/// </summary>
|
||||
/// <param name="bindingName">The property to bind to.</param>
|
||||
/// <returns>A progress bar value.</returns>
|
||||
public static AdaptiveProgressBarValue FromBinding(string bindingName)
|
||||
{
|
||||
return new AdaptiveProgressBarValue()
|
||||
{
|
||||
BindingName = bindingName
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,23 +25,17 @@
|
|||
</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</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);WINDOWS_UWP;WIN32</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</When>
|
||||
|
||||
<!--Non-desktop apps (UWP, libraries, ASP.NET servers)-->
|
||||
<Otherwise>
|
||||
<ItemGroup>
|
||||
<!--Remove the DesktopNotificationManager code-->
|
||||
<Compile Remove="DesktopNotificationManager\**\*" />
|
||||
</ItemGroup>
|
||||
</Otherwise>
|
||||
|
||||
</Choose>
|
||||
|
||||
<!--NET Core desktop apps also need the Registry NuGet package-->
|
||||
<!--NET Core desktop apps also need the Registry NuGet package and System.Reflection.Emit for generating COM class dynamically-->
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'">
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'native' ">
|
||||
|
|
|
@ -15,21 +15,21 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <summary>
|
||||
/// Small Square Tile
|
||||
/// </summary>
|
||||
Small = 0,
|
||||
Small = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Medium Square Tile
|
||||
/// </summary>
|
||||
Medium = 1,
|
||||
Medium = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Wide Rectangle Tile
|
||||
/// </summary>
|
||||
Wide = 2,
|
||||
Wide = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Large Square Tile
|
||||
/// </summary>
|
||||
Large = 4
|
||||
Large = 8
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// 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 Windows.Foundation;
|
||||
using Windows.UI.Notifications;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to set additional properties on the <see cref="ToastNotification"/> object before the toast is displayed.
|
||||
/// </summary>
|
||||
/// <param name="toast">The toast to modify that will be displayed.</param>
|
||||
public delegate void CustomizeToast(ToastNotification toast);
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to set additional properties on the <see cref="ToastNotification"/> object before the toast is displayed.
|
||||
/// </summary>
|
||||
/// <param name="toast">The toast to modify that will be displayed.</param>
|
||||
/// <returns>An operation.</returns>
|
||||
public delegate IAsyncAction CustomizeToastAsync(ToastNotification toast);
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to set additional properties on the <see cref="ScheduledToastNotification"/> object before the toast is scheduled.
|
||||
/// </summary>
|
||||
/// <param name="toast">The scheduled toast to modify that will be scheduled.</param>
|
||||
public delegate void CustomizeScheduledToast(ScheduledToastNotification toast);
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to set additional properties on the <see cref="ScheduledToastNotification"/> object before the toast is scheduled.
|
||||
/// </summary>
|
||||
/// <param name="toast">The scheduled toast to modify that will be scheduled.</param>
|
||||
/// <returns>An operation.</returns>
|
||||
public delegate IAsyncAction CustomizeScheduledToastAsync(ScheduledToastNotification toast);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -8,13 +8,16 @@ using System.Linq;
|
|||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
#if !WINRT
|
||||
#pragma warning disable SA1008
|
||||
#pragma warning disable SA1009
|
||||
/// <summary>
|
||||
/// Builder class used to create <see cref="ToastContent"/>
|
||||
/// </summary>
|
||||
public partial class ToastContentBuilder
|
||||
public
|
||||
#if WINRT
|
||||
sealed
|
||||
#endif
|
||||
partial class ToastContentBuilder
|
||||
{
|
||||
private IToastActions Actions
|
||||
{
|
||||
|
@ -45,6 +48,36 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
}
|
||||
}
|
||||
|
||||
private string SerializeArgumentsIncludingGeneric(ToastArguments arguments)
|
||||
{
|
||||
if (_genericArguments.Count == 0)
|
||||
{
|
||||
return arguments.ToString();
|
||||
}
|
||||
|
||||
foreach (var genericArg in _genericArguments)
|
||||
{
|
||||
if (!arguments.Contains(genericArg.Key))
|
||||
{
|
||||
arguments.Add(genericArg.Key, genericArg.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return arguments.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a button to the current toast.
|
||||
/// </summary>
|
||||
/// <param name="content">Text to display on the button.</param>
|
||||
/// <param name="activationType">Type of activation this button will use when clicked. Defaults to Foreground.</param>
|
||||
/// <param name="arguments">App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddButton(string content, ToastActivationType activationType, string arguments)
|
||||
{
|
||||
return AddButton(content, activationType, arguments, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a button to the current toast.
|
||||
/// </summary>
|
||||
|
@ -53,7 +86,10 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <param name="arguments">App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.</param>
|
||||
/// <param name="imageUri">Optional image icon for the button to display (required for buttons adjacent to inputs like quick reply).</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddButton(string content, ToastActivationType activationType, string arguments, Uri imageUri = default(Uri))
|
||||
#if WINRT
|
||||
[Windows.Foundation.Metadata.DefaultOverload]
|
||||
#endif
|
||||
public ToastContentBuilder AddButton(string content, ToastActivationType activationType, string arguments, Uri imageUri)
|
||||
{
|
||||
// Add new button
|
||||
ToastButton button = new ToastButton(content, arguments)
|
||||
|
@ -61,7 +97,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
ActivationType = activationType
|
||||
};
|
||||
|
||||
if (imageUri != default(Uri))
|
||||
if (imageUri != default)
|
||||
{
|
||||
button.ImageUri = imageUri.OriginalString;
|
||||
}
|
||||
|
@ -76,17 +112,46 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddButton(IToastButton button)
|
||||
{
|
||||
if (button is ToastButton toastButton && toastButton.Content == null)
|
||||
{
|
||||
throw new InvalidOperationException("Content is required on button.");
|
||||
}
|
||||
|
||||
// List has max 5 buttons
|
||||
if (ButtonList.Count == 5)
|
||||
{
|
||||
throw new InvalidOperationException("A toast can't have more than 5 buttons");
|
||||
}
|
||||
|
||||
if (button is ToastButton b && b.CanAddArguments())
|
||||
{
|
||||
foreach (var arg in _genericArguments)
|
||||
{
|
||||
if (!b.ContainsArgument(arg.Key))
|
||||
{
|
||||
b.AddArgument(arg.Key, arg.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ButtonList.Add(button);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an button to the toast that will be display to the right of the input text box, achieving a quick reply scenario.
|
||||
/// </summary>
|
||||
/// <param name="textBoxId">ID of an existing <see cref="ToastTextBox"/> in order to have this button display to the right of the input, achieving a quick reply scenario.</param>
|
||||
/// <param name="content">Text to display on the button.</param>
|
||||
/// <param name="activationType">Type of activation this button will use when clicked. Defaults to Foreground.</param>
|
||||
/// <param name="arguments">App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddButton(string textBoxId, string content, ToastActivationType activationType, string arguments)
|
||||
{
|
||||
return AddButton(textBoxId, content, activationType, arguments, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an button to the toast that will be display to the right of the input text box, achieving a quick reply scenario.
|
||||
/// </summary>
|
||||
|
@ -96,7 +161,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <param name="arguments">App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.</param>
|
||||
/// <param name="imageUri">An optional image icon for the button to display (required for buttons adjacent to inputs like quick reply)</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddButton(string textBoxId, string content, ToastActivationType activationType, string arguments, Uri imageUri = default(Uri))
|
||||
public ToastContentBuilder AddButton(string textBoxId, string content, ToastActivationType activationType, string arguments, Uri imageUri)
|
||||
{
|
||||
// Add new button
|
||||
ToastButton button = new ToastButton(content, arguments)
|
||||
|
@ -105,7 +170,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
TextBoxId = textBoxId
|
||||
};
|
||||
|
||||
if (imageUri != default(Uri))
|
||||
if (imageUri != default)
|
||||
{
|
||||
button.ImageUri = imageUri.OriginalString;
|
||||
}
|
||||
|
@ -113,6 +178,29 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
return AddButton(button);
|
||||
}
|
||||
|
||||
#if WINRT
|
||||
/// <summary>
|
||||
/// Add an input text box that the user can type into.
|
||||
/// </summary>
|
||||
/// <param name="id">Required ID property so that developers can retrieve user input once the app is activated.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddInputTextBox(string id)
|
||||
{
|
||||
return AddInputTextBox(id, default, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an input text box that the user can type into.
|
||||
/// </summary>
|
||||
/// <param name="id">Required ID property so that developers can retrieve user input once the app is activated.</param>
|
||||
/// <param name="placeHolderContent">Placeholder text to be displayed on the text box when the user hasn't typed any text yet.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddInputTextBox(string id, string placeHolderContent)
|
||||
{
|
||||
return AddInputTextBox(id, placeHolderContent, default);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Add an input text box that the user can type into.
|
||||
/// </summary>
|
||||
|
@ -120,16 +208,24 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <param name="placeHolderContent">Placeholder text to be displayed on the text box when the user hasn't typed any text yet.</param>
|
||||
/// <param name="title">Title text to display above the text box.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddInputTextBox(string id, string placeHolderContent = default(string), string title = default(string))
|
||||
public ToastContentBuilder AddInputTextBox(
|
||||
string id,
|
||||
#if WINRT
|
||||
string placeHolderContent,
|
||||
string title)
|
||||
#else
|
||||
string placeHolderContent = default,
|
||||
string title = default)
|
||||
#endif
|
||||
{
|
||||
var inputTextBox = new ToastTextBox(id);
|
||||
|
||||
if (placeHolderContent != default(string))
|
||||
if (placeHolderContent != default)
|
||||
{
|
||||
inputTextBox.PlaceholderContent = placeHolderContent;
|
||||
}
|
||||
|
||||
if (title != default(string))
|
||||
if (title != default)
|
||||
{
|
||||
inputTextBox.Title = title;
|
||||
}
|
||||
|
@ -137,6 +233,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
return AddToastInput(inputTextBox);
|
||||
}
|
||||
|
||||
#if !WINRT
|
||||
/// <summary>
|
||||
/// Add a combo box / drop-down menu that contain options for user to select.
|
||||
/// </summary>
|
||||
|
@ -145,7 +242,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddComboBox(string id, params (string comboBoxItemId, string comboBoxItemContent)[] choices)
|
||||
{
|
||||
return AddComboBox(id, default(string), choices);
|
||||
return AddComboBox(id, default, choices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -157,7 +254,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddComboBox(string id, string defaultSelectionBoxItemId, params (string comboBoxItemId, string comboBoxItemContent)[] choices)
|
||||
{
|
||||
return AddComboBox(id, default(string), defaultSelectionBoxItemId, choices);
|
||||
return AddComboBox(id, default, defaultSelectionBoxItemId, choices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -185,12 +282,12 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
{
|
||||
var box = new ToastSelectionBox(id);
|
||||
|
||||
if (defaultSelectionBoxItemId != default(string))
|
||||
if (defaultSelectionBoxItemId != default)
|
||||
{
|
||||
box.DefaultSelectionBoxItemId = defaultSelectionBoxItemId;
|
||||
}
|
||||
|
||||
if (title != default(string))
|
||||
if (title != default)
|
||||
{
|
||||
box.Title = title;
|
||||
}
|
||||
|
@ -203,6 +300,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
|
||||
return AddToastInput(box);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Add an input option to the Toast.
|
||||
|
@ -218,5 +316,4 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
}
|
||||
#pragma warning restore SA1008
|
||||
#pragma warning restore SA1009
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ using Windows.UI.Notifications;
|
|||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
#if !WINRT
|
||||
/// <summary>
|
||||
/// Builder class used to create <see cref="ToastContent"/>
|
||||
/// </summary>
|
||||
|
@ -81,7 +80,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
}
|
||||
|
||||
#if WINDOWS_UWP
|
||||
|
||||
#if !WINRT
|
||||
/// <summary>
|
||||
/// Create an instance of NotificationData that can be used to update toast that has a progress bar.
|
||||
/// </summary>
|
||||
|
@ -93,7 +92,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <param name="status"> A status string, which is displayed underneath the progress bar on the left. Default to empty.</param>
|
||||
/// <param name="sequence">A sequence number to prevent out-of-order updates, or assign 0 to indicate "always update".</param>
|
||||
/// <returns>An instance of NotificationData that can be used to update the toast.</returns>
|
||||
public static NotificationData CreateProgressBarData(ToastContent toast, int index = 0, string title = default(string), double? value = null, string valueStringOverride = default(string), string status = default(string), uint sequence = 0)
|
||||
public static NotificationData CreateProgressBarData(ToastContent toast, int index = 0, string title = default, double? value = default, string valueStringOverride = default, string status = default, uint sequence = 0)
|
||||
{
|
||||
var progressBar = toast.Visual.BindingGeneric.Children.Where(c => c is AdaptiveProgressBar).ElementAt(index) as AdaptiveProgressBar;
|
||||
if (progressBar == null)
|
||||
|
@ -104,30 +103,41 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
NotificationData data = new NotificationData();
|
||||
data.SequenceNumber = sequence;
|
||||
|
||||
if (progressBar.Title is BindableString bindableTitle && title != default(string))
|
||||
// Native C++ doesn't support BindableString
|
||||
if (progressBar.Title is BindableString bindableTitle && title != default)
|
||||
{
|
||||
data.Values[bindableTitle.BindingName] = title;
|
||||
}
|
||||
|
||||
if (progressBar.Value is BindableProgressBarValue bindableProgressValue && value != null)
|
||||
if (progressBar.Value is BindableProgressBarValue bindableProgressValue && value != default)
|
||||
{
|
||||
data.Values[bindableProgressValue.BindingName] = value.ToString();
|
||||
}
|
||||
|
||||
if (progressBar.ValueStringOverride is BindableString bindableValueStringOverride && valueStringOverride != default(string))
|
||||
if (progressBar.ValueStringOverride is BindableString bindableValueStringOverride && valueStringOverride != default)
|
||||
{
|
||||
data.Values[bindableValueStringOverride.BindingName] = valueStringOverride;
|
||||
}
|
||||
|
||||
if (progressBar.Status is BindableString bindableStatus && status != default(string))
|
||||
if (progressBar.Status is BindableString bindableStatus && status != default)
|
||||
{
|
||||
data.Values[bindableStatus.BindingName] = status;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Add an Attribution Text to be displayed on the toast.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to be displayed as Attribution Text</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddAttributionText(string text)
|
||||
{
|
||||
return AddAttributionText(text, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an Attribution Text to be displayed on the toast.
|
||||
|
@ -135,14 +145,14 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <param name="text">Text to be displayed as Attribution Text</param>
|
||||
/// <param name="language">The target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR".</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddAttributionText(string text, string language = default(string))
|
||||
public ToastContentBuilder AddAttributionText(string text, string language)
|
||||
{
|
||||
AttributionText = new ToastGenericAttributionText()
|
||||
{
|
||||
Text = text
|
||||
};
|
||||
|
||||
if (language != default(string))
|
||||
if (language != default)
|
||||
{
|
||||
AttributionText.Language = language;
|
||||
}
|
||||
|
@ -150,6 +160,41 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
return this;
|
||||
}
|
||||
|
||||
#if WINRT
|
||||
/// <summary>
|
||||
/// Override the app logo with custom image of choice that will be displayed on the toast.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddAppLogoOverride(Uri uri)
|
||||
{
|
||||
return AddAppLogoOverride(uri, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override the app logo with custom image of choice that will be displayed on the toast.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
|
||||
/// <param name="hintCrop">Specify how the image should be cropped.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddAppLogoOverride(Uri uri, ToastGenericAppLogoCrop? hintCrop)
|
||||
{
|
||||
return AddAppLogoOverride(uri, hintCrop, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override the app logo with custom image of choice that will be displayed on the toast.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
|
||||
/// <param name="hintCrop">Specify how the image should be cropped.</param>
|
||||
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddAppLogoOverride(Uri uri, ToastGenericAppLogoCrop? hintCrop, string alternateText)
|
||||
{
|
||||
return AddAppLogoOverride(uri, hintCrop, alternateText, default);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Override the app logo with custom image of choice that will be displayed on the toast.
|
||||
/// </summary>
|
||||
|
@ -158,24 +203,34 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
|
||||
/// <param name="addImageQuery">A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddAppLogoOverride(Uri uri, ToastGenericAppLogoCrop? hintCrop = null, string alternateText = default(string), bool? addImageQuery = default(bool?))
|
||||
public ToastContentBuilder AddAppLogoOverride(
|
||||
Uri uri,
|
||||
#if WINRT
|
||||
ToastGenericAppLogoCrop? hintCrop,
|
||||
string alternateText,
|
||||
bool? addImageQuery)
|
||||
#else
|
||||
ToastGenericAppLogoCrop? hintCrop = default,
|
||||
string alternateText = default,
|
||||
bool? addImageQuery = default)
|
||||
#endif
|
||||
{
|
||||
AppLogoOverrideUri = new ToastGenericAppLogo()
|
||||
{
|
||||
Source = uri.OriginalString
|
||||
};
|
||||
|
||||
if (hintCrop != null)
|
||||
if (hintCrop != default)
|
||||
{
|
||||
AppLogoOverrideUri.HintCrop = hintCrop.Value;
|
||||
}
|
||||
|
||||
if (alternateText != default(string))
|
||||
if (alternateText != default)
|
||||
{
|
||||
AppLogoOverrideUri.AlternateText = alternateText;
|
||||
}
|
||||
|
||||
if (addImageQuery != default(bool?))
|
||||
if (addImageQuery != default)
|
||||
{
|
||||
AppLogoOverrideUri.AddImageQuery = addImageQuery;
|
||||
}
|
||||
|
@ -183,6 +238,29 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
return this;
|
||||
}
|
||||
|
||||
#if WINRT
|
||||
/// <summary>
|
||||
/// Add a hero image to the toast.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddHeroImage(Uri uri)
|
||||
{
|
||||
return AddHeroImage(uri, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a hero image to the toast.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
|
||||
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddHeroImage(Uri uri, string alternateText)
|
||||
{
|
||||
return AddHeroImage(uri, alternateText, default);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Add a hero image to the toast.
|
||||
/// </summary>
|
||||
|
@ -190,19 +268,27 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
|
||||
/// <param name="addImageQuery">A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddHeroImage(Uri uri, string alternateText = default(string), bool? addImageQuery = default(bool?))
|
||||
public ToastContentBuilder AddHeroImage(
|
||||
Uri uri,
|
||||
#if WINRT
|
||||
string alternateText,
|
||||
bool? addImageQuery)
|
||||
#else
|
||||
string alternateText = default,
|
||||
bool? addImageQuery = default)
|
||||
#endif
|
||||
{
|
||||
HeroImage = new ToastGenericHeroImage()
|
||||
{
|
||||
Source = uri.OriginalString
|
||||
};
|
||||
|
||||
if (alternateText != default(string))
|
||||
if (alternateText != default)
|
||||
{
|
||||
HeroImage.AlternateText = alternateText;
|
||||
}
|
||||
|
||||
if (addImageQuery != default(bool?))
|
||||
if (addImageQuery != default)
|
||||
{
|
||||
HeroImage.AddImageQuery = addImageQuery;
|
||||
}
|
||||
|
@ -210,16 +296,72 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
return this;
|
||||
}
|
||||
|
||||
#if WINRT
|
||||
/// <summary>
|
||||
/// Add an image inline with other toast content.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddInlineImage(Uri uri)
|
||||
{
|
||||
return AddInlineImage(uri, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an image inline with other toast content.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
|
||||
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddInlineImage(Uri uri, string alternateText)
|
||||
{
|
||||
return AddInlineImage(uri, alternateText, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an image inline with other toast content.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
|
||||
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
|
||||
/// <param name="addImageQuery">A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.</param>
|
||||
/// <param name="hintCrop">A value whether a margin is removed. images have an 8px margin around them.</param>
|
||||
/// <param name="hintRemoveMargin">the horizontal alignment of the image.This is only supported when inside an <see cref="AdaptiveSubgroup"/>.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddInlineImage(Uri uri, string alternateText = default(string), bool? addImageQuery = default(bool?), AdaptiveImageCrop? hintCrop = null, bool? hintRemoveMargin = default(bool?))
|
||||
public ToastContentBuilder AddInlineImage(Uri uri, string alternateText, bool? addImageQuery)
|
||||
{
|
||||
return AddInlineImage(uri, alternateText, addImageQuery, default);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if WINRT
|
||||
/// <summary>
|
||||
/// Add an image inline with other toast content.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
|
||||
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
|
||||
/// <param name="addImageQuery">A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.</param>
|
||||
/// <param name="hintCrop">A value whether a margin is removed. images have an 8px margin around them.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddInlineImage(
|
||||
Uri uri,
|
||||
string alternateText,
|
||||
bool? addImageQuery,
|
||||
AdaptiveImageCrop? hintCrop)
|
||||
#else
|
||||
/// <summary>
|
||||
/// Add an image inline with other toast content.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
|
||||
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
|
||||
/// <param name="addImageQuery">A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.</param>
|
||||
/// <param name="hintCrop">A value whether a margin is removed. images have an 8px margin around them.</param>
|
||||
/// <param name="hintRemoveMargin">This property is not used. Setting this has no impact.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddInlineImage(
|
||||
Uri uri,
|
||||
string alternateText = default,
|
||||
bool? addImageQuery = default,
|
||||
AdaptiveImageCrop? hintCrop = default,
|
||||
bool? hintRemoveMargin = default)
|
||||
#endif
|
||||
{
|
||||
var inlineImage = new AdaptiveImage()
|
||||
{
|
||||
|
@ -231,24 +373,20 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
inlineImage.HintCrop = hintCrop.Value;
|
||||
}
|
||||
|
||||
if (alternateText != default(string))
|
||||
if (alternateText != default)
|
||||
{
|
||||
inlineImage.AlternateText = alternateText;
|
||||
}
|
||||
|
||||
if (addImageQuery != default(bool?))
|
||||
if (addImageQuery != default)
|
||||
{
|
||||
inlineImage.AddImageQuery = addImageQuery;
|
||||
}
|
||||
|
||||
if (hintRemoveMargin != default(bool?))
|
||||
{
|
||||
inlineImage.HintRemoveMargin = hintRemoveMargin;
|
||||
}
|
||||
|
||||
return AddVisualChild(inlineImage);
|
||||
}
|
||||
|
||||
#if !WINRT
|
||||
/// <summary>
|
||||
/// Add a progress bar to the toast.
|
||||
/// </summary>
|
||||
|
@ -259,7 +397,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <param name="status">A status string which is displayed underneath the progress bar. This string should reflect the status of the operation, like "Downloading..." or "Installing...". Default to empty.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
/// <remarks>More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-progress-bar </remarks>
|
||||
public ToastContentBuilder AddProgressBar(string title = default(string), double? value = null, bool isIndeterminate = false, string valueStringOverride = default(string), string status = default(string))
|
||||
public ToastContentBuilder AddProgressBar(string title = default, double? value = null, bool isIndeterminate = false, string valueStringOverride = default, string status = default)
|
||||
{
|
||||
int index = VisualChildren.Count(c => c is AdaptiveProgressBar);
|
||||
|
||||
|
@ -267,7 +405,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
{
|
||||
};
|
||||
|
||||
if (title == default(string))
|
||||
if (title == default)
|
||||
{
|
||||
progressBar.Title = new BindableString($"progressBarTitle_{index}");
|
||||
}
|
||||
|
@ -289,7 +427,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
progressBar.Value = value.Value;
|
||||
}
|
||||
|
||||
if (valueStringOverride == default(string))
|
||||
if (valueStringOverride == default)
|
||||
{
|
||||
progressBar.ValueStringOverride = new BindableString($"progressValueString_{index}");
|
||||
}
|
||||
|
@ -298,7 +436,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
progressBar.ValueStringOverride = valueStringOverride;
|
||||
}
|
||||
|
||||
if (status == default(string))
|
||||
if (status == default)
|
||||
{
|
||||
progressBar.Status = new BindableString($"progressStatus_{index}");
|
||||
}
|
||||
|
@ -309,16 +447,41 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
|
||||
return AddVisualChild(progressBar);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if WINRT
|
||||
/// <summary>
|
||||
/// Add text to the toast.
|
||||
/// </summary>
|
||||
/// <param name="text">Custom text to display on the tile.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
/// <exception cref="InvalidOperationException">Throws when attempting to add/reserve more than 4 lines on a single toast. </exception>
|
||||
/// <remarks>More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#text-elements</remarks>
|
||||
public ToastContentBuilder AddText(string text)
|
||||
{
|
||||
return AddText(text, default, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add text to the toast.
|
||||
/// </summary>
|
||||
/// <param name="text">Custom text to display on the tile.</param>
|
||||
/// <param name="hintStyle">Style that controls the text's font size, weight, and opacity.</param>
|
||||
/// <param name="hintWrap">Indicating whether text wrapping is enabled. For Tiles, this is false by default.</param>
|
||||
/// <param name="hintMaxLines">The maximum number of lines the text element is allowed to display. For Tiles, this is infinity by default</param>
|
||||
/// <param name="hintMinLines">The minimum number of lines the text element must display.</param>
|
||||
/// <param name="hintAlign">The horizontal alignment of the text</param>
|
||||
/// <param name="hintMaxLines">The maximum number of lines the text element is allowed to display.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
/// <exception cref="InvalidOperationException">Throws when attempting to add/reserve more than 4 lines on a single toast. </exception>
|
||||
/// <remarks>More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#text-elements</remarks>
|
||||
public ToastContentBuilder AddText(string text, int? hintMaxLines)
|
||||
{
|
||||
return AddText(text, hintMaxLines, default);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if WINRT
|
||||
/// <summary>
|
||||
/// Add text to the toast.
|
||||
/// </summary>
|
||||
/// <param name="text">Custom text to display on the tile.</param>
|
||||
/// <param name="hintMaxLines">The maximum number of lines the text element is allowed to display.</param>
|
||||
/// <param name="language">
|
||||
/// The target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR". The locale specified here overrides any other specified locale, such as that in binding or visual.
|
||||
/// </param>
|
||||
|
@ -326,7 +489,36 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <exception cref="InvalidOperationException">Throws when attempting to add/reserve more than 4 lines on a single toast. </exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Throws when <paramref name="hintMaxLines"/> value is larger than 2. </exception>
|
||||
/// <remarks>More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#text-elements</remarks>
|
||||
public ToastContentBuilder AddText(string text, AdaptiveTextStyle? hintStyle = null, bool? hintWrap = default(bool?), int? hintMaxLines = default(int?), int? hintMinLines = default(int?), AdaptiveTextAlign? hintAlign = null, string language = default(string))
|
||||
public ToastContentBuilder AddText(
|
||||
string text,
|
||||
int? hintMaxLines,
|
||||
string language)
|
||||
#else
|
||||
/// <summary>
|
||||
/// Add text to the toast.
|
||||
/// </summary>
|
||||
/// <param name="text">Custom text to display on the tile.</param>
|
||||
/// <param name="hintStyle">This property is not used. Setting this has no effect.</param>
|
||||
/// <param name="hintWrap">This property is not used. Setting this has no effect. If you need to disable wrapping, set hintMaxLines to 1.</param>
|
||||
/// <param name="hintMaxLines">The maximum number of lines the text element is allowed to display.</param>
|
||||
/// <param name="hintMinLines">hintMinLines is not used. Setting this has no effect.</param>
|
||||
/// <param name="hintAlign">hintAlign is not used. Setting this has no effect.</param>
|
||||
/// <param name="language">
|
||||
/// The target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR". The locale specified here overrides any other specified locale, such as that in binding or visual.
|
||||
/// </param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
/// <exception cref="InvalidOperationException">Throws when attempting to add/reserve more than 4 lines on a single toast. </exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Throws when <paramref name="hintMaxLines"/> value is larger than 2. </exception>
|
||||
/// <remarks>More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#text-elements</remarks>
|
||||
public ToastContentBuilder AddText(
|
||||
string text,
|
||||
AdaptiveTextStyle? hintStyle = null,
|
||||
bool? hintWrap = default,
|
||||
int? hintMaxLines = default,
|
||||
int? hintMinLines = default,
|
||||
AdaptiveTextAlign? hintAlign = null,
|
||||
string language = default)
|
||||
#endif
|
||||
{
|
||||
int lineCount = GetCurrentTextLineCount();
|
||||
if (GetCurrentTextLineCount() == 4)
|
||||
|
@ -340,22 +532,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
Text = text
|
||||
};
|
||||
|
||||
if (hintStyle != null)
|
||||
{
|
||||
adaptive.HintStyle = hintStyle.Value;
|
||||
}
|
||||
|
||||
if (hintAlign != null)
|
||||
{
|
||||
adaptive.HintAlign = hintAlign.Value;
|
||||
}
|
||||
|
||||
if (hintWrap != default(bool?))
|
||||
{
|
||||
adaptive.HintWrap = hintWrap;
|
||||
}
|
||||
|
||||
if (hintMaxLines != default(int?))
|
||||
if (hintMaxLines != default)
|
||||
{
|
||||
if (hintMaxLines > 2)
|
||||
{
|
||||
|
@ -369,12 +546,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
adaptive.HintMaxLines = hintMaxLines;
|
||||
}
|
||||
|
||||
if (hintMinLines != default(int?) && hintMinLines > 0)
|
||||
{
|
||||
adaptive.HintMinLines = hintMinLines;
|
||||
}
|
||||
|
||||
if (language != default(string))
|
||||
if (language != default)
|
||||
{
|
||||
adaptive.Language = language;
|
||||
}
|
||||
|
@ -419,6 +591,4 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -4,16 +4,21 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
#if !WINRT
|
||||
/// <summary>
|
||||
/// Builder class used to create <see cref="ToastContent"/>
|
||||
/// </summary>
|
||||
public partial class ToastContentBuilder
|
||||
#if !WINRT
|
||||
: IToastActivateableBuilder<ToastContentBuilder>
|
||||
#endif
|
||||
{
|
||||
private Dictionary<string, string> _genericArguments = new Dictionary<string, string>();
|
||||
|
||||
private bool _customArgumentsUsedOnToastItself;
|
||||
|
||||
/// <summary>
|
||||
/// Gets internal instance of <see cref="ToastContent"/>. This is equivalent to the call to <see cref="ToastContentBuilder.GetToastContent"/>.
|
||||
/// </summary>
|
||||
|
@ -35,13 +40,34 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// </summary>
|
||||
/// <param name="dateTime">Custom Time to be displayed on the toast</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddCustomTimeStamp(DateTime dateTime)
|
||||
public ToastContentBuilder AddCustomTimeStamp(
|
||||
#if WINRT
|
||||
DateTimeOffset dateTime)
|
||||
#else
|
||||
DateTime dateTime)
|
||||
#endif
|
||||
{
|
||||
Content.DisplayTimestamp = dateTime;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a header to a toast.
|
||||
/// </summary>
|
||||
/// <param name="id">A developer-created identifier that uniquely identifies this header. If two notifications have the same header id, they will be displayed underneath the same header in Action Center.</param>
|
||||
/// <param name="title">A title for the header.</param>
|
||||
/// <param name="arguments">Developer-defined arguments that are returned to the app when the user clicks this header.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
/// <remarks>More info about toast header: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-headers </remarks>
|
||||
#if WINRT
|
||||
[Windows.Foundation.Metadata.DefaultOverload]
|
||||
#endif
|
||||
public ToastContentBuilder AddHeader(string id, string title, ToastArguments arguments)
|
||||
{
|
||||
return AddHeader(id, title, arguments.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a header to a toast.
|
||||
/// </summary>
|
||||
|
@ -58,7 +84,201 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add info that can be used by the application when the app was activated/launched by the toast.
|
||||
/// Adds a key (without value) to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddArgument(string key)
|
||||
{
|
||||
return AddArgumentHelper(key, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
#if WINRT
|
||||
[Windows.Foundation.Metadata.DefaultOverload]
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
|
||||
#endif
|
||||
public ToastContentBuilder AddArgument(string key, string value)
|
||||
{
|
||||
return AddArgumentHelper(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
|
||||
#endif
|
||||
public ToastContentBuilder AddArgument(string key, int value)
|
||||
{
|
||||
return AddArgumentHelper(key, value.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
|
||||
#endif
|
||||
public ToastContentBuilder AddArgument(string key, double value)
|
||||
{
|
||||
return AddArgumentHelper(key, value.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
|
||||
#endif
|
||||
public ToastContentBuilder AddArgument(string key, float value)
|
||||
{
|
||||
return AddArgumentHelper(key, value.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
|
||||
#endif
|
||||
public ToastContentBuilder AddArgument(string key, bool value)
|
||||
{
|
||||
return AddArgumentHelper(key, value ? "1" : "0"); // Encode as 1 or 0 to save string space
|
||||
}
|
||||
|
||||
#if !WINRT
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself. Note that the enums are stored using their numeric value, so be aware that changing your enum number values might break existing activation of toasts currently in Action Center.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddArgument(string key, Enum value)
|
||||
{
|
||||
return AddArgumentHelper(key, ((int)(object)value).ToString());
|
||||
}
|
||||
#endif
|
||||
|
||||
private ToastContentBuilder AddArgumentHelper(string key, string value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
bool alreadyExists = _genericArguments.ContainsKey(key);
|
||||
|
||||
_genericArguments[key] = value;
|
||||
|
||||
if (Content.ActivationType != ToastActivationType.Protocol && !_customArgumentsUsedOnToastItself)
|
||||
{
|
||||
Content.Launch = alreadyExists ? SerializeArgumentsHelper(_genericArguments) : AddArgumentHelper(Content.Launch, key, value);
|
||||
}
|
||||
|
||||
if (Content.Actions is ToastActionsCustom actions)
|
||||
{
|
||||
foreach (var button in actions.Buttons)
|
||||
{
|
||||
if (button is ToastButton b && b.CanAddArguments() && !b.ContainsArgument(key))
|
||||
{
|
||||
b.AddArgument(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private string SerializeArgumentsHelper(IDictionary<string, string> arguments)
|
||||
{
|
||||
var args = new ToastArguments();
|
||||
|
||||
foreach (var a in arguments)
|
||||
{
|
||||
args.Add(a.Key, a.Value);
|
||||
}
|
||||
|
||||
return args.ToString();
|
||||
}
|
||||
|
||||
private string AddArgumentHelper(string existing, string key, string value)
|
||||
{
|
||||
string pair = ToastArguments.EncodePair(key, value);
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
return pair;
|
||||
}
|
||||
else
|
||||
{
|
||||
return existing + ToastArguments.Separator + pair;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the toast notification to launch the specified url when the toast body is clicked.
|
||||
/// </summary>
|
||||
/// <param name="protocol">The protocol to launch.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder SetProtocolActivation(Uri protocol)
|
||||
{
|
||||
return SetProtocolActivation(protocol, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the toast notification to launch the specified url when the toast body is clicked.
|
||||
/// </summary>
|
||||
/// <param name="protocol">The protocol to launch.</param>
|
||||
/// <param name="targetApplicationPfn">New in Creators Update: The target PFN, so that regardless of whether multiple apps are registered to handle the same protocol uri, your desired app will always be launched.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder SetProtocolActivation(Uri protocol, string targetApplicationPfn)
|
||||
{
|
||||
Content.Launch = protocol.ToString();
|
||||
Content.ActivationType = ToastActivationType.Protocol;
|
||||
|
||||
if (targetApplicationPfn != null)
|
||||
{
|
||||
if (Content.ActivationOptions == null)
|
||||
{
|
||||
Content.ActivationOptions = new ToastActivationOptions();
|
||||
}
|
||||
|
||||
Content.ActivationOptions.ProtocolActivationTargetApplicationPfn = targetApplicationPfn;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the toast notification to use background activation when the toast body is clicked.
|
||||
/// </summary>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder SetBackgroundActivation()
|
||||
{
|
||||
Content.ActivationType = ToastActivationType.Background;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instead of this method, for foreground/background activation, it is suggested to use <see cref="AddArgument(string, string)"/> and optionally <see cref="SetBackgroundActivation"/>. For protocol activation, you should use <see cref="SetProtocolActivation(Uri)"/>. Add info that can be used by the application when the app was activated/launched by the toast.
|
||||
/// </summary>
|
||||
/// <param name="launchArgs">Custom app-defined launch arguments to be passed along on toast activation</param>
|
||||
/// <param name="activationType">Set the activation type that will be used when the user click on this toast</param>
|
||||
|
@ -67,6 +287,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
{
|
||||
Content.Launch = launchArgs;
|
||||
Content.ActivationType = activationType;
|
||||
_customArgumentsUsedOnToastItself = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -93,6 +314,30 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
return this;
|
||||
}
|
||||
|
||||
#if WINRT
|
||||
/// <summary>
|
||||
/// Set custom audio to go along with the toast.
|
||||
/// </summary>
|
||||
/// <param name="src">Source to the media that will be played when the toast is pop</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
[Windows.Foundation.Metadata.DefaultOverload]
|
||||
public ToastContentBuilder AddAudio(Uri src)
|
||||
{
|
||||
return AddAudio(src, default, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set custom audio to go along with the toast.
|
||||
/// </summary>
|
||||
/// <param name="src">Source to the media that will be played when the toast is pop</param>
|
||||
/// <param name="loop">Indicating whether sound should repeat as long as the Toast is shown; false to play only once (default).</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddAudio(Uri src, bool? loop)
|
||||
{
|
||||
return AddAudio(src, loop, default);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Set custom audio to go along with the toast.
|
||||
/// </summary>
|
||||
|
@ -100,7 +345,15 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <param name="loop">Indicating whether sound should repeat as long as the Toast is shown; false to play only once (default).</param>
|
||||
/// <param name="silent">Indicating whether sound is muted; false to allow the Toast notification sound to play (default).</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddAudio(Uri src, bool? loop = null, bool? silent = null)
|
||||
public ToastContentBuilder AddAudio(
|
||||
Uri src,
|
||||
#if WINRT
|
||||
bool? loop,
|
||||
bool? silent)
|
||||
#else
|
||||
bool? loop = default,
|
||||
bool? silent = default)
|
||||
#endif
|
||||
{
|
||||
if (!src.IsFile)
|
||||
{
|
||||
|
@ -110,12 +363,12 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
Content.Audio = new ToastAudio();
|
||||
Content.Audio.Src = src;
|
||||
|
||||
if (loop != null)
|
||||
if (loop != default)
|
||||
{
|
||||
Content.Audio.Loop = loop.Value;
|
||||
}
|
||||
|
||||
if (silent != null)
|
||||
if (silent != default)
|
||||
{
|
||||
Content.Audio.Silent = silent.Value;
|
||||
}
|
||||
|
@ -123,6 +376,17 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set custom audio to go along with the toast.
|
||||
/// </summary>
|
||||
/// <param name="audio">The <see cref="ToastAudio"/> to set.</param>
|
||||
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
|
||||
public ToastContentBuilder AddAudio(ToastAudio audio)
|
||||
{
|
||||
Content.Audio = audio;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the instance of <see cref="ToastContent"/> that has been built by the builder with specified configuration so far.
|
||||
/// </summary>
|
||||
|
@ -131,7 +395,111 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
{
|
||||
return Content;
|
||||
}
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP
|
||||
/// <summary>
|
||||
/// Retrieves the notification XML content as a WinRT XmlDocument, so that it can be used with a local Toast notification's constructor on either <see cref="Windows.UI.Notifications.ToastNotification"/> or <see cref="Windows.UI.Notifications.ScheduledToastNotification"/>.
|
||||
/// </summary>
|
||||
/// <returns>The notification XML content as a WinRT XmlDocument.</returns>
|
||||
public Windows.Data.Xml.Dom.XmlDocument GetXml()
|
||||
{
|
||||
return GetToastContent().GetXml();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a new toast notification with the current content.
|
||||
/// </summary>
|
||||
public void Show()
|
||||
{
|
||||
CustomizeToast customize = null;
|
||||
Show(customize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a new toast notification with the current content.
|
||||
/// </summary>
|
||||
/// <param name="customize">Allows you to set additional properties on the <see cref="Windows.UI.Notifications.ToastNotification"/> object.</param>
|
||||
#if WINRT
|
||||
[Windows.Foundation.Metadata.DefaultOverload]
|
||||
#endif
|
||||
public void Show(CustomizeToast customize)
|
||||
{
|
||||
var notif = new Windows.UI.Notifications.ToastNotification(GetToastContent().GetXml());
|
||||
customize?.Invoke(notif);
|
||||
|
||||
ToastNotificationManagerCompat.CreateToastNotifier().Show(notif);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a new toast notification with the current content.
|
||||
/// </summary>
|
||||
/// <param name="customize">Allows you to set additional properties on the <see cref="Windows.UI.Notifications.ToastNotification"/> object.</param>
|
||||
/// <returns>An operation that completes after your async customizations have completed.</returns>
|
||||
public Windows.Foundation.IAsyncAction Show(CustomizeToastAsync customize)
|
||||
{
|
||||
return ShowAsyncHelper(customize).AsAsyncAction();
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task ShowAsyncHelper(CustomizeToastAsync customize)
|
||||
{
|
||||
var notif = new Windows.UI.Notifications.ToastNotification(GetToastContent().GetXml());
|
||||
|
||||
if (customize != null)
|
||||
{
|
||||
await customize.Invoke(notif);
|
||||
}
|
||||
|
||||
ToastNotificationManagerCompat.CreateToastNotifier().Show(notif);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules the notification.
|
||||
/// </summary>
|
||||
/// <param name="deliveryTime">The date and time that Windows should display the toast notification. This time must be in the future.</param>
|
||||
public void Schedule(DateTimeOffset deliveryTime)
|
||||
{
|
||||
CustomizeScheduledToast customize = null;
|
||||
Schedule(deliveryTime, customize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules the notification.
|
||||
/// </summary>
|
||||
/// <param name="deliveryTime">The date and time that Windows should display the toast notification. This time must be in the future.</param>
|
||||
/// <param name="customize">Allows you to set additional properties on the <see cref="Windows.UI.Notifications.ScheduledToastNotification"/> object.</param>
|
||||
#if WINRT
|
||||
[Windows.Foundation.Metadata.DefaultOverload]
|
||||
#endif
|
||||
public void Schedule(DateTimeOffset deliveryTime, CustomizeScheduledToast customize)
|
||||
{
|
||||
var notif = new Windows.UI.Notifications.ScheduledToastNotification(GetToastContent().GetXml(), deliveryTime);
|
||||
customize?.Invoke(notif);
|
||||
|
||||
ToastNotificationManagerCompat.CreateToastNotifier().AddToSchedule(notif);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules the notification.
|
||||
/// </summary>
|
||||
/// <param name="deliveryTime">The date and time that Windows should display the toast notification. This time must be in the future.</param>
|
||||
/// <param name="customize">Allows you to set additional properties on the <see cref="Windows.UI.Notifications.ScheduledToastNotification"/> object.</param>
|
||||
/// <returns>An operation that completes after your async customizations have completed.</returns>
|
||||
public Windows.Foundation.IAsyncAction Schedule(DateTimeOffset deliveryTime, CustomizeScheduledToastAsync customize)
|
||||
{
|
||||
return ScheduleAsyncHelper(deliveryTime, customize).AsAsyncAction();
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task ScheduleAsyncHelper(DateTimeOffset deliveryTime, CustomizeScheduledToastAsync customize = null)
|
||||
{
|
||||
var notif = new Windows.UI.Notifications.ScheduledToastNotification(GetToastContent().GetXml(), deliveryTime);
|
||||
|
||||
if (customize != null)
|
||||
{
|
||||
await customize.Invoke(notif);
|
||||
}
|
||||
|
||||
ToastNotificationManagerCompat.CreateToastNotifier().AddToSchedule(notif);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/tree/master/DesktopBridge.Helpers/Helpers.cs
|
||||
/// </summary>
|
||||
internal 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? _hasIdentity;
|
||||
|
||||
public static bool HasIdentity()
|
||||
{
|
||||
if (_hasIdentity == null)
|
||||
{
|
||||
if (IsWindows7OrLower)
|
||||
{
|
||||
_hasIdentity = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
int length = 0;
|
||||
var sb = new StringBuilder(0);
|
||||
GetCurrentPackageFullName(ref length, sb);
|
||||
|
||||
sb = new StringBuilder(length);
|
||||
int error = GetCurrentPackageFullName(ref length, sb);
|
||||
|
||||
_hasIdentity = error != APPMODEL_ERROR_NO_PACKAGE;
|
||||
}
|
||||
}
|
||||
|
||||
return _hasIdentity.Value;
|
||||
}
|
||||
|
||||
private static bool? _isContainerized;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the app is running in a container (MSIX) or false if not running in a container (sparse or plain Win32)
|
||||
/// </summary>
|
||||
/// <returns>Boolean</returns>
|
||||
public static bool IsContainerized()
|
||||
{
|
||||
if (_isContainerized == null)
|
||||
{
|
||||
// If MSIX or sparse
|
||||
if (HasIdentity())
|
||||
{
|
||||
// Sparse is identified if EXE is running outside of installed package location
|
||||
var packageInstalledLocation = Package.Current.InstalledLocation.Path;
|
||||
var actualExeFullPath = Process.GetCurrentProcess().MainModule.FileName;
|
||||
|
||||
// If inside package location
|
||||
if (actualExeFullPath.StartsWith(packageInstalledLocation))
|
||||
{
|
||||
_isContainerized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isContainerized = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Plain Win32
|
||||
else
|
||||
{
|
||||
_isContainerized = false;
|
||||
}
|
||||
}
|
||||
|
||||
return _isContainerized.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -2,6 +2,9 @@
|
|||
// 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.Collections.Generic;
|
||||
using Windows.UI.Notifications;
|
||||
|
||||
|
@ -10,6 +13,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <summary>
|
||||
/// Manages the toast notifications for an app including the ability the clear all toast history and removing individual toasts.
|
||||
/// </summary>
|
||||
[Obsolete("We recommend switching to the new ToastNotificationManagerCompat and ToastNotificationHistoryCompat.")]
|
||||
public sealed class DesktopNotificationHistoryCompat
|
||||
{
|
||||
private string _aumid;
|
||||
|
@ -99,4 +103,6 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -2,18 +2,20 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Windows.UI.Notifications;
|
||||
using static Microsoft.Toolkit.Uwp.Notifications.NotificationActivator;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper for .NET Framework applications to display toast notifications and respond to toast events
|
||||
/// </summary>
|
||||
[Obsolete("We recommend switching to the new ToastNotificationManagerCompat. For Win32 apps, it no longer requires a Start menu shortcut, and activation now uses a straightforward event handler (no NotificationActivator class and COM GUIDs needed)!")]
|
||||
public class DesktopNotificationManagerCompat
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -131,7 +133,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
// Create the instance of the .NET object
|
||||
ppvObject = Marshal.GetComInterfaceForObject(
|
||||
new T(),
|
||||
typeof(INotificationActivationCallback));
|
||||
typeof(NotificationActivator.INotificationActivationCallback));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -225,7 +227,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/edit/master/DesktopBridge.Helpers/Helpers.cs
|
||||
/// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/tree/master/DesktopBridge.Helpers/Helpers.cs
|
||||
/// </summary>
|
||||
private class DesktopBridgeHelpers
|
||||
{
|
||||
|
@ -273,3 +275,5 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,71 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not use this class. It must be public so that reflection can properly activate it, but consider it internal.
|
||||
/// </summary>
|
||||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public abstract class InternalNotificationActivator : InternalNotificationActivator.INotificationActivationCallback
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Activate(string appUserModelId, string invokedArgs, InternalNotificationActivator.NOTIFICATION_USER_INPUT_DATA[] data, uint dataCount)
|
||||
{
|
||||
ToastNotificationManagerCompat.OnActivatedInternal(invokedArgs, data, appUserModelId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single user input key/value pair.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[Serializable]
|
||||
public struct NOTIFICATION_USER_INPUT_DATA
|
||||
{
|
||||
/// <summary>
|
||||
/// The key of the user input.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string Key;
|
||||
|
||||
/// <summary>
|
||||
/// The value of the user input.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The COM callback that is triggered when your notification is clicked.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[Guid("53E31837-6600-4A81-9395-75CFFE746F94")]
|
||||
[ComVisible(true)]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface INotificationActivationCallback
|
||||
{
|
||||
/// <summary>
|
||||
/// The method called when your notification is clicked.
|
||||
/// </summary>
|
||||
/// <param name="appUserModelId">The app id of the app that sent the toast.</param>
|
||||
/// <param name="invokedArgs">The activation arguments from the toast.</param>
|
||||
/// <param name="data">The user input from the toast.</param>
|
||||
/// <param name="dataCount">The number of user inputs.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,91 @@
|
|||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
internal class ManifestHelper
|
||||
{
|
||||
private XmlDocument _doc;
|
||||
private XmlNamespaceManager _namespaceManager;
|
||||
|
||||
public ManifestHelper()
|
||||
{
|
||||
var appxManifestPath = Path.Combine(Package.Current.InstalledLocation.Path, "AppxManifest.xml");
|
||||
var doc = new XmlDocument();
|
||||
doc.Load(appxManifestPath);
|
||||
|
||||
var namespaceManager = new XmlNamespaceManager(doc.NameTable);
|
||||
namespaceManager.AddNamespace("default", "http://schemas.microsoft.com/appx/manifest/foundation/windows10");
|
||||
namespaceManager.AddNamespace("desktop", "http://schemas.microsoft.com/appx/manifest/desktop/windows10");
|
||||
namespaceManager.AddNamespace("com", "http://schemas.microsoft.com/appx/manifest/com/windows10");
|
||||
|
||||
_doc = doc;
|
||||
_namespaceManager = namespaceManager;
|
||||
}
|
||||
|
||||
internal string GetAumidFromPackageManifest()
|
||||
{
|
||||
var appNode = _doc.SelectSingleNode("/default:Package/default:Applications/default:Application[1]", _namespaceManager);
|
||||
|
||||
if (appNode == null)
|
||||
{
|
||||
throw new InvalidOperationException("Your MSIX app manifest must have an <Application> entry.");
|
||||
}
|
||||
|
||||
return Package.Current.Id.FamilyName + "!" + appNode.Attributes["Id"].Value;
|
||||
}
|
||||
|
||||
internal string GetClsidFromPackageManifest()
|
||||
{
|
||||
var activatorClsidNode = _doc.SelectSingleNode("/default:Package/default:Applications/default:Application[1]/default:Extensions/desktop:Extension[@Category='windows.toastNotificationActivation']/desktop:ToastNotificationActivation/@ToastActivatorCLSID", _namespaceManager);
|
||||
|
||||
if (activatorClsidNode == null)
|
||||
{
|
||||
throw new InvalidOperationException("Your app manifest must have a toastNotificationActivation extension with a valid ToastActivatorCLSID specified.");
|
||||
}
|
||||
|
||||
var clsid = activatorClsidNode.Value;
|
||||
|
||||
// Make sure they have a COM class registration matching the CLSID
|
||||
var comClassNode = _doc.SelectSingleNode($"/default:Package/default:Applications/default:Application[1]/default:Extensions/com:Extension[@Category='windows.comServer']/com:ComServer/com:ExeServer/com:Class[@Id='{clsid}']", _namespaceManager);
|
||||
|
||||
if (comClassNode == null)
|
||||
{
|
||||
throw new InvalidOperationException("Your app manifest must have a comServer extension with a class ID matching your toastNotificationActivator's CLSID.");
|
||||
}
|
||||
|
||||
// Make sure they have a COM class registration matching their current process executable
|
||||
var comExeServerNode = comClassNode.ParentNode;
|
||||
|
||||
var specifiedExeRelativePath = comExeServerNode.Attributes["Executable"].Value;
|
||||
var specifiedExeFullPath = Path.Combine(Package.Current.InstalledLocation.Path, specifiedExeRelativePath);
|
||||
var actualExeFullPath = Process.GetCurrentProcess().MainModule.FileName;
|
||||
|
||||
if (specifiedExeFullPath != actualExeFullPath)
|
||||
{
|
||||
var correctExeRelativePath = actualExeFullPath.Substring(Package.Current.InstalledLocation.Path.Length + 1);
|
||||
throw new InvalidOperationException($"Your app manifest's comServer extension's Executable value is incorrect. It should be \"{correctExeRelativePath}\".");
|
||||
}
|
||||
|
||||
// Make sure their arguments are set correctly
|
||||
var argumentsNode = comExeServerNode.Attributes.GetNamedItem("Arguments");
|
||||
if (argumentsNode == null || argumentsNode.Value != "-ToastActivated")
|
||||
{
|
||||
throw new InvalidOperationException("Your app manifest's comServer extension for toast activation must have its Arguments set exactly to \"-ToastActivated\"");
|
||||
}
|
||||
|
||||
return clsid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,20 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
[ComImport]
|
||||
[Guid("660b90c8-73a9-4b58-8cae-355b7f55341b")]
|
||||
[ClassInterface(ClassInterfaceType.None)]
|
||||
internal class CAppResolver
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,80 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
[ComImport]
|
||||
[Guid("DE25675A-72DE-44b4-9373-05170450C140")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
internal interface IApplicationResolver
|
||||
{
|
||||
void GetAppIDForShortcut(
|
||||
IntPtr psi,
|
||||
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID);
|
||||
|
||||
void GetAppIDForShortcutObject(
|
||||
IntPtr psl,
|
||||
IntPtr psi,
|
||||
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID);
|
||||
|
||||
void GetAppIDForWindow(
|
||||
int hwnd,
|
||||
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID,
|
||||
[Out] out bool pfPinningPrevented,
|
||||
[Out] out bool pfExplicitAppID,
|
||||
[Out] out bool pfEmbeddedShortcutValid);
|
||||
|
||||
/// <summary>
|
||||
/// Main way to obtain app ID for any process. Calls GetShortcutPathOrAppIdFromPid
|
||||
/// </summary>
|
||||
void GetAppIDForProcess(
|
||||
uint dwProcessID,
|
||||
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID,
|
||||
[Out] out bool pfPinningPrevented,
|
||||
[Out] out bool pfExplicitAppID,
|
||||
[Out] out bool pfEmbeddedShortcutValid);
|
||||
|
||||
void GetShortcutForProcess(
|
||||
uint dwProcessID,
|
||||
[Out] out IntPtr ppsi);
|
||||
|
||||
void GetBestShortcutForAppID(
|
||||
string pszAppID,
|
||||
[Out] out IShellItem ppsi);
|
||||
|
||||
void GetBestShortcutAndAppIDForAppPath(
|
||||
string pszAppPath,
|
||||
[Out] out IntPtr ppsi,
|
||||
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID);
|
||||
|
||||
void CanPinApp(IntPtr psi);
|
||||
|
||||
void CanPinAppShortcut(
|
||||
IntPtr psl,
|
||||
IntPtr psi);
|
||||
|
||||
void GetRelaunchProperties(
|
||||
int hwnd,
|
||||
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID,
|
||||
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszCmdLine,
|
||||
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszIconResource,
|
||||
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszDisplayNameResource,
|
||||
[Out] bool pfPinnable);
|
||||
|
||||
void GenerateShortcutFromWindowProperties(
|
||||
int hwnd,
|
||||
[Out] out IntPtr ppsi);
|
||||
|
||||
void GenerateShortcutFromItemProperties(
|
||||
IntPtr psi2,
|
||||
[Out] out IntPtr ppsi);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,41 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
[ComImport]
|
||||
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
internal interface IShellItem
|
||||
{
|
||||
void BindToHandler(
|
||||
IntPtr pbc,
|
||||
IntPtr bhid,
|
||||
IntPtr riid,
|
||||
[Out] out IntPtr ppv);
|
||||
|
||||
void GetParent(
|
||||
[Out] out IShellItem ppsi);
|
||||
|
||||
void GetDisplayName(
|
||||
int sigdnName,
|
||||
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
|
||||
|
||||
void GetAttributes(
|
||||
int sfgaoMask,
|
||||
[Out] out int psfgaoAttribs);
|
||||
|
||||
void Compare(
|
||||
IShellItem psi,
|
||||
int hint,
|
||||
[Out] out int piOrder);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,24 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
[ComImport]
|
||||
[Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
internal interface IShellItemImageFactory
|
||||
{
|
||||
void GetImage(
|
||||
[In, MarshalAs(UnmanagedType.Struct)] SIZE size,
|
||||
[In] SIIGBF flags,
|
||||
[Out] out IntPtr phbm);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,24 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
[Flags]
|
||||
internal enum SIIGBF
|
||||
{
|
||||
ResizeToFit = 0x00,
|
||||
BiggerSizeOk = 0x01,
|
||||
MemoryOnly = 0x02,
|
||||
IconOnly = 0x04,
|
||||
ThumbnailOnly = 0x08,
|
||||
InCacheOnly = 0x10,
|
||||
ScaleUp = 0x00000100
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,25 @@
|
|||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct SIZE
|
||||
{
|
||||
internal int X;
|
||||
internal int Y;
|
||||
|
||||
internal SIZE(int x, int y)
|
||||
{
|
||||
this.X = x;
|
||||
this.Y = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -2,6 +2,8 @@
|
|||
// 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.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
@ -16,6 +18,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// <summary>
|
||||
/// Apps must implement this activator to handle notification activation.
|
||||
/// </summary>
|
||||
[Obsolete("You can now subscribe to activation by simpy using the ToastNotificationManagerCompat.OnActivated event. We recommend deleting your NotificationActivator and switching to using the event.")]
|
||||
public abstract class NotificationActivator : NotificationActivator.INotificationActivationCallback
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
|
@ -77,4 +80,6 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
uint dataCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -2,6 +2,8 @@
|
|||
// 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.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
@ -18,9 +20,13 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// </summary>
|
||||
public class NotificationUserInput : IReadOnlyDictionary<string, string>
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
private NotificationActivator.NOTIFICATION_USER_INPUT_DATA[] _data;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
internal NotificationUserInput(NotificationActivator.NOTIFICATION_USER_INPUT_DATA[] data)
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
@ -92,4 +98,6 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,16 @@
|
|||
// 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
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Event triggered when a notification is clicked.
|
||||
/// </summary>
|
||||
/// <param name="e">Arguments that specify what action was taken and the user inputs.</param>
|
||||
public delegate void OnActivated(ToastNotificationActivatedEventArgsCompat e);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -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 Windows.Foundation.Collections;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides information about an event that occurs when the app is activated because a user tapped on the body of a toast notification or performed an action inside a toast notification.
|
||||
/// </summary>
|
||||
public class ToastNotificationActivatedEventArgsCompat
|
||||
{
|
||||
internal ToastNotificationActivatedEventArgsCompat()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the arguments from the toast XML payload related to the action that was invoked on the toast.
|
||||
/// </summary>
|
||||
public string Argument { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a set of values that you can use to obtain the user input from an interactive toast notification.
|
||||
/// </summary>
|
||||
public ValueSet UserInput { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,254 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
internal class Win32AppInfo
|
||||
{
|
||||
public string Aumid { get; set; }
|
||||
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
public string IconPath { get; set; }
|
||||
|
||||
public static Win32AppInfo Get()
|
||||
{
|
||||
var process = Process.GetCurrentProcess();
|
||||
|
||||
// First get the app ID
|
||||
IApplicationResolver appResolver = (IApplicationResolver)new CAppResolver();
|
||||
appResolver.GetAppIDForProcess(Convert.ToUInt32(process.Id), out string appId, out _, out _, out _);
|
||||
|
||||
// Then try to get the shortcut (for display name and icon)
|
||||
IShellItem shortcutItem = null;
|
||||
try
|
||||
{
|
||||
appResolver.GetBestShortcutForAppID(appId, out shortcutItem);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
string displayName = null;
|
||||
string iconPath = null;
|
||||
|
||||
// First we attempt to use display assets from the shortcut itself
|
||||
if (shortcutItem != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
shortcutItem.GetDisplayName(0, out displayName);
|
||||
|
||||
((IShellItemImageFactory)shortcutItem).GetImage(new SIZE(48, 48), SIIGBF.IconOnly | SIIGBF.BiggerSizeOk, out IntPtr nativeHBitmap);
|
||||
|
||||
if (nativeHBitmap != IntPtr.Zero)
|
||||
{
|
||||
try
|
||||
{
|
||||
Bitmap bmp = Bitmap.FromHbitmap(nativeHBitmap);
|
||||
|
||||
if (IsAlphaBitmap(bmp, out var bmpData))
|
||||
{
|
||||
var alphaBitmap = GetAlphaBitmapFromBitmapData(bmpData);
|
||||
iconPath = SaveIconToAppPath(alphaBitmap, appId);
|
||||
}
|
||||
else
|
||||
{
|
||||
iconPath = SaveIconToAppPath(bmp, appId);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
DeleteObject(nativeHBitmap);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't get a display name from shortcut
|
||||
if (string.IsNullOrWhiteSpace(displayName))
|
||||
{
|
||||
// We use the one from the process
|
||||
displayName = GetDisplayNameFromCurrentProcess(process);
|
||||
}
|
||||
|
||||
// If we didn't get an icon from shortcut
|
||||
if (string.IsNullOrWhiteSpace(iconPath))
|
||||
{
|
||||
// We use the one from the process
|
||||
iconPath = ExtractAndObtainIconFromCurrentProcess(process, appId);
|
||||
}
|
||||
|
||||
return new Win32AppInfo()
|
||||
{
|
||||
Aumid = appId,
|
||||
DisplayName = displayName,
|
||||
IconPath = iconPath
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetDisplayNameFromCurrentProcess(Process process)
|
||||
{
|
||||
// If AssemblyTitle is set, use that
|
||||
var assemblyTitleAttr = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyTitleAttribute>();
|
||||
if (assemblyTitleAttr != null)
|
||||
{
|
||||
return assemblyTitleAttr.Title;
|
||||
}
|
||||
|
||||
// Otherwise, fall back to process name
|
||||
return process.ProcessName;
|
||||
}
|
||||
|
||||
private static string ExtractAndObtainIconFromCurrentProcess(Process process, string appId)
|
||||
{
|
||||
return ExtractAndObtainIconFromPath(process.MainModule.FileName, appId);
|
||||
}
|
||||
|
||||
private static string ExtractAndObtainIconFromPath(string pathToExtract, string appId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Extract the icon
|
||||
var icon = Icon.ExtractAssociatedIcon(pathToExtract);
|
||||
|
||||
using (var bmp = icon.ToBitmap())
|
||||
{
|
||||
return SaveIconToAppPath(bmp, appId);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string SaveIconToAppPath(Bitmap bitmap, string appId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(GetAppDataFolderPath(appId), "Icon.png");
|
||||
|
||||
// Ensure the directories exist
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
bitmap.Save(path, ImageFormat.Png);
|
||||
|
||||
return path;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the app data folder path within the ToastNotificationManagerCompat folder, used for storing icon assets and any additional data.
|
||||
/// </summary>
|
||||
/// <returns>Returns a string of the absolute folder path.</returns>
|
||||
public static string GetAppDataFolderPath(string appId)
|
||||
{
|
||||
string conciseAumid = appId.Contains("\\") ? GenerateGuid(appId) : appId;
|
||||
|
||||
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ToastNotificationManagerCompat", "Apps", conciseAumid);
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/9291151
|
||||
private static Bitmap GetAlphaBitmapFromBitmapData(BitmapData bmpData)
|
||||
{
|
||||
return new Bitmap(
|
||||
bmpData.Width,
|
||||
bmpData.Height,
|
||||
bmpData.Stride,
|
||||
PixelFormat.Format32bppArgb,
|
||||
bmpData.Scan0);
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/9291151
|
||||
private static bool IsAlphaBitmap(Bitmap bmp, out BitmapData bmpData)
|
||||
{
|
||||
var bmBounds = new Rectangle(0, 0, bmp.Width, bmp.Height);
|
||||
|
||||
bmpData = bmp.LockBits(bmBounds, ImageLockMode.ReadOnly, bmp.PixelFormat);
|
||||
|
||||
try
|
||||
{
|
||||
for (int y = 0; y <= bmpData.Height - 1; y++)
|
||||
{
|
||||
for (int x = 0; x <= bmpData.Width - 1; x++)
|
||||
{
|
||||
var pixelColor = Color.FromArgb(
|
||||
Marshal.ReadInt32(bmpData.Scan0, (bmpData.Stride * y) + (4 * x)));
|
||||
|
||||
if (pixelColor.A > 0 & pixelColor.A < 255)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
bmp.UnlockBits(bmpData);
|
||||
}
|
||||
|
||||
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)
|
||||
/// and if name byte length is less than 16 - it will be fetched directly into guid, if over 16 bytes - then we compute sha-1
|
||||
/// hash from string and then pass it to guid.
|
||||
/// </summary>
|
||||
/// <param name="name">Unique name which is unique across where this guid will be used.</param>
|
||||
/// <returns>For example "706C7567-696E-7300-0000-000000000000" for "plugins"</returns>
|
||||
public static string GenerateGuid(string name)
|
||||
{
|
||||
byte[] buf = Encoding.UTF8.GetBytes(name);
|
||||
byte[] guid = new byte[16];
|
||||
if (buf.Length < 16)
|
||||
{
|
||||
Array.Copy(buf, guid, buf.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (SHA1 sha1 = SHA1.Create())
|
||||
{
|
||||
byte[] hash = sha1.ComputeHash(buf);
|
||||
|
||||
// Hash is 20 bytes, but we need 16. We loose some of "uniqueness", but I doubt it will be fatal
|
||||
Array.Copy(hash, guid, 16);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't use Guid constructor, it tends to swap bytes. We want to preserve original string as hex dump.
|
||||
string guidS = $"{guid[0]:X2}{guid[1]:X2}{guid[2]:X2}{guid[3]:X2}-{guid[4]:X2}{guid[5]:X2}-{guid[6]:X2}{guid[7]:X2}-{guid[8]:X2}{guid[9]:X2}-{guid[10]:X2}{guid[11]:X2}{guid[12]:X2}{guid[13]:X2}{guid[14]:X2}{guid[15]:X2}";
|
||||
|
||||
return guidS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,101 @@
|
|||
// 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>
|
||||
/// Manages the toast notifications for an app including the ability the clear all toast history and removing individual toasts.
|
||||
/// </summary>
|
||||
public class ToastNotificationHistoryCompat
|
||||
{
|
||||
private string _aumid;
|
||||
private ToastNotificationHistory _history;
|
||||
|
||||
internal ToastNotificationHistoryCompat(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,478 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Notifications;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to sending and managing toast notifications. Works for all types of apps, even Win32 non-MSIX/sparse apps.
|
||||
/// </summary>
|
||||
public static class ToastNotificationManagerCompat
|
||||
{
|
||||
#if WIN32
|
||||
private const string TOAST_ACTIVATED_LAUNCH_ARG = "-ToastActivated";
|
||||
|
||||
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 static bool _registeredOnActivated;
|
||||
private static List<OnActivated> _onActivated = new List<OnActivated>();
|
||||
|
||||
/// <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>
|
||||
public static event OnActivated OnActivated
|
||||
{
|
||||
add
|
||||
{
|
||||
lock (_onActivated)
|
||||
{
|
||||
if (!_registeredOnActivated)
|
||||
{
|
||||
// Desktop Bridge apps will dynamically register upon first subscription to event
|
||||
try
|
||||
{
|
||||
CreateAndRegisterActivator();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_initializeEx = ex;
|
||||
}
|
||||
}
|
||||
|
||||
_onActivated.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
lock (_onActivated)
|
||||
{
|
||||
_onActivated.Remove(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void OnActivatedInternal(string args, Internal.InternalNotificationActivator.NOTIFICATION_USER_INPUT_DATA[] input, string aumid)
|
||||
{
|
||||
ValueSet userInput = new ValueSet();
|
||||
|
||||
if (input != null)
|
||||
{
|
||||
foreach (var val in input)
|
||||
{
|
||||
userInput.Add(val.Key, val.Value);
|
||||
}
|
||||
}
|
||||
|
||||
var e = new ToastNotificationActivatedEventArgsCompat()
|
||||
{
|
||||
Argument = args,
|
||||
UserInput = userInput
|
||||
};
|
||||
|
||||
OnActivated[] listeners;
|
||||
lock (_onActivated)
|
||||
{
|
||||
listeners = _onActivated.ToArray();
|
||||
}
|
||||
|
||||
foreach (var listener in listeners)
|
||||
{
|
||||
listener(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static string _win32Aumid;
|
||||
private static string _clsid;
|
||||
private static Exception _initializeEx;
|
||||
|
||||
static ToastNotificationManagerCompat()
|
||||
{
|
||||
try
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// We catch the exception so that things like subscribing to the event handler doesn't crash app
|
||||
_initializeEx = ex;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Initialize()
|
||||
{
|
||||
// If containerized
|
||||
if (DesktopBridgeHelpers.IsContainerized())
|
||||
{
|
||||
// No need to do anything additional, already registered through manifest
|
||||
return;
|
||||
}
|
||||
|
||||
Win32AppInfo win32AppInfo = null;
|
||||
|
||||
// If sparse
|
||||
if (DesktopBridgeHelpers.HasIdentity())
|
||||
{
|
||||
_win32Aumid = new ManifestHelper().GetAumidFromPackageManifest();
|
||||
}
|
||||
else
|
||||
{
|
||||
win32AppInfo = Win32AppInfo.Get();
|
||||
_win32Aumid = win32AppInfo.Aumid;
|
||||
}
|
||||
|
||||
// Create and register activator
|
||||
var activatorType = CreateAndRegisterActivator();
|
||||
|
||||
// Register via registry
|
||||
using (var rootKey = Registry.CurrentUser.CreateSubKey(@"Software\Classes\AppUserModelId\" + _win32Aumid))
|
||||
{
|
||||
// If they don't have identity, we need to specify the display assets
|
||||
if (!DesktopBridgeHelpers.HasIdentity())
|
||||
{
|
||||
// Set the display name and icon uri
|
||||
rootKey.SetValue("DisplayName", win32AppInfo.DisplayName);
|
||||
|
||||
if (win32AppInfo.IconPath != null)
|
||||
{
|
||||
rootKey.SetValue("IconUri", win32AppInfo.IconPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rootKey.GetValue("IconUri") != null)
|
||||
{
|
||||
rootKey.DeleteValue("IconUri");
|
||||
}
|
||||
}
|
||||
|
||||
// Background color only appears in the settings page, format is
|
||||
// hex without leading #, like "FFDDDDDD"
|
||||
rootKey.SetValue("IconBackgroundColor", "FFDDDDDD");
|
||||
}
|
||||
|
||||
rootKey.SetValue("CustomActivator", string.Format("{{{0}}}", activatorType.GUID));
|
||||
}
|
||||
}
|
||||
|
||||
private static Type CreateActivatorType()
|
||||
{
|
||||
// https://stackoverflow.com/questions/24069352/c-sharp-typebuilder-generate-class-with-function-dynamically
|
||||
// For .NET Core we use https://stackoverflow.com/questions/36937276/is-there-any-replace-of-assemblybuilder-definedynamicassembly-in-net-core
|
||||
AssemblyName aName = new AssemblyName("DynamicComActivator");
|
||||
AssemblyBuilder aBuilder = AssemblyBuilder.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run);
|
||||
|
||||
// For a single-module assembly, the module name is usually the assembly name plus an extension.
|
||||
ModuleBuilder mb = aBuilder.DefineDynamicModule(aName.Name);
|
||||
|
||||
// Create class which extends NotificationActivator
|
||||
TypeBuilder tb = mb.DefineType(
|
||||
name: "MyNotificationActivator",
|
||||
attr: TypeAttributes.Public,
|
||||
parent: typeof(Internal.InternalNotificationActivator),
|
||||
interfaces: new Type[0]);
|
||||
|
||||
if (DesktopBridgeHelpers.IsContainerized())
|
||||
{
|
||||
_clsid = new ManifestHelper().GetClsidFromPackageManifest();
|
||||
}
|
||||
else
|
||||
{
|
||||
_clsid = Win32AppInfo.GenerateGuid(_win32Aumid);
|
||||
}
|
||||
|
||||
tb.SetCustomAttribute(new CustomAttributeBuilder(
|
||||
con: typeof(GuidAttribute).GetConstructor(new Type[] { typeof(string) }),
|
||||
constructorArgs: new object[] { _clsid }));
|
||||
|
||||
tb.SetCustomAttribute(new CustomAttributeBuilder(
|
||||
con: typeof(ComVisibleAttribute).GetConstructor(new Type[] { typeof(bool) }),
|
||||
constructorArgs: new object[] { true }));
|
||||
|
||||
tb.SetCustomAttribute(new CustomAttributeBuilder(
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
con: typeof(ComSourceInterfacesAttribute).GetConstructor(new Type[] { typeof(Type) }),
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
constructorArgs: new object[] { typeof(Internal.InternalNotificationActivator.INotificationActivationCallback) }));
|
||||
|
||||
tb.SetCustomAttribute(new CustomAttributeBuilder(
|
||||
con: typeof(ClassInterfaceAttribute).GetConstructor(new Type[] { typeof(ClassInterfaceType) }),
|
||||
constructorArgs: new object[] { ClassInterfaceType.None }));
|
||||
|
||||
return tb.CreateType();
|
||||
}
|
||||
|
||||
private static Type CreateAndRegisterActivator()
|
||||
{
|
||||
var activatorType = CreateActivatorType();
|
||||
RegisterActivator(activatorType);
|
||||
_registeredOnActivated = true;
|
||||
return activatorType;
|
||||
}
|
||||
|
||||
private static void RegisterActivator(Type activatorType)
|
||||
{
|
||||
if (!DesktopBridgeHelpers.IsContainerized())
|
||||
{
|
||||
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||
RegisterComServer(activatorType, exePath);
|
||||
}
|
||||
|
||||
// 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(
|
||||
uuid,
|
||||
new NotificationActivatorClassFactory(activatorType),
|
||||
CLSCTX_LOCAL_SERVER,
|
||||
REGCLS_MULTIPLEUSE,
|
||||
out _);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the current process was activated due to a toast activation. If so, the OnActivated event will be triggered soon after process launch.
|
||||
/// </summary>
|
||||
/// <returns>True if the current process was activated due to a toast activation, otherwise false.</returns>
|
||||
public static bool WasCurrentProcessToastActivated()
|
||||
{
|
||||
return Environment.GetCommandLineArgs().Contains(TOAST_ACTIVATED_LAUNCH_ARG);
|
||||
}
|
||||
|
||||
[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 class NotificationActivatorClassFactory : IClassFactory
|
||||
{
|
||||
private Type _activatorType;
|
||||
|
||||
public NotificationActivatorClassFactory(Type activatorType)
|
||||
{
|
||||
_activatorType = activatorType;
|
||||
}
|
||||
|
||||
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 == _activatorType.GUID || riid == IUnknownGuid)
|
||||
{
|
||||
// Create the instance of the .NET object
|
||||
ppvObject = Marshal.GetComInterfaceForObject(
|
||||
Activator.CreateInstance(_activatorType),
|
||||
typeof(Internal.InternalNotificationActivator.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);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a toast notifier.
|
||||
/// </summary>
|
||||
/// <returns><see cref="ToastNotifier"/></returns>
|
||||
public static ToastNotifier CreateToastNotifier()
|
||||
{
|
||||
#if WIN32
|
||||
if (_initializeEx != null)
|
||||
{
|
||||
throw _initializeEx;
|
||||
}
|
||||
|
||||
if (DesktopBridgeHelpers.HasIdentity())
|
||||
{
|
||||
return ToastNotificationManager.CreateToastNotifier();
|
||||
}
|
||||
else
|
||||
{
|
||||
return ToastNotificationManager.CreateToastNotifier(_win32Aumid);
|
||||
}
|
||||
#else
|
||||
return ToastNotificationManager.CreateToastNotifier();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ToastNotificationHistoryCompat"/> object.
|
||||
/// </summary>
|
||||
public static ToastNotificationHistoryCompat History
|
||||
{
|
||||
get
|
||||
{
|
||||
#if WIN32
|
||||
if (_initializeEx != null)
|
||||
{
|
||||
throw _initializeEx;
|
||||
}
|
||||
|
||||
return new ToastNotificationHistoryCompat(DesktopBridgeHelpers.HasIdentity() ? null : _win32Aumid);
|
||||
#else
|
||||
return new ToastNotificationHistoryCompat(null);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether http images can be used within toasts. This is true if running with package identity (UWP, MSIX, or sparse package).
|
||||
/// </summary>
|
||||
public static bool CanUseHttpImages
|
||||
{
|
||||
get
|
||||
{
|
||||
#if WIN32
|
||||
return DesktopBridgeHelpers.HasIdentity();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if WIN32
|
||||
/// <summary>
|
||||
/// If you're not using MSIX, call this when your app is being uninstalled to properly clean up all notifications and notification-related resources. Note that this must be called from your app's main EXE (the one that you used notifications for) and not a separate uninstall EXE. If called from a MSIX app, this method no-ops.
|
||||
/// </summary>
|
||||
public static void Uninstall()
|
||||
{
|
||||
if (DesktopBridgeHelpers.IsContainerized())
|
||||
{
|
||||
// Packaged containerized apps automatically clean everything up already
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DesktopBridgeHelpers.HasIdentity())
|
||||
{
|
||||
try
|
||||
{
|
||||
// Remove all scheduled notifications (do this first before clearing current notifications)
|
||||
var notifier = CreateToastNotifier();
|
||||
foreach (var scheduled in CreateToastNotifier().GetScheduledToastNotifications())
|
||||
{
|
||||
try
|
||||
{
|
||||
notifier.RemoveFromSchedule(scheduled);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Clear all current notifications
|
||||
History.Clear();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Remove registry key
|
||||
if (_win32Aumid != null)
|
||||
{
|
||||
Registry.CurrentUser.DeleteSubKey(@"Software\Classes\AppUserModelId\" + _win32Aumid);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_clsid != null)
|
||||
{
|
||||
Registry.CurrentUser.DeleteSubKey(string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}\\LocalServer32", _clsid));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (!DesktopBridgeHelpers.HasIdentity() && _win32Aumid != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Delete any of the app files
|
||||
var appDataFolderPath = Win32AppInfo.GetAppDataFolderPath(_win32Aumid);
|
||||
if (Directory.Exists(appDataFolderPath))
|
||||
{
|
||||
Directory.Delete(appDataFolderPath, recursive: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,96 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Interfaces for classes that can have activation info added to them.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the host object.</typeparam>
|
||||
internal interface IToastActivateableBuilder<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a key (without value) to the activation arguments that will be returned when the content is clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns>The current instance of the object.</returns>
|
||||
T AddArgument(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the content is clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of the object.</returns>
|
||||
#if WINRT
|
||||
[Windows.Foundation.Metadata.DefaultOverload]
|
||||
#endif
|
||||
T AddArgument(string key, string value);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the content is clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of the object.</returns>
|
||||
T AddArgument(string key, int value);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the content is clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of the object.</returns>
|
||||
T AddArgument(string key, double value);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the content is clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of the object.</returns>
|
||||
T AddArgument(string key, float value);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the content is clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of the object.</returns>
|
||||
T AddArgument(string key, bool value);
|
||||
|
||||
#if !WINRT
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the content is clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself. Note that the enums are stored using their numeric value, so be aware that changing your enum number values might break existing activation of toasts currently in Action Center.</param>
|
||||
/// <returns>The current instance of the object.</returns>
|
||||
T AddArgument(string key, Enum value);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Configures the content to use background activation when it is clicked.
|
||||
/// </summary>
|
||||
/// <returns>The current instance of the object.</returns>
|
||||
T SetBackgroundActivation();
|
||||
|
||||
/// <summary>
|
||||
/// Configures the content to use protocol activation when it is clicked.
|
||||
/// </summary>
|
||||
/// <param name="protocol">The protocol to launch.</param>
|
||||
/// <returns>The current instance of the object.</returns>
|
||||
T SetProtocolActivation(Uri protocol);
|
||||
|
||||
/// <summary>
|
||||
/// Configures the content to use protocol activation when it is clicked.
|
||||
/// </summary>
|
||||
/// <param name="protocol">The protocol to launch.</param>
|
||||
/// <param name="targetApplicationPfn">New in Creators Update: The target PFN, so that regardless of whether multiple apps are registered to handle the same protocol uri, your desired app will always be launched.</param>
|
||||
/// <returns>The current instance of the object.</returns>
|
||||
T SetProtocolActivation(Uri protocol, string targetApplicationPfn);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,462 @@
|
|||
// 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;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that supports serializing simple key/value pairs into a format that's friendly for being used within toast notifications. The serialized format is similar to a query string, however optimized for being placed within an XML property (uses semicolons instead of ampersands since those don't need to be XML-escaped, doesn't url-encode all special characters since not being used within a URL, etc).
|
||||
/// </summary>
|
||||
public sealed class ToastArguments : IEnumerable<KeyValuePair<string, string>>
|
||||
{
|
||||
private Dictionary<string, string> _dictionary = new Dictionary<string, string>();
|
||||
|
||||
internal ToastArguments Clone()
|
||||
{
|
||||
return new ToastArguments()
|
||||
{
|
||||
_dictionary = new Dictionary<string, string>(_dictionary)
|
||||
};
|
||||
}
|
||||
|
||||
#if !WINRT
|
||||
/// <summary>
|
||||
/// Gets the value of the specified key. Throws <see cref="KeyNotFoundException"/> if the key could not be found.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to find.</param>
|
||||
/// <returns>The value of the specified key.</returns>
|
||||
public string this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (TryGetValue(key, out string value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"A key with name '{key}' could not be found.");
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
_dictionary[key] = value;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the value of the specified key. If no key exists, returns false.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to find.</param>
|
||||
/// <param name="value">The key's value will be written here if found.</param>
|
||||
/// <returns>True if found the key and set the value, otherwise false.</returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("found")]
|
||||
#endif
|
||||
public bool TryGetValue(string key, out string value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
return _dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
#if !WINRT
|
||||
/// <summary>
|
||||
/// Attempts to get the value of the specified key. If no key exists, returns false.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The enum to parse.</typeparam>
|
||||
/// <param name="key">The key to find.</param>
|
||||
/// <param name="value">The key's value will be written here if found.</param>
|
||||
/// <returns>True if found the key and set the value, otherwise false.</returns>
|
||||
public bool TryGetValue<T>(string key, out T value)
|
||||
where T : struct, Enum
|
||||
{
|
||||
if (TryGetValue(key, out string strValue))
|
||||
{
|
||||
return Enum.TryParse(strValue, out value);
|
||||
}
|
||||
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to get.</param>
|
||||
/// <returns>The value of the key.</returns>
|
||||
public string Get(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (_dictionary.TryGetValue(key, out string value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to get.</param>
|
||||
/// <returns>The value of the key.</returns>
|
||||
public int GetInt(string key)
|
||||
{
|
||||
return int.Parse(Get(key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to get.</param>
|
||||
/// <returns>The value of the key.</returns>
|
||||
public double GetDouble(string key)
|
||||
{
|
||||
return double.Parse(Get(key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to get.</param>
|
||||
/// <returns>The value of the key.</returns>
|
||||
public float GetFloat(string key)
|
||||
{
|
||||
return float.Parse(Get(key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to get.</param>
|
||||
/// <returns>The value of the key.</returns>
|
||||
public byte GetByte(string key)
|
||||
{
|
||||
return byte.Parse(Get(key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to get.</param>
|
||||
/// <returns>The value of the key.</returns>
|
||||
public bool GetBool(string key)
|
||||
{
|
||||
return Get(key) == "1" ? true : false;
|
||||
}
|
||||
|
||||
#if !WINRT
|
||||
/// <summary>
|
||||
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The enum to parse.</typeparam>
|
||||
/// <param name="key">The key to get.</param>
|
||||
/// <returns>The value of the key.</returns>
|
||||
public T GetEnum<T>(string key)
|
||||
where T : struct, Enum
|
||||
{
|
||||
if (TryGetValue(key, out T value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of key/value pairs contained in the toast arguments.
|
||||
/// </summary>
|
||||
public int Count => _dictionary.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key. If there is an existing key, it is replaced.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
|
||||
#endif
|
||||
public ToastArguments Add(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
_dictionary[key] = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key and optional value. If there is an existing key, it is replaced.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">The optional value of the key.</param>
|
||||
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
|
||||
#if WINRT
|
||||
[Windows.Foundation.Metadata.DefaultOverload]
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
|
||||
#endif
|
||||
public ToastArguments Add(string key, string value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
_dictionary[key] = value;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key and value. If there is an existing key, it is replaced.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">The value of the key.</param>
|
||||
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
|
||||
#endif
|
||||
public ToastArguments Add(string key, int value)
|
||||
{
|
||||
return AddHelper(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key and value. If there is an existing key, it is replaced.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">The value of the key.</param>
|
||||
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
|
||||
#endif
|
||||
public ToastArguments Add(string key, double value)
|
||||
{
|
||||
return AddHelper(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key and value. If there is an existing key, it is replaced.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">The value of the key.</param>
|
||||
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
|
||||
#endif
|
||||
public ToastArguments Add(string key, float value)
|
||||
{
|
||||
return AddHelper(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key and value. If there is an existing key, it is replaced.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">The value of the key.</param>
|
||||
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
|
||||
#endif
|
||||
public ToastArguments Add(string key, bool value)
|
||||
{
|
||||
return Add(key, value ? "1" : "0"); // Encode as 1 or 0 to save string space
|
||||
}
|
||||
|
||||
#if !WINRT
|
||||
/// <summary>
|
||||
/// Adds a key and value. If there is an existing key, it is replaced.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">The value of the key. Note that the enums are stored using their numeric value, so be aware that changing your enum number values might break existing activation of toasts currently in Action Center.</param>
|
||||
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
|
||||
public ToastArguments Add(string key, Enum value)
|
||||
{
|
||||
return Add(key, (int)(object)value);
|
||||
}
|
||||
#endif
|
||||
|
||||
private ToastArguments AddHelper(string key, object value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
_dictionary[key] = value.ToString();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the specified key is present.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to look for.</param>
|
||||
/// <returns>True if the key is present, otherwise false.</returns>
|
||||
public bool Contains(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
return _dictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if specified key and value are present.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to look for.</param>
|
||||
/// <param name="value">The value to look for when the key has been matched.</param>
|
||||
/// <returns>True if the key and value were found, else false.</returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("doesContain")]
|
||||
#endif
|
||||
public bool Contains(string key, string value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
return _dictionary.TryGetValue(key, out string actualValue) && actualValue == value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified key and its associated value.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to remove.</param>
|
||||
/// <returns>True if the key was removed, else false.</returns>
|
||||
public bool Remove(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
return _dictionary.Remove(key);
|
||||
}
|
||||
|
||||
private static string Encode(string str)
|
||||
{
|
||||
return str
|
||||
.Replace("%", "%25")
|
||||
.Replace(";", "%3B")
|
||||
.Replace("=", "%3D");
|
||||
}
|
||||
|
||||
private static string Decode(string str)
|
||||
{
|
||||
return str
|
||||
.Replace("%25", "%")
|
||||
.Replace("%3B", ";")
|
||||
.Replace("%3D", "=");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a string that was generated using ToastArguments into a <see cref="ToastArguments"/> object.
|
||||
/// </summary>
|
||||
/// <param name="toastArgumentsStr">The toast arguments string to deserialize.</param>
|
||||
/// <returns>The parsed toast arguments.</returns>
|
||||
public static ToastArguments Parse(string toastArgumentsStr)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(toastArgumentsStr))
|
||||
{
|
||||
return new ToastArguments();
|
||||
}
|
||||
|
||||
string[] pairs = toastArgumentsStr.Split(';');
|
||||
|
||||
ToastArguments answer = new ToastArguments();
|
||||
|
||||
foreach (string pair in pairs)
|
||||
{
|
||||
string name;
|
||||
string value;
|
||||
|
||||
int indexOfEquals = pair.IndexOf('=');
|
||||
|
||||
if (indexOfEquals == -1)
|
||||
{
|
||||
name = Decode(pair);
|
||||
value = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = Decode(pair.Substring(0, indexOfEquals));
|
||||
value = Decode(pair.Substring(indexOfEquals + 1));
|
||||
}
|
||||
|
||||
answer.Add(name, value);
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the key-value pairs into a string that can be used within a toast notification.
|
||||
/// </summary>
|
||||
/// <returns>A string that can be used within a toast notification.</returns>
|
||||
public sealed override string ToString()
|
||||
{
|
||||
return string.Join(Separator, this.Select(pair => EncodePair(pair.Key, pair.Value)));
|
||||
}
|
||||
|
||||
internal static string EncodePair(string key, string value)
|
||||
{
|
||||
// Key
|
||||
return Encode(key) +
|
||||
|
||||
// Write value if not null
|
||||
((value == null) ? string.Empty : ("=" + Encode(value)));
|
||||
}
|
||||
|
||||
internal const string Separator = ";";
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator to enumerate the arguments. Note that order of the arguments is NOT preserved.
|
||||
/// </summary>
|
||||
/// <returns>An enumeartor of the key/value pairs.</returns>
|
||||
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
||||
{
|
||||
return _dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator to enumerate the query string parameters.
|
||||
/// </summary>
|
||||
/// <returns>An enumeartor of the key/value pairs.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,14 +3,23 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// A button that the user can click on a Toast notification.
|
||||
/// </summary>
|
||||
public sealed class ToastButton : IToastButton
|
||||
public sealed class ToastButton :
|
||||
#if !WINRT
|
||||
IToastActivateableBuilder<ToastButton>,
|
||||
#endif
|
||||
IToastButton
|
||||
{
|
||||
private Dictionary<string, string> _arguments = new Dictionary<string, string>();
|
||||
|
||||
private bool _usingCustomArguments;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToastButton"/> class.
|
||||
/// </summary>
|
||||
|
@ -30,6 +39,17 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
|
||||
Content = content;
|
||||
Arguments = arguments;
|
||||
|
||||
_usingCustomArguments = arguments.Length > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToastButton"/> class.
|
||||
/// </summary>
|
||||
public ToastButton()
|
||||
{
|
||||
// Arguments are required (we'll initialize to empty string which is fine).
|
||||
Arguments = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -41,7 +61,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// Gets app-defined string of arguments that the app can later retrieve once it is
|
||||
/// activated when the user clicks the button. Required
|
||||
/// </summary>
|
||||
public string Arguments { get; private set; }
|
||||
public string Arguments { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets what type of activation this button will use when clicked. Defaults to Foreground.
|
||||
|
@ -71,6 +91,272 @@ namespace Microsoft.Toolkit.Uwp.Notifications
|
|||
/// </summary>
|
||||
public string HintActionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the text to display on the button.
|
||||
/// </summary>
|
||||
/// <param name="content">The text to display on the button.</param>
|
||||
/// <returns>The current instance of the <see cref="ToastButton"/>.</returns>
|
||||
public ToastButton SetContent(string content)
|
||||
{
|
||||
Content = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key (without value) to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
public ToastButton AddArgument(string key)
|
||||
{
|
||||
return AddArgumentHelper(key, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
#if WINRT
|
||||
[Windows.Foundation.Metadata.DefaultOverload]
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
|
||||
#endif
|
||||
public ToastButton AddArgument(string key, string value)
|
||||
{
|
||||
return AddArgumentHelper(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
|
||||
#endif
|
||||
public ToastButton AddArgument(string key, int value)
|
||||
{
|
||||
return AddArgumentHelper(key, value.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
|
||||
#endif
|
||||
public ToastButton AddArgument(string key, double value)
|
||||
{
|
||||
return AddArgumentHelper(key, value.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
|
||||
#endif
|
||||
public ToastButton AddArgument(string key, float value)
|
||||
{
|
||||
return AddArgumentHelper(key, value.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
#if WINRT
|
||||
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
|
||||
#endif
|
||||
public ToastButton AddArgument(string key, bool value)
|
||||
{
|
||||
return AddArgumentHelper(key, value ? "1" : "0"); // Encode as 1 or 0 to save string space
|
||||
}
|
||||
|
||||
#if !WINRT
|
||||
/// <summary>
|
||||
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for this value.</param>
|
||||
/// <param name="value">The value itself. Note that the enums are stored using their numeric value, so be aware that changing your enum number values might break existing activation of toasts currently in Action Center.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
public ToastButton AddArgument(string key, Enum value)
|
||||
{
|
||||
return AddArgumentHelper(key, ((int)(object)value).ToString());
|
||||
}
|
||||
#endif
|
||||
|
||||
private ToastButton AddArgumentHelper(string key, string value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (_usingCustomArguments)
|
||||
{
|
||||
throw new InvalidOperationException("You cannot use the AddArgument methods if you've set the Arguments property. Use the default ToastButton constructor instead.");
|
||||
}
|
||||
|
||||
if (ActivationType == ToastActivationType.Protocol)
|
||||
{
|
||||
throw new InvalidOperationException("You cannot use the AddArgument methods when using protocol activation.");
|
||||
}
|
||||
|
||||
bool alreadyExists = _arguments.ContainsKey(key);
|
||||
|
||||
_arguments[key] = value;
|
||||
|
||||
Arguments = alreadyExists ? SerializeArgumentsHelper(_arguments) : AddArgumentHelper(Arguments, key, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private string SerializeArgumentsHelper(IDictionary<string, string> arguments)
|
||||
{
|
||||
var args = new ToastArguments();
|
||||
|
||||
foreach (var a in arguments)
|
||||
{
|
||||
args.Add(a.Key, a.Value);
|
||||
}
|
||||
|
||||
return args.ToString();
|
||||
}
|
||||
|
||||
private string AddArgumentHelper(string existing, string key, string value)
|
||||
{
|
||||
string pair = ToastArguments.EncodePair(key, value);
|
||||
|
||||
if (string.IsNullOrEmpty(existing))
|
||||
{
|
||||
return pair;
|
||||
}
|
||||
else
|
||||
{
|
||||
return existing + ToastArguments.Separator + pair;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the button to launch the specified url when the button is clicked.
|
||||
/// </summary>
|
||||
/// <param name="protocol">The protocol to launch.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
public ToastButton SetProtocolActivation(Uri protocol)
|
||||
{
|
||||
return SetProtocolActivation(protocol, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the button to launch the specified url when the button is clicked.
|
||||
/// </summary>
|
||||
/// <param name="protocol">The protocol to launch.</param>
|
||||
/// <param name="targetApplicationPfn">New in Creators Update: The target PFN, so that regardless of whether multiple apps are registered to handle the same protocol uri, your desired app will always be launched.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
public ToastButton SetProtocolActivation(Uri protocol, string targetApplicationPfn)
|
||||
{
|
||||
if (_arguments.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException("SetProtocolActivation cannot be used in conjunction with AddArgument");
|
||||
}
|
||||
|
||||
Arguments = protocol.ToString();
|
||||
ActivationType = ToastActivationType.Protocol;
|
||||
|
||||
if (targetApplicationPfn != null)
|
||||
{
|
||||
if (ActivationOptions == null)
|
||||
{
|
||||
ActivationOptions = new ToastActivationOptions();
|
||||
}
|
||||
|
||||
ActivationOptions.ProtocolActivationTargetApplicationPfn = targetApplicationPfn;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the button to use background activation when the button is clicked.
|
||||
/// </summary>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
public ToastButton SetBackgroundActivation()
|
||||
{
|
||||
ActivationType = ToastActivationType.Background;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the behavior that the toast should use when the user invokes this button. Desktop-only, supported in builds 16251 or higher. New in Fall Creators Update.
|
||||
/// </summary>
|
||||
/// <param name="afterActivationBehavior">The behavior that the toast should use when the user invokes this button.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
public ToastButton SetAfterActivationBehavior(ToastAfterActivationBehavior afterActivationBehavior)
|
||||
{
|
||||
if (ActivationOptions == null)
|
||||
{
|
||||
ActivationOptions = new ToastActivationOptions();
|
||||
}
|
||||
|
||||
ActivationOptions.AfterActivationBehavior = afterActivationBehavior;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an identifier used in telemetry to identify your category of action. This should be something like "Delete", "Reply", or "Archive". In the upcoming toast telemetry dashboard in Dev Center, you will be able to view how frequently your actions are being clicked.
|
||||
/// </summary>
|
||||
/// <param name="actionId">An identifier used in telemetry to identify your category of action.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
public ToastButton SetHintActionId(string actionId)
|
||||
{
|
||||
HintActionId = actionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an optional image icon for the button to display (required for buttons adjacent to inputs like quick reply).
|
||||
/// </summary>
|
||||
/// <param name="imageUri">An optional image icon for the button to display.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
public ToastButton SetImageUri(Uri imageUri)
|
||||
{
|
||||
ImageUri = imageUri.ToString();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the ID of an existing <see cref="ToastTextBox"/> in order to have this button display to the right of the input, achieving a quick reply scenario.
|
||||
/// </summary>
|
||||
/// <param name="textBoxId">The ID of an existing <see cref="ToastTextBox"/>.</param>
|
||||
/// <returns>The current instance of <see cref="ToastButton"/></returns>
|
||||
public ToastButton SetTextBoxId(string textBoxId)
|
||||
{
|
||||
TextBoxId = textBoxId;
|
||||
return this;
|
||||
}
|
||||
|
||||
internal bool CanAddArguments()
|
||||
{
|
||||
return ActivationType != ToastActivationType.Protocol && !_usingCustomArguments;
|
||||
}
|
||||
|
||||
internal bool ContainsArgument(string key)
|
||||
{
|
||||
return _arguments.ContainsKey(key);
|
||||
}
|
||||
|
||||
internal Element_ToastAction ConvertToElement()
|
||||
{
|
||||
var el = new Element_ToastAction()
|
||||
|
|
|
@ -272,6 +272,7 @@
|
|||
<Content Include="Icons\More.png" />
|
||||
<Content Include="Icons\Notifications.png" />
|
||||
<Content Include="Icons\Services.png" />
|
||||
<Content Include="SamplePages\ColorPicker\ColorPicker.png" />
|
||||
<Content Include="SamplePages\TilesBrush\TilesBrush.png" />
|
||||
<Content Include="SamplePages\Eyedropper\Eyedropper.png" />
|
||||
<Content Include="SamplePages\OnDevice\OnDevice.png" />
|
||||
|
@ -509,6 +510,12 @@
|
|||
<Compile Include="SamplePages\AutoFocusBehavior\AutoFocusBehaviorPage.xaml.cs">
|
||||
<DependentUpon>AutoFocusBehaviorPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="SamplePages\ColorPicker\ColorPickerButtonPage.xaml.cs">
|
||||
<DependentUpon>ColorPickerButtonPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="SamplePages\ColorPicker\ColorPickerPage.xaml.cs">
|
||||
<DependentUpon>ColorPickerPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="SamplePages\EnumValuesExtension\EnumValuesExtensionPage.xaml.cs">
|
||||
<DependentUpon>EnumValuesExtensionPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -620,6 +627,8 @@
|
|||
<Content Include="SamplePages\EnumValuesExtension\EnumValuesExtensionCode.bind" />
|
||||
<Content Include="SamplePages\FocusBehavior\FocusBehaviorXaml.bind" />
|
||||
<Content Include="SamplePages\AutoFocusBehavior\AutoFocusBehaviorXaml.bind" />
|
||||
<Content Include="SamplePages\ColorPicker\ColorPickerXaml.bind" />
|
||||
<Content Include="SamplePages\ColorPicker\ColorPickerButtonXaml.bind" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="App.xaml.cs">
|
||||
|
@ -993,6 +1002,14 @@
|
|||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="SamplePages\ColorPicker\ColorPickerButtonPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="SamplePages\ColorPicker\ColorPickerPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="SamplePages\EnumValuesExtension\EnumValuesExtensionPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
|
@ -1471,12 +1488,10 @@
|
|||
<Page Include="Styles\Custom\PivotHeaderItemUnderlineStyle.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Page>
|
||||
<Page Include="Styles\Generic.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Page>
|
||||
<Page Include="Styles\GithubIcon.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
|
@ -1485,7 +1500,6 @@
|
|||
<Page Include="Styles\Themes.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using Windows.ApplicationModel.Background;
|
||||
using Windows.UI.Notifications;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
|
||||
{
|
||||
|
@ -12,28 +11,9 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
|
|||
{
|
||||
public void Run(IBackgroundTaskInstance taskInstance)
|
||||
{
|
||||
// Create content of the toast notification
|
||||
var toastContent = new ToastContent()
|
||||
{
|
||||
Scenario = ToastScenario.Default,
|
||||
Visual = new ToastVisual
|
||||
{
|
||||
BindingGeneric = new ToastBindingGeneric
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new AdaptiveText
|
||||
{
|
||||
Text = "New toast notification (BackgroundTaskHelper)."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create & show toast notification
|
||||
var toastNotification = new ToastNotification(toastContent.GetXml());
|
||||
ToastNotificationManager.CreateToastNotifier().Show(toastNotification);
|
||||
new ToastContentBuilder()
|
||||
.AddText("New toast notification (BackgroundTaskHelper).")
|
||||
.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
После Ширина: | Высота: | Размер: 7.3 KiB |
|
@ -0,0 +1,15 @@
|
|||
<Page x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.ColorPickerButtonPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
|
||||
xmlns:primitives="using:Microsoft.Toolkit.Uwp.UI.Controls.Primitives"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Visibility="Collapsed">
|
||||
<controls:ColorPickerButton />
|
||||
<primitives:ColorPickerSlider />
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,20 @@
|
|||
// 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 Microsoft.Toolkit.Uwp.UI.Controls;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
|
||||
{
|
||||
/// <summary>
|
||||
/// A page that shows how to use the <see cref="UI.Controls.ColorPicker"/> control.
|
||||
/// </summary>
|
||||
public sealed partial class ColorPickerButtonPage : Page
|
||||
{
|
||||
public ColorPickerButtonPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
<Page
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<SolidColorBrush Color="{ThemeResource SystemChromeLowColor}" x:Key="SystemControlForegroundChromeLowBrush"/>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<!-- Example 1 -->
|
||||
<StackPanel Grid.Row="0"
|
||||
Orientation="Vertical"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="20">
|
||||
<Border Background="{ThemeResource SystemChromeMediumColor}"
|
||||
CornerRadius="4"
|
||||
Height="100"
|
||||
Width="300"
|
||||
Padding="10">
|
||||
<TextBlock TextAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
Box-shaped spectrum <LineBreak />
|
||||
Alpha channel disabled
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<controls:ColorPickerButton x:Name="ColorPickerButton1"
|
||||
SelectedColor="Navy">
|
||||
<controls:ColorPickerButton.ColorPickerStyle>
|
||||
<Style TargetType="controls:ColorPicker">
|
||||
<Setter Property="ColorSpectrumShape" Value="Box"/>
|
||||
<Setter Property="IsAlphaEnabled" Value="False"/>
|
||||
<Setter Property="IsHexInputVisible" Value="True"/>
|
||||
</Style>
|
||||
</controls:ColorPickerButton.ColorPickerStyle>
|
||||
</controls:ColorPickerButton>
|
||||
</StackPanel>
|
||||
<!-- Example 2 -->
|
||||
<StackPanel Grid.Row="1"
|
||||
Orientation="Vertical"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="20">
|
||||
<Border Background="{ThemeResource SystemChromeMediumColor}"
|
||||
CornerRadius="4"
|
||||
Height="100"
|
||||
Width="300"
|
||||
Padding="10">
|
||||
<TextBlock TextAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
Box-shaped spectrum <LineBreak />
|
||||
Alpha channel enabled
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<controls:ColorPickerButton x:Name="ColorPickerButton2"
|
||||
SelectedColor="Green">
|
||||
<controls:ColorPickerButton.ColorPickerStyle>
|
||||
<Style TargetType="controls:ColorPicker">
|
||||
<Setter Property="ColorSpectrumShape" Value="Box"/>
|
||||
<Setter Property="IsAlphaEnabled" Value="True"/>
|
||||
<Setter Property="IsHexInputVisible" Value="False"/>
|
||||
</Style>
|
||||
</controls:ColorPickerButton.ColorPickerStyle>
|
||||
</controls:ColorPickerButton>
|
||||
</StackPanel>
|
||||
<!-- Example 3 -->
|
||||
<StackPanel Grid.Row="2"
|
||||
Orientation="Vertical"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="20">
|
||||
<Border Background="{ThemeResource SystemChromeMediumColor}"
|
||||
CornerRadius="4"
|
||||
Height="100"
|
||||
Width="300"
|
||||
Padding="10">
|
||||
<TextBlock TextAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
Ring-shaped spectrum <LineBreak />
|
||||
Alpha channel enabled
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<controls:ColorPickerButton x:Name="ColorPickerButton3"
|
||||
SelectedColor="Transparent">
|
||||
<controls:ColorPickerButton.ColorPickerStyle>
|
||||
<Style TargetType="controls:ColorPicker">
|
||||
<Setter Property="ColorSpectrumShape" Value="Ring"/>
|
||||
<Setter Property="IsAlphaEnabled" Value="True"/>
|
||||
<Setter Property="IsHexInputVisible" Value="True"/>
|
||||
</Style>
|
||||
</controls:ColorPickerButton.ColorPickerStyle>
|
||||
</controls:ColorPickerButton>
|
||||
</StackPanel>
|
||||
<!-- Example 4 -->
|
||||
<StackPanel Grid.Row="3"
|
||||
Orientation="Vertical"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="20">
|
||||
<Border Background="{ThemeResource SystemChromeMediumColor}"
|
||||
CornerRadius="4"
|
||||
Height="100"
|
||||
Width="300"
|
||||
Padding="10">
|
||||
<TextBlock TextAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
Ring-shaped spectrum <LineBreak />
|
||||
Alpha channel enabled <LineBreak />
|
||||
Saturation+Value spectrum channels
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<controls:ColorPickerButton x:Name="ColorPickerButton4"
|
||||
SelectedColor="Maroon">
|
||||
<controls:ColorPickerButton.ColorPickerStyle>
|
||||
<Style TargetType="controls:ColorPicker">
|
||||
<Setter Property="ColorSpectrumShape" Value="Ring"/>
|
||||
<Setter Property="ColorSpectrumComponents" Value="SaturationValue"/>
|
||||
<Setter Property="IsAlphaEnabled" Value="True"/>
|
||||
<Setter Property="IsHexInputVisible" Value="True"/>
|
||||
</Style>
|
||||
</controls:ColorPickerButton.ColorPickerStyle>
|
||||
</controls:ColorPickerButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,17 @@
|
|||
<Page x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.ColorPickerPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
|
||||
xmlns:primitives="using:Microsoft.Toolkit.Uwp.UI.Controls.Primitives"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid
|
||||
Visibility="Collapsed">
|
||||
<controls:ColorPicker />
|
||||
<primitives:ColorPickerSlider />
|
||||
</Grid>
|
||||
|
||||
</Page>
|
|
@ -0,0 +1,20 @@
|
|||
// 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 Microsoft.Toolkit.Uwp.UI.Controls;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
|
||||
{
|
||||
/// <summary>
|
||||
/// A page that shows how to use the <see cref="UI.Controls.ColorPicker"/> control.
|
||||
/// </summary>
|
||||
public sealed partial class ColorPickerPage : Page
|
||||
{
|
||||
public ColorPickerPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<Page
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<SolidColorBrush Color="{ThemeResource SystemChromeLowColor}" x:Key="SystemControlForegroundChromeLowBrush"/>
|
||||
</Page.Resources>
|
||||
|
||||
<ScrollViewer>
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center"
|
||||
Spacing="20">
|
||||
<!-- Example 1 -->
|
||||
<Border Background="{ThemeResource SystemChromeMediumColor}"
|
||||
CornerRadius="4"
|
||||
Height="100"
|
||||
Width="300"
|
||||
Padding="10">
|
||||
<TextBlock TextAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
Box-shaped spectrum <LineBreak />
|
||||
Alpha channel disabled
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<controls:ColorPicker x:Name="ColorPicker1"
|
||||
Color="Navy"
|
||||
ColorSpectrumShape="Box"
|
||||
IsAlphaEnabled="False"
|
||||
IsHexInputVisible="True" />
|
||||
<!-- Example 2 -->
|
||||
<Border Background="{ThemeResource SystemChromeMediumColor}"
|
||||
CornerRadius="4"
|
||||
Height="100"
|
||||
Width="300"
|
||||
Padding="10">
|
||||
<TextBlock TextAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
Box-shaped spectrum <LineBreak />
|
||||
Alpha channel enabled
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<controls:ColorPicker x:Name="ColorPicker2"
|
||||
Color="Green"
|
||||
ColorSpectrumShape="Box"
|
||||
IsAlphaEnabled="True"
|
||||
IsHexInputVisible="False" />
|
||||
<!-- Example 3 -->
|
||||
<Border Background="{ThemeResource SystemChromeMediumColor}"
|
||||
CornerRadius="4"
|
||||
Height="100"
|
||||
Width="300"
|
||||
Padding="10">
|
||||
<TextBlock TextAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
Ring-shaped spectrum <LineBreak />
|
||||
Alpha channel enabled
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<controls:ColorPicker x:Name="ColorPickerButton3"
|
||||
Color="Transparent"
|
||||
ColorSpectrumShape="Ring"
|
||||
IsAlphaEnabled="True"
|
||||
IsHexInputVisible="True" />
|
||||
<!-- Example 4 -->
|
||||
<Border Background="{ThemeResource SystemChromeMediumColor}"
|
||||
CornerRadius="4"
|
||||
Height="100"
|
||||
Width="300"
|
||||
Padding="10">
|
||||
<TextBlock TextAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
Ring-shaped spectrum <LineBreak />
|
||||
Alpha channel enabled <LineBreak />
|
||||
Saturation+Value spectrum channels
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<controls:ColorPicker x:Name="ColorPickerButton4"
|
||||
Color="Maroon"
|
||||
ColorSpectrumShape="Ring"
|
||||
ColorSpectrumComponents="SaturationValue"
|
||||
IsAlphaEnabled="True"
|
||||
IsHexInputVisible="True"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Page>
|
|
@ -1,14 +1,10 @@
|
|||
private void PopToast()
|
||||
{
|
||||
// Generate the toast notification content and pop the toast
|
||||
ToastContent content = GenerateToastContent();
|
||||
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(content.GetXml()));
|
||||
}
|
||||
|
||||
public static ToastContent GenerateToastContent()
|
||||
{
|
||||
var builder = new ToastContentBuilder().SetToastScenario(ToastScenario.Reminder)
|
||||
.AddToastActivationInfo("action=viewEvent&eventId=1983", ToastActivationType.Foreground)
|
||||
new ToastContentBuilder()
|
||||
.SetToastScenario(ToastScenario.Reminder)
|
||||
.AddArgument("action", "viewEvent")
|
||||
.AddArgument("eventId", 1983)
|
||||
.AddText("Adaptive Tiles Meeting")
|
||||
.AddText("Conf Room 2001 / Building 135")
|
||||
.AddText("10:00 AM - 10:30 AM")
|
||||
|
@ -18,7 +14,6 @@ public static ToastContent GenerateToastContent()
|
|||
("240", "4 hours"),
|
||||
("1440", "1 day"))
|
||||
.AddButton(new ToastButtonSnooze() { SelectionBoxId = "snoozeTime" })
|
||||
.AddButton(new ToastButtonDismiss());
|
||||
|
||||
return builder.Content;
|
||||
}
|
||||
.AddButton(new ToastButtonDismiss())
|
||||
.Show();
|
||||
}
|
|
@ -28,8 +28,10 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
|
|||
|
||||
public static ToastContent GenerateToastContent()
|
||||
{
|
||||
var builder = new ToastContentBuilder().SetToastScenario(ToastScenario.Reminder)
|
||||
.AddToastActivationInfo("action=viewEvent&eventId=1983", ToastActivationType.Foreground)
|
||||
var builder = new ToastContentBuilder()
|
||||
.SetToastScenario(ToastScenario.Reminder)
|
||||
.AddArgument("action", "viewEvent")
|
||||
.AddArgument("eventId", 1983)
|
||||
.AddText("Adaptive Tiles Meeting")
|
||||
.AddText("Conf Room 2001 / Building 135")
|
||||
.AddText("10:00 AM - 10:30 AM")
|
||||
|
@ -54,7 +56,7 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
|
|||
|
||||
private void PopToast()
|
||||
{
|
||||
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(_toastContent.GetXml()));
|
||||
ToastNotificationManagerCompat.CreateToastNotifier().Show(new ToastNotification(_toastContent.GetXml()));
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
|
|
|
@ -1,40 +1,11 @@
|
|||
private void PopToast()
|
||||
{
|
||||
// Generate the toast notification content and pop the toast
|
||||
ToastContent content = GenerateToastContent();
|
||||
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(content.GetXml()));
|
||||
}
|
||||
|
||||
private async void PinTile()
|
||||
{
|
||||
SecondaryTile tile = new SecondaryTile(DateTime.Now.Ticks.ToString())
|
||||
{
|
||||
DisplayName = "WeatherSample",
|
||||
Arguments = "args"
|
||||
};
|
||||
tile.VisualElements.ShowNameOnSquare150x150Logo = true;
|
||||
tile.VisualElements.ShowNameOnSquare310x310Logo = true;
|
||||
tile.VisualElements.ShowNameOnWide310x150Logo = true;
|
||||
tile.VisualElements.Square150x150Logo = Constants.Square150x150Logo;
|
||||
tile.VisualElements.Wide310x150Logo = Constants.Wide310x150Logo;
|
||||
tile.VisualElements.Square310x310Logo = Constants.Square310x310Logo;
|
||||
|
||||
if (!await tile.RequestCreateAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate the tile notification content and update the tile
|
||||
TileContent content = GenerateTileContent();
|
||||
TileUpdateManager.CreateTileUpdaterForSecondaryTile(tile.TileId).Update(new TileNotification(content.GetXml()));
|
||||
}
|
||||
|
||||
public static ToastContent GenerateToastContent()
|
||||
{
|
||||
// Generate the toast notification content
|
||||
ToastContentBuilder builder = new ToastContentBuilder();
|
||||
|
||||
// Include launch string so we know what to open when user clicks toast
|
||||
builder.AddToastActivationInfo("action=viewForecast&zip=98008", ToastActivationType.Foreground);
|
||||
builder.AddArgument("action", "viewForecast");
|
||||
builder.AddArgument("zip", 98008);
|
||||
|
||||
// We'll always have this summary text on our toast notification
|
||||
// (it is required that your toast starts with a text element)
|
||||
|
@ -68,8 +39,33 @@ public static ToastContent GenerateToastContent()
|
|||
// Set the base URI for the images, so we don't redundantly specify the entire path
|
||||
builder.Content.Visual.BaseUri = new Uri("Assets/NotificationAssets/", UriKind.Relative);
|
||||
|
||||
return builder.Content;
|
||||
}
|
||||
// Show the toast
|
||||
builder.Show();
|
||||
}
|
||||
|
||||
private async void PinTile()
|
||||
{
|
||||
SecondaryTile tile = new SecondaryTile(DateTime.Now.Ticks.ToString())
|
||||
{
|
||||
DisplayName = "WeatherSample",
|
||||
Arguments = "args"
|
||||
};
|
||||
tile.VisualElements.ShowNameOnSquare150x150Logo = true;
|
||||
tile.VisualElements.ShowNameOnSquare310x310Logo = true;
|
||||
tile.VisualElements.ShowNameOnWide310x150Logo = true;
|
||||
tile.VisualElements.Square150x150Logo = Constants.Square150x150Logo;
|
||||
tile.VisualElements.Wide310x150Logo = Constants.Wide310x150Logo;
|
||||
tile.VisualElements.Square310x310Logo = Constants.Square310x310Logo;
|
||||
|
||||
if (!await tile.RequestCreateAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate the tile notification content and update the tile
|
||||
TileContent content = GenerateTileContent();
|
||||
TileUpdateManager.CreateTileUpdaterForSecondaryTile(tile.TileId).Update(new TileNotification(content.GetXml()));
|
||||
}
|
||||
|
||||
public static TileContent GenerateTileContent()
|
||||
{
|
||||
|
@ -78,8 +74,8 @@ public static TileContent GenerateTileContent()
|
|||
// Small Tile
|
||||
builder.AddTile(Notifications.TileSize.Small)
|
||||
.SetTextStacking(TileTextStacking.Center, Notifications.TileSize.Small)
|
||||
.AddText("Mon", hintStyle: AdaptiveTextStyle.Body, hintAlign: AdaptiveTextAlign.Center)
|
||||
.AddText("63°", hintStyle: AdaptiveTextStyle.Base, hintAlign: AdaptiveTextAlign.Center);
|
||||
.AddText("Mon", Notifications.TileSize.Small, hintStyle: AdaptiveTextStyle.Body, hintAlign: AdaptiveTextAlign.Center)
|
||||
.AddText("63°", Notifications.TileSize.Small, hintStyle: AdaptiveTextStyle.Base, hintAlign: AdaptiveTextAlign.Center);
|
||||
|
||||
// Medium Tile
|
||||
builder.AddTile(Notifications.TileSize.Medium)
|
||||
|
|
|
@ -29,7 +29,8 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
|
|||
ToastContentBuilder builder = new ToastContentBuilder();
|
||||
|
||||
// Include launch string so we know what to open when user clicks toast
|
||||
builder.AddToastActivationInfo("action=viewForecast&zip=98008", ToastActivationType.Foreground);
|
||||
builder.AddArgument("action", "viewForecast");
|
||||
builder.AddArgument("zip", 98008);
|
||||
|
||||
// We'll always have this summary text on our toast notification
|
||||
// (it is required that your toast starts with a text element)
|
||||
|
@ -73,8 +74,8 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
|
|||
// Small Tile
|
||||
builder.AddTile(Notifications.TileSize.Small)
|
||||
.SetTextStacking(TileTextStacking.Center, Notifications.TileSize.Small)
|
||||
.AddText("Mon", hintStyle: AdaptiveTextStyle.Body, hintAlign: AdaptiveTextAlign.Center)
|
||||
.AddText("63°", hintStyle: AdaptiveTextStyle.Base, hintAlign: AdaptiveTextAlign.Center);
|
||||
.AddText("Mon", Notifications.TileSize.Small, hintStyle: AdaptiveTextStyle.Body, hintAlign: AdaptiveTextAlign.Center)
|
||||
.AddText("63°", Notifications.TileSize.Small, hintStyle: AdaptiveTextStyle.Base, hintAlign: AdaptiveTextAlign.Center);
|
||||
|
||||
// Medium Tile
|
||||
builder.AddTile(Notifications.TileSize.Medium)
|
||||
|
@ -285,7 +286,7 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
|
|||
|
||||
private void PopToast()
|
||||
{
|
||||
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(_toastContent.GetXml()));
|
||||
ToastNotificationManagerCompat.CreateToastNotifier().Show(new ToastNotification(_toastContent.GetXml()));
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
|
|
|
@ -34,6 +34,26 @@
|
|||
"Icon": "/SamplePages/Carousel/Carousel.png",
|
||||
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/Carousel.md"
|
||||
},
|
||||
{
|
||||
"Name": "ColorPicker",
|
||||
"Type": "ColorPickerPage",
|
||||
"Subcategory": "Input",
|
||||
"About": "An improved color picker control providing more options to select colors.",
|
||||
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker",
|
||||
"XamlCodeFile": "ColorPickerXaml.bind",
|
||||
"Icon": "/SamplePages/ColorPicker/ColorPicker.png",
|
||||
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/ColorPicker.md"
|
||||
},
|
||||
{
|
||||
"Name": "ColorPickerButton",
|
||||
"Type": "ColorPickerButtonPage",
|
||||
"Subcategory": "Input",
|
||||
"About": "A color picker within a flyout opened by pressing a dropdown button containing the selected color.",
|
||||
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker",
|
||||
"XamlCodeFile": "/SamplePages/ColorPicker/ColorPickerButtonXaml.bind",
|
||||
"Icon": "/SamplePages/ColorPicker/ColorPicker.png",
|
||||
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/ColorPickerButton.md"
|
||||
},
|
||||
{
|
||||
"Name": "AdaptiveGridView",
|
||||
"Type": "AdaptiveGridViewPage",
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace Microsoft.Toolkit.Uwp.Samples.BackgroundTasks
|
|||
|
||||
// Create & show toast notification
|
||||
var toastNotification = new ToastNotification(toastContent.GetXml());
|
||||
ToastNotificationManager.CreateToastNotifier().Show(toastNotification);
|
||||
ToastNotificationManagerCompat.CreateToastNotifier().Show(toastNotification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,4 +46,20 @@
|
|||
<Message Text="CSFiles: @(GeneratedCSFiles->'"%(Identity)"')" />
|
||||
<Exec Command="for %%f in (@(GeneratedCSFiles->'"%(Identity)"')) do echo #pragma warning disable > %%f.temp && type %%f >> %%f.temp && move /y %%f.temp %%f > NUL" />
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Required workaround for ProjectReference inclusion of the Controls package
|
||||
The UWP project system is including the Controls resources in the pri file because
|
||||
it doesn't know it'll be an independent package later during packing.
|
||||
Therefore, we need to remove these extra resources in the PRI pipeline so the
|
||||
Markdown pri file is properly generated and doesn't include duplicate references to Control resources.
|
||||
-->
|
||||
<Target Name="RemoveUnwantedPri" AfterTargets="GetPackagingOutputs">
|
||||
<!--<Message Text="Files Before: @(PackagingOutputs)" Importance="high" />-->
|
||||
<ItemGroup>
|
||||
<PackagingOutputs Remove="@(PackagingOutputs)" Condition="'%(PackagingOutputs.Filename)%(PackagingOutputs.Extension)' == 'Microsoft.Toolkit.Uwp.UI.Controls.pri'" />
|
||||
<PackagingOutputs Remove="@(PackagingOutputs)" Condition="$([System.String]::new('%(PackagingOutputs.TargetPath)').StartsWith('Microsoft.Toolkit.Uwp.UI.Controls\'))" />
|
||||
</ItemGroup>
|
||||
<!--<Message Text="Files After: @(PackagingOutputs)" Importance="high" />-->
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
@ -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;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a specific channel within a color representation.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||
public enum ColorChannel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the alpha channel.
|
||||
/// </summary>
|
||||
Alpha,
|
||||
|
||||
/// <summary>
|
||||
/// Represents the first color channel which is Red when RGB or Hue when HSV.
|
||||
/// </summary>
|
||||
Channel1,
|
||||
|
||||
/// <summary>
|
||||
/// Represents the second color channel which is Green when RGB or Saturation when HSV.
|
||||
/// </summary>
|
||||
Channel2,
|
||||
|
||||
/// <summary>
|
||||
/// Represents the third color channel which is Blue when RGB or Value when HSV.
|
||||
/// </summary>
|
||||
Channel3
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
// 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.Collections.ObjectModel;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains all properties for the <see cref="ColorPicker"/>.
|
||||
/// </summary>
|
||||
public partial class ColorPicker
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="CustomPaletteColors"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty CustomPaletteColorsProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(CustomPaletteColors),
|
||||
typeof(ObservableCollection<Windows.UI.Color>),
|
||||
typeof(ColorPicker),
|
||||
new PropertyMetadata(Windows.UI.Color.FromArgb(0x00, 0x00, 0x00, 0x00)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of custom palette colors.
|
||||
/// </summary>
|
||||
public ObservableCollection<Windows.UI.Color> CustomPaletteColors
|
||||
{
|
||||
get => (ObservableCollection<Windows.UI.Color>)this.GetValue(CustomPaletteColorsProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="CustomPaletteColumnCount"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty CustomPaletteColumnCountProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(CustomPaletteColumnCount),
|
||||
typeof(int),
|
||||
typeof(ColorPicker),
|
||||
new PropertyMetadata(4));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of colors in each row (section) of the custom color palette.
|
||||
/// Within a standard palette, rows are shades and columns are unique colors.
|
||||
/// </summary>
|
||||
public int CustomPaletteColumnCount
|
||||
{
|
||||
get => (int)this.GetValue(CustomPaletteColumnCountProperty);
|
||||
set
|
||||
{
|
||||
if (object.Equals(value, this.GetValue(CustomPaletteColumnCountProperty)) == false)
|
||||
{
|
||||
this.SetValue(CustomPaletteColumnCountProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="CustomPalette"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty CustomPaletteProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(CustomPalette),
|
||||
typeof(IColorPalette),
|
||||
typeof(ColorPicker),
|
||||
new PropertyMetadata(DependencyProperty.UnsetValue));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom color palette.
|
||||
/// This will automatically set <see cref="CustomPaletteColors"/> and <see cref="CustomPaletteColumnCount"/>
|
||||
/// overwriting any existing values.
|
||||
/// </summary>
|
||||
public IColorPalette CustomPalette
|
||||
{
|
||||
get => (IColorPalette)this.GetValue(CustomPaletteProperty);
|
||||
set
|
||||
{
|
||||
if (object.Equals(value, this.GetValue(CustomPaletteProperty)) == false)
|
||||
{
|
||||
this.SetValue(CustomPaletteProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="IsColorPaletteVisible"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsColorPaletteVisibleProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(IsColorPaletteVisible),
|
||||
typeof(bool),
|
||||
typeof(ColorPicker),
|
||||
new PropertyMetadata(true));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the color palette is visible.
|
||||
/// </summary>
|
||||
public bool IsColorPaletteVisible
|
||||
{
|
||||
get => (bool)this.GetValue(IsColorPaletteVisibleProperty);
|
||||
set
|
||||
{
|
||||
if (object.Equals(value, this.GetValue(IsColorPaletteVisibleProperty)) == false)
|
||||
{
|
||||
this.SetValue(IsColorPaletteVisibleProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="DropDownButton"/> which displays a color as its <c>Content</c> and it's <c>Flyout</c> is a <see cref="ColorPicker"/>.
|
||||
/// </summary>
|
||||
[TemplatePart(Name = nameof(CheckeredBackgroundBorder), Type = typeof(Border))]
|
||||
public class ColorPickerButton : DropDownButton
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Controls.ColorPicker"/> instances contained by the <see cref="DropDownButton"/>.
|
||||
/// </summary>
|
||||
public ColorPicker ColorPicker { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Style"/> for the <see cref="Controls.ColorPicker"/> control used in the button.
|
||||
/// </summary>
|
||||
public Style ColorPickerStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Style)GetValue(ColorPickerStyleProperty);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetValue(ColorPickerStyleProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ColorPickerStyle"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ColorPickerStyleProperty = DependencyProperty.Register("ColorPickerStyle", typeof(Style), typeof(ColorPickerButton), new PropertyMetadata(default(Style)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Style"/> for the <see cref="FlyoutPresenter"/> used within the <see cref="Flyout"/> of the <see cref="DropDownButton"/>.
|
||||
/// </summary>
|
||||
public Style FlyoutPresenterStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Style)GetValue(FlyoutPresenterStyleProperty);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetValue(FlyoutPresenterStyleProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="FlyoutPresenterStyle"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty FlyoutPresenterStyleProperty = DependencyProperty.Register("FlyoutPresenterStyle", typeof(Style), typeof(ColorPickerButton), new PropertyMetadata(default(Style)));
|
||||
|
||||
#pragma warning disable CS0419 // Ambiguous reference in cref attribute
|
||||
/// <summary>
|
||||
/// Gets or sets the selected <see cref="Windows.UI.Color"/> the user has picked from the <see cref="ColorPicker"/>.
|
||||
/// </summary>
|
||||
#pragma warning restore CS0419 // Ambiguous reference in cref attribute
|
||||
public Color SelectedColor
|
||||
{
|
||||
get { return (Color)GetValue(SelectedColorProperty); }
|
||||
set { SetValue(SelectedColorProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="SelectedColor"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty SelectedColorProperty =
|
||||
DependencyProperty.Register(nameof(SelectedColor), typeof(Color), typeof(ColorPickerButton), new PropertyMetadata(null));
|
||||
|
||||
#pragma warning disable SA1306 // Field names should begin with lower-case letter
|
||||
//// Template Parts
|
||||
private Border CheckeredBackgroundBorder;
|
||||
#pragma warning restore SA1306 // Field names should begin with lower-case letter
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ColorPickerButton"/> class.
|
||||
/// </summary>
|
||||
public ColorPickerButton()
|
||||
{
|
||||
this.DefaultStyleKey = typeof(ColorPickerButton);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
if (ColorPicker != null)
|
||||
{
|
||||
ColorPicker.ColorChanged -= ColorPicker_ColorChanged;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
if (ColorPickerStyle != null)
|
||||
{
|
||||
ColorPicker = new ColorPicker() { Style = ColorPickerStyle };
|
||||
}
|
||||
else
|
||||
{
|
||||
ColorPicker = new ColorPicker();
|
||||
}
|
||||
|
||||
ColorPicker.Color = SelectedColor;
|
||||
ColorPicker.ColorChanged += ColorPicker_ColorChanged;
|
||||
|
||||
if (Flyout == null)
|
||||
{
|
||||
Flyout = new Flyout()
|
||||
{
|
||||
// TODO: Expose Placement
|
||||
Placement = Windows.UI.Xaml.Controls.Primitives.FlyoutPlacementMode.BottomEdgeAlignedLeft,
|
||||
FlyoutPresenterStyle = FlyoutPresenterStyle,
|
||||
Content = ColorPicker
|
||||
};
|
||||
}
|
||||
|
||||
if (CheckeredBackgroundBorder != null)
|
||||
{
|
||||
CheckeredBackgroundBorder.Loaded -= this.CheckeredBackgroundBorder_Loaded;
|
||||
}
|
||||
|
||||
CheckeredBackgroundBorder = GetTemplateChild(nameof(CheckeredBackgroundBorder)) as Border;
|
||||
|
||||
if (CheckeredBackgroundBorder != null)
|
||||
{
|
||||
CheckeredBackgroundBorder.Loaded += this.CheckeredBackgroundBorder_Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
private void ColorPicker_ColorChanged(Windows.UI.Xaml.Controls.ColorPicker sender, ColorChangedEventArgs args)
|
||||
{
|
||||
SelectedColor = args.NewColor;
|
||||
}
|
||||
|
||||
private async void CheckeredBackgroundBorder_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await ColorPickerRenderingHelpers.UpdateBorderBackgroundWithCheckerAsync(
|
||||
sender as Border,
|
||||
ColorPicker.CheckerBackgroundColor); // TODO: Check initialization
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls">
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultColorPickerButtonStyle}"
|
||||
TargetType="controls:ColorPickerButton" />
|
||||
|
||||
<!-- Copy of WinUI 2 style -->
|
||||
<Style x:Key="DefaultColorPickerButtonStyle"
|
||||
TargetType="controls:ColorPickerButton">
|
||||
<Setter Property="Background" Value="{ThemeResource ButtonBackground}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
|
||||
<Setter Property="Padding" Value="0,0,8,0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-3" />
|
||||
<Setter Property="CornerRadius" Value="2" />
|
||||
<!-- {ThemeResource ControlCornerRadius} -->
|
||||
<Setter Property="SelectedColor" Value="White" />
|
||||
<Setter Property="FlyoutPresenterStyle" Value="{StaticResource ColorPickerButtonFlyoutPresenterStyle}" />
|
||||
<Setter Property="Template" Value="{StaticResource ColorPickerButtonTemplate}" />
|
||||
</Style>
|
||||
|
||||
<ControlTemplate x:Key="ColorPickerButtonTemplate"
|
||||
TargetType="controls:ColorPickerButton">
|
||||
<!-- Default Template Mostly Unchanged -->
|
||||
<Grid x:Name="RootGrid"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Grid x:Name="InnerGrid"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Modification to Template here to provide Border within the DropDown, Checker/Color set in Codebehind as generated image -->
|
||||
<Border x:Name="CheckeredBackgroundBorder"
|
||||
MinWidth="24"
|
||||
MinHeight="24"
|
||||
Margin="0,0,2,0"
|
||||
CornerRadius="2,0,0,2" />
|
||||
<Border x:Name="PreviewBorder"
|
||||
CornerRadius="2,0,0,2">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{Binding SelectedColor, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
|
||||
</Border.Background>
|
||||
<ContentPresenter x:Name="ContentPresenter"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}" />
|
||||
</Border>
|
||||
|
||||
<TextBlock x:Name="ChevronTextBlock"
|
||||
Grid.Column="1"
|
||||
Margin="6,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="12"
|
||||
IsTextScaleFactorEnabled="False"
|
||||
Text="" />
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal">
|
||||
<Storyboard>
|
||||
<PointerUpThemeAnimation Storyboard.TargetName="RootGrid" />
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid"
|
||||
Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ButtonBackgroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InnerGrid"
|
||||
Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ButtonBorderBrushPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ButtonForegroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<PointerUpThemeAnimation Storyboard.TargetName="RootGrid" />
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid"
|
||||
Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ButtonBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InnerGrid"
|
||||
Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ButtonBorderBrushPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ButtonForegroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<PointerDownThemeAnimation Storyboard.TargetName="RootGrid" />
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid"
|
||||
Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ButtonBackgroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InnerGrid"
|
||||
Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ButtonBorderBrushDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ButtonForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ChevronTextBlock"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ButtonForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="ColorPickerButtonFlyoutPresenterStyle"
|
||||
TargetType="FlyoutPresenter">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource OverlayCornerRadius}" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
|
@ -0,0 +1,531 @@
|
|||
// 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;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Uwp.Helpers;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Media.Imaging;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the rendering methods used within <see cref="ColorPicker"/>.
|
||||
/// </summary>
|
||||
internal class ColorPickerRenderingHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a new bitmap of the specified size by changing a specific color channel.
|
||||
/// This will produce a gradient representing all possible differences of that color channel.
|
||||
/// </summary>
|
||||
/// <param name="width">The pixel width (X, horizontal) of the resulting bitmap.</param>
|
||||
/// <param name="height">The pixel height (Y, vertical) of the resulting bitmap.</param>
|
||||
/// <param name="orientation">The orientation of the resulting bitmap (gradient direction).</param>
|
||||
/// <param name="colorRepresentation">The color representation being used: RGBA or HSVA.</param>
|
||||
/// <param name="channel">The specific color channel to vary.</param>
|
||||
/// <param name="baseHsvColor">The base HSV color used for channels not being changed.</param>
|
||||
/// <param name="checkerColor">The color of the checker background square.</param>
|
||||
/// <param name="isAlphaMaxForced">Fix the alpha channel value to maximum during calculation.
|
||||
/// This will remove any alpha/transparency from the other channel backgrounds.</param>
|
||||
/// <param name="isSaturationValueMaxForced">Fix the saturation and value channels to maximum
|
||||
/// during calculation in HSVA color representation.
|
||||
/// This will ensure colors are always discernible regardless of saturation/value.</param>
|
||||
/// <returns>A new bitmap representing a gradient of color channel values.</returns>
|
||||
public static async Task<byte[]> CreateChannelBitmapAsync(
|
||||
int width,
|
||||
int height,
|
||||
Orientation orientation,
|
||||
ColorRepresentation colorRepresentation,
|
||||
ColorChannel channel,
|
||||
HsvColor baseHsvColor,
|
||||
Color? checkerColor,
|
||||
bool isAlphaMaxForced,
|
||||
bool isSaturationValueMaxForced)
|
||||
{
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bitmap = await Task.Run<byte[]>(async () =>
|
||||
{
|
||||
int pixelDataIndex = 0;
|
||||
double channelStep;
|
||||
byte[] bgraPixelData;
|
||||
byte[] bgraCheckeredPixelData = null;
|
||||
Color baseRgbColor = Colors.White;
|
||||
Color rgbColor;
|
||||
int bgraPixelDataHeight;
|
||||
int bgraPixelDataWidth;
|
||||
|
||||
// Allocate the buffer
|
||||
// BGRA formatted color channels 1 byte each (4 bytes in a pixel)
|
||||
bgraPixelData = new byte[width * height * 4];
|
||||
bgraPixelDataHeight = height * 4;
|
||||
bgraPixelDataWidth = width * 4;
|
||||
|
||||
// Maximize alpha channel value
|
||||
if (isAlphaMaxForced &&
|
||||
channel != ColorChannel.Alpha)
|
||||
{
|
||||
baseHsvColor = new HsvColor()
|
||||
{
|
||||
H = baseHsvColor.H,
|
||||
S = baseHsvColor.S,
|
||||
V = baseHsvColor.V,
|
||||
A = 1.0
|
||||
};
|
||||
}
|
||||
|
||||
// Convert HSV to RGB once
|
||||
if (colorRepresentation == ColorRepresentation.Rgba)
|
||||
{
|
||||
baseRgbColor = Uwp.Helpers.ColorHelper.FromHsv(
|
||||
baseHsvColor.H,
|
||||
baseHsvColor.S,
|
||||
baseHsvColor.V,
|
||||
baseHsvColor.A);
|
||||
}
|
||||
|
||||
// Maximize Saturation and Value channels when in HSVA mode
|
||||
if (isSaturationValueMaxForced &&
|
||||
colorRepresentation == ColorRepresentation.Hsva &&
|
||||
channel != ColorChannel.Alpha)
|
||||
{
|
||||
switch (channel)
|
||||
{
|
||||
case ColorChannel.Channel1:
|
||||
baseHsvColor = new HsvColor()
|
||||
{
|
||||
H = baseHsvColor.H,
|
||||
S = 1.0,
|
||||
V = 1.0,
|
||||
A = baseHsvColor.A
|
||||
};
|
||||
break;
|
||||
case ColorChannel.Channel2:
|
||||
baseHsvColor = new HsvColor()
|
||||
{
|
||||
H = baseHsvColor.H,
|
||||
S = baseHsvColor.S,
|
||||
V = 1.0,
|
||||
A = baseHsvColor.A
|
||||
};
|
||||
break;
|
||||
case ColorChannel.Channel3:
|
||||
baseHsvColor = new HsvColor()
|
||||
{
|
||||
H = baseHsvColor.H,
|
||||
S = 1.0,
|
||||
V = baseHsvColor.V,
|
||||
A = baseHsvColor.A
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a checkered background
|
||||
if (checkerColor != null)
|
||||
{
|
||||
bgraCheckeredPixelData = await CreateCheckeredBitmapAsync(
|
||||
width,
|
||||
height,
|
||||
checkerColor.Value);
|
||||
}
|
||||
|
||||
// Create the color channel gradient
|
||||
if (orientation == Orientation.Horizontal)
|
||||
{
|
||||
// Determine the numerical increment of the color steps within the channel
|
||||
if (colorRepresentation == ColorRepresentation.Hsva)
|
||||
{
|
||||
if (channel == ColorChannel.Channel1)
|
||||
{
|
||||
channelStep = 360.0 / width;
|
||||
}
|
||||
else
|
||||
{
|
||||
channelStep = 1.0 / width;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
channelStep = 255.0 / width;
|
||||
}
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
if (y == 0)
|
||||
{
|
||||
rgbColor = GetColor(x * channelStep);
|
||||
|
||||
// Get a new color
|
||||
bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(rgbColor.B * rgbColor.A / 255);
|
||||
bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(rgbColor.G * rgbColor.A / 255);
|
||||
bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(rgbColor.R * rgbColor.A / 255);
|
||||
bgraPixelData[pixelDataIndex + 3] = rgbColor.A;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the color in the row above
|
||||
// Remember the pixel data is 1 dimensional instead of 2
|
||||
bgraPixelData[pixelDataIndex + 0] = bgraPixelData[pixelDataIndex + 0 - bgraPixelDataWidth];
|
||||
bgraPixelData[pixelDataIndex + 1] = bgraPixelData[pixelDataIndex + 1 - bgraPixelDataWidth];
|
||||
bgraPixelData[pixelDataIndex + 2] = bgraPixelData[pixelDataIndex + 2 - bgraPixelDataWidth];
|
||||
bgraPixelData[pixelDataIndex + 3] = bgraPixelData[pixelDataIndex + 3 - bgraPixelDataWidth];
|
||||
}
|
||||
|
||||
pixelDataIndex += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Determine the numerical increment of the color steps within the channel
|
||||
if (colorRepresentation == ColorRepresentation.Hsva)
|
||||
{
|
||||
if (channel == ColorChannel.Channel1)
|
||||
{
|
||||
channelStep = 360.0 / height;
|
||||
}
|
||||
else
|
||||
{
|
||||
channelStep = 1.0 / height;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
channelStep = 255.0 / height;
|
||||
}
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
if (x == 0)
|
||||
{
|
||||
// The lowest channel value should be at the 'bottom' of the bitmap
|
||||
rgbColor = GetColor((height - 1 - y) * channelStep);
|
||||
|
||||
// Get a new color
|
||||
bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(rgbColor.B * rgbColor.A / 255);
|
||||
bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(rgbColor.G * rgbColor.A / 255);
|
||||
bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(rgbColor.R * rgbColor.A / 255);
|
||||
bgraPixelData[pixelDataIndex + 3] = rgbColor.A;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the color in the column to the left
|
||||
// Remember the pixel data is 1 dimensional instead of 2
|
||||
bgraPixelData[pixelDataIndex + 0] = bgraPixelData[pixelDataIndex - 4];
|
||||
bgraPixelData[pixelDataIndex + 1] = bgraPixelData[pixelDataIndex - 3];
|
||||
bgraPixelData[pixelDataIndex + 2] = bgraPixelData[pixelDataIndex - 2];
|
||||
bgraPixelData[pixelDataIndex + 3] = bgraPixelData[pixelDataIndex - 1];
|
||||
}
|
||||
|
||||
pixelDataIndex += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Composite the checkered background with color channel gradient for final result
|
||||
// The height/width are not checked as both bitmaps were built with the same values
|
||||
if ((checkerColor != null) &&
|
||||
(bgraCheckeredPixelData != null))
|
||||
{
|
||||
pixelDataIndex = 0;
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
/* The following algorithm is used to blend the two bitmaps creating the final composite.
|
||||
* In this formula, pixel data is normalized 0..1, actual pixel data is in the range 0..255.
|
||||
* The color channel gradient should apply OVER the checkered background.
|
||||
*
|
||||
* R = R0 * A0 * (1 - A1) + R1 * A1 = RA0 * (1 - A1) + RA1
|
||||
* G = G0 * A0 * (1 - A1) + G1 * A1 = GA0 * (1 - A1) + GA1
|
||||
* B = B0 * A0 * (1 - A1) + B1 * A1 = BA0 * (1 - A1) + BA1
|
||||
* A = A0 * (1 - A1) + A1 = A0 * (1 - A1) + A1
|
||||
*
|
||||
* Considering only the red channel, some algebraic transformation is applied to
|
||||
* make the math quicker to solve.
|
||||
*
|
||||
* => ((RA0 / 255.0) * (1.0 - A1 / 255.0) + (RA1 / 255.0)) * 255.0
|
||||
* => ((RA0 * 255) - (RA0 * A1) + (RA1 * 255)) / 255
|
||||
*/
|
||||
|
||||
// Bottom layer
|
||||
byte rXa0 = bgraCheckeredPixelData[pixelDataIndex + 2];
|
||||
byte gXa0 = bgraCheckeredPixelData[pixelDataIndex + 1];
|
||||
byte bXa0 = bgraCheckeredPixelData[pixelDataIndex + 0];
|
||||
byte a0 = bgraCheckeredPixelData[pixelDataIndex + 3];
|
||||
|
||||
// Top layer
|
||||
byte rXa1 = bgraPixelData[pixelDataIndex + 2];
|
||||
byte gXa1 = bgraPixelData[pixelDataIndex + 1];
|
||||
byte bXa1 = bgraPixelData[pixelDataIndex + 0];
|
||||
byte a1 = bgraPixelData[pixelDataIndex + 3];
|
||||
|
||||
bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(((bXa0 * 255) - (bXa0 * a1) + (bXa1 * 255)) / 255);
|
||||
bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(((gXa0 * 255) - (gXa0 * a1) + (gXa1 * 255)) / 255);
|
||||
bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(((rXa0 * 255) - (rXa0 * a1) + (rXa1 * 255)) / 255);
|
||||
bgraPixelData[pixelDataIndex + 3] = Convert.ToByte(((a0 * 255) - (a0 * a1) + (a1 * 255)) / 255);
|
||||
|
||||
pixelDataIndex += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Color GetColor(double channelValue)
|
||||
{
|
||||
Color newRgbColor = Colors.White;
|
||||
|
||||
switch (channel)
|
||||
{
|
||||
case ColorChannel.Channel1:
|
||||
{
|
||||
if (colorRepresentation == ColorRepresentation.Hsva)
|
||||
{
|
||||
// Sweep hue
|
||||
newRgbColor = Uwp.Helpers.ColorHelper.FromHsv(
|
||||
Math.Clamp(channelValue, 0.0, 360.0),
|
||||
baseHsvColor.S,
|
||||
baseHsvColor.V,
|
||||
baseHsvColor.A);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sweep red
|
||||
newRgbColor = new Color
|
||||
{
|
||||
R = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)),
|
||||
G = baseRgbColor.G,
|
||||
B = baseRgbColor.B,
|
||||
A = baseRgbColor.A
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ColorChannel.Channel2:
|
||||
{
|
||||
if (colorRepresentation == ColorRepresentation.Hsva)
|
||||
{
|
||||
// Sweep saturation
|
||||
newRgbColor = Uwp.Helpers.ColorHelper.FromHsv(
|
||||
baseHsvColor.H,
|
||||
Math.Clamp(channelValue, 0.0, 1.0),
|
||||
baseHsvColor.V,
|
||||
baseHsvColor.A);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sweep green
|
||||
newRgbColor = new Color
|
||||
{
|
||||
R = baseRgbColor.R,
|
||||
G = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)),
|
||||
B = baseRgbColor.B,
|
||||
A = baseRgbColor.A
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ColorChannel.Channel3:
|
||||
{
|
||||
if (colorRepresentation == ColorRepresentation.Hsva)
|
||||
{
|
||||
// Sweep value
|
||||
newRgbColor = Uwp.Helpers.ColorHelper.FromHsv(
|
||||
baseHsvColor.H,
|
||||
baseHsvColor.S,
|
||||
Math.Clamp(channelValue, 0.0, 1.0),
|
||||
baseHsvColor.A);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sweep blue
|
||||
newRgbColor = new Color
|
||||
{
|
||||
R = baseRgbColor.R,
|
||||
G = baseRgbColor.G,
|
||||
B = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)),
|
||||
A = baseRgbColor.A
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ColorChannel.Alpha:
|
||||
{
|
||||
if (colorRepresentation == ColorRepresentation.Hsva)
|
||||
{
|
||||
// Sweep alpha
|
||||
newRgbColor = Uwp.Helpers.ColorHelper.FromHsv(
|
||||
baseHsvColor.H,
|
||||
baseHsvColor.S,
|
||||
baseHsvColor.V,
|
||||
Math.Clamp(channelValue, 0.0, 1.0));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sweep alpha
|
||||
newRgbColor = new Color
|
||||
{
|
||||
R = baseRgbColor.R,
|
||||
G = baseRgbColor.G,
|
||||
B = baseRgbColor.B,
|
||||
A = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0))
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return newRgbColor;
|
||||
}
|
||||
|
||||
return bgraPixelData;
|
||||
});
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new checkered bitmap of the specified size.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a port and heavy modification of the code here:
|
||||
/// https://github.com/microsoft/microsoft-ui-xaml/blob/865e4fcc00e8649baeaec1ba7daeca398671aa72/dev/ColorPicker/ColorHelpers.cpp#L363
|
||||
/// UWP needs TiledBrush support.
|
||||
/// </remarks>
|
||||
/// <param name="width">The pixel width (X, horizontal) of the checkered bitmap.</param>
|
||||
/// <param name="height">The pixel height (Y, vertical) of the checkered bitmap.</param>
|
||||
/// <param name="checkerColor">The color of the checker square.</param>
|
||||
/// <returns>A new checkered bitmap of the specified size.</returns>
|
||||
public static async Task<byte[]> CreateCheckeredBitmapAsync(
|
||||
int width,
|
||||
int height,
|
||||
Color checkerColor)
|
||||
{
|
||||
// The size of the checker is important. You want it big enough that the grid is clearly discernible.
|
||||
// However, the squares should be small enough they don't appear unnaturally cut at the edge of backgrounds.
|
||||
int checkerSize = 4;
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bitmap = await Task.Run<byte[]>(() =>
|
||||
{
|
||||
int pixelDataIndex = 0;
|
||||
byte[] bgraPixelData;
|
||||
|
||||
// Allocate the buffer
|
||||
// BGRA formatted color channels 1 byte each (4 bytes in a pixel)
|
||||
bgraPixelData = new byte[width * height * 4];
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
// We want the checkered pattern to alternate both vertically and horizontally.
|
||||
// In order to achieve that, we'll toggle visibility of the current pixel on or off
|
||||
// depending on both its x- and its y-position. If x == CheckerSize, we'll turn visibility off,
|
||||
// but then if y == CheckerSize, we'll turn it back on.
|
||||
// The below is a shorthand for the above intent.
|
||||
bool pixelShouldBeBlank = ((x / checkerSize) + (y / checkerSize)) % 2 == 0 ? true : false;
|
||||
|
||||
// Remember, use BGRA pixel format with pre-multiplied alpha values
|
||||
if (pixelShouldBeBlank)
|
||||
{
|
||||
bgraPixelData[pixelDataIndex + 0] = 0;
|
||||
bgraPixelData[pixelDataIndex + 1] = 0;
|
||||
bgraPixelData[pixelDataIndex + 2] = 0;
|
||||
bgraPixelData[pixelDataIndex + 3] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(checkerColor.B * checkerColor.A / 255);
|
||||
bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(checkerColor.G * checkerColor.A / 255);
|
||||
bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(checkerColor.R * checkerColor.A / 255);
|
||||
bgraPixelData[pixelDataIndex + 3] = checkerColor.A;
|
||||
}
|
||||
|
||||
pixelDataIndex += 4;
|
||||
}
|
||||
}
|
||||
|
||||
return bgraPixelData;
|
||||
});
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given bitmap (in raw BGRA pre-multiplied alpha pixels) into an image brush
|
||||
/// that can be used in the UI.
|
||||
/// </summary>
|
||||
/// <param name="bitmap">The bitmap (in raw BGRA pre-multiplied alpha pixels) to convert to a brush.</param>
|
||||
/// <param name="width">The pixel width of the bitmap.</param>
|
||||
/// <param name="height">The pixel height of the bitmap.</param>
|
||||
/// <returns>A new ImageBrush.</returns>
|
||||
public static async Task<ImageBrush> BitmapToBrushAsync(
|
||||
byte[] bitmap,
|
||||
int width,
|
||||
int height)
|
||||
{
|
||||
var writableBitmap = new WriteableBitmap(width, height);
|
||||
using (Stream stream = writableBitmap.PixelBuffer.AsStream())
|
||||
{
|
||||
await stream.WriteAsync(bitmap, 0, bitmap.Length);
|
||||
}
|
||||
|
||||
var brush = new ImageBrush()
|
||||
{
|
||||
ImageSource = writableBitmap,
|
||||
Stretch = Stretch.None
|
||||
};
|
||||
|
||||
return brush;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Centralizes code to create a checker brush for a <see cref="Border"/>.
|
||||
/// </summary>
|
||||
/// <param name="border">Border which will have its Background modified.</param>
|
||||
/// <param name="color">Color to use for transparent checkerboard.</param>
|
||||
/// <returns>Task</returns>
|
||||
public static async Task UpdateBorderBackgroundWithCheckerAsync(Border border, Color color)
|
||||
{
|
||||
if (border != null)
|
||||
{
|
||||
int width = Convert.ToInt32(border.ActualWidth);
|
||||
int height = Convert.ToInt32(border.ActualHeight);
|
||||
|
||||
var bitmap = await ColorPickerRenderingHelpers.CreateCheckeredBitmapAsync(
|
||||
width,
|
||||
height,
|
||||
color);
|
||||
|
||||
if (bitmap != null)
|
||||
{
|
||||
border.Background = await ColorPickerRenderingHelpers.BitmapToBrushAsync(bitmap, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
// 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 Microsoft.Toolkit.Uwp.Helpers;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls.Primitives
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public partial class ColorPickerSlider : Slider
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="Color"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ColorProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(Color),
|
||||
typeof(Color),
|
||||
typeof(ColorPickerSlider),
|
||||
new PropertyMetadata(
|
||||
Colors.White,
|
||||
(s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the RGB color represented by the slider.
|
||||
/// For accuracy use <see cref="HsvColor"/> instead.
|
||||
/// </summary>
|
||||
public Color Color
|
||||
{
|
||||
get => (Color)this.GetValue(ColorProperty);
|
||||
set
|
||||
{
|
||||
if (object.Equals(value, this.GetValue(ColorProperty)) == false)
|
||||
{
|
||||
this.SetValue(ColorProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ColorChannel"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ColorChannelProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(ColorChannel),
|
||||
typeof(ColorChannel),
|
||||
typeof(ColorPickerSlider),
|
||||
new PropertyMetadata(
|
||||
ColorChannel.Channel1,
|
||||
(s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color channel represented by the slider.
|
||||
/// </summary>
|
||||
public ColorChannel ColorChannel
|
||||
{
|
||||
get => (ColorChannel)this.GetValue(ColorChannelProperty);
|
||||
set
|
||||
{
|
||||
if (object.Equals(value, this.GetValue(ColorChannelProperty)) == false)
|
||||
{
|
||||
this.SetValue(ColorChannelProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ColorRepresentation"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ColorRepresentationProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(ColorRepresentation),
|
||||
typeof(ColorRepresentation),
|
||||
typeof(ColorPickerSlider),
|
||||
new PropertyMetadata(
|
||||
ColorRepresentation.Rgba,
|
||||
(s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color representation used by the slider.
|
||||
/// </summary>
|
||||
public ColorRepresentation ColorRepresentation
|
||||
{
|
||||
get => (ColorRepresentation)this.GetValue(ColorRepresentationProperty);
|
||||
set
|
||||
{
|
||||
if (object.Equals(value, this.GetValue(ColorRepresentationProperty)) == false)
|
||||
{
|
||||
this.SetValue(ColorRepresentationProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="DefaultForeground"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DefaultForegroundProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(DefaultForeground),
|
||||
typeof(Brush),
|
||||
typeof(ColorPickerSlider),
|
||||
new PropertyMetadata(
|
||||
new SolidColorBrush(Colors.Gray),
|
||||
(s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default foreground brush to use when the slider background is hardly visible and nearly transparent.
|
||||
/// Generally, this should be the default Foreground text brush.
|
||||
/// </summary>
|
||||
public Brush DefaultForeground
|
||||
{
|
||||
get => (Brush)this.GetValue(DefaultForegroundProperty);
|
||||
set
|
||||
{
|
||||
if (object.Equals(value, this.GetValue(DefaultForegroundProperty)) == false)
|
||||
{
|
||||
this.SetValue(DefaultForegroundProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="HsvColor"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty HsvColorProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(HsvColor),
|
||||
typeof(HsvColor),
|
||||
typeof(ColorPickerSlider),
|
||||
new PropertyMetadata(
|
||||
Colors.White.ToHsv(),
|
||||
(s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the HSV color represented by the slider.
|
||||
/// This is the preferred color property for accuracy.
|
||||
/// </summary>
|
||||
public HsvColor HsvColor
|
||||
{
|
||||
get => (HsvColor)this.GetValue(HsvColorProperty);
|
||||
set
|
||||
{
|
||||
if (object.Equals(value, this.GetValue(HsvColorProperty)) == false)
|
||||
{
|
||||
this.SetValue(HsvColorProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="IsAlphaMaxForced"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsAlphaMaxForcedProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(IsAlphaMaxForced),
|
||||
typeof(bool),
|
||||
typeof(ColorPickerSlider),
|
||||
new PropertyMetadata(
|
||||
true,
|
||||
(s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the alpha channel is always forced to maximum for channels
|
||||
/// other than <see cref="ColorChannel"/>.
|
||||
/// This ensures that the background is always visible and never transparent regardless of the actual color.
|
||||
/// </summary>
|
||||
public bool IsAlphaMaxForced
|
||||
{
|
||||
get => (bool)this.GetValue(IsAlphaMaxForcedProperty);
|
||||
set
|
||||
{
|
||||
if (object.Equals(value, this.GetValue(IsAlphaMaxForcedProperty)) == false)
|
||||
{
|
||||
this.SetValue(IsAlphaMaxForcedProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="IsAutoUpdatingEnabled"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsAutoUpdatingEnabledProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(IsAutoUpdatingEnabled),
|
||||
typeof(bool),
|
||||
typeof(ColorPickerSlider),
|
||||
new PropertyMetadata(
|
||||
true,
|
||||
(s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether automatic background and foreground updates will be
|
||||
/// calculated when the set color changes. This can be disabled for performance reasons when working with
|
||||
/// multiple sliders.
|
||||
/// </summary>
|
||||
public bool IsAutoUpdatingEnabled
|
||||
{
|
||||
get => (bool)this.GetValue(IsAutoUpdatingEnabledProperty);
|
||||
set
|
||||
{
|
||||
if (object.Equals(value, this.GetValue(IsAutoUpdatingEnabledProperty)) == false)
|
||||
{
|
||||
this.SetValue(IsAutoUpdatingEnabledProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="IsSaturationValueMaxForced"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsSaturationValueMaxForcedProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(IsSaturationValueMaxForced),
|
||||
typeof(bool),
|
||||
typeof(ColorPickerSlider),
|
||||
new PropertyMetadata(
|
||||
true,
|
||||
(s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the saturation and value channels are always forced to maximum values
|
||||
/// when in HSVA color representation. Only channel values other than <see cref="ColorChannel"/> will be changed.
|
||||
/// This ensures, for example, that the Hue background is always visible and never washed out regardless of the actual color.
|
||||
/// </summary>
|
||||
public bool IsSaturationValueMaxForced
|
||||
{
|
||||
get => (bool)this.GetValue(IsSaturationValueMaxForcedProperty);
|
||||
set
|
||||
{
|
||||
if (object.Equals(value, this.GetValue(IsSaturationValueMaxForcedProperty)) == false)
|
||||
{
|
||||
this.SetValue(IsSaturationValueMaxForcedProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,301 @@
|
|||
// 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;
|
||||
using Microsoft.Toolkit.Uwp.Helpers;
|
||||
using Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls.Primitives
|
||||
{
|
||||
/// <summary>
|
||||
/// A slider that represents a single color channel for use in the <see cref="ColorPicker"/>.
|
||||
/// </summary>
|
||||
public partial class ColorPickerSlider : Slider
|
||||
{
|
||||
// TODO Combine this with the ColorPicker field or make a property
|
||||
internal Color CheckerBackgroundColor { get; set; } = Color.FromArgb(0x19, 0x80, 0x80, 0x80); // Overridden later
|
||||
|
||||
private Size oldSize = Size.Empty;
|
||||
private Size measuredSize = Size.Empty;
|
||||
private Size cachedSize = Size.Empty;
|
||||
|
||||
/***************************************************************************************
|
||||
*
|
||||
* Constructor/Destructor
|
||||
*
|
||||
***************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ColorPickerSlider"/> class.
|
||||
/// </summary>
|
||||
public ColorPickerSlider()
|
||||
: base()
|
||||
{
|
||||
this.DefaultStyleKey = typeof(ColorPickerSlider);
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
*
|
||||
* Methods
|
||||
*
|
||||
***************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Update the slider's Foreground and Background brushes based on the current slider state and color.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Manually refreshes the background gradient of the slider.
|
||||
/// This is callable separately for performance reasons.
|
||||
/// </remarks>
|
||||
public void UpdateColors()
|
||||
{
|
||||
HsvColor hsvColor = this.HsvColor;
|
||||
|
||||
// Calculate and set the background
|
||||
this.UpdateBackground(hsvColor);
|
||||
|
||||
// Calculate and set the foreground ensuring contrast with the background
|
||||
Color rgbColor = Uwp.Helpers.ColorHelper.FromHsv(hsvColor.H, hsvColor.S, hsvColor.V, hsvColor.A);
|
||||
Color selectedRgbColor;
|
||||
double sliderPercent = this.Value / (this.Maximum - this.Minimum);
|
||||
|
||||
if (this.ColorRepresentation == ColorRepresentation.Hsva)
|
||||
{
|
||||
if (this.IsAlphaMaxForced &&
|
||||
this.ColorChannel != ColorChannel.Alpha)
|
||||
{
|
||||
hsvColor = new HsvColor()
|
||||
{
|
||||
H = hsvColor.H,
|
||||
S = hsvColor.S,
|
||||
V = hsvColor.V,
|
||||
A = 1.0
|
||||
};
|
||||
}
|
||||
|
||||
switch (this.ColorChannel)
|
||||
{
|
||||
case ColorChannel.Channel1:
|
||||
{
|
||||
var channelValue = Math.Clamp(sliderPercent * 360.0, 0.0, 360.0);
|
||||
|
||||
hsvColor = new HsvColor()
|
||||
{
|
||||
H = channelValue,
|
||||
S = this.IsSaturationValueMaxForced ? 1.0 : hsvColor.S,
|
||||
V = this.IsSaturationValueMaxForced ? 1.0 : hsvColor.V,
|
||||
A = hsvColor.A
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case ColorChannel.Channel2:
|
||||
{
|
||||
var channelValue = Math.Clamp(sliderPercent * 1.0, 0.0, 1.0);
|
||||
|
||||
hsvColor = new HsvColor()
|
||||
{
|
||||
H = hsvColor.H,
|
||||
S = channelValue,
|
||||
V = this.IsSaturationValueMaxForced ? 1.0 : hsvColor.V,
|
||||
A = hsvColor.A
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case ColorChannel.Channel3:
|
||||
{
|
||||
var channelValue = Math.Clamp(sliderPercent * 1.0, 0.0, 1.0);
|
||||
|
||||
hsvColor = new HsvColor()
|
||||
{
|
||||
H = hsvColor.H,
|
||||
S = this.IsSaturationValueMaxForced ? 1.0 : hsvColor.S,
|
||||
V = channelValue,
|
||||
A = hsvColor.A
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
selectedRgbColor = Uwp.Helpers.ColorHelper.FromHsv(
|
||||
hsvColor.H,
|
||||
hsvColor.S,
|
||||
hsvColor.V,
|
||||
hsvColor.A);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.IsAlphaMaxForced &&
|
||||
this.ColorChannel != ColorChannel.Alpha)
|
||||
{
|
||||
rgbColor = new Color()
|
||||
{
|
||||
R = rgbColor.R,
|
||||
G = rgbColor.G,
|
||||
B = rgbColor.B,
|
||||
A = 255
|
||||
};
|
||||
}
|
||||
|
||||
byte channelValue = Convert.ToByte(Math.Clamp(sliderPercent * 255.0, 0.0, 255.0));
|
||||
|
||||
switch (this.ColorChannel)
|
||||
{
|
||||
case ColorChannel.Channel1:
|
||||
rgbColor = new Color()
|
||||
{
|
||||
R = channelValue,
|
||||
G = rgbColor.G,
|
||||
B = rgbColor.B,
|
||||
A = rgbColor.A
|
||||
};
|
||||
break;
|
||||
case ColorChannel.Channel2:
|
||||
rgbColor = new Color()
|
||||
{
|
||||
R = rgbColor.R,
|
||||
G = channelValue,
|
||||
B = rgbColor.B,
|
||||
A = rgbColor.A
|
||||
};
|
||||
break;
|
||||
case ColorChannel.Channel3:
|
||||
rgbColor = new Color()
|
||||
{
|
||||
R = rgbColor.R,
|
||||
G = rgbColor.G,
|
||||
B = channelValue,
|
||||
A = rgbColor.A
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
selectedRgbColor = rgbColor;
|
||||
}
|
||||
|
||||
var converter = new ContrastBrushConverter();
|
||||
this.Foreground = converter.Convert(selectedRgbColor, typeof(Brush), this.DefaultForeground, null) as Brush;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new background image for the color channel slider and applies it.
|
||||
/// </summary>
|
||||
private async void UpdateBackground(HsvColor color)
|
||||
{
|
||||
/* Updates may be requested when sliders are not in the visual tree.
|
||||
* For first-time load this is handled by the Loaded event.
|
||||
* However, after that problems may arise, consider the following case:
|
||||
*
|
||||
* (1) Backgrounds are drawn normally the first time on Loaded.
|
||||
* Actual height/width are available.
|
||||
* (2) The palette tab is selected which has no sliders
|
||||
* (3) The picker flyout is closed
|
||||
* (4) Externally the color is changed
|
||||
* The color change will trigger slider background updates but
|
||||
* with the flyout closed, actual height/width are zero.
|
||||
* No zero size bitmap can be generated.
|
||||
* (5) The picker flyout is re-opened by the user and the default
|
||||
* last-opened tab will be viewed: palette.
|
||||
* No loaded events will be fired for sliders. The color change
|
||||
* event was already handled in (4). The sliders will never
|
||||
* be updated.
|
||||
*
|
||||
* In this case the sliders become out of sync with the Color because there is no way
|
||||
* to tell when they actually come into view. To work around this, force a re-render of
|
||||
* the background with the last size of the slider. This last size will be when it was
|
||||
* last loaded or updated.
|
||||
*
|
||||
* In the future additional consideration may be required for SizeChanged of the control.
|
||||
* This work-around will also cause issues if display scaling changes in the special
|
||||
* case where cached sizes are required.
|
||||
*/
|
||||
var width = Convert.ToInt32(this.ActualWidth);
|
||||
var height = Convert.ToInt32(this.ActualHeight);
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
// Attempt to use the last size if it was available
|
||||
if (this.cachedSize.IsEmpty == false)
|
||||
{
|
||||
width = Convert.ToInt32(this.cachedSize.Width);
|
||||
height = Convert.ToInt32(this.cachedSize.Height);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.cachedSize = new Size(width, height);
|
||||
}
|
||||
|
||||
var bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync(
|
||||
width,
|
||||
height,
|
||||
this.Orientation,
|
||||
this.ColorRepresentation,
|
||||
this.ColorChannel,
|
||||
color,
|
||||
this.CheckerBackgroundColor,
|
||||
this.IsAlphaMaxForced,
|
||||
this.IsSaturationValueMaxForced);
|
||||
|
||||
if (bitmap != null)
|
||||
{
|
||||
this.Background = await ColorPickerRenderingHelpers.BitmapToBrushAsync(bitmap, width, height);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Measures the size in layout required for child elements and determines a size for the
|
||||
/// FrameworkElement-derived class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// Slider has some critical bugs:
|
||||
///
|
||||
/// * https://github.com/microsoft/microsoft-ui-xaml/issues/477
|
||||
/// * https://social.msdn.microsoft.com/Forums/sqlserver/en-US/0d3a2e64-d192-4250-b583-508a02bd75e1/uwp-bug-crash-layoutcycleexception-because-of-slider-under-certain-circumstances?forum=wpdevelop
|
||||
///
|
||||
/// </remarks>
|
||||
/// <param name="availableSize">The available size that this element can give to child elements.
|
||||
/// Infinity can be specified as a value to indicate that the element will size to whatever content
|
||||
/// is available.</param>
|
||||
/// <returns>The size that this element determines it needs during layout,
|
||||
/// based on its calculations of child element sizes.</returns>
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
if (!Size.Equals(oldSize, availableSize))
|
||||
{
|
||||
measuredSize = base.MeasureOverride(availableSize);
|
||||
oldSize = availableSize;
|
||||
}
|
||||
|
||||
return measuredSize;
|
||||
}
|
||||
|
||||
private void OnDependencyPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (object.ReferenceEquals(args.Property, ColorProperty))
|
||||
{
|
||||
// Sync with HSV (which is primary)
|
||||
this.HsvColor = this.Color.ToHsv();
|
||||
}
|
||||
|
||||
if (this.IsAutoUpdatingEnabled)
|
||||
{
|
||||
this.UpdateColors();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls.Primitives">
|
||||
|
||||
<Style BasedOn="{StaticResource ColorPickerSliderStyle}"
|
||||
TargetType="controls:ColorPickerSlider" />
|
||||
|
||||
<Style x:Key="ColorPickerSliderStyle"
|
||||
TargetType="controls:ColorPickerSlider">
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="IsThumbToolTipEnabled" Value="False" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<!-- Based on WinUI version 2.4.2 -->
|
||||
<ControlTemplate TargetType="controls:ColorPickerSlider">
|
||||
<Grid Margin="{TemplateBinding Padding}">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="SliderThumbStyle"
|
||||
TargetType="Thumb">
|
||||
<Setter Property="BorderThickness" Value="3" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{ThemeResource SliderThumbCornerRadius}" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid x:Name="SliderContainer"
|
||||
Background="{ThemeResource SliderContainerBackground}"
|
||||
Control.IsTemplateFocusTarget="True">
|
||||
<Grid x:Name="HorizontalTemplate"
|
||||
MinHeight="20">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="0" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="0" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Rectangle x:Name="HorizontalTrackRect"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="20"
|
||||
Fill="{TemplateBinding Background}"
|
||||
RadiusX="10"
|
||||
RadiusY="10" />
|
||||
<Rectangle x:Name="HorizontalDecreaseRect"
|
||||
Grid.Row="1"
|
||||
Fill="Transparent"
|
||||
RadiusX="8"
|
||||
RadiusY="8" />
|
||||
<Thumb x:Name="HorizontalThumb"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="20"
|
||||
Height="20"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
BorderBrush="{TemplateBinding Foreground}"
|
||||
CornerRadius="10"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-14,-6,-14,-6"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
<Grid x:Name="VerticalTemplate"
|
||||
MinWidth="20"
|
||||
Visibility="Collapsed">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="0" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="0" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Rectangle x:Name="VerticalTrackRect"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="20"
|
||||
Fill="{TemplateBinding Background}"
|
||||
RadiusX="10"
|
||||
RadiusY="10" />
|
||||
<Rectangle x:Name="VerticalDecreaseRect"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Fill="Transparent"
|
||||
RadiusX="8"
|
||||
RadiusY="8" />
|
||||
<Thumb x:Name="VerticalThumb"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Width="20"
|
||||
Height="20"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
BorderBrush="{TemplateBinding Foreground}"
|
||||
CornerRadius="10"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-6,-14,-6,-14"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
|
||||
<!--
|
||||
The Pressed state is purposely the same as normal.
|
||||
This ensures that the thumb always has the correct contrast with
|
||||
the selected color underneath it when dragging or moving.
|
||||
-->
|
||||
<VisualState x:Name="Pressed" />
|
||||
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="HorizontalThumb.Foreground" Value="{ThemeResource SliderThumbBackgroundDisabled}" />
|
||||
<Setter Target="VerticalThumb.Foreground" Value="{ThemeResource SliderThumbBackgroundDisabled}" />
|
||||
<Setter Target="SliderContainer.Background" Value="{ThemeResource SliderContainerBackgroundDisabled}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="PointerOver">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="HorizontalThumb.Foreground" Value="{ThemeResource SliderThumbBackgroundPointerOver}" />
|
||||
<Setter Target="VerticalThumb.Foreground" Value="{ThemeResource SliderThumbBackgroundPointerOver}" />
|
||||
<Setter Target="SliderContainer.Background" Value="{ThemeResource SliderContainerBackgroundPointerOver}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="FocusEngagementStates">
|
||||
<VisualState x:Name="FocusDisengaged" />
|
||||
<VisualState x:Name="FocusEngagedHorizontal">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SliderContainer.(Control.IsTemplateFocusTarget)" Value="False" />
|
||||
<Setter Target="HorizontalThumb.(Control.IsTemplateFocusTarget)" Value="True" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="FocusEngagedVertical">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SliderContainer.(Control.IsTemplateFocusTarget)" Value="False" />
|
||||
<Setter Target="VerticalThumb.(Control.IsTemplateFocusTarget)" Value="True" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
|
@ -0,0 +1,30 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines how colors are represented.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||
public enum ColorRepresentation
|
||||
{
|
||||
/// <summary>
|
||||
/// Color is represented by hue, saturation, value and alpha channels.
|
||||
/// </summary>
|
||||
Hsva,
|
||||
|
||||
/// <summary>
|
||||
/// Color is represented by red, green, blue and alpha channels.
|
||||
/// </summary>
|
||||
Rgba
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
// 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;
|
||||
using Microsoft.Toolkit.Uwp.Helpers;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an accent color shade from a color value.
|
||||
/// Only +/- 3 shades from the given color are supported.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1025:Code should not contain multiple whitespace in a row", Justification = "Whitespace is used to align code in columns for readability.")]
|
||||
public class ColorToColorShadeConverter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public object Convert(
|
||||
object value,
|
||||
Type targetType,
|
||||
object parameter,
|
||||
string language)
|
||||
{
|
||||
int shade;
|
||||
byte tolerance = 0x05;
|
||||
double valueDelta = 0.25;
|
||||
Color rgbColor;
|
||||
|
||||
// Get the current color in HSV
|
||||
if (value is Color valueColor)
|
||||
{
|
||||
rgbColor = valueColor;
|
||||
}
|
||||
else if (value is SolidColorBrush valueBrush)
|
||||
{
|
||||
rgbColor = valueBrush.Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Invalid color value provided");
|
||||
}
|
||||
|
||||
// Get the value component delta
|
||||
try
|
||||
{
|
||||
shade = System.Convert.ToInt32(parameter?.ToString());
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new ArgumentException("Invalid parameter provided, unable to convert to integer");
|
||||
}
|
||||
|
||||
// Specially handle minimum (black) and maximum (white)
|
||||
if (rgbColor.R <= (0x00 + tolerance) &&
|
||||
rgbColor.G <= (0x00 + tolerance) &&
|
||||
rgbColor.B <= (0x00 + tolerance))
|
||||
{
|
||||
switch (shade)
|
||||
{
|
||||
case 1:
|
||||
return Color.FromArgb(rgbColor.A, 0x3F, 0x3F, 0x3F);
|
||||
case 2:
|
||||
return Color.FromArgb(rgbColor.A, 0x80, 0x80, 0x80);
|
||||
case 3:
|
||||
return Color.FromArgb(rgbColor.A, 0xBF, 0xBF, 0xBF);
|
||||
}
|
||||
|
||||
return rgbColor;
|
||||
}
|
||||
else if (rgbColor.R >= (0xFF + tolerance) &&
|
||||
rgbColor.G >= (0xFF + tolerance) &&
|
||||
rgbColor.B >= (0xFF + tolerance))
|
||||
{
|
||||
switch (shade)
|
||||
{
|
||||
case -1:
|
||||
return Color.FromArgb(rgbColor.A, 0xBF, 0xBF, 0xBF);
|
||||
case -2:
|
||||
return Color.FromArgb(rgbColor.A, 0x80, 0x80, 0x80);
|
||||
case -3:
|
||||
return Color.FromArgb(rgbColor.A, 0x3F, 0x3F, 0x3F);
|
||||
}
|
||||
|
||||
return rgbColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
HsvColor hsvColor = rgbColor.ToHsv();
|
||||
|
||||
double colorHue = hsvColor.H;
|
||||
double colorSaturation = hsvColor.S;
|
||||
double colorValue = hsvColor.V;
|
||||
double colorAlpha = hsvColor.A;
|
||||
|
||||
// Use the HSV representation as it's more perceptual.
|
||||
// Only the value is changed by a fixed percentage so the algorithm is reproducible.
|
||||
// This does not account for perceptual differences and also does not match with
|
||||
// system accent color calculation.
|
||||
if (shade != 0)
|
||||
{
|
||||
colorValue *= 1.0 + (shade * valueDelta);
|
||||
}
|
||||
|
||||
return Uwp.Helpers.ColorHelper.FromHsv(
|
||||
Math.Clamp(colorHue, 0.0, 360.0),
|
||||
Math.Clamp(colorSaturation, 0.0, 1.0),
|
||||
Math.Clamp(colorValue, 0.0, 1.0),
|
||||
Math.Clamp(colorAlpha, 0.0, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ConvertBack(
|
||||
object value,
|
||||
Type targetType,
|
||||
object parameter,
|
||||
string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// 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;
|
||||
using Microsoft.Toolkit.Uwp.Helpers;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a color to a hex string and vice versa.
|
||||
/// </summary>
|
||||
public class ColorToHexConverter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public object Convert(
|
||||
object value,
|
||||
Type targetType,
|
||||
object parameter,
|
||||
string language)
|
||||
{
|
||||
Color color;
|
||||
|
||||
if (value is Color valueColor)
|
||||
{
|
||||
color = valueColor;
|
||||
}
|
||||
else if (value is SolidColorBrush valueBrush)
|
||||
{
|
||||
color = valueBrush.Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Invalid color value provided");
|
||||
}
|
||||
|
||||
string hexColor = color.ToHex().Replace("#", string.Empty);
|
||||
return hexColor;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ConvertBack(
|
||||
object value,
|
||||
Type targetType,
|
||||
object parameter,
|
||||
string language)
|
||||
{
|
||||
string hexValue = value.ToString();
|
||||
|
||||
if (hexValue.StartsWith("#"))
|
||||
{
|
||||
try
|
||||
{
|
||||
return hexValue.ToColor();
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new ArgumentException("Invalid hex color value provided");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
return ("#" + hexValue).ToColor();
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new ArgumentException("Invalid hex color value provided");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
// 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;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a color, either black or white, depending on the brightness of the supplied color.
|
||||
/// </summary>
|
||||
public class ContrastBrushConverter : IValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the alpha channel threshold below which a default color is used instead of black/white.
|
||||
/// </summary>
|
||||
public byte AlphaThreshold { get; set; } = 128;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object Convert(
|
||||
object value,
|
||||
Type targetType,
|
||||
object parameter,
|
||||
string language)
|
||||
{
|
||||
Color comparisonColor;
|
||||
Color? defaultColor = null;
|
||||
|
||||
// Get the changing color to compare against
|
||||
if (value is Color valueColor)
|
||||
{
|
||||
comparisonColor = valueColor;
|
||||
}
|
||||
else if (value is SolidColorBrush valueBrush)
|
||||
{
|
||||
comparisonColor = valueBrush.Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Invalid color value provided");
|
||||
}
|
||||
|
||||
// Get the default color when transparency is high
|
||||
if (parameter is Color parameterColor)
|
||||
{
|
||||
defaultColor = parameterColor;
|
||||
}
|
||||
else if (parameter is SolidColorBrush parameterBrush)
|
||||
{
|
||||
defaultColor = parameterBrush.Color;
|
||||
}
|
||||
|
||||
if (comparisonColor.A < AlphaThreshold &&
|
||||
defaultColor.HasValue)
|
||||
{
|
||||
// If the transparency is less than 50 %, just use the default brush
|
||||
// This can commonly be something like the TextControlForeground brush
|
||||
return new SolidColorBrush(defaultColor.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chose a white/black brush based on contrast to the base color
|
||||
if (this.UseLightContrastColor(comparisonColor))
|
||||
{
|
||||
return new SolidColorBrush(Colors.White);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SolidColorBrush(Colors.Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ConvertBack(
|
||||
object value,
|
||||
Type targetType,
|
||||
object parameter,
|
||||
string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a light or dark contrast color should be used with the given displayed color.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This code is using the WinUI algorithm.
|
||||
/// </remarks>
|
||||
private bool UseLightContrastColor(Color displayedColor)
|
||||
{
|
||||
// The selection ellipse should be light if and only if the chosen color
|
||||
// contrasts more with black than it does with white.
|
||||
// To find how much something contrasts with white, we use the equation
|
||||
// for relative luminance, which is given by
|
||||
//
|
||||
// L = 0.2126 * Rg + 0.7152 * Gg + 0.0722 * Bg
|
||||
//
|
||||
// where Xg = { X/3294 if X <= 10, (R/269 + 0.0513)^2.4 otherwise }
|
||||
//
|
||||
// If L is closer to 1, then the color is closer to white; if it is closer to 0,
|
||||
// then the color is closer to black. This is based on the fact that the human
|
||||
// eye perceives green to be much brighter than red, which in turn is perceived to be
|
||||
// brighter than blue.
|
||||
//
|
||||
// If the third dimension is value, then we won't be updating the spectrum's displayed colors,
|
||||
// so in that case we should use a value of 1 when considering the backdrop
|
||||
// for the selection ellipse.
|
||||
double rg = displayedColor.R <= 10 ? displayedColor.R / 3294.0 : Math.Pow((displayedColor.R / 269.0) + 0.0513, 2.4);
|
||||
double gg = displayedColor.G <= 10 ? displayedColor.G / 3294.0 : Math.Pow((displayedColor.G / 269.0) + 0.0513, 2.4);
|
||||
double bg = displayedColor.B <= 10 ? displayedColor.B / 3294.0 : Math.Pow((displayedColor.B / 269.0) + 0.0513, 2.4);
|
||||
|
||||
return (0.2126 * rg) + (0.7152 * gg) + (0.0722 * bg) <= 0.5;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
// 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;
|
||||
using Color = Windows.UI.Color; // Type can be changed to CoreColor, etc.
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the standard Windows 10 color palette.
|
||||
/// </summary>
|
||||
public class FluentColorPalette : IColorPalette
|
||||
{
|
||||
/* Values were taken from the Settings App, Personalization > Colors which match with
|
||||
* https://docs.microsoft.com/en-us/windows/uwp/whats-new/windows-docs-december-2017
|
||||
*
|
||||
* The default ordering and grouping of colors was undesirable so was modified.
|
||||
* Colors were transposed: the colors in rows within the Settings app became columns here.
|
||||
* This is because columns in an IColorPalette generally should contain different shades of
|
||||
* the same color. In the settings app this concept is somewhat loosely reversed.
|
||||
* The first 'column' ordering, after being transposed, was then reversed so 'red' colors
|
||||
* were near to each other.
|
||||
*
|
||||
* This new ordering most closely follows the Windows standard while:
|
||||
*
|
||||
* 1. Keeping colors in a 'spectrum' order
|
||||
* 2. Keeping like colors next to each both in rows and columns
|
||||
* (which is unique for the windows palette).
|
||||
* For example, similar red colors are next to each other in both
|
||||
* rows within the same column and rows within the column next to it.
|
||||
* This follows a 'snake-like' pattern as illustrated below.
|
||||
* 3. A downside of this ordering is colors don't follow strict 'shades'
|
||||
* as in other palettes.
|
||||
*
|
||||
* The colors will be displayed in the below pattern.
|
||||
* This pattern follows a spectrum while keeping like-colors near to one
|
||||
* another across both rows and columns.
|
||||
*
|
||||
* ┌Red───┐ ┌Blue──┐ ┌Gray──┐
|
||||
* │ │ │ │ │ |
|
||||
* │ │ │ │ │ |
|
||||
* Yellow └Violet┘ └Green─┘ Brown
|
||||
*/
|
||||
private static Color[,] colorChart = new Color[,]
|
||||
{
|
||||
{
|
||||
// Ordering reversed for this section only
|
||||
Color.FromArgb(255, 255, 67, 67), /* #FF4343 */
|
||||
Color.FromArgb(255, 209, 52, 56), /* #D13438 */
|
||||
Color.FromArgb(255, 239, 105, 80), /* #EF6950 */
|
||||
Color.FromArgb(255, 218, 59, 1), /* #DA3B01 */
|
||||
Color.FromArgb(255, 202, 80, 16), /* #CA5010 */
|
||||
Color.FromArgb(255, 247, 99, 12), /* #F7630C */
|
||||
Color.FromArgb(255, 255, 140, 0), /* #FF8C00 */
|
||||
Color.FromArgb(255, 255, 185, 0), /* #FFB900 */
|
||||
},
|
||||
{
|
||||
Color.FromArgb(255, 231, 72, 86), /* #E74856 */
|
||||
Color.FromArgb(255, 232, 17, 35), /* #E81123 */
|
||||
Color.FromArgb(255, 234, 0, 94), /* #EA005E */
|
||||
Color.FromArgb(255, 195, 0, 82), /* #C30052 */
|
||||
Color.FromArgb(255, 227, 0, 140), /* #E3008C */
|
||||
Color.FromArgb(255, 191, 0, 119), /* #BF0077 */
|
||||
Color.FromArgb(255, 194, 57, 179), /* #C239B3 */
|
||||
Color.FromArgb(255, 154, 0, 137), /* #9A0089 */
|
||||
},
|
||||
{
|
||||
Color.FromArgb(255, 0, 120, 215), /* #0078D7 */
|
||||
Color.FromArgb(255, 0, 99, 177), /* #0063B1 */
|
||||
Color.FromArgb(255, 142, 140, 216), /* #8E8CD8 */
|
||||
Color.FromArgb(255, 107, 105, 214), /* #6B69D6 */
|
||||
Color.FromArgb(255, 135, 100, 184), /* #8764B8 */
|
||||
Color.FromArgb(255, 116, 77, 169), /* #744DA9 */
|
||||
Color.FromArgb(255, 177, 70, 194), /* #B146C2 */
|
||||
Color.FromArgb(255, 136, 23, 152), /* #881798 */
|
||||
},
|
||||
{
|
||||
Color.FromArgb(255, 0, 153, 188), /* #0099BC */
|
||||
Color.FromArgb(255, 45, 125, 154), /* #2D7D9A */
|
||||
Color.FromArgb(255, 0, 183, 195), /* #00B7C3 */
|
||||
Color.FromArgb(255, 3, 131, 135), /* #038387 */
|
||||
Color.FromArgb(255, 0, 178, 148), /* #00B294 */
|
||||
Color.FromArgb(255, 1, 133, 116), /* #018574 */
|
||||
Color.FromArgb(255, 0, 204, 106), /* #00CC6A */
|
||||
Color.FromArgb(255, 16, 137, 62), /* #10893E */
|
||||
},
|
||||
{
|
||||
Color.FromArgb(255, 122, 117, 116), /* #7A7574 */
|
||||
Color.FromArgb(255, 93, 90, 80), /* #5D5A58 */
|
||||
Color.FromArgb(255, 104, 118, 138), /* #68768A */
|
||||
Color.FromArgb(255, 81, 92, 107), /* #515C6B */
|
||||
Color.FromArgb(255, 86, 124, 115), /* #567C73 */
|
||||
Color.FromArgb(255, 72, 104, 96), /* #486860 */
|
||||
Color.FromArgb(255, 73, 130, 5), /* #498205 */
|
||||
Color.FromArgb(255, 16, 124, 16), /* #107C10 */
|
||||
},
|
||||
{
|
||||
Color.FromArgb(255, 118, 118, 118), /* #767676 */
|
||||
Color.FromArgb(255, 76, 74, 72), /* #4C4A48 */
|
||||
Color.FromArgb(255, 105, 121, 126), /* #69797E */
|
||||
Color.FromArgb(255, 74, 84, 89), /* #4A5459 */
|
||||
Color.FromArgb(255, 100, 124, 100), /* #647C64 */
|
||||
Color.FromArgb(255, 82, 94, 84), /* #525E54 */
|
||||
Color.FromArgb(255, 132, 117, 69), /* #847545 */
|
||||
Color.FromArgb(255, 126, 115, 95), /* #7E735F */
|
||||
}
|
||||
};
|
||||
|
||||
/***************************************************************************************
|
||||
*
|
||||
* Color Indexes
|
||||
*
|
||||
***************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the default shade of colors in this palette.
|
||||
/// This has little meaning in this palette as colors are not strictly separated by shade.
|
||||
/// </summary>
|
||||
public const int DefaultShadeIndex = 0;
|
||||
|
||||
/***************************************************************************************
|
||||
*
|
||||
* Property Accessors
|
||||
*
|
||||
***************************************************************************************/
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// Palette
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of colors in this palette.
|
||||
/// A color is not necessarily a single value and may be composed of several shades.
|
||||
/// This has little meaning in this palette as colors are not strictly separated.
|
||||
/// </summary>
|
||||
public int ColorCount
|
||||
{
|
||||
get { return colorChart.GetLength(0); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of shades for each color in this palette.
|
||||
/// Shades are usually a variation of the color lightening or darkening it.
|
||||
/// This has little meaning in this palette as colors are not strictly separated by shade.
|
||||
/// </summary>
|
||||
public int ShadeCount
|
||||
{
|
||||
get { return colorChart.GetLength(1); }
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
*
|
||||
* Methods
|
||||
*
|
||||
***************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Gets a color in the palette by index.
|
||||
/// </summary>
|
||||
/// <param name="colorIndex">The index of the color in the palette.
|
||||
/// The index must be between zero and <see cref="ColorCount"/>.</param>
|
||||
/// <param name="shadeIndex">The index of the color shade in the palette.
|
||||
/// The index must be between zero and <see cref="ShadeCount"/>.</param>
|
||||
/// <returns>The color at the specified index or an exception.</returns>
|
||||
public Color GetColor(
|
||||
int colorIndex,
|
||||
int shadeIndex)
|
||||
{
|
||||
return colorChart[
|
||||
Math.Clamp(colorIndex, 0, colorChart.GetLength(0)),
|
||||
Math.Clamp(shadeIndex, 0, colorChart.GetLength(1))];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// 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 Windows.UI;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to define a color palette.
|
||||
/// </summary>
|
||||
public interface IColorPalette
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the total number of colors in this palette.
|
||||
/// A color is not necessarily a single value and may be composed of several shades.
|
||||
/// </summary>
|
||||
int ColorCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of shades for each color in this palette.
|
||||
/// Shades are usually a variation of the color lightening or darkening it.
|
||||
/// </summary>
|
||||
int ShadeCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a color in the palette by index.
|
||||
/// </summary>
|
||||
/// <param name="colorIndex">The index of the color in the palette.
|
||||
/// The index must be between zero and <see cref="ColorCount"/>.</param>
|
||||
/// <param name="shadeIndex">The index of the color shade in the palette.
|
||||
/// The index must be between zero and <see cref="ShadeCount"/>.</param>
|
||||
/// <returns>The color at the specified index or an exception.</returns>
|
||||
Color GetColor(int colorIndex, int shadeIndex);
|
||||
}
|
||||
}
|
|
@ -108,7 +108,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
SelectedTextDrawable?.UpdateBounds(_canvasTextBox.ActualWidth, _canvasTextBox.ActualHeight);
|
||||
}
|
||||
|
||||
private void CanvasTextBoxColorPicker_ColorChanged(ColorPicker sender, ColorChangedEventArgs args)
|
||||
private void CanvasTextBoxColorPicker_ColorChanged(Windows.UI.Xaml.Controls.ColorPicker sender, ColorChangedEventArgs args)
|
||||
{
|
||||
if (SelectedTextDrawable != null)
|
||||
{
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
/// InfiniteCanvas is a canvas that supports Ink, Text, Format Text, Zoom in/out, Redo, Undo, Export canvas data, Import canvas data.
|
||||
/// </summary>
|
||||
[TemplatePart(Name = CanvasTextBoxToolsName, Type = typeof(StackPanel))]
|
||||
[TemplatePart(Name = CanvasTextBoxColorPickerName, Type = typeof(ColorPicker))]
|
||||
[TemplatePart(Name = CanvasTextBoxColorPickerName, Type = typeof(Windows.UI.Xaml.Controls.ColorPicker))]
|
||||
[TemplatePart(Name = CanvasTextBoxFontSizeTextBoxName, Type = typeof(TextBox))]
|
||||
[TemplatePart(Name = CanvasTextBoxItalicButtonName, Type = typeof(ToggleButton))]
|
||||
[TemplatePart(Name = CanvasTextBoxBoldButtonName, Type = typeof(ToggleButton))]
|
||||
|
@ -69,7 +69,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
private InkToolbarCustomToggleButton _enableTouchInkingButton;
|
||||
private InfiniteCanvasTextBox _canvasTextBox;
|
||||
private StackPanel _canvasTextBoxTools;
|
||||
private ColorPicker _canvasTextBoxColorPicker;
|
||||
private Windows.UI.Xaml.Controls.ColorPicker _canvasTextBoxColorPicker;
|
||||
|
||||
private TextBox _canvasTextBoxFontSizeTextBox;
|
||||
private ToggleButton _canvasTextBoxItalicButton;
|
||||
|
@ -243,7 +243,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
protected override void OnApplyTemplate()
|
||||
{
|
||||
_canvasTextBoxTools = (StackPanel)GetTemplateChild(CanvasTextBoxToolsName);
|
||||
_canvasTextBoxColorPicker = (ColorPicker)GetTemplateChild(CanvasTextBoxColorPickerName);
|
||||
this._canvasTextBoxColorPicker = (Windows.UI.Xaml.Controls.ColorPicker)GetTemplateChild(CanvasTextBoxColorPickerName);
|
||||
_canvasTextBoxFontSizeTextBox = (TextBox)GetTemplateChild(CanvasTextBoxFontSizeTextBoxName);
|
||||
_canvasTextBoxItalicButton = (ToggleButton)GetTemplateChild(CanvasTextBoxItalicButtonName);
|
||||
_canvasTextBoxBoldButton = (ToggleButton)GetTemplateChild(CanvasTextBoxBoldButtonName);
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
<Grid Width="2"
|
||||
Height="48"
|
||||
Background="Black" />
|
||||
<!-- TODO: Replace this with ColorPickerButton -->
|
||||
<Button Padding="0,2,0,0"
|
||||
Style="{StaticResource CanvasTextBoxButtonStyle}"
|
||||
ToolTipService.ToolTip="Text Color">
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
- BladeView: Provides a horizontal collection of blades for master-detail scenarios.
|
||||
- CameraPreview: Easily preview video from camera sources and get realtime frames from the selected source.
|
||||
- Carousel: Presents items in a carousel control.
|
||||
- ColorPicker/ColorPickerButton: Improved ColorPicker and DropDownButton version.
|
||||
- DockPanel: Define areas where you can arrange child elements either horizontally or vertically, relative to each other.
|
||||
- DropShadowPanel: DropShadowPanel control allows the creation of a DropShadow for any Xaml FrameworkElement in markup.
|
||||
- Expander: Expander allows user to show/hide content based on a boolean state.
|
||||
|
@ -57,37 +58,13 @@
|
|||
<None Include="VisualStudioToolsManifest.xml" Pack="true" PackagePath="tools" />
|
||||
<None Include="$(OutDir)\Design\$(MSBuildProjectName).Design.dll;$(OutDir)\Design\$(MSBuildProjectName).Design.pdb" Pack="true" PackagePath="lib\$(TargetFramework)\Design" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="ImageCropper\ImageCropper.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="ImageCropper\ImageCropperThumb.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="RemoteDevicePicker\RemoteDevicePicker.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Update="TokenizingTextBox\TokenizingTextBox.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="TokenizingTextBox\TokenizingTextBoxItem.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Strings\en-us\Resources.resw" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<Import Project="$(MSBuildSDKExtrasTargets)" Condition="Exists('$(MSBuildSDKExtrasTargets)')" />
|
||||
|
||||
|
||||
<!-- https://weblogs.asp.net/rweigelt/disable-warnings-in-generated-c-files-of-uwp-app -->
|
||||
<Target Name="PragmaWarningDisablePrefixer" AfterTargets="MarkupCompilePass2">
|
||||
<ItemGroup>
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
// 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;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Markup;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Case"/> is the value container for the <see cref="SwitchPresenter"/>.
|
||||
/// </summary>
|
||||
[ContentProperty(Name = nameof(Content))]
|
||||
public class Case : DependencyObject
|
||||
{
|
||||
internal SwitchPresenter Parent { get; set; } // TODO: Can we remove Parent need here and just use events?
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the <see cref="Value"/> property changes.
|
||||
/// </summary>
|
||||
public event EventHandler ValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Content to display when this case is active.
|
||||
/// </summary>
|
||||
public UIElement Content
|
||||
{
|
||||
get { return (UIElement)GetValue(ContentProperty); }
|
||||
set { SetValue(ContentProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="Content"/> property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ContentProperty =
|
||||
DependencyProperty.Register(nameof(Content), typeof(UIElement), typeof(Case), new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this is the default case to display when no values match the specified value in the <see cref="SwitchPresenter"/>. There should only be a single default case provided. Do not set the <see cref="Value"/> property when setting <see cref="IsDefault"/> to <c>true</c>. Default is <c>false</c>.
|
||||
/// </summary>
|
||||
public bool IsDefault
|
||||
{
|
||||
get { return (bool)GetValue(IsDefaultProperty); }
|
||||
set { SetValue(IsDefaultProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="IsDefault"/> property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsDefaultProperty =
|
||||
DependencyProperty.Register(nameof(IsDefault), typeof(bool), typeof(Case), new PropertyMetadata(false));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Value"/> that this case represents. If it matches the <see cref="SwitchPresenter.Value"/> this case's <see cref="Content"/> will be displayed in the presenter.
|
||||
/// </summary>
|
||||
public object Value
|
||||
{
|
||||
get { return (object)GetValue(ValueProperty); }
|
||||
set { SetValue(ValueProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="Value"/> property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ValueProperty =
|
||||
DependencyProperty.Register(nameof(Value), typeof(object), typeof(Case), new PropertyMetadata(null, OnValuePropertyChanged));
|
||||
|
||||
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var xcase = (Case)d;
|
||||
|
||||
xcase.ValueChanged?.Invoke(xcase, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Case"/> class.
|
||||
/// </summary>
|
||||
public Case()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
// 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;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// An collection of <see cref="Case"/> to help with XAML interop.
|
||||
/// </summary>
|
||||
public class CaseCollection : IList<Case>, IEnumerable<Case> // TODO: Do we need this or can we use an ObservableCollection directly??? (Or is it useful to have it manage the registration of the child events?)
|
||||
{
|
||||
internal SwitchPresenter Parent { get; set; } // TODO: Can we remove Parent need here and just use events?
|
||||
|
||||
private readonly List<Case> _internalList = new List<Case>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Count => _internalList.Count;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Case this[int index] { get => _internalList[index]; set => Insert(index, value); }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when an animation has been added/removed or modified
|
||||
/// </summary>
|
||||
public event EventHandler CaseCollectionChanged;
|
||||
|
||||
private void ValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
CaseCollectionChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CaseCollection"/> class.
|
||||
/// </summary>
|
||||
public CaseCollection()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int IndexOf(Case item)
|
||||
{
|
||||
return _internalList.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Insert(int index, Case item)
|
||||
{
|
||||
item.ValueChanged += ValueChanged;
|
||||
item.Parent = Parent;
|
||||
_internalList.Insert(index, item);
|
||||
CaseCollectionChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
if (index >= 0 && index < _internalList.Count)
|
||||
{
|
||||
var xcase = _internalList[index];
|
||||
xcase.ValueChanged -= ValueChanged;
|
||||
xcase.Parent = null;
|
||||
}
|
||||
|
||||
_internalList.RemoveAt(index);
|
||||
CaseCollectionChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Add(Case item)
|
||||
{
|
||||
item.ValueChanged += ValueChanged;
|
||||
item.Parent = Parent;
|
||||
_internalList.Add(item);
|
||||
CaseCollectionChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var xcase in _internalList)
|
||||
{
|
||||
xcase.ValueChanged -= ValueChanged;
|
||||
xcase.Parent = null;
|
||||
}
|
||||
|
||||
_internalList.Clear();
|
||||
CaseCollectionChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Contains(Case item)
|
||||
{
|
||||
return _internalList.Contains(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CopyTo(Case[] array, int arrayIndex)
|
||||
{
|
||||
_internalList.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Remove(Case item)
|
||||
{
|
||||
var result = _internalList.Remove(item);
|
||||
if (result)
|
||||
{
|
||||
item.ValueChanged -= ValueChanged;
|
||||
item.Parent = null;
|
||||
CaseCollectionChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<Case> GetEnumerator()
|
||||
{
|
||||
return _internalList.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _internalList.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
// 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;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Markup;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="SwitchPresenter"/> is a <see cref="ContentPresenter"/> which can allow a developer to mimic a <c>switch</c> statement within XAML.
|
||||
/// When provided a set of <see cref="Case"/>s and a <see cref="Value"/>, it will pick the matching <see cref="Case"/> with the corresponding <see cref="Case.Value"/>.
|
||||
/// </summary>
|
||||
[ContentProperty(Name = nameof(SwitchCases))]
|
||||
public sealed class SwitchPresenter : ContentPresenter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current <see cref="Case"/> which is being displayed.
|
||||
/// </summary>
|
||||
public Case CurrentCase
|
||||
{
|
||||
get { return (Case)GetValue(CurrentCaseProperty); }
|
||||
private set { SetValue(CurrentCaseProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the <see cref="CurrentCase"/> property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty CurrentCaseProperty =
|
||||
DependencyProperty.Register(nameof(CurrentCase), typeof(Case), typeof(SwitchPresenter), new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value representing the collection of cases to evaluate.
|
||||
/// </summary>
|
||||
public CaseCollection SwitchCases
|
||||
{
|
||||
get { return (CaseCollection)GetValue(SwitchCasesProperty); }
|
||||
set { SetValue(SwitchCasesProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the <see cref="SwitchCases"/> property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty SwitchCasesProperty =
|
||||
DependencyProperty.Register(nameof(SwitchCases), typeof(CaseCollection), typeof(SwitchPresenter), new PropertyMetadata(null, new PropertyChangedCallback(OnSwitchCasesPropertyChanged)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the value to compare all cases against. When this value is bound to and changes, the presenter will automatically evaluate cases and select the new appropriate content from the switch.
|
||||
/// </summary>
|
||||
public object Value
|
||||
{
|
||||
get { return (object)GetValue(ValueProperty); }
|
||||
set { SetValue(ValueProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the <see cref="Value"/> property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ValueProperty =
|
||||
DependencyProperty.Register(nameof(Value), typeof(object), typeof(SwitchPresenter), new PropertyMetadata(null, new PropertyChangedCallback(OnValuePropertyChanged)));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating which type to first cast and compare provided values against.
|
||||
/// </summary>
|
||||
public Type TargetType
|
||||
{
|
||||
get { return (Type)GetValue(DataTypeProperty); }
|
||||
set { SetValue(DataTypeProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the <see cref="TargetType"/> property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DataTypeProperty =
|
||||
DependencyProperty.Register(nameof(TargetType), typeof(Type), typeof(SwitchPresenter), new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the content is removed from the visual tree when switching between cases.
|
||||
/// </summary>
|
||||
public bool IsVisualTreeDisconnectedOnChange { get; set; }
|
||||
|
||||
private static void OnSwitchCasesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.OldValue != null)
|
||||
{
|
||||
((SwitchPresenter)e.OldValue).SwitchCases.CaseCollectionChanged -= OnCaseValuePropertyChanged;
|
||||
}
|
||||
|
||||
var xswitch = (SwitchPresenter)d;
|
||||
|
||||
foreach (var xcase in xswitch.SwitchCases)
|
||||
{
|
||||
// Set our parent
|
||||
xcase.Parent = xswitch;
|
||||
}
|
||||
|
||||
// Will trigger on collection change and case value changed
|
||||
xswitch.SwitchCases.Parent = xswitch;
|
||||
xswitch.SwitchCases.CaseCollectionChanged += OnCaseValuePropertyChanged;
|
||||
}
|
||||
|
||||
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
// When our Switch's expression changes, re-evaluate.
|
||||
var xswitch = (SwitchPresenter)d;
|
||||
|
||||
xswitch.EvaluateCases();
|
||||
}
|
||||
|
||||
private static void OnCaseValuePropertyChanged(object sender, EventArgs e)
|
||||
{
|
||||
// When something about our collection of cases changes, re-evaluate.
|
||||
var collection = (CaseCollection)sender;
|
||||
|
||||
collection.Parent.EvaluateCases();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SwitchPresenter"/> class.
|
||||
/// </summary>
|
||||
public SwitchPresenter()
|
||||
{
|
||||
this.SwitchCases = new CaseCollection();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
EvaluateCases();
|
||||
}
|
||||
|
||||
private void EvaluateCases()
|
||||
{
|
||||
if (CurrentCase != null &&
|
||||
CurrentCase.Value != null &&
|
||||
CurrentCase.Value.Equals(Value))
|
||||
{
|
||||
// If the current case we're on already matches our current value,
|
||||
// then we don't have any work to do.
|
||||
return;
|
||||
}
|
||||
|
||||
Case xdefault = null;
|
||||
Case newcase = null;
|
||||
|
||||
foreach (var xcase in SwitchCases)
|
||||
{
|
||||
if (xcase.IsDefault)
|
||||
{
|
||||
// If there are multiple default cases provided, this will override and just grab the last one, the developer will have to fix this in their XAML. We call this out in the case comments.
|
||||
xdefault = xcase;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CompareValues(Value, xcase.Value))
|
||||
{
|
||||
newcase = xcase;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newcase == null && xdefault != null)
|
||||
{
|
||||
// Inject default if we found one.
|
||||
newcase = xdefault;
|
||||
}
|
||||
|
||||
// Only bother changing things around if we have a new case.
|
||||
if (newcase != CurrentCase)
|
||||
{
|
||||
// Disconnect old content from visual tree.
|
||||
if (CurrentCase != null && CurrentCase.Content != null && IsVisualTreeDisconnectedOnChange)
|
||||
{
|
||||
// TODO: If we disconnect here, we need to recreate later??? Need to Test...
|
||||
VisualTreeHelper.DisconnectChildrenRecursive(CurrentCase.Content);
|
||||
}
|
||||
|
||||
// Hookup new content.
|
||||
Content = newcase.Content;
|
||||
|
||||
CurrentCase = newcase;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two values using the TargetType.
|
||||
/// </summary>
|
||||
/// <param name="compare">Our main value in our SwitchPresenter.</param>
|
||||
/// <param name="value">The value from the case to compare to.</param>
|
||||
/// <returns>true if the two values are equal</returns>
|
||||
private bool CompareValues(object compare, object value)
|
||||
{
|
||||
if (compare == null || value == null)
|
||||
{
|
||||
return compare == value;
|
||||
}
|
||||
|
||||
if (TargetType == null ||
|
||||
(TargetType == compare.GetType() &&
|
||||
TargetType == value.GetType()))
|
||||
{
|
||||
// Default direct object comparison or we're all the proper type
|
||||
return compare.Equals(value);
|
||||
}
|
||||
else if (compare.GetType() == TargetType)
|
||||
{
|
||||
// If we have a TargetType and the first value is ther right type
|
||||
// Then our 2nd value isn't, so convert to string and coerce.
|
||||
var valueBase2 = ConvertValue(TargetType, value);
|
||||
|
||||
return compare.Equals(valueBase2);
|
||||
}
|
||||
|
||||
// Neither of our two values matches the type so
|
||||
// we'll convert both to a String and try and coerce it to the proper type.
|
||||
var compareBase = ConvertValue(TargetType, compare);
|
||||
|
||||
var valueBase = ConvertValue(TargetType, value);
|
||||
|
||||
return compareBase.Equals(valueBase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to convert a value from a source type to a target type.
|
||||
/// </summary>
|
||||
/// <param name="targetType">The target type</param>
|
||||
/// <param name="value">The value to convert</param>
|
||||
/// <returns>The converted value</returns>
|
||||
internal static object ConvertValue(Type targetType, object value)
|
||||
{
|
||||
if (targetType.IsInstanceOfType(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return XamlBindingHelper.ConvertValue(targetType, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,9 @@
|
|||
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/BladeView/BladeView.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/CameraPreview/CameraPreview.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/Carousel/Carousel.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/DropShadowPanel/DropShadowPanel.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/Expander/Expander.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter/GridSplitter.xaml" />
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
// 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;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the approximated display name for the color.
|
||||
/// </summary>
|
||||
public class ColorToDisplayNameConverter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public object Convert(
|
||||
object value,
|
||||
Type targetType,
|
||||
object parameter,
|
||||
string language)
|
||||
{
|
||||
Color color;
|
||||
|
||||
if (value is Color valueColor)
|
||||
{
|
||||
color = valueColor;
|
||||
}
|
||||
else if (value is SolidColorBrush valueBrush)
|
||||
{
|
||||
color = valueBrush.Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Invalid color value provided");
|
||||
}
|
||||
|
||||
return ColorHelper.ToDisplayName(color);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object ConvertBack(
|
||||
object value,
|
||||
Type targetType,
|
||||
object parameter,
|
||||
string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,9 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using Microsoft.Toolkit.Diagnostics;
|
||||
using Windows.UI;
|
||||
using Color = Windows.UI.Color;
|
||||
|
||||
|
@ -22,10 +24,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
|
|||
/// <returns>The created <see cref="Color"/>.</returns>
|
||||
public static Color ToColor(this string colorString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(colorString))
|
||||
{
|
||||
throw new ArgumentException(nameof(colorString));
|
||||
}
|
||||
Guard.IsNotNullOrEmpty(colorString, nameof(colorString));
|
||||
|
||||
if (colorString[0] == '#')
|
||||
{
|
||||
|
@ -80,8 +79,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
|
|||
return Color.FromArgb(255, r, g, b);
|
||||
}
|
||||
|
||||
default:
|
||||
throw new FormatException(string.Format("The {0} string passed in the colorString argument is not a recognized Color format.", colorString));
|
||||
default: return ThrowHelper.ThrowFormatException<Color>("The string passed in the colorString argument is not a recognized Color format.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,24 +89,24 @@ namespace Microsoft.Toolkit.Uwp.Helpers
|
|||
|
||||
if (values.Length == 4)
|
||||
{
|
||||
var scA = double.Parse(values[0].Substring(3));
|
||||
var scR = double.Parse(values[1]);
|
||||
var scG = double.Parse(values[2]);
|
||||
var scB = double.Parse(values[3]);
|
||||
var scA = double.Parse(values[0].Substring(3), CultureInfo.InvariantCulture);
|
||||
var scR = double.Parse(values[1], CultureInfo.InvariantCulture);
|
||||
var scG = double.Parse(values[2], CultureInfo.InvariantCulture);
|
||||
var scB = double.Parse(values[3], CultureInfo.InvariantCulture);
|
||||
|
||||
return Color.FromArgb((byte)(scA * 255), (byte)(scR * 255), (byte)(scG * 255), (byte)(scB * 255));
|
||||
}
|
||||
|
||||
if (values.Length == 3)
|
||||
{
|
||||
var scR = double.Parse(values[0].Substring(3));
|
||||
var scG = double.Parse(values[1]);
|
||||
var scB = double.Parse(values[2]);
|
||||
var scR = double.Parse(values[0].Substring(3), CultureInfo.InvariantCulture);
|
||||
var scG = double.Parse(values[1], CultureInfo.InvariantCulture);
|
||||
var scB = double.Parse(values[2], CultureInfo.InvariantCulture);
|
||||
|
||||
return Color.FromArgb(255, (byte)(scR * 255), (byte)(scG * 255), (byte)(scB * 255));
|
||||
}
|
||||
|
||||
throw new FormatException(string.Format("The {0} string passed in the colorString argument is not a recognized Color format (sc#[scA,]scR,scG,scB).", colorString));
|
||||
return ThrowHelper.ThrowFormatException<Color>("The string passed in the colorString argument is not a recognized Color format (sc#[scA,]scR,scG,scB).");
|
||||
}
|
||||
|
||||
var prop = typeof(Colors).GetTypeInfo().GetDeclaredProperty(colorString);
|
||||
|
@ -118,7 +116,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
|
|||
return (Color)prop.GetValue(null);
|
||||
}
|
||||
|
||||
throw new FormatException(string.Format("The {0} string passed in the colorString argument is not a recognized Color.", colorString));
|
||||
return ThrowHelper.ThrowFormatException<Color>("The string passed in the colorString argument is not a recognized Color.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
Двоичные данные
Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/LockScreenLogo.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 1.4 KiB |
Двоичные данные
Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/SplashScreen.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 7.5 KiB |
Двоичные данные
Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Square150x150Logo.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 2.9 KiB |
Двоичные данные
Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Square44x44Logo.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 1.6 KiB |
После Ширина: | Высота: | Размер: 1.2 KiB |
Двоичные данные
Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/StoreLogo.png
Normal file
После Ширина: | Высота: | Размер: 1.4 KiB |
Двоичные данные
Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject/Images/Wide310x150Logo.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 3.1 KiB |
|
@ -0,0 +1,80 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '15.0'">
|
||||
<VisualStudioVersion>15.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x86">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x86">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|AnyCPU">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|AnyCPU">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>54349ab0-9e41-4aa6-849c-ec9ce80cdd2a</ProjectGuid>
|
||||
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
|
||||
<EntryPointProjectUniqueName>..\Microsoft.Toolkit.Win32.WpfCore.SampleApp\Microsoft.Toolkit.Win32.WpfCore.SampleApp.csproj</EntryPointProjectUniqueName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Images\SplashScreen.scale-200.png" />
|
||||
<Content Include="Images\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Images\StoreLogo.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Toolkit.Win32.WpfCore.SampleApp\Microsoft.Toolkit.Win32.WpfCore.SampleApp.csproj">
|
||||
<SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
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 com desktop">
|
||||
|
||||
<Identity
|
||||
Name="2f4b322e-d4a6-4e18-ba97-a8ee9232d8e5"
|
||||
Publisher="CN=aleader"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject</DisplayName>
|
||||
<PublisherDisplayName>aleader</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject"
|
||||
Description="Microsoft.Toolkit.Win32.WpfCore.SampleApp.PackagingProject"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Images\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
|
||||
<Extensions>
|
||||
|
||||
<!--Register COM CLSID LocalServer32 registry key-->
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:ExeServer Executable="Microsoft.Toolkit.Win32.WpfCore.SampleApp\Microsoft.Toolkit.Win32.WpfCore.SampleApp.exe" Arguments="-ToastActivated" DisplayName="Toast activator">
|
||||
<com:Class Id="80cc4528-e348-11ea-87d0-0242ac130003" DisplayName="Toast activator"/>
|
||||
</com:ExeServer>
|
||||
</com:ComServer>
|
||||
</com:Extension>
|
||||
|
||||
<!--Specify which CLSID to activate when toast clicked-->
|
||||
<desktop:Extension Category="windows.toastNotificationActivation">
|
||||
<desktop:ToastNotificationActivation ToastActivatorCLSID="80cc4528-e348-11ea-87d0-0242ac130003" />
|
||||
</desktop:Extension>
|
||||
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
|
@ -0,0 +1,8 @@
|
|||
<Application x:Class="Microsoft.Toolkit.Win32.WpfCore.SampleApp.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Microsoft.Toolkit.Win32.WpfCore.SampleApp">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
|
@ -0,0 +1,151 @@
|
|||
// 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.Windows;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
|
||||
namespace Microsoft.Toolkit.Win32.WpfCore.SampleApp
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
// Listen to toast notification activations
|
||||
ToastNotificationManagerCompat.OnActivated += this.ToastNotificationManagerCompat_OnActivated;
|
||||
|
||||
// If launched from a toast
|
||||
if (ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
|
||||
{
|
||||
// Our OnActivated callback will run after this completes,
|
||||
// and will show a window if necessary.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show the window
|
||||
// In App.xaml, be sure to remove the StartupUri so that a window doesn't
|
||||
// get created by default, since we're creating windows ourselves (and sometimes we
|
||||
// don't want to create a window if handling a background activation).
|
||||
new MainWindow().Show();
|
||||
}
|
||||
}
|
||||
|
||||
private void ToastNotificationManagerCompat_OnActivated(ToastNotificationActivatedEventArgsCompat e)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
// If arguments are empty, that means the app title within Action Center was clicked.
|
||||
if (e.Argument.Length == 0)
|
||||
{
|
||||
OpenWindowIfNeeded();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the toast arguments
|
||||
ToastArguments args = ToastArguments.Parse(e.Argument);
|
||||
|
||||
int conversationId = args.GetInt("conversationId");
|
||||
|
||||
// If no specific action, view the conversation
|
||||
if (args.TryGetValue("action", out MyToastActions action))
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
// View conversation
|
||||
case MyToastActions.ViewConversation:
|
||||
|
||||
// Make sure we have a window open and in foreground
|
||||
OpenWindowIfNeeded();
|
||||
|
||||
// And then show the conversation
|
||||
(Current.Windows[0] as MainWindow).ShowConversation(conversationId);
|
||||
|
||||
break;
|
||||
|
||||
// Open the image
|
||||
case MyToastActions.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
|
||||
(Current.Windows[0] as MainWindow).ShowImage(imageUrl);
|
||||
|
||||
break;
|
||||
|
||||
// Background: Quick reply to the conversation
|
||||
case MyToastActions.Reply:
|
||||
|
||||
// Get the response the user typed
|
||||
string msg = e.UserInput["tbReply"] as string;
|
||||
|
||||
// And send this message
|
||||
ShowToast("Message sent: " + msg + "\nconversationId: " + conversationId);
|
||||
|
||||
// If there's no windows open, exit the app
|
||||
if (Current.Windows.Count == 0)
|
||||
{
|
||||
Current.Shutdown();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// Background: Send a like
|
||||
case MyToastActions.Like:
|
||||
|
||||
ShowToast($"Like sent to conversation {conversationId}!");
|
||||
|
||||
// If there's no windows open, exit the app
|
||||
if (Current.Windows.Count == 0)
|
||||
{
|
||||
Current.Shutdown();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
OpenWindowIfNeeded();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
// If your app has an installer, you should call this when your app is uninstalled. Otherwise, if your app is a "portable app" and you no longer need notifications while the app is closed, you can call this upon exit.
|
||||
// ToastNotificationManagerCompat.Uninstall();
|
||||
}
|
||||
|
||||
private void ShowToast(string msg)
|
||||
{
|
||||
// Construct the visuals of the toast and show it!
|
||||
new ToastContentBuilder()
|
||||
.AddText(msg)
|
||||
.Show();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// 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.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located (used if a resource is not found in the page, or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly) // where the generic resource dictionary is located (used if a resource is not found in the page, app, or any theme specific resource dictionaries)
|
||||
]
|
|
@ -0,0 +1,38 @@
|
|||
<Window x:Class="Microsoft.Toolkit.Win32.WpfCore.SampleApp.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Microsoft.Toolkit.Win32.WpfCore.SampleApp"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="450" Width="800">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="80"/>
|
||||
<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>
|
||||
|
||||
<ContentControl
|
||||
x:Name="ContentBody"
|
||||
Grid.Row="1"
|
||||
Margin="20"/>
|
||||
</Grid>
|
||||
</Window>
|
|
@ -0,0 +1,148 @@
|
|||
// 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;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
|
||||
namespace Microsoft.Toolkit.Win32.WpfCore.SampleApp
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// IMPORTANT: Look at App.xaml.cs for handling toast activation
|
||||
}
|
||||
|
||||
private async void ButtonPopToast_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string title = "Andrew sent you a picture";
|
||||
string content = "Check this out, The Enchantments!";
|
||||
string image = "https://picsum.photos/364/202?image=883";
|
||||
int conversationId = 5;
|
||||
|
||||
// Construct the toast content and show it!
|
||||
new ToastContentBuilder()
|
||||
|
||||
// Arguments that are returned when the user clicks the toast or a button
|
||||
.AddArgument("action", MyToastActions.ViewConversation)
|
||||
.AddArgument("conversationId", conversationId)
|
||||
|
||||
// Visual content
|
||||
.AddText(title)
|
||||
.AddText(content)
|
||||
.AddInlineImage(new Uri(await DownloadImageToDisk(image)))
|
||||
.AddAppLogoOverride(new Uri(await DownloadImageToDisk("https://unsplash.it/64?image=1005")), ToastGenericAppLogoCrop.Circle)
|
||||
|
||||
// Text box for typing a reply
|
||||
.AddInputTextBox("tbReply", "Type a reply")
|
||||
|
||||
// Buttons
|
||||
.AddButton(new ToastButton()
|
||||
.SetContent("Reply")
|
||||
.AddArgument("action", MyToastActions.Reply)
|
||||
.SetBackgroundActivation())
|
||||
|
||||
.AddButton(new ToastButton()
|
||||
.SetContent("Like")
|
||||
.AddArgument("action", MyToastActions.Like)
|
||||
.SetBackgroundActivation())
|
||||
|
||||
.AddButton(new ToastButton()
|
||||
.SetContent("View")
|
||||
.AddArgument("action", MyToastActions.ViewImage)
|
||||
.AddArgument("imageUrl", image))
|
||||
|
||||
// And show the toast!
|
||||
.Show();
|
||||
}
|
||||
|
||||
private static bool _hasPerformedCleanup;
|
||||
|
||||
private static async Task<string> DownloadImageToDisk(string httpImage)
|
||||
{
|
||||
// Toasts can live for up to 3 days, so we cache images for up to 3 days.
|
||||
// Note that this is a very simple cache that doesn't account for space usage, so
|
||||
// this could easily consume a lot of space within the span of 3 days.
|
||||
try
|
||||
{
|
||||
if (ToastNotificationManagerCompat.CanUseHttpImages)
|
||||
{
|
||||
return httpImage;
|
||||
}
|
||||
|
||||
var directory = Directory.CreateDirectory(System.IO.Path.GetTempPath() + "WindowsNotifications.DesktopToasts.Images");
|
||||
|
||||
if (!_hasPerformedCleanup)
|
||||
{
|
||||
// First time we run, we'll perform cleanup of old images
|
||||
_hasPerformedCleanup = true;
|
||||
|
||||
foreach (var d in directory.EnumerateDirectories())
|
||||
{
|
||||
if (d.CreationTimeUtc.Date < DateTime.UtcNow.Date.AddDays(-3))
|
||||
{
|
||||
d.Delete(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dayDirectory = directory.CreateSubdirectory(DateTime.UtcNow.Day.ToString());
|
||||
string imagePath = dayDirectory.FullName + "\\" + (uint)httpImage.GetHashCode();
|
||||
|
||||
if (File.Exists(imagePath))
|
||||
{
|
||||
return imagePath;
|
||||
}
|
||||
|
||||
HttpClient c = new HttpClient();
|
||||
using (var stream = await c.GetStreamAsync(httpImage))
|
||||
{
|
||||
using (var fileStream = File.OpenWrite(imagePath))
|
||||
{
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
return imagePath;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
internal void ShowConversation(int conversationId)
|
||||
{
|
||||
ContentBody.Content = new TextBlock()
|
||||
{
|
||||
Text = "You've just opened conversation " + conversationId,
|
||||
FontWeight = FontWeights.Bold
|
||||
};
|
||||
}
|
||||
|
||||
internal void ShowImage(string imageUrl)
|
||||
{
|
||||
ContentBody.Content = new Image()
|
||||
{
|
||||
Source = new BitmapImage(new Uri(imageUrl))
|
||||
};
|
||||
}
|
||||
|
||||
private void ButtonClearToasts_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ToastNotificationManagerCompat.History.Clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationIcon>ToolkitIcon.ico</ApplicationIcon>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Toolkit.Uwp.Notifications\Microsoft.Toolkit.Uwp.Notifications.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,29 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Toolkit.Win32.WpfCore.SampleApp
|
||||
{
|
||||
public enum MyToastActions
|
||||
{
|
||||
/// <summary>
|
||||
/// View the conversation
|
||||
/// </summary>
|
||||
ViewConversation,
|
||||
|
||||
/// <summary>
|
||||
/// Inline reply to a message
|
||||
/// </summary>
|
||||
Reply,
|
||||
|
||||
/// <summary>
|
||||
/// Like a message
|
||||
/// </summary>
|
||||
Like,
|
||||
|
||||
/// <summary>
|
||||
/// View the image included in the message
|
||||
/// </summary>
|
||||
ViewImage
|
||||
}
|
||||
}
|
После Ширина: | Высота: | Размер: 19 KiB |
|
@ -0,0 +1,7 @@
|
|||
<Application
|
||||
x:Class="SmokeTest.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:SmokeTest">
|
||||
|
||||
</Application>
|
|
@ -0,0 +1,106 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
|
||||
namespace SmokeTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public sealed partial class App : Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.Suspending += OnSuspending;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched normally by the end user. Other entry points
|
||||
/// will be used such as when the application is launched to open a specific file.
|
||||
/// </summary>
|
||||
/// <param name="e">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs e)
|
||||
{
|
||||
Frame rootFrame = Window.Current.Content as Frame;
|
||||
|
||||
// Do not repeat app initialization when the Window already has content,
|
||||
// just ensure that the window is active
|
||||
if (rootFrame == null)
|
||||
{
|
||||
// Create a Frame to act as the navigation context and navigate to the first page
|
||||
rootFrame = new Frame();
|
||||
|
||||
rootFrame.NavigationFailed += OnNavigationFailed;
|
||||
|
||||
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
|
||||
{
|
||||
// TODO: Load state from previously suspended application
|
||||
}
|
||||
|
||||
// Place the frame in the current Window
|
||||
Window.Current.Content = rootFrame;
|
||||
}
|
||||
|
||||
if (e.PrelaunchActivated == false)
|
||||
{
|
||||
if (rootFrame.Content == null)
|
||||
{
|
||||
// When the navigation stack isn't restored navigate to the first page,
|
||||
// configuring the new page by passing required information as a navigation
|
||||
// parameter
|
||||
rootFrame.Navigate(typeof(MainPage), e.Arguments);
|
||||
}
|
||||
|
||||
// Ensure the current window is active
|
||||
Window.Current.Activate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when Navigation to a certain page fails
|
||||
/// </summary>
|
||||
/// <param name="sender">The Frame which failed navigation</param>
|
||||
/// <param name="e">Details about the navigation failure</param>
|
||||
private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
{
|
||||
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when application execution is being suspended. Application state is saved
|
||||
/// without knowing whether the application will be terminated or resumed with the contents
|
||||
/// of memory still intact.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the suspend request.</param>
|
||||
/// <param name="e">Details about the suspend request.</param>
|
||||
private void OnSuspending(object sender, SuspendingEventArgs e)
|
||||
{
|
||||
var deferral = e.SuspendingOperation.GetDeferral();
|
||||
|
||||
// TODO: Save application state and stop any background activity
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
}
|
После Ширина: | Высота: | Размер: 1.4 KiB |