Merge branch 'master' into feature/tabbedcommandbar

This commit is contained in:
Yoshi Askharoun 2020-11-13 01:09:19 -06:00 коммит произвёл GitHub
Родитель 43dccca794 15edbed387
Коммит 94c5f4f1dc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
215 изменённых файлов: 15140 добавлений и 802 удалений

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

@ -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/>

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

@ -10,6 +10,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
namespace Microsoft.Toolkit.HighPerformance.Buffers
{
@ -233,7 +234,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <inheritdoc/>
public Memory<T> GetMemory(int sizeHint = 0)
{
CheckAndResizeBuffer(sizeHint);
CheckBufferAndEnsureCapacity(sizeHint);
return this.array.AsMemory(this.index);
}
@ -241,7 +242,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <inheritdoc/>
public Span<T> GetSpan(int sizeHint = 0)
{
CheckAndResizeBuffer(sizeHint);
CheckBufferAndEnsureCapacity(sizeHint);
return this.array.AsSpan(this.index);
}
@ -251,9 +252,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// </summary>
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="array"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CheckAndResizeBuffer(int sizeHint)
private void CheckBufferAndEnsureCapacity(int sizeHint)
{
if (this.array is null)
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
@ -268,14 +271,34 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
sizeHint = 1;
}
if (sizeHint > FreeCapacity)
if (sizeHint > array!.Length - this.index)
{
int minimumSize = this.index + sizeHint;
this.pool.Resize(ref this.array, minimumSize);
ResizeBuffer(sizeHint);
}
}
/// <summary>
/// Resizes <see cref="array"/> to ensure it can fit the specified number of new items.
/// </summary>
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="array"/>.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private void ResizeBuffer(int sizeHint)
{
int minimumSize = this.index + sizeHint;
// The ArrayPool<T> class has a maximum threshold of 1024 * 1024 for the maximum length of
// pooled arrays, and once this is exceeded it will just allocate a new array every time
// of exactly the requested size. In that case, we manually round up the requested size to
// the nearest power of two, to ensure that repeated consecutive writes when the array in
// use is bigger than that threshold don't end up causing a resize every single time.
if (minimumSize > 1024 * 1024)
{
minimumSize = BitOperations.RoundUpPowerOfTwo(minimumSize);
}
this.pool.Resize(ref this.array, minimumSize);
}
/// <inheritdoc/>
public void Dispose()
{

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

@ -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.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
using Microsoft.Toolkit.HighPerformance.Extensions;
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <typeparamref name="TFrom"/> array, to <typeparamref name="TTo"/> values.
/// </summary>
/// <typeparam name="TFrom">The source type of items to read.</typeparam>
/// <typeparam name="TTo">The target type to cast the source items to.</typeparam>
internal sealed class ArrayMemoryManager<TFrom, TTo> : MemoryManager<TTo>, IMemoryManager
where TFrom : unmanaged
where TTo : unmanaged
{
/// <summary>
/// The source <typeparamref name="TFrom"/> array to read data from.
/// </summary>
private readonly TFrom[] array;
/// <summary>
/// The starting offset within <see name="array"/>.
/// </summary>
private readonly int offset;
/// <summary>
/// The original used length for <see name="array"/>.
/// </summary>
private readonly int length;
/// <summary>
/// Initializes a new instance of the <see cref="ArrayMemoryManager{TFrom, TTo}"/> class.
/// </summary>
/// <param name="array">The source <typeparamref name="TFrom"/> array to read data from.</param>
/// <param name="offset">The starting offset within <paramref name="array"/>.</param>
/// <param name="length">The original used length for <paramref name="array"/>.</param>
public ArrayMemoryManager(TFrom[] array, int offset, int length)
{
this.array = array;
this.offset = offset;
this.length = length;
}
/// <inheritdoc/>
public override Span<TTo> GetSpan()
{
#if SPAN_RUNTIME_SUPPORT
ref TFrom r0 = ref this.array.DangerousGetReferenceAt(this.offset);
ref TTo r1 = ref Unsafe.As<TFrom, TTo>(ref r0);
int length = RuntimeHelpers.ConvertLength<TFrom, TTo>(this.length);
return MemoryMarshal.CreateSpan(ref r1, length);
#else
Span<TFrom> span = this.array.AsSpan(this.offset, this.length);
// We rely on MemoryMarshal.Cast here to deal with calculating the effective
// size of the new span to return. This will also make the behavior consistent
// for users that are both using this type as well as casting spans directly.
return MemoryMarshal.Cast<TFrom, TTo>(span);
#endif
}
/// <inheritdoc/>
public override unsafe MemoryHandle Pin(int elementIndex = 0)
{
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))
{
ThrowArgumentOutOfRangeExceptionForInvalidIndex();
}
int
bytePrefix = this.offset * Unsafe.SizeOf<TFrom>(),
byteSuffix = elementIndex * Unsafe.SizeOf<TTo>(),
byteOffset = bytePrefix + byteSuffix;
GCHandle handle = GCHandle.Alloc(this.array, GCHandleType.Pinned);
ref TFrom r0 = ref this.array.DangerousGetReference();
ref byte r1 = ref Unsafe.As<TFrom, byte>(ref r0);
ref byte r2 = ref Unsafe.Add(ref r1, byteOffset);
void* pi = Unsafe.AsPointer(ref r2);
return new MemoryHandle(pi, handle);
}
/// <inheritdoc/>
public override void Unpin()
{
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
/// <inheritdoc/>
public Memory<T> GetMemory<T>(int offset, int length)
where T : unmanaged
{
// We need to calculate the right offset and length of the new Memory<T>. The local offset
// is the original offset into the wrapped TFrom[] array, while the input offset is the one
// with respect to TTo items in the Memory<TTo> instance that is currently being cast.
int
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, TFrom>(offset),
absoluteLength = RuntimeHelpers.ConvertLength<TTo, TFrom>(length);
// We have a special handling in cases where the user is circling back to the original type
// of the wrapped array. In this case we can just return a memory wrapping that array directly,
// with offset and length being adjusted, without the memory manager indirection.
if (typeof(T) == typeof(TFrom))
{
return (Memory<T>)(object)this.array.AsMemory(absoluteOffset, absoluteLength);
}
return new ArrayMemoryManager<TFrom, T>(this.array, absoluteOffset, absoluteLength).Memory;
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the target index for <see cref="Pin"/> is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForInvalidIndex()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range");
}
}
}

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

@ -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.
using System;
using System.Buffers;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces
{
/// <summary>
/// An interface for a <see cref="MemoryManager{T}"/> instance that can reinterpret its underlying data.
/// </summary>
internal interface IMemoryManager
{
/// <summary>
/// Creates a new <see cref="Memory{T}"/> that reinterprets the underlying data for the current instance.
/// </summary>
/// <typeparam name="T">The target type to cast the items to.</typeparam>
/// <param name="offset">The starting offset within the data store.</param>
/// <param name="length">The original used length for the data store.</param>
/// <returns>A new <see cref="Memory{T}"/> instance of the specified type, reinterpreting the current items.</returns>
Memory<T> GetMemory<T>(int offset, int length)
where T : unmanaged;
}
}

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

@ -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.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <see cref="MemoryManager{T}"/> of <typeparamref name="TFrom"/>, to <typeparamref name="TTo"/> values.
/// </summary>
/// <typeparam name="TFrom">The source type of items to read.</typeparam>
/// <typeparam name="TTo">The target type to cast the source items to.</typeparam>
internal sealed class ProxyMemoryManager<TFrom, TTo> : MemoryManager<TTo>, IMemoryManager
where TFrom : unmanaged
where TTo : unmanaged
{
/// <summary>
/// The source <see cref="MemoryManager{T}"/> to read data from.
/// </summary>
private readonly MemoryManager<TFrom> memoryManager;
/// <summary>
/// The starting offset within <see name="memoryManager"/>.
/// </summary>
private readonly int offset;
/// <summary>
/// The original used length for <see name="memoryManager"/>.
/// </summary>
private readonly int length;
/// <summary>
/// Initializes a new instance of the <see cref="ProxyMemoryManager{TFrom, TTo}"/> class.
/// </summary>
/// <param name="memoryManager">The source <see cref="MemoryManager{T}"/> to read data from.</param>
/// <param name="offset">The starting offset within <paramref name="memoryManager"/>.</param>
/// <param name="length">The original used length for <paramref name="memoryManager"/>.</param>
public ProxyMemoryManager(MemoryManager<TFrom> memoryManager, int offset, int length)
{
this.memoryManager = memoryManager;
this.offset = offset;
this.length = length;
}
/// <inheritdoc/>
public override Span<TTo> GetSpan()
{
Span<TFrom> span = this.memoryManager.GetSpan().Slice(this.offset, this.length);
return MemoryMarshal.Cast<TFrom, TTo>(span);
}
/// <inheritdoc/>
public override MemoryHandle Pin(int elementIndex = 0)
{
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))
{
ThrowArgumentExceptionForInvalidIndex();
}
int
bytePrefix = this.offset * Unsafe.SizeOf<TFrom>(),
byteSuffix = elementIndex * Unsafe.SizeOf<TTo>(),
byteOffset = bytePrefix + byteSuffix;
#if NETSTANDARD1_4
int
shiftedOffset = byteOffset / Unsafe.SizeOf<TFrom>(),
remainder = byteOffset - (shiftedOffset * Unsafe.SizeOf<TFrom>());
#else
int shiftedOffset = Math.DivRem(byteOffset, Unsafe.SizeOf<TFrom>(), out int remainder);
#endif
if (remainder != 0)
{
ThrowArgumentExceptionForInvalidAlignment();
}
return this.memoryManager.Pin(shiftedOffset);
}
/// <inheritdoc/>
public override void Unpin()
{
this.memoryManager.Unpin();
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
((IDisposable)this.memoryManager).Dispose();
}
/// <inheritdoc/>
public Memory<T> GetMemory<T>(int offset, int length)
where T : unmanaged
{
// Like in the other memory manager, calculate the absolute offset and length
int
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, TFrom>(offset),
absoluteLength = RuntimeHelpers.ConvertLength<TTo, TFrom>(length);
// Skip one indirection level and slice the original memory manager, if possible
if (typeof(T) == typeof(TFrom))
{
return (Memory<T>)(object)this.memoryManager.Memory.Slice(absoluteOffset, absoluteLength);
}
return new ProxyMemoryManager<TFrom, T>(this.memoryManager, absoluteOffset, absoluteLength).Memory;
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the target index for <see cref="Pin"/> is invalid.
/// </summary>
private static void ThrowArgumentExceptionForInvalidIndex()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Pin"/> receives an invalid target index.
/// </summary>
private static void ThrowArgumentExceptionForInvalidAlignment()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input index doesn't result in an aligned item access");
}
}
}

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

@ -0,0 +1,126 @@
// 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.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
using Microsoft.Toolkit.HighPerformance.Extensions;
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <see cref="string"/> to <typeparamref name="TTo"/> values.
/// </summary>
/// <typeparam name="TTo">The target type to cast the source characters to.</typeparam>
internal sealed class StringMemoryManager<TTo> : MemoryManager<TTo>, IMemoryManager
where TTo : unmanaged
{
/// <summary>
/// The source <see cref="string"/> to read data from.
/// </summary>
private readonly string text;
/// <summary>
/// The starting offset within <see name="array"/>.
/// </summary>
private readonly int offset;
/// <summary>
/// The original used length for <see name="array"/>.
/// </summary>
private readonly int length;
/// <summary>
/// Initializes a new instance of the <see cref="StringMemoryManager{T}"/> class.
/// </summary>
/// <param name="text">The source <see cref="string"/> to read data from.</param>
/// <param name="offset">The starting offset within <paramref name="text"/>.</param>
/// <param name="length">The original used length for <paramref name="text"/>.</param>
public StringMemoryManager(string text, int offset, int length)
{
this.text = text;
this.offset = offset;
this.length = length;
}
/// <inheritdoc/>
public override Span<TTo> GetSpan()
{
#if SPAN_RUNTIME_SUPPORT
ref char r0 = ref this.text.DangerousGetReferenceAt(this.offset);
ref TTo r1 = ref Unsafe.As<char, TTo>(ref r0);
int length = RuntimeHelpers.ConvertLength<char, TTo>(this.length);
return MemoryMarshal.CreateSpan(ref r1, length);
#else
ReadOnlyMemory<char> memory = this.text.AsMemory(this.offset, this.length);
Span<char> span = MemoryMarshal.AsMemory(memory).Span;
return MemoryMarshal.Cast<char, TTo>(span);
#endif
}
/// <inheritdoc/>
public override unsafe MemoryHandle Pin(int elementIndex = 0)
{
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<char>() / Unsafe.SizeOf<TTo>()))
{
ThrowArgumentOutOfRangeExceptionForInvalidIndex();
}
int
bytePrefix = this.offset * Unsafe.SizeOf<char>(),
byteSuffix = elementIndex * Unsafe.SizeOf<TTo>(),
byteOffset = bytePrefix + byteSuffix;
GCHandle handle = GCHandle.Alloc(this.text, GCHandleType.Pinned);
ref char r0 = ref this.text.DangerousGetReference();
ref byte r1 = ref Unsafe.As<char, byte>(ref r0);
ref byte r2 = ref Unsafe.Add(ref r1, byteOffset);
void* pi = Unsafe.AsPointer(ref r2);
return new MemoryHandle(pi, handle);
}
/// <inheritdoc/>
public override void Unpin()
{
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
/// <inheritdoc/>
public Memory<T> GetMemory<T>(int offset, int length)
where T : unmanaged
{
int
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, char>(offset),
absoluteLength = RuntimeHelpers.ConvertLength<TTo, char>(length);
if (typeof(T) == typeof(char))
{
ReadOnlyMemory<char> memory = this.text.AsMemory(absoluteOffset, absoluteLength);
return (Memory<T>)(object)MemoryMarshal.AsMemory(memory);
}
return new StringMemoryManager<T>(this.text, absoluteOffset, absoluteLength).Memory;
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the target index for <see cref="Pin"/> is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForInvalidIndex()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range");
}
}
}

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

@ -7,6 +7,9 @@ using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if NETCORE_RUNTIME
using System.Runtime.InteropServices;
#endif
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
using Microsoft.Toolkit.HighPerformance.Extensions;
@ -180,7 +183,22 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
ThrowObjectDisposedException();
}
#if NETCORE_RUNTIME
ref T r0 = ref array!.DangerousGetReferenceAt(this.start);
// On .NET Core runtimes, we can manually create a span from the starting reference to
// skip the argument validations, which include an explicit null check, covariance check
// for the array and the actual validation for the starting offset and target length. We
// only do this on .NET Core as we can leverage the runtime-specific array layout to get
// a fast access to the initial element, which makes this trick worth it. Otherwise, on
// runtimes where we would need to at least access a static field to retrieve the base
// byte offset within an SZ array object, we can get better performance by just using the
// default Span<T> constructor and paying the cost of the extra conditional branches,
// especially if T is a value type, in which case the covariance check is JIT removed.
return MemoryMarshal.CreateSpan(ref r0, this.length);
#else
return new Span<T>(array!, this.start, this.length);
#endif
}
}
@ -208,6 +226,31 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
return ref array!.DangerousGetReferenceAt(this.start);
}
/// <summary>
/// Gets an <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.
/// </summary>
/// <returns>An <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.</returns>
/// <exception cref="ObjectDisposedException">Thrown when the buffer in use has already been disposed.</exception>
/// <remarks>
/// This method is meant to be used when working with APIs that only accept an array as input, and should be used with caution.
/// In particular, the returned array is rented from an array pool, and it is responsibility of the caller to ensure that it's
/// not used after the current <see cref="MemoryOwner{T}"/> instance is disposed. Doing so is considered undefined behavior,
/// as the same array might be in use within another <see cref="MemoryOwner{T}"/> instance.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArraySegment<T> DangerousGetArray()
{
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
return new ArraySegment<T>(array!, this.start, this.length);
}
/// <summary>
/// Slices the buffer currently in use and returns a new <see cref="MemoryOwner{T}"/> instance.
/// </summary>
@ -222,7 +265,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// size and copy the previous items into the new one, or needing an additional variable/field
/// to manually handle to track the used range within a given <see cref="MemoryOwner{T}"/> instance.
/// </remarks>
[Pure]
public MemoryOwner<T> Slice(int start, int length)
{
T[]? array = this.array;
@ -244,6 +286,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
ThrowInvalidLengthException();
}
// We're transferring the ownership of the underlying array, so the current
// instance no longer needs to be disposed. Because of this, we can manually
// suppress the finalizer to reduce the overhead on the garbage collector.
GC.SuppressFinalize(this);
return new MemoryOwner<T>(start, length, this.pool, array!);
}

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

@ -7,6 +7,9 @@ using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if NETCORE_RUNTIME
using System.Runtime.InteropServices;
#endif
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
using Microsoft.Toolkit.HighPerformance.Extensions;
@ -143,7 +146,16 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
public Span<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new Span<T>(this.array, 0, this.length);
get
{
#if NETCORE_RUNTIME
ref T r0 = ref array!.DangerousGetReference();
return MemoryMarshal.CreateSpan(ref r0, this.length);
#else
return new Span<T>(this.array, 0, this.length);
#endif
}
}
/// <summary>
@ -157,6 +169,23 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
return ref this.array.DangerousGetReference();
}
/// <summary>
/// Gets an <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.
/// </summary>
/// <returns>An <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.</returns>
/// <remarks>
/// This method is meant to be used when working with APIs that only accept an array as input, and should be used with caution.
/// In particular, the returned array is rented from an array pool, and it is responsibility of the caller to ensure that it's
/// not used after the current <see cref="SpanOwner{T}"/> instance is disposed. Doing so is considered undefined behavior,
/// as the same array might be in use within another <see cref="SpanOwner{T}"/> instance.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArraySegment<T> DangerousGetArray()
{
return new ArraySegment<T>(array!, 0, this.length);
}
/// <summary>
/// Implements the duck-typed <see cref="IDisposable.Dispose"/> method.
/// </summary>

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

@ -5,15 +5,13 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
#if NETCOREAPP3_1
using System.Numerics;
#endif
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.Toolkit.HighPerformance.Extensions;
#if !NETSTANDARD1_4
using Microsoft.Toolkit.HighPerformance.Helpers;
#endif
using BitOperations = Microsoft.Toolkit.HighPerformance.Helpers.Internals.BitOperations;
#nullable enable
@ -79,8 +77,8 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
a = Math.Sqrt((double)size / factor),
b = factor * a;
x = RoundUpPowerOfTwo((int)a);
y = RoundUpPowerOfTwo((int)b);
x = BitOperations.RoundUpPowerOfTwo((int)a);
y = BitOperations.RoundUpPowerOfTwo((int)b);
}
// We want to find two powers of 2 factors that produce a number
@ -130,30 +128,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
Size = p2;
}
/// <summary>
/// Rounds up an <see cref="int"/> value to a power of 2.
/// </summary>
/// <param name="x">The input value to round up.</param>
/// <returns>The smallest power of two greater than or equal to <paramref name="x"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int RoundUpPowerOfTwo(int x)
{
#if NETCOREAPP3_1
return 1 << (32 - BitOperations.LeadingZeroCount((uint)(x - 1)));
#else
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x++;
return x;
#endif
}
/// <summary>
/// Gets the shared <see cref="StringPool"/> instance.
/// </summary>
@ -422,11 +396,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 +417,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 +438,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 +462,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 +501,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 +521,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;

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

@ -6,6 +6,7 @@ using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Memory;
#endif
@ -64,6 +65,41 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
}
#endif
/// <summary>
/// Casts a <see cref="Memory{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="Memory{T}"/> of bytes.
/// </summary>
/// <typeparam name="T">The type if items in the source <see cref="Memory{T}"/>.</typeparam>
/// <param name="memory">The source <see cref="Memory{T}"/>, of type <typeparamref name="T"/>.</param>
/// <returns>A <see cref="Memory{T}"/> of bytes.</returns>
/// <exception cref="OverflowException">
/// Thrown if the <see cref="Memory{T}.Length"/> property of the new <see cref="Memory{T}"/> would exceed <see cref="int.MaxValue"/>.
/// </exception>
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<byte> AsBytes<T>(this Memory<T> memory)
where T : unmanaged
{
return MemoryMarshal.AsMemory(((ReadOnlyMemory<T>)memory).Cast<T, byte>());
}
/// <summary>
/// Casts a <see cref="Memory{T}"/> of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
/// </summary>
/// <typeparam name="TFrom">The type of items in the source <see cref="Memory{T}"/>.</typeparam>
/// <typeparam name="TTo">The type of items in the destination <see cref="Memory{T}"/>.</typeparam>
/// <param name="memory">The source <see cref="Memory{T}"/>, of type <typeparamref name="TFrom"/>.</param>
/// <returns>A <see cref="Memory{T}"/> of type <typeparamref name="TTo"/></returns>
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<TTo> Cast<TFrom, TTo>(this Memory<TFrom> memory)
where TFrom : unmanaged
where TTo : unmanaged
{
return MemoryMarshal.AsMemory(((ReadOnlyMemory<TFrom>)memory).Cast<TFrom, TTo>());
}
/// <summary>
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="Memory{T}"/> of <see cref="byte"/> instance.
/// </summary>

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

@ -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);

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

@ -3,9 +3,13 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Memory;
#endif
@ -64,6 +68,77 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
}
#endif
/// <summary>
/// Casts a <see cref="ReadOnlyMemory{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="ReadOnlyMemory{T}"/> of bytes.
/// </summary>
/// <typeparam name="T">The type if items in the source <see cref="ReadOnlyMemory{T}"/>.</typeparam>
/// <param name="memory">The source <see cref="ReadOnlyMemory{T}"/>, of type <typeparamref name="T"/>.</param>
/// <returns>A <see cref="ReadOnlyMemory{T}"/> of bytes.</returns>
/// <exception cref="OverflowException">
/// Thrown if the <see cref="ReadOnlyMemory{T}.Length"/> property of the new <see cref="ReadOnlyMemory{T}"/> would exceed <see cref="int.MaxValue"/>.
/// </exception>
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlyMemory<byte> AsBytes<T>(this ReadOnlyMemory<T> memory)
where T : unmanaged
{
return Cast<T, byte>(memory);
}
/// <summary>
/// Casts a <see cref="ReadOnlyMemory{T}"/> of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
/// </summary>
/// <typeparam name="TFrom">The type of items in the source <see cref="ReadOnlyMemory{T}"/>.</typeparam>
/// <typeparam name="TTo">The type of items in the destination <see cref="ReadOnlyMemory{T}"/>.</typeparam>
/// <param name="memory">The source <see cref="ReadOnlyMemory{T}"/>, of type <typeparamref name="TFrom"/>.</param>
/// <returns>A <see cref="ReadOnlyMemory{T}"/> of type <typeparamref name="TTo"/></returns>
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlyMemory<TTo> Cast<TFrom, TTo>(this ReadOnlyMemory<TFrom> memory)
where TFrom : unmanaged
where TTo : unmanaged
{
if (memory.IsEmpty)
{
return default;
}
if (typeof(TFrom) == typeof(char) &&
MemoryMarshal.TryGetString((ReadOnlyMemory<char>)(object)memory, out string? text, out int start, out int length))
{
return new StringMemoryManager<TTo>(text!, start, length).Memory;
}
if (MemoryMarshal.TryGetArray(memory, out ArraySegment<TFrom> segment))
{
return new ArrayMemoryManager<TFrom, TTo>(segment.Array!, segment.Offset, segment.Count).Memory;
}
if (MemoryMarshal.TryGetMemoryManager<TFrom, MemoryManager<TFrom>>(memory, out var memoryManager, out start, out length))
{
// If the memory manager is the one resulting from a previous cast, we can use it directly to retrieve
// a new manager for the target type that wraps the original data store, instead of creating one that
// wraps the current manager. This ensures that doing repeated casts always results in only up to one
// indirection level in the chain of memory managers needed to access the target data buffer to use.
if (memoryManager is IMemoryManager wrappingManager)
{
return wrappingManager.GetMemory<TTo>(start, length);
}
return new ProxyMemoryManager<TFrom, TTo>(memoryManager, start, length).Memory;
}
// Throws when the memory instance has an unsupported backing store
static ReadOnlyMemory<TTo> ThrowArgumentExceptionForUnsupportedMemory()
{
throw new ArgumentException("The input instance doesn't have a supported underlying data store.");
}
return ThrowArgumentExceptionForUnsupportedMemory();
}
/// <summary>
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="ReadOnlyMemory{T}"/> of <see cref="byte"/> instance.
/// </summary>

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

@ -259,14 +259,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <summary>
/// Casts a <see cref="ReadOnlySpan{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="ReadOnlySpan{T}"/> of bytes.
/// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
/// </summary>
/// <typeparam name="T">The type if items in the source <see cref="ReadOnlySpan{T}"/>.</typeparam>
/// <param name="span">The source slice, of type <typeparamref name="T"/>.</param>
/// <returns>A <see cref="ReadOnlySpan{T}"/> of bytes.</returns>
/// <exception cref="ArgumentException">
/// Thrown when <typeparamref name="T"/> contains pointers.
/// </exception>
/// <exception cref="OverflowException">
/// Thrown if the <see cref="ReadOnlySpan{T}.Length"/> property of the new <see cref="ReadOnlySpan{T}"/> would exceed <see cref="int.MaxValue"/>.
/// </exception>
@ -280,7 +276,6 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <summary>
/// Casts a <see cref="ReadOnlySpan{T}"/> of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
/// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety.
/// </summary>
/// <typeparam name="TFrom">The type of items in the source <see cref="ReadOnlySpan{T}"/>.</typeparam>
/// <typeparam name="TTo">The type of items in the destination <see cref="ReadOnlySpan{T}"/>.</typeparam>
@ -289,14 +284,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>
/// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
/// </remarks>
/// <exception cref="ArgumentException">
/// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(this ReadOnlySpan<TFrom> span)
where TFrom : struct
where TTo : struct
where TFrom : unmanaged
where TTo : unmanaged
{
return MemoryMarshal.Cast<TFrom, TTo>(span);
}

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

@ -117,14 +117,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <summary>
/// Casts a <see cref="Span{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="Span{T}"/> of bytes.
/// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
/// </summary>
/// <typeparam name="T">The type if items in the source <see cref="Span{T}"/>.</typeparam>
/// <param name="span">The source slice, of type <typeparamref name="T"/>.</param>
/// <returns>A <see cref="Span{T}"/> of bytes.</returns>
/// <exception cref="ArgumentException">
/// Thrown when <typeparamref name="T"/> contains pointers.
/// </exception>
/// <exception cref="OverflowException">
/// Thrown if the <see cref="Span{T}.Length"/> property of the new <see cref="Span{T}"/> would exceed <see cref="int.MaxValue"/>.
/// </exception>
@ -138,7 +134,6 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <summary>
/// Casts a <see cref="Span{T}"/> of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
/// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety.
/// </summary>
/// <typeparam name="TFrom">The type of items in the source <see cref="Span{T}"/>.</typeparam>
/// <typeparam name="TTo">The type of items in the destination <see cref="Span{T}"/>.</typeparam>
@ -147,14 +142,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>
/// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
/// </remarks>
/// <exception cref="ArgumentException">
/// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<TTo> Cast<TFrom, TTo>(this Span<TFrom> span)
where TFrom : struct
where TTo : struct
where TFrom : unmanaged
where TTo : unmanaged
{
return MemoryMarshal.Cast<TFrom, TTo>(span);
}

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

@ -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

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

@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if NETCOREAPP3_1
using static System.Numerics.BitOperations;
#endif
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
{
/// <summary>
/// Utility methods for intrinsic bit-twiddling operations. The methods use hardware intrinsics
/// when available on the underlying platform, otherwise they use optimized software fallbacks.
/// </summary>
internal static class BitOperations
{
/// <summary>
/// Rounds up an <see cref="int"/> value to a power of 2.
/// </summary>
/// <param name="x">The input value to round up.</param>
/// <returns>The smallest power of two greater than or equal to <paramref name="x"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RoundUpPowerOfTwo(int x)
{
#if NETCOREAPP3_1
return 1 << (32 - LeadingZeroCount((uint)(x - 1)));
#else
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x++;
return x;
#endif
}
}
}

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

@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// 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.
@ -18,10 +18,40 @@ using Microsoft.Toolkit.HighPerformance.Extensions;
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
{
/// <summary>
/// A helper class that act as polyfill for .NET Standard 2.0 and below.
/// A helper class that with utility methods for dealing with references, and other low-level details.
/// It also contains some APIs that act as polyfills for .NET Standard 2.0 and below.
/// </summary>
internal static class RuntimeHelpers
{
/// <summary>
/// Converts a length of items from one size to another (rounding towards zero).
/// </summary>
/// <typeparam name="TFrom">The source type of items.</typeparam>
/// <typeparam name="TTo">The target type of items.</typeparam>
/// <param name="length">The input length to convert.</param>
/// <returns>The converted length for the specified argument and types.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int ConvertLength<TFrom, TTo>(int length)
where TFrom : unmanaged
where TTo : unmanaged
{
if (sizeof(TFrom) == sizeof(TTo))
{
return length;
}
else if (sizeof(TFrom) == 1)
{
return length / sizeof(TTo);
}
else
{
ulong targetLength = (ulong)(uint)length * (uint)sizeof(TFrom) / (uint)sizeof(TTo);
return checked((int)targetLength);
}
}
/// <summary>
/// Gets the length of a given array as a native integer.
/// </summary>

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

@ -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>

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

@ -27,25 +27,39 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
public event PropertyChangingEventHandler? PropertyChanging;
/// <summary>
/// Performs the required configuration when a property has changed, and then
/// raises the <see cref="PropertyChanged"/> event to notify listeners of the update.
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <remarks>The base implementation only raises the <see cref="PropertyChanged"/> event.</remarks>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
/// <param name="e">The input <see cref="PropertyChangedEventArgs"/> instance.</param>
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
PropertyChanged?.Invoke(this, e);
}
/// <summary>
/// Performs the required configuration when a property is changing, and then
/// raises the <see cref="PropertyChanged"/> event to notify listeners of the update.
/// Raises the <see cref="PropertyChanging"/> event.
/// </summary>
/// <param name="e">The input <see cref="PropertyChangingEventArgs"/> instance.</param>
protected virtual void OnPropertyChanging(PropertyChangingEventArgs e)
{
PropertyChanging?.Invoke(this, e);
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <remarks>The base implementation only raises the <see cref="PropertyChanging"/> event.</remarks>
protected virtual void OnPropertyChanging([CallerMemberName] string? propertyName = null)
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Raises the <see cref="PropertyChanging"/> event.
/// </summary>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
protected void OnPropertyChanging([CallerMemberName] string? propertyName = null)
{
OnPropertyChanging(new PropertyChangingEventArgs(propertyName));
}
/// <summary>

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

@ -19,33 +19,28 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </summary>
public abstract class ObservableValidator : ObservableObject, INotifyDataErrorInfo
{
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="HasErrors"/>.
/// </summary>
private static readonly PropertyChangedEventArgs HasErrorsChangedEventArgs = new PropertyChangedEventArgs(nameof(HasErrors));
/// <summary>
/// The <see cref="Dictionary{TKey,TValue}"/> instance used to store previous validation results.
/// </summary>
private readonly Dictionary<string, List<ValidationResult>> errors = new Dictionary<string, List<ValidationResult>>();
/// <summary>
/// Indicates the total number of properties with errors (not total errors).
/// This is used to allow <see cref="HasErrors"/> to operate in O(1) time, as it can just
/// check whether this value is not 0 instead of having to traverse <see cref="errors"/>.
/// </summary>
private int totalErrors;
/// <inheritdoc/>
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
/// <inheritdoc/>
public bool HasErrors
{
get
{
// This uses the value enumerator for Dictionary<TKey, TValue>.ValueCollection, so it doesn't
// allocate. Accessing this property is O(n), but we can stop as soon as we find at least one
// error in the whole entity, and doing this saves 8 bytes in the object size (no fields needed).
foreach (var value in this.errors.Values)
{
if (value.Count > 0)
{
return true;
}
}
return false;
}
}
public bool HasErrors => this.totalErrors > 0;
/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
@ -67,12 +62,14 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </remarks>
protected bool SetProperty<T>(ref T field, T newValue, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
bool propertyChanged = SetProperty(ref field, newValue, propertyName);
if (propertyChanged && validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(ref field, newValue, propertyName);
return propertyChanged;
}
/// <summary>
@ -90,12 +87,14 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(ref T field, T newValue, IEqualityComparer<T> comparer, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
bool propertyChanged = SetProperty(ref field, newValue, comparer, propertyName);
if (propertyChanged && validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(ref field, newValue, comparer, propertyName);
return propertyChanged;
}
/// <summary>
@ -120,12 +119,14 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </remarks>
protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
bool propertyChanged = SetProperty(oldValue, newValue, callback, propertyName);
if (propertyChanged && validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, callback, propertyName);
return propertyChanged;
}
/// <summary>
@ -144,12 +145,14 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
bool propertyChanged = SetProperty(oldValue, newValue, comparer, callback, propertyName);
if (propertyChanged && validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, comparer, callback, propertyName);
return propertyChanged;
}
/// <summary>
@ -172,12 +175,14 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string? propertyName = null)
where TModel : class
{
if (validate)
bool propertyChanged = SetProperty(oldValue, newValue, model, callback, propertyName);
if (propertyChanged && validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, model, callback, propertyName);
return propertyChanged;
}
/// <summary>
@ -202,12 +207,123 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string? propertyName = null)
where TModel : class
{
if (validate)
bool propertyChanged = SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
if (propertyChanged && validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
return propertyChanged;
}
/// <summary>
/// Tries to validate a new value for a specified property. If the validation is successful,
/// <see cref="ObservableObject.SetProperty{T}(ref T,T,string?)"/> is called, otherwise no state change is performed.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
protected bool TrySetProperty<T>(ref T field, T newValue, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string? propertyName = null)
{
return TryValidateProperty(newValue, propertyName, out errors) &&
SetProperty(ref field, newValue, propertyName);
}
/// <summary>
/// Tries to validate a new value for a specified property. If the validation is successful,
/// <see cref="ObservableObject.SetProperty{T}(ref T,T,IEqualityComparer{T},string?)"/> is called, otherwise no state change is performed.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
protected bool TrySetProperty<T>(ref T field, T newValue, IEqualityComparer<T> comparer, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string? propertyName = null)
{
return TryValidateProperty(newValue, propertyName, out errors) &&
SetProperty(ref field, newValue, comparer, propertyName);
}
/// <summary>
/// Tries to validate a new value for a specified property. If the validation is successful,
/// <see cref="ObservableObject.SetProperty{T}(T,T,Action{T},string?)"/> is called, otherwise no state change is performed.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="callback">A callback to invoke to update the property value.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
protected bool TrySetProperty<T>(T oldValue, T newValue, Action<T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string? propertyName = null)
{
return TryValidateProperty(newValue, propertyName, out errors) &&
SetProperty(oldValue, newValue, callback, propertyName);
}
/// <summary>
/// Tries to validate a new value for a specified property. If the validation is successful,
/// <see cref="ObservableObject.SetProperty{T}(T,T,IEqualityComparer{T},Action{T},string?)"/> is called, otherwise no state change is performed.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="callback">A callback to invoke to update the property value.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
protected bool TrySetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string? propertyName = null)
{
return TryValidateProperty(newValue, propertyName, out errors) &&
SetProperty(oldValue, newValue, comparer, callback, propertyName);
}
/// <summary>
/// Tries to validate a new value for a specified property. If the validation is successful,
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string?)"/> is called, otherwise no state change is performed.
/// </summary>
/// <typeparam name="TModel">The type of model whose property (or field) to set.</typeparam>
/// <typeparam name="T">The type of property (or field) to set.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="model">The model </param>
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
protected bool TrySetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string? propertyName = null)
where TModel : class
{
return TryValidateProperty(newValue, propertyName, out errors) &&
SetProperty(oldValue, newValue, model, callback, propertyName);
}
/// <summary>
/// Tries to validate a new value for a specified property. If the validation is successful,
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,IEqualityComparer{T},TModel,Action{TModel,T},string?)"/> is called, otherwise no state change is performed.
/// </summary>
/// <typeparam name="TModel">The type of model whose property (or field) to set.</typeparam>
/// <typeparam name="T">The type of property (or field) to set.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="model">The model </param>
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
protected bool TrySetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string? propertyName = null)
where TModel : class
{
return TryValidateProperty(newValue, propertyName, out errors) &&
SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
}
/// <inheritdoc/>
@ -285,6 +401,34 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
new ValidationContext(this, null, null) { MemberName = propertyName },
propertyErrors);
// Update the shared counter for the number of errors, and raise the
// property changed event if necessary. We decrement the number of total
// errors if the current property is valid but it wasn't so before this
// validation, and we increment it if the validation failed after being
// correct before. The property changed event is raised whenever the
// number of total errors is either decremented to 0, or incremented to 1.
if (isValid)
{
if (errorsChanged)
{
this.totalErrors--;
if (this.totalErrors == 0)
{
OnPropertyChanged(HasErrorsChangedEventArgs);
}
}
}
else if (!errorsChanged)
{
this.totalErrors++;
if (this.totalErrors == 1)
{
OnPropertyChanged(HasErrorsChangedEventArgs);
}
}
// Only raise the event once if needed. This happens either when the target property
// had existing errors and is now valid, or if the validation has failed and there are
// new errors to broadcast, regardless of the previous validation state for the property.
@ -294,6 +438,61 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
}
}
/// <summary>
/// Tries to validate a property with a specified name and a given input value, and returns
/// the computed errors, if any. If the property is valid, it is assumed that its value is
/// about to be set in the current object. Otherwise, no observable local state is modified.
/// </summary>
/// <param name="value">The value to test for the specified property.</param>
/// <param name="propertyName">The name of the property to validate.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="propertyName"/> is <see langword="null"/>.</exception>
private bool TryValidateProperty(object? value, string? propertyName, out IReadOnlyCollection<ValidationResult> errors)
{
if (propertyName is null)
{
ThrowArgumentNullExceptionForNullPropertyName();
}
// Add the cached errors list for later use.
if (!this.errors.TryGetValue(propertyName!, out List<ValidationResult>? propertyErrors))
{
propertyErrors = new List<ValidationResult>();
this.errors.Add(propertyName!, propertyErrors);
}
bool hasErrors = propertyErrors.Count > 0;
List<ValidationResult> localErrors = new List<ValidationResult>();
// Validate the property, by adding new errors to the local list
bool isValid = Validator.TryValidateProperty(
value,
new ValidationContext(this, null, null) { MemberName = propertyName },
localErrors);
// We only modify the state if the property is valid and it wasn't so before. In this case, we
// clear the cached list of errors (which is visible to consumers) and raise the necessary events.
if (isValid && hasErrors)
{
propertyErrors.Clear();
this.totalErrors--;
if (this.totalErrors == 0)
{
OnPropertyChanged(HasErrorsChangedEventArgs);
}
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
errors = localErrors;
return isValid;
}
#pragma warning disable SA1204
/// <summary>
/// Throws an <see cref="ArgumentNullException"/> when a property name given as input is <see langword="null"/>.

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

@ -0,0 +1,167 @@
// 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.Threading;
#nullable enable
namespace Microsoft.Toolkit.Mvvm.DependencyInjection
{
/// <summary>
/// A type that facilitates the use of the <see cref="IServiceProvider"/> type.
/// The <see cref="Ioc"/> provides the ability to configure services in a singleton, thread-safe
/// service provider instance, which can then be used to resolve service instances.
/// The first step to use this feature is to declare some services, for instance:
/// <code>
/// public interface ILogger
/// {
/// void Log(string text);
/// }
/// </code>
/// <code>
/// public class ConsoleLogger : ILogger
/// {
/// void Log(string text) => Console.WriteLine(text);
/// }
/// </code>
/// Then the services configuration should then be done at startup, by calling the <see cref="ConfigureServices"/>
/// method and passing an <see cref="IServiceProvider"/> instance with the services to use. That instance can
/// be from any library offering dependency injection functionality, such as Microsoft.Extensions.DependencyInjection.
/// For instance, using that library, <see cref="ConfigureServices"/> can be used as follows in this example:
/// <code>
/// Ioc.Default.ConfigureServices(
/// new ServiceCollection()
/// .AddSingleton&lt;ILogger, Logger&gt;()
/// .BuildServiceProvider());
/// </code>
/// Finally, you can use the <see cref="Ioc"/> instance (which implements <see cref="IServiceProvider"/>)
/// to retrieve the service instances from anywhere in your application, by doing as follows:
/// <code>
/// Ioc.Default.GetService&lt;ILogger&gt;().Log("Hello world!");
/// </code>
/// </summary>
public sealed class Ioc : IServiceProvider
{
/// <summary>
/// Gets the default <see cref="Ioc"/> instance.
/// </summary>
public static Ioc Default { get; } = new Ioc();
/// <summary>
/// The <see cref="IServiceProvider"/> instance to use, if initialized.
/// </summary>
private volatile IServiceProvider? serviceProvider;
/// <inheritdoc/>
public object? GetService(Type serviceType)
{
// As per section I.12.6.6 of the official CLI ECMA-335 spec:
// "[...] read and write access to properly aligned memory locations no larger than the native
// word size is atomic when all the write accesses to a location are the same size. Atomic writes
// shall alter no bits other than those written. Unless explicit layout control is used [...],
// data elements no larger than the natural word size [...] shall be properly aligned.
// Object references shall be treated as though they are stored in the native word size."
// The field being accessed here is of native int size (reference type), and is only ever accessed
// directly and atomically by a compare exchange instruction (see below), or here. We can therefore
// assume this read is thread safe with respect to accesses to this property or to invocations to one
// of the available configuration methods. So we can just read the field directly and make the necessary
// check with our local copy, without the need of paying the locking overhead from this get accessor.
IServiceProvider? provider = this.serviceProvider;
if (provider is null)
{
ThrowInvalidOperationExceptionForMissingInitialization();
}
return provider!.GetService(serviceType);
}
/// <summary>
/// Tries to resolve an instance of a specified service type.
/// </summary>
/// <typeparam name="T">The type of service to resolve.</typeparam>
/// <returns>An instance of the specified service, or <see langword="null"/>.</returns>
/// <exception cref="InvalidOperationException">Throw if the current <see cref="Ioc"/> instance has not been initialized.</exception>
public T? GetService<T>()
where T : class
{
IServiceProvider? provider = this.serviceProvider;
if (provider is null)
{
ThrowInvalidOperationExceptionForMissingInitialization();
}
return (T?)provider!.GetService(typeof(T));
}
/// <summary>
/// Resolves an instance of a specified service type.
/// </summary>
/// <typeparam name="T">The type of service to resolve.</typeparam>
/// <returns>An instance of the specified service, or <see langword="null"/>.</returns>
/// <exception cref="InvalidOperationException">
/// Throw if the current <see cref="Ioc"/> instance has not been initialized, or if the
/// requested service type was not registered in the service provider currently in use.
/// </exception>
public T GetRequiredService<T>()
where T : class
{
IServiceProvider? provider = this.serviceProvider;
if (provider is null)
{
ThrowInvalidOperationExceptionForMissingInitialization();
}
T? service = (T?)provider!.GetService(typeof(T));
if (service is null)
{
ThrowInvalidOperationExceptionForUnregisteredType();
}
return service!;
}
/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="serviceProvider">The input <see cref="IServiceProvider"/> instance to use.</param>
public void ConfigureServices(IServiceProvider serviceProvider)
{
IServiceProvider? oldServices = Interlocked.CompareExchange(ref this.serviceProvider, serviceProvider, null);
if (!(oldServices is null))
{
ThrowInvalidOperationExceptionForRepeatedConfiguration();
}
}
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when the <see cref="IServiceProvider"/> property is used before initialization.
/// </summary>
private static void ThrowInvalidOperationExceptionForMissingInitialization()
{
throw new InvalidOperationException("The service provider has not been configured yet");
}
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when the <see cref="IServiceProvider"/> property is missing a type registration.
/// </summary>
private static void ThrowInvalidOperationExceptionForUnregisteredType()
{
throw new InvalidOperationException("The requested service type was not registered");
}
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when a configuration is attempted more than once.
/// </summary>
private static void ThrowInvalidOperationExceptionForRepeatedConfiguration()
{
throw new InvalidOperationException("The default service provider has already been configured");
}
}
}

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

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@ -18,6 +19,21 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// </summary>
public sealed class AsyncRelayCommand : ObservableObject, IAsyncRelayCommand
{
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="CanBeCanceled"/>.
/// </summary>
internal static readonly PropertyChangedEventArgs CanBeCanceledChangedEventArgs = new PropertyChangedEventArgs(nameof(CanBeCanceled));
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="IsCancellationRequested"/>.
/// </summary>
internal static readonly PropertyChangedEventArgs IsCancellationRequestedChangedEventArgs = new PropertyChangedEventArgs(nameof(IsCancellationRequested));
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="IsRunning"/>.
/// </summary>
internal static readonly PropertyChangedEventArgs IsRunningChangedEventArgs = new PropertyChangedEventArgs(nameof(IsRunning));
/// <summary>
/// The <see cref="Func{TResult}"/> to invoke when <see cref="Execute"/> is used.
/// </summary>
@ -91,15 +107,22 @@ namespace Microsoft.Toolkit.Mvvm.Input
get => this.executionTask;
private set
{
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning))))
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, value, _ =>
{
OnPropertyChanged(nameof(IsRunning));
// When the task completes
OnPropertyChanged(IsRunningChangedEventArgs);
OnPropertyChanged(CanBeCanceledChangedEventArgs);
}))
{
// When setting the task
OnPropertyChanged(IsRunningChangedEventArgs);
OnPropertyChanged(CanBeCanceledChangedEventArgs);
}
}
}
/// <inheritdoc/>
public bool CanBeCanceled => !(this.cancelableExecute is null);
public bool CanBeCanceled => !(this.cancelableExecute is null) && IsRunning;
/// <inheritdoc/>
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
@ -142,7 +165,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
OnPropertyChanged(nameof(IsCancellationRequested));
OnPropertyChanged(IsCancellationRequestedChangedEventArgs);
// Invoke the cancelable command delegate with a new linked token
return ExecutionTask = this.cancelableExecute!(cancellationTokenSource.Token);
@ -156,7 +179,8 @@ namespace Microsoft.Toolkit.Mvvm.Input
{
this.cancellationTokenSource?.Cancel();
OnPropertyChanged(nameof(IsCancellationRequested));
OnPropertyChanged(IsCancellationRequestedChangedEventArgs);
OnPropertyChanged(CanBeCanceledChangedEventArgs);
}
}
}

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

@ -91,15 +91,22 @@ namespace Microsoft.Toolkit.Mvvm.Input
get => this.executionTask;
private set
{
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning))))
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, value, _ =>
{
OnPropertyChanged(nameof(IsRunning));
// When the task completes
OnPropertyChanged(AsyncRelayCommand.IsRunningChangedEventArgs);
OnPropertyChanged(AsyncRelayCommand.CanBeCanceledChangedEventArgs);
}))
{
// When setting the task
OnPropertyChanged(AsyncRelayCommand.IsRunningChangedEventArgs);
OnPropertyChanged(AsyncRelayCommand.CanBeCanceledChangedEventArgs);
}
}
}
/// <inheritdoc/>
public bool CanBeCanceled => !(this.cancelableExecute is null);
public bool CanBeCanceled => !(this.cancelableExecute is null) && IsRunning;
/// <inheritdoc/>
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
@ -163,7 +170,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
OnPropertyChanged(nameof(IsCancellationRequested));
OnPropertyChanged(AsyncRelayCommand.IsCancellationRequestedChangedEventArgs);
// Invoke the cancelable command delegate with a new linked token
return ExecutionTask = this.cancelableExecute!(parameter, cancellationTokenSource.Token);
@ -183,7 +190,8 @@ namespace Microsoft.Toolkit.Mvvm.Input
{
this.cancellationTokenSource?.Cancel();
OnPropertyChanged(nameof(IsCancellationRequested));
OnPropertyChanged(AsyncRelayCommand.IsCancellationRequestedChangedEventArgs);
OnPropertyChanged(AsyncRelayCommand.CanBeCanceledChangedEventArgs);
}
}
}

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

@ -27,27 +27,10 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// </summary>
private static class MethodInfos
{
/// <summary>
/// Initializes static members of the <see cref="MethodInfos"/> class.
/// </summary>
static MethodInfos()
{
RegisterIRecipient = (
from methodInfo in typeof(IMessengerExtensions).GetMethods()
where methodInfo.Name == nameof(Register) &&
methodInfo.IsGenericMethod &&
methodInfo.GetGenericArguments().Length == 2
let parameters = methodInfo.GetParameters()
where parameters.Length == 3 &&
parameters[1].ParameterType.IsGenericType &&
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(IRecipient<>)
select methodInfo).First();
}
/// <summary>
/// The <see cref="MethodInfo"/> instance associated with <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/>.
/// </summary>
public static readonly MethodInfo RegisterIRecipient;
public static readonly MethodInfo RegisterIRecipient = new Action<IMessenger, IRecipient<object>, Unit>(Register).Method.GetGenericMethodDefinition();
}
/// <summary>

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

@ -65,19 +65,19 @@ namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
unchecked
{
// To combine the two hashes, we can simply use the fast djb2 hash algorithm.
// This is not a problem in this case since we already know that the base
// RuntimeHelpers.GetHashCode method is providing hashes with a good enough distribution.
int hash = RuntimeHelpers.GetHashCode(TMessage);
// To combine the two hashes, we can simply use the fast djb2 hash algorithm. Unfortunately we
// can't really skip the callvirt here (eg. by using RuntimeHelpers.GetHashCode like in other
// cases), as there are some niche cases mentioned above that might break when doing so.
// However since this method is not generally used in a hot path (eg. the message broadcasting
// only invokes this a handful of times when initially retrieving the target mapping), this
// doesn't actually make a noticeable difference despite the minor overhead of the virtual call.
int hash = TMessage.GetHashCode();
hash = (hash << 5) + hash;
hash = (hash << 5) + hash;
hash += RuntimeHelpers.GetHashCode(TToken);
hash += TToken.GetHashCode();
return hash;
}
return hash;
}
}
}

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

@ -371,8 +371,6 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
// that doesn't expose the single standard Current property.
while (mappingEnumerator.MoveNext())
{
object recipient = mappingEnumerator.Key.Target;
// Pick the target handler, if the token is a match for the recipient
if (mappingEnumerator.Value.TryGetValue(token, out object? handler))
{
@ -382,7 +380,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
// We're still using a checked span accesses here though to make sure an out of
// bounds write can never happen even if an error was present in the logic above.
pairs[2 * i] = handler!;
pairs[(2 * i) + 1] = recipient;
pairs[(2 * i) + 1] = mappingEnumerator.Key.Target;
i++;
}
}

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

@ -8,7 +8,7 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Helpers;
using Microsoft.Toolkit.Uwp.Extensions;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Enumeration;
@ -197,7 +197,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// <param name="args">The advertisement.</param>
private async void AdvertisementWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
() =>
{
if (_readerWriterLockSlim.TryEnterReadLock(TimeSpan.FromSeconds(1)))
@ -281,7 +281,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
// Protect against race condition if the task runs after the app stopped the deviceWatcher.
if (sender == _deviceWatcher)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
() =>
{
if (_readerWriterLockSlim.TryEnterWriteLock(TimeSpan.FromSeconds(1)))
@ -331,7 +331,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
if (connectable)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
() =>
{
if (_readerWriterLockSlim.TryEnterWriteLock(TimeSpan.FromSeconds(1)))

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

@ -11,12 +11,11 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Helpers;
using Microsoft.Toolkit.Uwp.Extensions;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.Xaml.Media.Imaging;
namespace Microsoft.Toolkit.Uwp.Connectivity
@ -404,7 +403,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// <exception cref="Exception">Throws Exception when no permission to access device</exception>
public async Task ConnectAsync()
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
async () =>
{
if (BluetoothLEDevice == null)
@ -478,7 +477,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// <returns>The task of the update.</returns>
public async Task UpdateAsync(DeviceInformationUpdate deviceUpdate)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
() =>
{
DeviceInfo.Update(deviceUpdate);
@ -521,7 +520,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// <param name="args">The arguments.</param>
private async void BluetoothLEDevice_NameChanged(BluetoothLEDevice sender, object args)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(() => { Name = BluetoothLEDevice.Name; }, DispatcherQueuePriority.Normal);
await DispatcherQueue.EnqueueAsync(() => { Name = BluetoothLEDevice.Name; }, DispatcherQueuePriority.Normal);
}
/// <summary>
@ -531,7 +530,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// <param name="args">The arguments.</param>
private async void BluetoothLEDevice_ConnectionStatusChanged(BluetoothLEDevice sender, object args)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
() =>
{
IsPaired = DeviceInfo.Pairing.IsPaired;
@ -544,7 +543,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// </summary>
private async void LoadGlyph()
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
async () =>
{
var deviceThumbnail = await DeviceInfo.GetGlyphThumbnailAsync();

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

@ -7,7 +7,7 @@ using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Helpers;
using Microsoft.Toolkit.Uwp.Extensions;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Security.Cryptography;
@ -469,7 +469,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// <param name="args">The <see cref="GattValueChangedEventArgs"/> instance containing the event data.</param>
private async void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(() => { SetValue(args.CharacteristicValue); }, DispatcherQueuePriority.Normal);
await DispatcherQueue.EnqueueAsync(() => { SetValue(args.CharacteristicValue); }, DispatcherQueuePriority.Normal);
}
/// <summary>

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

@ -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()

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

@ -273,6 +273,7 @@
<Content Include="Icons\Notifications.png" />
<Content Include="Icons\Services.png" />
<Content Include="SamplePages\TabbedCommandBar\TabbedCommandBar.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" />
@ -510,6 +511,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>
@ -624,6 +631,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">
@ -997,6 +1006,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>
@ -1482,12 +1499,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>
@ -1496,7 +1511,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>

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

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Helpers;
using Windows.System;
using Windows.UI.Xaml;
using Microsoft.Toolkit.Uwp.Extensions;
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
@ -21,7 +22,7 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
int crossThreadReturnedValue = await Task.Run<int>(async () =>
{
int returnedFromUIThread = await dispatcherQueue.ExecuteOnUIThreadAsync<int>(() =>
int returnedFromUIThread = await dispatcherQueue.EnqueueAsync<int>(() =>
{
NormalTextBlock.Text = "Updated from a random thread!";
return 1;

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

@ -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->'&quot;%(Identity)&quot;')" />
<Exec Command="for %%f in (@(GeneratedCSFiles->'&quot;%(Identity)&quot;')) do echo #pragma warning disable &gt; %%f.temp &amp;&amp; type %%f &gt;&gt; %%f.temp &amp;&amp; move /y %%f.temp %%f &gt; 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="&#xE70D;" />
</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.
@ -62,33 +63,10 @@
<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>
<ItemGroup>
<Page Update="TabbedCommandBar\TabbedCommandBarItem.xaml">
@ -108,7 +86,6 @@
<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()
{
}
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше