Merge remote-tracking branch 'origin/master' into wrapPanel.verticalAlignment

This commit is contained in:
Vincent Gromfeld 2020-10-06 10:14:48 +02:00
Родитель 030444f62a 2b610c4497
Коммит 1952e16977
178 изменённых файлов: 3509 добавлений и 2615 удалений

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

@ -14,8 +14,8 @@
<IsTestProject>$(MSBuildProjectName.Contains('Test'))</IsTestProject>
<IsUwpProject Condition="'$(IsDesignProject)' != 'true'">$(MSBuildProjectName.Contains('Uwp'))</IsUwpProject>
<IsSampleProject>$(MSBuildProjectName.Contains('Sample'))</IsSampleProject>
<DefaultTargetPlatformVersion>18362</DefaultTargetPlatformVersion>
<DefaultTargetPlatformMinVersion>16299</DefaultTargetPlatformMinVersion>
<DefaultTargetPlatformVersion>19041</DefaultTargetPlatformVersion>
<DefaultTargetPlatformMinVersion>17763</DefaultTargetPlatformMinVersion>
<PackageOutputPath>$(MSBuildThisFileDirectory)bin\nupkg</PackageOutputPath>
</PropertyGroup>

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

@ -1,6 +1,6 @@
<Project>
<Choose>
<When Condition="'$(TargetFramework)' == 'uap10.0' or '$(TargetFramework)' == 'uap10.0.16299' or '$(TargetFramework)' == 'native' or '$(TargetFramework)' == 'net461'">
<When Condition="'$(TargetFramework)' == 'uap10.0' or '$(TargetFramework)' == 'uap10.0.17763' or '$(TargetFramework)' == 'native' or '$(TargetFramework)' == 'net461'">
<!-- UAP versions for uap10.0 where TPMV isn't implied -->
<PropertyGroup>
<TargetPlatformVersion>10.0.$(DefaultTargetPlatformVersion).0</TargetPlatformVersion>
@ -15,9 +15,6 @@
<SDKReference Condition="'$(UseWindowsDesktopSdk)' == 'true' " Include="WindowsDesktop, Version=$(TargetPlatformVersion)">
<Name>Windows Desktop Extensions for the UWP</Name>
</SDKReference>
<SDKReference Condition="'$(UseWindowsMobileSdk)' == 'true' " Include="WindowsMobile, Version=$(TargetPlatformVersion)">
<Name>Windows Mobile Extensions for the UWP</Name>
</SDKReference>
</ItemGroup>
</When>
</Choose>

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

@ -11,7 +11,7 @@
<AssemblyName>GazeInputTest</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.18362.0</TargetPlatformVersion>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.19041.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17134.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>

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

@ -125,7 +125,7 @@ namespace Microsoft.Toolkit.HighPerformance
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator T(Box<T> box)
{
return Unsafe.Unbox<T>(box);
return (T)(object)box;
}
/// <summary>
@ -180,7 +180,6 @@ namespace Microsoft.Toolkit.HighPerformance
/// <summary>
/// Throws an <see cref="InvalidCastException"/> when a cast from an invalid <see cref="object"/> is attempted.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidCastExceptionForGetFrom()
{
throw new InvalidCastException($"Can't cast the input object to the type Box<{typeof(T)}>");

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

@ -33,6 +33,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// </summary>
private const int DefaultInitialBufferSize = 256;
/// <summary>
/// The <see cref="ArrayPool{T}"/> instance used to rent <see cref="array"/>.
/// </summary>
private readonly ArrayPool<T> pool;
/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
@ -49,36 +54,52 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
/// </summary>
public ArrayPoolBufferWriter()
: this(ArrayPool<T>.Shared, DefaultInitialBufferSize)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
/// </summary>
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
public ArrayPoolBufferWriter(ArrayPool<T> pool)
: this(pool, DefaultInitialBufferSize)
{
// Since we're using pooled arrays, we can rent the buffer with the
// default size immediately, we don't need to use lazy initialization
// to save unnecessary memory allocations in this case.
this.array = ArrayPool<T>.Shared.Rent(DefaultInitialBufferSize);
this.index = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
/// </summary>
/// <param name="initialCapacity">The minimum capacity with which to initialize the underlying buffer.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="initialCapacity"/> is not positive (i.e. less than or equal to 0).
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="initialCapacity"/> is not valid.</exception>
public ArrayPoolBufferWriter(int initialCapacity)
: this(ArrayPool<T>.Shared, initialCapacity)
{
if (initialCapacity <= 0)
{
ThrowArgumentOutOfRangeExceptionForInitialCapacity();
}
}
this.array = ArrayPool<T>.Shared.Rent(initialCapacity);
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
/// </summary>
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
/// <param name="initialCapacity">The minimum capacity with which to initialize the underlying buffer.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="initialCapacity"/> is not valid.</exception>
public ArrayPoolBufferWriter(ArrayPool<T> pool, int initialCapacity)
{
// Since we're using pooled arrays, we can rent the buffer with the
// default size immediately, we don't need to use lazy initialization
// to save unnecessary memory allocations in this case.
// Additionally, we don't need to manually throw the exception if
// the requested size is not valid, as that'll be thrown automatically
// by the array pool in use when we try to rent an array with that size.
this.pool = pool;
this.array = pool.Rent(initialCapacity);
this.index = 0;
}
/// <summary>
/// Finalizes an instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
/// </summary>
~ArrayPoolBufferWriter() => this.Dispose();
~ArrayPoolBufferWriter() => Dispose();
/// <inheritdoc/>
Memory<T> IMemoryOwner<T>.Memory
@ -182,6 +203,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
}
array.AsSpan(0, this.index).Clear();
this.index = 0;
}
@ -250,7 +272,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
{
int minimumSize = this.index + sizeHint;
ArrayPool<T>.Shared.Resize(ref this.array, minimumSize);
this.pool.Resize(ref this.array, minimumSize);
}
}
@ -268,7 +290,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
this.array = null;
ArrayPool<T>.Shared.Return(array);
this.pool.Return(array);
}
/// <inheritdoc/>
@ -286,19 +308,9 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
return $"Microsoft.Toolkit.HighPerformance.Buffers.ArrayPoolBufferWriter<{typeof(T)}>[{this.index}]";
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the initial capacity is invalid.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForInitialCapacity()
{
throw new ArgumentOutOfRangeException("initialCapacity", "The initial capacity must be a positive value");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForNegativeCount()
{
throw new ArgumentOutOfRangeException("count", "The count can't be a negative value");
@ -307,7 +319,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the size hint is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint()
{
throw new ArgumentOutOfRangeException("sizeHint", "The size hint can't be a negative value");
@ -316,7 +327,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentExceptionForAdvancedTooFar()
{
throw new ArgumentException("The buffer writer has advanced too far");
@ -325,7 +335,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <summary>
/// Throws an <see cref="ObjectDisposedException"/> when <see cref="array"/> is <see langword="null"/>.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException("The current buffer has already been disposed");

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

@ -106,7 +106,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <inheritdoc/>
public Memory<T> GetMemory(int sizeHint = 0)
{
this.ValidateSizeHint(sizeHint);
ValidateSizeHint(sizeHint);
return this.memory.Slice(this.index);
}
@ -114,7 +114,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <inheritdoc/>
public Span<T> GetSpan(int sizeHint = 0)
{
this.ValidateSizeHint(sizeHint);
ValidateSizeHint(sizeHint);
return this.memory.Slice(this.index).Span;
}
@ -159,7 +159,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForNegativeCount()
{
throw new ArgumentOutOfRangeException("count", "The count can't be a negative value");
@ -168,7 +167,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the size hint is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint()
{
throw new ArgumentOutOfRangeException("sizeHint", "The size hint can't be a negative value");
@ -177,7 +175,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentExceptionForAdvancedTooFar()
{
throw new ArgumentException("The buffer writer has advanced too far");
@ -186,7 +183,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <summary>
/// Throws an <see cref="ArgumentException"/> when the requested size exceeds the capacity.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentExceptionForCapacityExceeded()
{
throw new ArgumentException("The buffer writer doesn't have enough capacity left");

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

@ -32,6 +32,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
private readonly int length;
#pragma warning restore IDE0032
/// <summary>
/// The <see cref="ArrayPool{T}"/> instance used to rent <see cref="array"/>.
/// </summary>
private readonly ArrayPool<T> pool;
/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
@ -41,12 +46,14 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters.
/// </summary>
/// <param name="length">The length of the new memory buffer to use.</param>
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
private MemoryOwner(int length, AllocationMode mode)
private MemoryOwner(int length, ArrayPool<T> pool, AllocationMode mode)
{
this.start = 0;
this.length = length;
this.array = ArrayPool<T>.Shared.Rent(length);
this.pool = pool;
this.array = pool.Rent(length);
if (mode == AllocationMode.Clear)
{
@ -57,20 +64,22 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <summary>
/// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters.
/// </summary>
/// <param name="array">The input <typeparamref name="T"/> array to use.</param>
/// <param name="start">The starting offset within <paramref name="array"/>.</param>
/// <param name="length">The length of the array to use.</param>
private MemoryOwner(T[] array, int start, int length)
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance currently in use.</param>
/// <param name="array">The input <typeparamref name="T"/> array to use.</param>
private MemoryOwner(int start, int length, ArrayPool<T> pool, T[] array)
{
this.start = start;
this.length = length;
this.pool = pool;
this.array = array;
}
/// <summary>
/// Finalizes an instance of the <see cref="MemoryOwner{T}"/> class.
/// </summary>
~MemoryOwner() => this.Dispose();
~MemoryOwner() => Dispose();
/// <summary>
/// Gets an empty <see cref="MemoryOwner{T}"/> instance.
@ -79,7 +88,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
public static MemoryOwner<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new MemoryOwner<T>(0, AllocationMode.Default);
get => new MemoryOwner<T>(0, ArrayPool<T>.Shared, AllocationMode.Default);
}
/// <summary>
@ -91,7 +100,19 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> Allocate(int size) => new MemoryOwner<T>(size, AllocationMode.Default);
public static MemoryOwner<T> Allocate(int size) => new MemoryOwner<T>(size, ArrayPool<T>.Shared, AllocationMode.Default);
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified parameters.
/// </summary>
/// <param name="size">The length of the new memory buffer to use.</param>
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance currently in use.</param>
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> Allocate(int size, ArrayPool<T> pool) => new MemoryOwner<T>(size, pool, AllocationMode.Default);
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified parameters.
@ -103,7 +124,20 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> Allocate(int size, AllocationMode mode) => new MemoryOwner<T>(size, mode);
public static MemoryOwner<T> Allocate(int size, AllocationMode mode) => new MemoryOwner<T>(size, ArrayPool<T>.Shared, mode);
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified parameters.
/// </summary>
/// <param name="size">The length of the new memory buffer to use.</param>
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance currently in use.</param>
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> Allocate(int size, ArrayPool<T> pool, AllocationMode mode) => new MemoryOwner<T>(size, pool, mode);
/// <summary>
/// Gets the number of items in the current instance
@ -210,7 +244,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
ThrowInvalidLengthException();
}
return new MemoryOwner<T>(array!, start, length);
return new MemoryOwner<T>(start, length, this.pool, array!);
}
/// <inheritdoc/>
@ -227,7 +261,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
this.array = null;
ArrayPool<T>.Shared.Return(array);
this.pool.Return(array);
}
/// <inheritdoc/>
@ -251,7 +285,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <summary>
/// Throws an <see cref="ObjectDisposedException"/> when <see cref="array"/> is <see langword="null"/>.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException(nameof(MemoryOwner<T>), "The current buffer has already been disposed");
@ -260,7 +293,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="start"/> is invalid.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidOffsetException()
{
throw new ArgumentOutOfRangeException(nameof(start), "The input start parameter was not valid");
@ -269,7 +301,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="length"/> is invalid.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidLengthException()
{
throw new ArgumentOutOfRangeException(nameof(length), "The input length parameter was not valid");

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

@ -42,6 +42,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
private readonly int length;
#pragma warning restore IDE0032
/// <summary>
/// The <see cref="ArrayPool{T}"/> instance used to rent <see cref="array"/>.
/// </summary>
private readonly ArrayPool<T> pool;
/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
@ -51,11 +56,13 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// Initializes a new instance of the <see cref="SpanOwner{T}"/> struct with the specified parameters.
/// </summary>
/// <param name="length">The length of the new memory buffer to use.</param>
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
private SpanOwner(int length, AllocationMode mode)
private SpanOwner(int length, ArrayPool<T> pool, AllocationMode mode)
{
this.length = length;
this.array = ArrayPool<T>.Shared.Rent(length);
this.pool = pool;
this.array = pool.Rent(length);
if (mode == AllocationMode.Clear)
{
@ -70,7 +77,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
public static SpanOwner<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new SpanOwner<T>(0, AllocationMode.Default);
get => new SpanOwner<T>(0, ArrayPool<T>.Shared, AllocationMode.Default);
}
/// <summary>
@ -82,7 +89,19 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Allocate(int size) => new SpanOwner<T>(size, AllocationMode.Default);
public static SpanOwner<T> Allocate(int size) => new SpanOwner<T>(size, ArrayPool<T>.Shared, AllocationMode.Default);
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
/// </summary>
/// <param name="size">The length of the new memory buffer to use.</param>
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Allocate(int size, ArrayPool<T> pool) => new SpanOwner<T>(size, pool, AllocationMode.Default);
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
@ -94,7 +113,20 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Allocate(int size, AllocationMode mode) => new SpanOwner<T>(size, mode);
public static SpanOwner<T> Allocate(int size, AllocationMode mode) => new SpanOwner<T>(size, ArrayPool<T>.Shared, mode);
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
/// </summary>
/// <param name="size">The length of the new memory buffer to use.</param>
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Allocate(int size, ArrayPool<T> pool, AllocationMode mode) => new SpanOwner<T>(size, pool, mode);
/// <summary>
/// Gets the number of items in the current instance
@ -111,7 +143,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
public Span<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new Span<T>(array, 0, this.length);
get => new Span<T>(this.array, 0, this.length);
}
/// <summary>
@ -122,7 +154,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T DangerousGetReference()
{
return ref array.DangerousGetReference();
return ref this.array.DangerousGetReference();
}
/// <summary>
@ -131,7 +163,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
ArrayPool<T>.Shared.Return(array);
this.pool.Return(this.array);
}
/// <inheritdoc/>

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

@ -205,7 +205,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="column"/> is invalid.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForInvalidColumn()
{
throw new ArgumentOutOfRangeException(nameof(column), "The target column parameter was not valid");

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

@ -160,7 +160,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="row"/> is invalid.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForInvalidRow()
{
throw new ArgumentOutOfRangeException(nameof(row), "The target row parameter was not valid");

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

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -42,8 +43,9 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="ReadOnlySpanEnumerable{T}"/> instance targeting the current <see cref="ReadOnlySpan{T}"/> value.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpanEnumerable<T> GetEnumerator() => this;
public readonly ReadOnlySpanEnumerable<T> GetEnumerator() => this;
/// <summary>
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
@ -67,7 +69,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
public Item Current
public readonly Item Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get

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

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.HighPerformance.Enumerables
@ -54,8 +55,9 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="ReadOnlySpanTokenizer{T}"/> instance targeting the current <see cref="ReadOnlySpan{T}"/> value.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpanTokenizer<T> GetEnumerator() => this;
public readonly ReadOnlySpanTokenizer<T> GetEnumerator() => this;
/// <summary>
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
@ -94,7 +96,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
public ReadOnlySpan<T> Current
public readonly ReadOnlySpan<T> Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.span.Slice(this.start, this.end - this.start);

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

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -42,8 +43,9 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="SpanEnumerable{T}"/> instance targeting the current <see cref="Span{T}"/> value.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanEnumerable<T> GetEnumerator() => this;
public readonly SpanEnumerable<T> GetEnumerator() => this;
/// <summary>
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
@ -67,7 +69,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
public Item Current
public readonly Item Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
@ -76,12 +78,12 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
ref T r0 = ref MemoryMarshal.GetReference(this.span);
ref T ri = ref Unsafe.Add(ref r0, this.index);
// On .NET Standard 2.1 we can save 4 bytes by piggybacking
// the current index in the length of the wrapped span.
// We're going to use the first item as the target reference,
// and the length as a host for the current original offset.
// This is not possible on .NET Standard 2.1 as we lack
// the API to create spans from arbitrary references.
// On .NET Standard 2.1 and .NET Core (or on any target that offers runtime
// support for the Span<T> types), we can save 4 bytes by piggybacking the
// current index in the length of the wrapped span. We're going to use the
// first item as the target reference, and the length as a host for the
// current original offset. This is not possible on eg. .NET Standard 2.0,
// as we lack the API to create Span<T>-s from arbitrary references.
return new Item(ref ri, this.index);
#else
return new Item(this.span, this.index);

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

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.HighPerformance.Enumerables
@ -54,8 +55,9 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="SpanTokenizer{T}"/> instance targeting the current <see cref="Span{T}"/> value.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanTokenizer<T> GetEnumerator() => this;
public readonly SpanTokenizer<T> GetEnumerator() => this;
/// <summary>
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
@ -94,7 +96,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
public Span<T> Current
public readonly Span<T> Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.span.Slice(this.start, this.end - this.start);

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

@ -46,7 +46,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
T[] newArray = pool.Rent(newSize);
int itemsToCopy = Math.Min(array.Length, newSize);
array.AsSpan(0, itemsToCopy).CopyTo(newArray);
Array.Copy(array, 0, newArray, 0, itemsToCopy);
pool.Return(array, clearArray);

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

@ -102,7 +102,6 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <summary>
/// Throws an <see cref="ArgumentException"/> when trying to write too many bytes to the target writer.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentExceptionForEndOfBuffer()
{
throw new ArgumentException("The current buffer writer can't contain the requested input data.");

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

@ -164,7 +164,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <param name="value">The <typeparamref name="T"/> value to look for.</param>
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.NoInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count<T>(this ReadOnlySpan<T> span, T value)
where T : IEquatable<T>
{

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

@ -223,7 +223,6 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the given reference is out of range.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void ThrowArgumentOutOfRangeExceptionForInvalidReference()
{
throw new ArgumentOutOfRangeException("value", "The input reference does not belong to an element of the input span");

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

@ -245,7 +245,6 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when <see cref="Read{T}"/> fails.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidOperationExceptionForEndOfStream()
{
throw new InvalidOperationException("The stream didn't contain enough data to read the requested item");

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

@ -5,7 +5,9 @@
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if !NETCOREAPP3_1
using System.Runtime.InteropServices;
#endif
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
@ -107,7 +109,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// string value = item.Value;
/// char value = item.Value;
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.

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

@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
@ -15,7 +14,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid parameter is specified for the minimum actions per thread.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread()
{
// Having the argument name here manually typed is
@ -30,7 +28,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid start parameter is specified for 1D loops.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForStartGreaterThanEnd()
{
throw new ArgumentOutOfRangeException("start", "The start parameter must be less than or equal to end");
@ -39,7 +36,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// <summary>
/// Throws an <see cref="ArgumentException"/> when a range has an index starting from an end.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentExceptionForRangeIndexFromEnd(string name)
{
throw new ArgumentException("The bounds of the range can't start from an end", name);
@ -48,7 +44,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid top parameter is specified for 2D loops.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForTopGreaterThanBottom()
{
throw new ArgumentOutOfRangeException("top", "The top parameter must be less than or equal to bottom");
@ -57,7 +52,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid left parameter is specified for 2D loops.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForLeftGreaterThanRight()
{
throw new ArgumentOutOfRangeException("left", "The left parameter must be less than or equal to right");

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

@ -129,7 +129,6 @@ namespace Microsoft.Toolkit.HighPerformance
/// <summary>
/// Throws a <see cref="InvalidOperationException"/> when trying to access <see cref="Value"/> for a default instance.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidOperationException()
{
throw new InvalidOperationException("The current instance doesn't have a value that can be accessed");

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

@ -113,7 +113,6 @@ namespace Microsoft.Toolkit.HighPerformance
/// <summary>
/// Throws a <see cref="InvalidOperationException"/> when trying to access <see cref="Value"/> for a default instance.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidOperationException()
{
throw new InvalidOperationException("The current instance doesn't have a value that can be accessed");

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

@ -4,7 +4,6 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.HighPerformance.Streams
{
@ -16,7 +15,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when setting the <see cref="Stream.Position"/> property.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForPosition()
{
throw new ArgumentOutOfRangeException(nameof(Position), "The value for the property was not in the valid range.");
@ -25,7 +23,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// <summary>
/// Throws an <see cref="ArgumentNullException"/> when an input buffer is <see langword="null"/>.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentNullExceptionForBuffer()
{
throw new ArgumentNullException("buffer", "The buffer is null.");
@ -34,7 +31,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the input count is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForOffset()
{
throw new ArgumentOutOfRangeException("offset", "Offset can't be negative.");
@ -43,7 +39,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the input count is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForCount()
{
throw new ArgumentOutOfRangeException("count", "Count can't be negative.");
@ -52,7 +47,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// <summary>
/// Throws an <see cref="ArgumentException"/> when the sum of offset and count exceeds the length of the target buffer.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentExceptionForLength()
{
throw new ArgumentException("The sum of offset and count can't be larger than the buffer length.", "buffer");
@ -61,7 +55,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// <summary>
/// Throws a <see cref="NotSupportedException"/> when trying to write on a readonly stream.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowNotSupportedExceptionForCanWrite()
{
throw new NotSupportedException("The current stream doesn't support writing.");
@ -70,7 +63,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// <summary>
/// Throws an <see cref="ArgumentException"/> when trying to write too many bytes to the target stream.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentExceptionForEndOfStreamOnWrite()
{
throw new ArgumentException("The current stream can't contain the requested input data.");
@ -79,7 +71,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// <summary>
/// Throws a <see cref="NotSupportedException"/> when trying to set the length of the stream.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowNotSupportedExceptionForSetLength()
{
throw new NotSupportedException("Setting the length is not supported for this stream.");
@ -89,7 +80,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// Throws an <see cref="ArgumentException"/> when using an invalid seek mode.
/// </summary>
/// <returns>Nothing, as this method throws unconditionally.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
private static long ThrowArgumentExceptionForSeekOrigin()
{
throw new ArgumentException("The input seek mode is not valid.", "origin");
@ -98,7 +88,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// <summary>
/// Throws an <see cref="ObjectDisposedException"/> when using a disposed <see cref="Stream"/> instance.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException(nameof(memory), "The current stream has already been disposed");

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

@ -10,8 +10,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
@ -123,6 +121,11 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// This overload is much less efficient than <see cref="SetProperty{T}(ref T,T,string)"/> and it
/// should only be used when the former is not viable (eg. when the target property being
/// updated does not directly expose a backing field that can be passed by reference).
/// For performance reasons, it is recommended to use a stateful callback if possible through
/// the <see cref="SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string?)"/> whenever possible
/// instead of this overload, as that will allow the C# compiler to cache the input callback and
/// reduce the memory allocations. More info on that overload are available in the related XML
/// docs. This overload is here for completeness and in cases where that is not applicable.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="oldValue">The current property value.</param>
@ -136,7 +139,19 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </remarks>
protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, [CallerMemberName] string? propertyName = null)
{
return SetProperty(oldValue, newValue, EqualityComparer<T>.Default, callback, propertyName);
// We avoid calling the overload again to ensure the comparison is inlined
if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
{
return false;
}
OnPropertyChanging(propertyName);
callback(newValue);
OnPropertyChanged(propertyName);
return true;
}
/// <summary>
@ -197,32 +212,43 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// public string Name
/// {
/// get => Model.Name;
/// set => Set(() => Model.Name, value);
/// set => Set(Model.Name, value, Model, (model, name) => model.Name = name);
/// }
/// }
/// </code>
/// This way we can then use the wrapping object in our application, and all those "proxy" properties will
/// also raise notifications when changed. Note that this method is not meant to be a replacement for
/// <see cref="SetProperty{T}(ref T,T,string)"/>, which offers better performance and less memory usage. Only use this
/// overload when relaying properties to a model that doesn't support notifications, and only if you can't
/// implement notifications to that model directly (eg. by having it inherit from <see cref="ObservableObject"/>).
/// <see cref="SetProperty{T}(ref T,T,string)"/>, and it should only be used when relaying properties to a model that
/// doesn't support notifications, and only if you can't implement notifications to that model directly (eg. by having
/// it inherit from <see cref="ObservableObject"/>). The syntax relies on passing the target model and a stateless callback
/// to allow the C# compiler to cache the function, which results in much better performance and no memory usage.
/// </summary>
/// <typeparam name="T">The type of property to set.</typeparam>
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
/// <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="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised
/// if the current and new value for the target property are the same. Additionally, <paramref name="propertyExpression"/>
/// must return a property from a model that is stored as another property in the current instance.
/// This method only supports one level of indirection: <paramref name="propertyExpression"/> can only
/// be used to access properties of a model that is directly stored as a property of the current instance.
/// Additionally, this method can only be used if the wrapped item is a reference type.
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not
/// raised if the current and new value for the target property are the same.
/// </remarks>
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, [CallerMemberName] string? propertyName = null)
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, [CallerMemberName] string? propertyName = null)
{
return SetProperty(propertyExpression, newValue, EqualityComparer<T>.Default, out _, propertyName);
if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
{
return false;
}
OnPropertyChanging(propertyName);
callback(model, newValue);
OnPropertyChanged(propertyName);
return true;
}
/// <summary>
@ -230,56 +256,19 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// raises the <see cref="PropertyChanging"/> event, updates the property and then raises the
/// <see cref="PropertyChanged"/> event. The behavior mirrors that of <see cref="SetProperty{T}(ref T,T,string)"/>,
/// with the difference being that this method is used to relay properties from a wrapped model in the
/// current instance. See additional notes about this overload in <see cref="SetProperty{T}(Expression{Func{T}},T,string)"/>.
/// current instance. See additional notes about this overload in <see cref="SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string)"/>.
/// </summary>
/// <typeparam name="T">The type of property to set.</typeparam>
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
/// <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="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, IEqualityComparer<T> comparer, [CallerMemberName] string? propertyName = null)
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, [CallerMemberName] string? propertyName = null)
{
return SetProperty(propertyExpression, newValue, comparer, out _, propertyName);
}
/// <summary>
/// Implements the shared logic for <see cref="SetProperty{T}(Expression{Func{T}},T,IEqualityComparer{T},string)"/>
/// </summary>
/// <typeparam name="T">The type of property to set.</typeparam>
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</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="oldValue">The resulting initial value for the target property.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
private protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, IEqualityComparer<T> comparer, out T oldValue, [CallerMemberName] string? propertyName = null)
{
PropertyInfo? parentPropertyInfo;
FieldInfo? parentFieldInfo = null;
// Get the target property info
if (!(propertyExpression.Body is MemberExpression targetExpression &&
targetExpression.Member is PropertyInfo targetPropertyInfo &&
targetExpression.Expression is MemberExpression parentExpression &&
(!((parentPropertyInfo = parentExpression.Member as PropertyInfo) is null) ||
!((parentFieldInfo = parentExpression.Member as FieldInfo) is null)) &&
parentExpression.Expression is ConstantExpression instanceExpression &&
instanceExpression.Value is object instance))
{
ThrowArgumentExceptionForInvalidPropertyExpression();
// This is never executed, as the method above always throws
oldValue = default!;
return false;
}
object parent = parentPropertyInfo is null
? parentFieldInfo!.GetValue(instance)
: parentPropertyInfo.GetValue(instance);
oldValue = (T)targetPropertyInfo.GetValue(parent);
if (comparer.Equals(oldValue, newValue))
{
return false;
@ -287,7 +276,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
OnPropertyChanging(propertyName);
targetPropertyInfo.SetValue(parent, newValue);
callback(model, newValue);
OnPropertyChanged(propertyName);
@ -298,38 +287,36 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// Compares the current and new values for a given field (which should be the backing
/// field for a property). If the value has changed, raises the <see cref="PropertyChanging"/>
/// event, updates the field and then raises the <see cref="PropertyChanged"/> event.
/// The behavior mirrors that of <see cref="SetProperty{T}(ref T,T,string)"/>, with the difference being that this method
/// will also monitor the new value of the property (a generic <see cref="Task"/>) and will also
/// The behavior mirrors that of <see cref="SetProperty{T}(ref T,T,string)"/>, with the difference being that
/// this method will also monitor the new value of the property (a generic <see cref="Task"/>) and will also
/// raise the <see cref="PropertyChanged"/> again for the target property when it completes.
/// This can be used to update bindings observing that <see cref="Task"/> or any of its properties.
/// This method and its overload specifically rely on the <see cref="TaskNotifier"/> type, which needs
/// to be used in the backing field for the target <see cref="Task"/> property. The field doesn't need to be
/// initialized, as this method will take care of doing that automatically. The <see cref="TaskNotifier"/>
/// type also includes an implicit operator, so it can be assigned to any <see cref="Task"/> instance directly.
/// Here is a sample property declaration using this method:
/// <code>
/// private Task myTask;
/// private TaskNotifier myTask;
///
/// public Task MyTask
/// {
/// get => myTask;
/// private set => SetAndNotifyOnCompletion(ref myTask, () => myTask, value);
/// private set => SetAndNotifyOnCompletion(ref myTask, value);
/// }
/// </code>
/// </summary>
/// <typeparam name="TTask">The type of <see cref="Task"/> to set and monitor.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="fieldExpression">
/// An <see cref="Expression{TDelegate}"/> returning the field to update. This is needed to be
/// able to raise the <see cref="PropertyChanged"/> to notify the completion of the input task.
/// </param>
/// <param name="taskNotifier">The field notifier to modify.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised if the current
/// and new value for the target property are the same. The return value being <see langword="true"/> only
/// indicates that the new value being assigned to <paramref name="field"/> is different than the previous one,
/// and it does not mean the new <typeparamref name="TTask"/> instance passed as argument is in any particular state.
/// indicates that the new value being assigned to <paramref name="taskNotifier"/> is different than the previous one,
/// and it does not mean the new <see cref="Task"/> instance passed as argument is in any particular state.
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion<TTask>(ref TTask? field, Expression<Func<TTask?>> fieldExpression, TTask? newValue, [CallerMemberName] string? propertyName = null)
where TTask : Task
protected bool SetPropertyAndNotifyOnCompletion(ref TaskNotifier? taskNotifier, Task? newValue, [CallerMemberName] string? propertyName = null)
{
// We invoke the overload with a callback here to avoid code duplication, and simply pass an empty callback.
// The lambda expression here is transformed by the C# compiler into an empty closure class with a
@ -337,21 +324,18 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
// instance. This will result in no further allocations after the first time this method is called for a given
// generic type. We only pay the cost of the virtual call to the delegate, but this is not performance critical
// code and that overhead would still be much lower than the rest of the method anyway, so that's fine.
return SetPropertyAndNotifyOnCompletion(ref field, fieldExpression, newValue, _ => { }, propertyName);
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, _ => { }, propertyName);
}
/// <summary>
/// Compares the current and new values for a given field (which should be the backing
/// field for a property). If the value has changed, raises the <see cref="PropertyChanging"/>
/// event, updates the field and then raises the <see cref="PropertyChanged"/> event.
/// This method is just like <see cref="SetPropertyAndNotifyOnCompletion{TTask}(ref TTask,Expression{Func{TTask}},TTask,string)"/>,
/// This method is just like <see cref="SetPropertyAndNotifyOnCompletion(ref TaskNotifier,Task,string)"/>,
/// with the difference being an extra <see cref="Action{T}"/> parameter with a callback being invoked
/// either immediately, if the new task has already completed or is <see langword="null"/>, or upon completion.
/// </summary>
/// <typeparam name="TTask">The type of <see cref="Task"/> to set and monitor.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="fieldExpression">
/// An <see cref="Expression{TDelegate}"/> returning the field to update.</param>
/// <param name="taskNotifier">The field notifier to modify.</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="propertyName">(optional) The name of the property that changed.</param>
@ -360,10 +344,86 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised
/// if the current and new value for the target property are the same.
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion<TTask>(ref TTask? field, Expression<Func<TTask?>> fieldExpression, TTask? newValue, Action<TTask?> callback, [CallerMemberName] string? propertyName = null)
protected bool SetPropertyAndNotifyOnCompletion(ref TaskNotifier? taskNotifier, Task? newValue, Action<Task?> callback, [CallerMemberName] string? propertyName = null)
{
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, callback, propertyName);
}
/// <summary>
/// Compares the current and new values for a given field (which should be the backing
/// field for a property). If the value has changed, raises the <see cref="PropertyChanging"/>
/// event, updates the field and then raises the <see cref="PropertyChanged"/> event.
/// The behavior mirrors that of <see cref="SetProperty{T}(ref T,T,string)"/>, with the difference being that
/// this method will also monitor the new value of the property (a generic <see cref="Task"/>) and will also
/// raise the <see cref="PropertyChanged"/> again for the target property when it completes.
/// This can be used to update bindings observing that <see cref="Task"/> or any of its properties.
/// This method and its overload specifically rely on the <see cref="TaskNotifier{T}"/> type, which needs
/// to be used in the backing field for the target <see cref="Task"/> property. The field doesn't need to be
/// initialized, as this method will take care of doing that automatically. The <see cref="TaskNotifier{T}"/>
/// type also includes an implicit operator, so it can be assigned to any <see cref="Task"/> instance directly.
/// Here is a sample property declaration using this method:
/// <code>
/// private TaskNotifier&lt;int&gt; myTask;
///
/// public Task&lt;int&gt; MyTask
/// {
/// get => myTask;
/// private set => SetAndNotifyOnCompletion(ref myTask, value);
/// }
/// </code>
/// </summary>
/// <typeparam name="T">The type of result for the <see cref="Task{TResult}"/> to set and monitor.</typeparam>
/// <param name="taskNotifier">The field notifier to modify.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised if the current
/// and new value for the target property are the same. The return value being <see langword="true"/> only
/// indicates that the new value being assigned to <paramref name="taskNotifier"/> is different than the previous one,
/// and it does not mean the new <see cref="Task{TResult}"/> instance passed as argument is in any particular state.
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>? taskNotifier, Task<T>? newValue, [CallerMemberName] string? propertyName = null)
{
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier<T>(), newValue, _ => { }, propertyName);
}
/// <summary>
/// Compares the current and new values for a given field (which should be the backing
/// field for a property). If the value has changed, raises the <see cref="PropertyChanging"/>
/// event, updates the field and then raises the <see cref="PropertyChanged"/> event.
/// This method is just like <see cref="SetPropertyAndNotifyOnCompletion{T}(ref TaskNotifier{T},Task{T},string)"/>,
/// with the difference being an extra <see cref="Action{T}"/> parameter with a callback being invoked
/// either immediately, if the new task has already completed or is <see langword="null"/>, or upon completion.
/// </summary>
/// <typeparam name="T">The type of result for the <see cref="Task{TResult}"/> to set and monitor.</typeparam>
/// <param name="taskNotifier">The field notifier to modify.</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="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised
/// if the current and new value for the target property are the same.
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>? taskNotifier, Task<T>? newValue, Action<Task<T>?> callback, [CallerMemberName] string? propertyName = null)
{
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier<T>(), newValue, callback, propertyName);
}
/// <summary>
/// Implements the notification logic for the related methods.
/// </summary>
/// <typeparam name="TTask">The type of <see cref="Task"/> to set and monitor.</typeparam>
/// <param name="taskNotifier">The field notifier.</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="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
private bool SetPropertyAndNotifyOnCompletion<TTask>(ITaskNotifier<TTask> taskNotifier, TTask? newValue, Action<TTask?> callback, [CallerMemberName] string? propertyName = null)
where TTask : Task
{
if (ReferenceEquals(field, newValue))
if (ReferenceEquals(taskNotifier.Task, newValue))
{
return false;
}
@ -376,7 +436,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
OnPropertyChanging(propertyName);
field = newValue;
taskNotifier.Task = newValue;
OnPropertyChanged(propertyName);
@ -393,16 +453,6 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
return true;
}
// Get the target field to set. This is needed because we can't
// capture the ref field in a closure (for the async method).
if (!((fieldExpression.Body as MemberExpression)?.Member is FieldInfo fieldInfo))
{
ThrowArgumentExceptionForInvalidFieldExpression();
// This is never executed, as the method above always throws
return false;
}
// We use a local async function here so that the main method can
// remain synchronous and return a value that can be immediately
// used by the caller. This mirrors Set<T>(ref T, T, string).
@ -422,10 +472,8 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
{
}
TTask? currentTask = (TTask?)fieldInfo.GetValue(this);
// Only notify if the property hasn't changed
if (ReferenceEquals(newValue, currentTask))
if (ReferenceEquals(taskNotifier.Task, newValue))
{
OnPropertyChanged(propertyName);
}
@ -439,19 +487,79 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when a given <see cref="Expression{TDelegate}"/> is invalid for a property.
/// An interface for task notifiers of a specified type.
/// </summary>
private static void ThrowArgumentExceptionForInvalidPropertyExpression()
/// <typeparam name="TTask">The type of value to store.</typeparam>
private interface ITaskNotifier<TTask>
where TTask : Task
{
throw new ArgumentException("The given expression must be in the form () => MyModel.MyProperty");
/// <summary>
/// Gets or sets the wrapped <typeparamref name="TTask"/> value.
/// </summary>
TTask? Task { get; set; }
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when a given <see cref="Expression{TDelegate}"/> is invalid for a property field.
/// A wrapping class that can hold a <see cref="Task"/> value.
/// </summary>
private static void ThrowArgumentExceptionForInvalidFieldExpression()
protected sealed class TaskNotifier : ITaskNotifier<Task>
{
throw new ArgumentException("The given expression must be in the form () => field");
/// <summary>
/// Initializes a new instance of the <see cref="TaskNotifier"/> class.
/// </summary>
internal TaskNotifier()
{
}
private Task? task;
/// <inheritdoc/>
Task? ITaskNotifier<Task>.Task
{
get => this.task;
set => this.task = value;
}
/// <summary>
/// Unwraps the <see cref="Task"/> value stored in the current instance.
/// </summary>
/// <param name="notifier">The input <see cref="TaskNotifier{TTask}"/> instance.</param>
public static implicit operator Task?(TaskNotifier? notifier)
{
return notifier?.task;
}
}
/// <summary>
/// A wrapping class that can hold a <see cref="Task{T}"/> value.
/// </summary>
/// <typeparam name="T">The type of value for the wrapped <see cref="Task{T}"/> instance.</typeparam>
protected sealed class TaskNotifier<T> : ITaskNotifier<Task<T>>
{
/// <summary>
/// Initializes a new instance of the <see cref="TaskNotifier{TTask}"/> class.
/// </summary>
internal TaskNotifier()
{
}
private Task<T>? task;
/// <inheritdoc/>
Task<T>? ITaskNotifier<Task<T>>.Task
{
get => this.task;
set => this.task = value;
}
/// <summary>
/// Unwraps the <see cref="Task{T}"/> value stored in the current instance.
/// </summary>
/// <param name="notifier">The input <see cref="TaskNotifier{TTask}"/> instance.</param>
public static implicit operator Task<T>?(TaskNotifier<T>? notifier)
{
return notifier?.task;
}
}
}
}

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

@ -9,7 +9,6 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.Mvvm.Messaging;
using Microsoft.Toolkit.Mvvm.Messaging.Messages;
@ -26,11 +25,11 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// Initializes a new instance of the <see cref="ObservableRecipient"/> class.
/// </summary>
/// <remarks>
/// This constructor will produce an instance that will use the <see cref="Messaging.Messenger.Default"/> instance
/// This constructor will produce an instance that will use the <see cref="WeakReferenceMessenger.Default"/> instance
/// to perform requested operations. It will also be available locally through the <see cref="Messenger"/> property.
/// </remarks>
protected ObservableRecipient()
: this(Messaging.Messenger.Default)
: this(WeakReferenceMessenger.Default)
{
}
@ -79,7 +78,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// <remarks>
/// The base implementation registers all messages for this recipients that have been declared
/// explicitly through the <see cref="IRecipient{TMessage}"/> interface, using the default channel.
/// For more details on how this works, see the <see cref="MessengerExtensions.RegisterAll"/> method.
/// For more details on how this works, see the <see cref="IMessengerExtensions.RegisterAll"/> method.
/// If you need more fine tuned control, want to register messages individually or just prefer
/// the lambda-style syntax for message registration, override this method and register manually.
/// </remarks>
@ -204,7 +203,14 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </remarks>
protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool broadcast, [CallerMemberName] string? propertyName = null)
{
return SetProperty(oldValue, newValue, EqualityComparer<T>.Default, callback, broadcast, propertyName);
bool propertyChanged = SetProperty(oldValue, newValue, callback, propertyName);
if (propertyChanged && broadcast)
{
Broadcast(oldValue, newValue, propertyName);
}
return propertyChanged;
}
/// <summary>
@ -237,40 +243,53 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// Compares the current and new values for a given nested property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property and then raises the
/// <see cref="ObservableObject.PropertyChanged"/> event. The behavior mirrors that of
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,string)"/>, with the difference being that this
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string)"/>, with the difference being that this
/// method is used to relay properties from a wrapped model in the current instance. For more info, see the docs for
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,string)"/>.
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string)"/>.
/// </summary>
/// <typeparam name="T">The type of property to set.</typeparam>
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
/// <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="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, bool broadcast, [CallerMemberName] string? propertyName = null)
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, bool broadcast, [CallerMemberName] string? propertyName = null)
{
return SetProperty(propertyExpression, newValue, EqualityComparer<T>.Default, broadcast, propertyName);
bool propertyChanged = SetProperty(oldValue, newValue, model, callback, propertyName);
if (propertyChanged && broadcast)
{
Broadcast(oldValue, newValue, propertyName);
}
return propertyChanged;
}
/// <summary>
/// Compares the current and new values for a given nested property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property and then raises the
/// <see cref="ObservableObject.PropertyChanged"/> event. The behavior mirrors that of
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,IEqualityComparer{T},string)"/>,
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,IEqualityComparer{T},TModel,Action{TModel,T},string)"/>,
/// with the difference being that this method is used to relay properties from a wrapped model in the
/// current instance. For more info, see the docs for
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,IEqualityComparer{T},string)"/>.
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,IEqualityComparer{T},TModel,Action{TModel,T},string)"/>.
/// </summary>
/// <typeparam name="T">The type of property to set.</typeparam>
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
/// <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="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, IEqualityComparer<T> comparer, bool broadcast, [CallerMemberName] string? propertyName = null)
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, bool broadcast, [CallerMemberName] string? propertyName = null)
{
bool propertyChanged = SetProperty(propertyExpression, newValue, comparer, out T oldValue, propertyName);
bool propertyChanged = SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
if (propertyChanged && broadcast)
{

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

@ -0,0 +1,305 @@
// 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.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.Mvvm.ComponentModel
{
/// <summary>
/// A base class for objects implementing the <see cref="INotifyDataErrorInfo"/> interface. This class
/// also inherits from <see cref="ObservableObject"/>, so it can be used for observable items too.
/// </summary>
public abstract class ObservableValidator : ObservableObject, INotifyDataErrorInfo
{
/// <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>>();
/// <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;
}
}
/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event.
/// </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="validate">If <see langword="true"/>, <paramref name="newValue"/> will also be validated.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// This method is just like <see cref="ObservableObject.SetProperty{T}(ref T,T,string)"/>, just with the addition
/// of the <paramref name="validate"/> parameter. If that is set to <see langword="true"/>, the new value will be
/// validated and <see cref="ErrorsChanged"/> will be raised if needed. Following the behavior of the base method,
/// the <see cref="ObservableObject.PropertyChanging"/> and <see cref="ObservableObject.PropertyChanged"/> events
/// are not raised if the current and new value for the target property are the same.
/// </remarks>
protected bool SetProperty<T>(ref T field, T newValue, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(ref field, newValue, propertyName);
}
/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event.
/// See additional notes about this overload in <see cref="SetProperty{T}(ref T,T,bool,string)"/>.
/// </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="validate">If <see langword="true"/>, <paramref name="newValue"/> will also be validated.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <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)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(ref field, newValue, comparer, propertyName);
}
/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event. Similarly to
/// the <see cref="ObservableObject.SetProperty{T}(T,T,Action{T},string)"/> method, this overload should only be
/// used when <see cref="ObservableObject.SetProperty{T}(ref T,T,string)"/> can't be used directly.
/// </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="validate">If <see langword="true"/>, <paramref name="newValue"/> will also be validated.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// This method is just like <see cref="ObservableObject.SetProperty{T}(T,T,Action{T},string)"/>, just with the addition
/// of the <paramref name="validate"/> parameter. As such, following the behavior of the base method,
/// the <see cref="ObservableObject.PropertyChanging"/> and <see cref="ObservableObject.PropertyChanged"/> events
/// are not raised if the current and new value for the target property are the same.
/// </remarks>
protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, callback, propertyName);
}
/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event.
/// See additional notes about this overload in <see cref="SetProperty{T}(T,T,Action{T},bool,string)"/>.
/// </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="validate">If <see langword="true"/>, <paramref name="newValue"/> will also be validated.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <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)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, comparer, callback, propertyName);
}
/// <summary>
/// Compares the current and new values for a given nested property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property and then raises the
/// <see cref="ObservableObject.PropertyChanged"/> event. The behavior mirrors that of
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string)"/>, with the difference being that this
/// method is used to relay properties from a wrapped model in the current instance. For more info, see the docs for
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string)"/>.
/// </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="validate">If <see langword="true"/>, <paramref name="newValue"/> will also be validated.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, model, callback, propertyName);
}
/// <summary>
/// Compares the current and new values for a given nested property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property and then raises the
/// <see cref="ObservableObject.PropertyChanged"/> event. The behavior mirrors that of
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,IEqualityComparer{T},TModel,Action{TModel,T},string)"/>,
/// with the difference being that this method is used to relay properties from a wrapped model in the
/// current instance. For more info, see the docs for
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,IEqualityComparer{T},TModel,Action{TModel,T},string)"/>.
/// </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="validate">If <see langword="true"/>, <paramref name="newValue"/> will also be validated.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
}
/// <inheritdoc/>
[Pure]
public IEnumerable GetErrors(string? propertyName)
{
// Entity-level errors when the target property is null or empty
if (string.IsNullOrEmpty(propertyName))
{
return this.GetAllErrors();
}
// Property-level errors, if any
if (this.errors.TryGetValue(propertyName!, out List<ValidationResult> errors))
{
return errors;
}
// The INotifyDataErrorInfo.GetErrors method doesn't specify exactly what to
// return when the input property name is invalid, but given that the return
// type is marked as a non-nullable reference type, here we're returning an
// empty array to respect the contract. This also matches the behavior of
// this method whenever errors for a valid properties are retrieved.
return Array.Empty<ValidationResult>();
}
/// <summary>
/// Implements the logic for entity-level errors gathering for <see cref="GetErrors"/>.
/// </summary>
/// <returns>An <see cref="IEnumerable"/> instance with all the errors in <see cref="errors"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.NoInlining)]
private IEnumerable GetAllErrors()
{
return this.errors.Values.SelectMany(errors => errors);
}
/// <summary>
/// Validates a property with a specified name and a given input value.
/// </summary>
/// <param name="value">The value to test for the specified property.</param>
/// <param name="propertyName">The name of the property to validate.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="propertyName"/> is <see langword="null"/>.</exception>
private void ValidateProperty(object? value, string? propertyName)
{
if (propertyName is null)
{
ThrowArgumentNullExceptionForNullPropertyName();
}
// Check if the property had already been previously validated, and if so retrieve
// the reusable list of validation errors from the errors dictionary. This list is
// used to add new validation errors below, if any are produced by the validator.
// If the property isn't present in the dictionary, add it now to avoid allocations.
if (!this.errors.TryGetValue(propertyName!, out List<ValidationResult>? propertyErrors))
{
propertyErrors = new List<ValidationResult>();
this.errors.Add(propertyName!, propertyErrors);
}
bool errorsChanged = false;
// Clear the errors for the specified property, if any
if (propertyErrors.Count > 0)
{
propertyErrors.Clear();
errorsChanged = true;
}
// Validate the property, by adding new errors to the existing list
bool isValid = Validator.TryValidateProperty(
value,
new ValidationContext(this, null, null) { MemberName = propertyName },
propertyErrors);
// 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.
if (errorsChanged || !isValid)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
}
#pragma warning disable SA1204
/// <summary>
/// Throws an <see cref="ArgumentNullException"/> when a property name given as input is <see langword="null"/>.
/// </summary>
private static void ThrowArgumentNullExceptionForNullPropertyName()
{
throw new ArgumentNullException("propertyName", "The input property name cannot be null when validating a property");
}
#pragma warning restore SA1204
}
}

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

@ -1,145 +0,0 @@
// 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;
using Microsoft.Extensions.DependencyInjection;
#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 one of
/// the available <see cref="ConfigureServices(IServiceCollection)"/> overloads, like so:
/// <code>
/// Ioc.Default.ConfigureServices(services =>
/// {
/// services.AddSingleton&lt;ILogger, Logger&gt;();
/// });
/// </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="ServiceProvider"/> instance to use, if initialized.
/// </summary>
private volatile ServiceProvider? serviceProvider;
/// <inheritdoc/>
object? IServiceProvider.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.
ServiceProvider? provider = this.serviceProvider;
if (provider is null)
{
ThrowInvalidOperationExceptionForMissingInitialization();
}
return provider!.GetService(serviceType);
}
/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="setup">The configuration delegate to use to add services.</param>
public void ConfigureServices(Action<IServiceCollection> setup)
{
ConfigureServices(setup, new ServiceProviderOptions());
}
/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="setup">The configuration delegate to use to add services.</param>
/// <param name="options">The <see cref="ServiceProviderOptions"/> instance to configure the service provider behaviors.</param>
public void ConfigureServices(Action<IServiceCollection> setup, ServiceProviderOptions options)
{
var collection = new ServiceCollection();
setup(collection);
ConfigureServices(collection, options);
}
/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="services">The input <see cref="IServiceCollection"/> instance to use.</param>
public void ConfigureServices(IServiceCollection services)
{
ConfigureServices(services, new ServiceProviderOptions());
}
/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="services">The input <see cref="IServiceCollection"/> instance to use.</param>
/// <param name="options">The <see cref="ServiceProviderOptions"/> instance to configure the service provider behaviors.</param>
public void ConfigureServices(IServiceCollection services, ServiceProviderOptions options)
{
ServiceProvider newServices = services.BuildServiceProvider(options);
ServiceProvider? oldServices = Interlocked.CompareExchange(ref this.serviceProvider, newServices, null);
if (!(oldServices is null))
{
ThrowInvalidOperationExceptionForRepeatedConfiguration();
}
}
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when the <see cref="ServiceProvider"/> 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 a configuration is attempted more than once.
/// </summary>
private static void ThrowInvalidOperationExceptionForRepeatedConfiguration()
{
throw new InvalidOperationException("The default service provider has already been configured");
}
}
}

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

@ -4,6 +4,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Toolkit.Mvvm.ComponentModel;
@ -20,13 +21,25 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <summary>
/// The <see cref="Func{TResult}"/> to invoke when <see cref="Execute"/> is used.
/// </summary>
private readonly Func<Task> execute;
private readonly Func<Task>? execute;
/// <summary>
/// The cancelable <see cref="Func{T,TResult}"/> to invoke when <see cref="Execute"/> is used.
/// </summary>
/// <remarks>Only one between this and <see cref="execute"/> is not <see langword="null"/>.</remarks>
private readonly Func<CancellationToken, Task>? cancelableExecute;
/// <summary>
/// The optional action to invoke when <see cref="CanExecute"/> is used.
/// </summary>
private readonly Func<bool>? canExecute;
/// <summary>
/// The <see cref="CancellationTokenSource"/> instance to use to cancel <see cref="cancelableExecute"/>.
/// </summary>
/// <remarks>This is only used when <see cref="cancelableExecute"/> is not <see langword="null"/>.</remarks>
private CancellationTokenSource? cancellationTokenSource;
/// <inheritdoc/>
public event EventHandler? CanExecuteChanged;
@ -42,6 +55,15 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <summary>
/// Initializes a new instance of the <see cref="AsyncRelayCommand"/> class that can always execute.
/// </summary>
/// <param name="cancelableExecute">The cancelable execution logic.</param>
public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute)
{
this.cancelableExecute = cancelableExecute;
}
/// <summary>
/// Initializes a new instance of the <see cref="AsyncRelayCommand"/> class.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public AsyncRelayCommand(Func<Task> execute, Func<bool> canExecute)
@ -50,7 +72,18 @@ namespace Microsoft.Toolkit.Mvvm.Input
this.canExecute = canExecute;
}
private Task? executionTask;
/// <summary>
/// Initializes a new instance of the <see cref="AsyncRelayCommand"/> class.
/// </summary>
/// <param name="cancelableExecute">The cancelable execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute, Func<bool> canExecute)
{
this.cancelableExecute = cancelableExecute;
this.canExecute = canExecute;
}
private TaskNotifier? executionTask;
/// <inheritdoc/>
public Task? ExecutionTask
@ -58,13 +91,19 @@ namespace Microsoft.Toolkit.Mvvm.Input
get => this.executionTask;
private set
{
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, () => this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning))))
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning))))
{
OnPropertyChanged(nameof(IsRunning));
}
}
}
/// <inheritdoc/>
public bool CanBeCanceled => !(this.cancelableExecute is null);
/// <inheritdoc/>
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
/// <inheritdoc/>
public bool IsRunning => ExecutionTask?.IsCompleted == false;
@ -92,10 +131,32 @@ namespace Microsoft.Toolkit.Mvvm.Input
{
if (CanExecute(parameter))
{
return ExecutionTask = this.execute();
// Non cancelable command delegate
if (!(this.execute is null))
{
return ExecutionTask = this.execute();
}
// Cancel the previous operation, if one is pending
this.cancellationTokenSource?.Cancel();
var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
OnPropertyChanged(nameof(IsCancellationRequested));
// Invoke the cancelable command delegate with a new linked token
return ExecutionTask = this.cancelableExecute!(cancellationTokenSource.Token);
}
return Task.CompletedTask;
}
/// <inheritdoc/>
public void Cancel()
{
this.cancellationTokenSource?.Cancel();
OnPropertyChanged(nameof(IsCancellationRequested));
}
}
}

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

@ -4,6 +4,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Toolkit.Mvvm.ComponentModel;
@ -18,13 +19,23 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <summary>
/// The <see cref="Func{TResult}"/> to invoke when <see cref="Execute(T)"/> is used.
/// </summary>
private readonly Func<T, Task> execute;
private readonly Func<T, Task>? execute;
/// <summary>
/// The cancelable <see cref="Func{T1,T2,TResult}"/> to invoke when <see cref="Execute(object?)"/> is used.
/// </summary>
private readonly Func<T, CancellationToken, Task>? cancelableExecute;
/// <summary>
/// The optional action to invoke when <see cref="CanExecute(T)"/> is used.
/// </summary>
private readonly Func<T, bool>? canExecute;
/// <summary>
/// The <see cref="CancellationTokenSource"/> instance to use to cancel <see cref="cancelableExecute"/>.
/// </summary>
private CancellationTokenSource? cancellationTokenSource;
/// <inheritdoc/>
public event EventHandler? CanExecuteChanged;
@ -41,6 +52,16 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <summary>
/// Initializes a new instance of the <see cref="AsyncRelayCommand{T}"/> class that can always execute.
/// </summary>
/// <param name="cancelableExecute">The cancelable execution logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute)
{
this.cancelableExecute = cancelableExecute;
}
/// <summary>
/// Initializes a new instance of the <see cref="AsyncRelayCommand{T}"/> class.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
@ -50,7 +71,19 @@ namespace Microsoft.Toolkit.Mvvm.Input
this.canExecute = canExecute;
}
private Task? executionTask;
/// <summary>
/// Initializes a new instance of the <see cref="AsyncRelayCommand{T}"/> class.
/// </summary>
/// <param name="cancelableExecute">The cancelable execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute, Func<T, bool> canExecute)
{
this.cancelableExecute = cancelableExecute;
this.canExecute = canExecute;
}
private TaskNotifier? executionTask;
/// <inheritdoc/>
public Task? ExecutionTask
@ -58,13 +91,19 @@ namespace Microsoft.Toolkit.Mvvm.Input
get => this.executionTask;
private set
{
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, () => this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning))))
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning))))
{
OnPropertyChanged(nameof(IsRunning));
}
}
}
/// <inheritdoc/>
public bool CanBeCanceled => !(this.cancelableExecute is null);
/// <inheritdoc/>
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
/// <inheritdoc/>
public bool IsRunning => ExecutionTask?.IsCompleted == false;
@ -113,7 +152,21 @@ namespace Microsoft.Toolkit.Mvvm.Input
{
if (CanExecute(parameter))
{
return ExecutionTask = this.execute(parameter);
// Non cancelable command delegate
if (!(this.execute is null))
{
return ExecutionTask = this.execute(parameter);
}
// Cancel the previous operation, if one is pending
this.cancellationTokenSource?.Cancel();
var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
OnPropertyChanged(nameof(IsCancellationRequested));
// Invoke the cancelable command delegate with a new linked token
return ExecutionTask = this.cancelableExecute!(parameter, cancellationTokenSource.Token);
}
return Task.CompletedTask;
@ -124,5 +177,13 @@ namespace Microsoft.Toolkit.Mvvm.Input
{
return ExecuteAsync((T)parameter!);
}
/// <inheritdoc/>
public void Cancel()
{
this.cancellationTokenSource?.Cancel();
OnPropertyChanged(nameof(IsCancellationRequested));
}
}
}

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

@ -18,6 +18,16 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// </summary>
Task? ExecutionTask { get; }
/// <summary>
/// Gets a value indicating whether running operations for this command can be canceled.
/// </summary>
bool CanBeCanceled { get; }
/// <summary>
/// Gets a value indicating whether a cancelation request has been issued for the current operation.
/// </summary>
bool IsCancellationRequested { get; }
/// <summary>
/// Gets a value indicating whether the command currently has a pending operation being executed.
/// </summary>
@ -30,5 +40,14 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <param name="parameter">The input parameter.</param>
/// <returns>The <see cref="Task"/> representing the async operation being executed.</returns>
Task ExecuteAsync(object? parameter);
/// <summary>
/// Communicates a request for cancelation.
/// </summary>
/// <remarks>
/// If the underlying command is not running, or if it does not support cancelation, this method will perform no action.
/// Note that even with a successful cancelation, the completion of the current operation might not be immediate.
/// </remarks>
void Cancel();
}
}

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

@ -7,8 +7,70 @@ using System.Diagnostics.Contracts;
namespace Microsoft.Toolkit.Mvvm.Messaging
{
/// <summary>
/// A <see langword="delegate"/> used to represent actions to invoke when a message is received.
/// The recipient is given as an input argument to allow message registrations to avoid creating
/// closures: if an instance method on a recipient needs to be invoked it is possible to just
/// cast the recipient to the right type and then access the local method from that instance.
/// </summary>
/// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
/// <param name="recipient">The recipient that is receiving the message.</param>
/// <param name="message">The message being received.</param>
public delegate void MessageHandler<in TRecipient, in TMessage>(TRecipient recipient, TMessage message)
where TRecipient : class
where TMessage : class;
/// <summary>
/// An interface for a type providing the ability to exchange messages between different objects.
/// This can be useful to decouple different modules of an application without having to keep strong
/// references to types being referenced. It is also possible to send messages to specific channels, uniquely
/// identified by a token, and to have different messengers in different sections of an applications.
/// In order to use the <see cref="IMessenger"/> functionalities, first define a message type, like so:
/// <code>
/// public sealed class LoginCompletedMessage { }
/// </code>
/// Then, register your a recipient for this message:
/// <code>
/// Messenger.Default.Register&lt;MyRecipientType, LoginCompletedMessage&gt;(this, (r, m) =>
/// {
/// // Handle the message here...
/// });
/// </code>
/// The message handler here is a lambda expression taking two parameters: the recipient and the message.
/// This is done to avoid the allocations for the closures that would've been generated if the expression
/// had captured the current instance. The recipient type parameter is used so that the recipient can be
/// directly accessed within the handler without the need to manually perform type casts. This allows the
/// code to be less verbose and more reliable, as all the checks are done just at build time. If the handler
/// is defined within the same type as the recipient, it is also possible to directly access private members.
/// This allows the message handler to be a static method, which enables the C# compiler to perform a number
/// of additional memory optimizations (such as caching the delegate, avoiding unnecessary memory allocations).
/// Finally, send a message when needed, like so:
/// <code>
/// Messenger.Default.Send&lt;LoginCompletedMessage&gt;();
/// </code>
/// Additionally, the method group syntax can also be used to specify the message handler
/// to invoke when receiving a message, if a method with the right signature is available
/// in the current scope. This is helpful to keep the registration and handling logic separate.
/// Following up from the previous example, consider a class having this method:
/// <code>
/// private static void Receive(MyRecipientType recipient, LoginCompletedMessage message)
/// {
/// // Handle the message there
/// }
/// </code>
/// The registration can then be performed in a single line like so:
/// <code>
/// Messenger.Default.Register(this, Receive);
/// </code>
/// The C# compiler will automatically convert that expression to a <see cref="MessageHandler{TRecipient,TMessage}"/> instance
/// compatible with <see cref="IMessengerExtensions.Register{TRecipient,TMessage}(IMessenger,TRecipient,MessageHandler{TRecipient,TMessage})"/>.
/// This will also work if multiple overloads of that method are available, each handling a different
/// message type: the C# compiler will automatically pick the right one for the current message type.
/// It is also possible to register message handlers explicitly using the <see cref="IRecipient{TMessage}"/> interface.
/// To do so, the recipient just needs to implement the interface and then call the
/// <see cref="IMessengerExtensions.RegisterAll(IMessenger,object)"/> extension, which will automatically register
/// all the handlers that are declared by the recipient type. Registration for individual handlers is supported as well.
/// </summary>
public interface IMessenger
{
@ -28,13 +90,15 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// <summary>
/// Registers a recipient for a given type of message.
/// </summary>
/// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
/// <typeparam name="TToken">The type of token to use to pick the messages to receive.</typeparam>
/// <param name="recipient">The recipient that will receive the messages.</param>
/// <param name="token">A token used to determine the receiving channel to use.</param>
/// <param name="action">The <see cref="Action{T}"/> to invoke when a message is received.</param>
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
void Register<TMessage, TToken>(object recipient, TToken token, Action<TMessage> action)
void Register<TRecipient, TMessage, TToken>(TRecipient recipient, TToken token, MessageHandler<TRecipient, TMessage> handler)
where TRecipient : class
where TMessage : class
where TToken : IEquatable<TToken>;
@ -83,6 +147,14 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
where TMessage : class
where TToken : IEquatable<TToken>;
/// <summary>
/// Performs a cleanup on the current messenger.
/// Invoking this method does not unregister any of the currently registered
/// recipient, and it can be used to perform cleanup operations such as
/// trimming the internal data structures of a messenger implementation.
/// </summary>
void Cleanup();
/// <summary>
/// Resets the <see cref="IMessenger"/> instance and unregisters all the existing recipients.
/// </summary>

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

@ -8,19 +8,20 @@ using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.Mvvm.Messaging.Internals;
namespace Microsoft.Toolkit.Mvvm.Messaging
{
/// <summary>
/// Extensions for the <see cref="IMessenger"/> type.
/// </summary>
public static partial class MessengerExtensions
public static class IMessengerExtensions
{
/// <summary>
/// A class that acts as a container to load the <see cref="MethodInfo"/> instance linked to
/// the <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/> method.
/// This class is needed to avoid forcing the initialization code in the static constructor to run as soon as
/// the <see cref="MessengerExtensions"/> type is referenced, even if that is done just to use methods
/// the <see cref="IMessengerExtensions"/> type is referenced, even if that is done just to use methods
/// that do not actually require this <see cref="MethodInfo"/> instance to be available.
/// We're effectively using this type to leverage the lazy loading of static constructors done by the runtime.
/// </summary>
@ -32,7 +33,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
static MethodInfos()
{
RegisterIRecipient = (
from methodInfo in typeof(MessengerExtensions).GetMethods()
from methodInfo in typeof(IMessengerExtensions).GetMethods()
where methodInfo.Name == nameof(Register) &&
methodInfo.IsGenericMethod &&
methodInfo.GetGenericArguments().Length == 2
@ -174,7 +175,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
public static void Register<TMessage>(this IMessenger messenger, IRecipient<TMessage> recipient)
where TMessage : class
{
messenger.Register<TMessage, Unit>(recipient, default, recipient.Receive);
messenger.Register<IRecipient<TMessage>, TMessage, Unit>(recipient, default, (r, m) => r.Receive(m));
}
/// <summary>
@ -191,7 +192,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
where TMessage : class
where TToken : IEquatable<TToken>
{
messenger.Register<TMessage, TToken>(recipient, token, recipient.Receive);
messenger.Register<IRecipient<TMessage>, TMessage, TToken>(recipient, token, (r, m) => r.Receive(m));
}
/// <summary>
@ -200,13 +201,47 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
/// <param name="recipient">The recipient that will receive the messages.</param>
/// <param name="action">The <see cref="Action{T}"/> to invoke when a message is received.</param>
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
/// <remarks>This method will use the default channel to perform the requested registration.</remarks>
public static void Register<TMessage>(this IMessenger messenger, object recipient, Action<TMessage> action)
public static void Register<TMessage>(this IMessenger messenger, object recipient, MessageHandler<object, TMessage> handler)
where TMessage : class
{
messenger.Register(recipient, default(Unit), action);
messenger.Register(recipient, default(Unit), handler);
}
/// <summary>
/// Registers a recipient for a given type of message.
/// </summary>
/// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
/// <param name="recipient">The recipient that will receive the messages.</param>
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
/// <remarks>This method will use the default channel to perform the requested registration.</remarks>
public static void Register<TRecipient, TMessage>(this IMessenger messenger, TRecipient recipient, MessageHandler<TRecipient, TMessage> handler)
where TRecipient : class
where TMessage : class
{
messenger.Register(recipient, default(Unit), handler);
}
/// <summary>
/// Registers a recipient for a given type of message.
/// </summary>
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
/// <typeparam name="TToken">The type of token to use to pick the messages to receive.</typeparam>
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
/// <param name="recipient">The recipient that will receive the messages.</param>
/// <param name="token">A token used to determine the receiving channel to use.</param>
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
public static void Register<TMessage, TToken>(this IMessenger messenger, object recipient, TToken token, MessageHandler<object, TMessage> handler)
where TMessage : class
where TToken : IEquatable<TToken>
{
messenger.Register(recipient, token, handler);
}
/// <summary>

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

@ -130,7 +130,11 @@ namespace Microsoft.Collections.Extensions
this.entries = InitialEntries;
}
/// <inheritdoc cref="Dictionary{TKey,TValue}.ContainsKey"/>
/// <summary>
/// Checks whether or not the dictionary contains a pair with a specified key.
/// </summary>
/// <param name="key">The key to look for.</param>
/// <returns>Whether or not the key was present in the dictionary.</returns>
public bool ContainsKey(TKey key)
{
Entry[] entries = this.entries;
@ -176,7 +180,18 @@ namespace Microsoft.Collections.Extensions
}
/// <inheritdoc/>
public bool TryRemove(TKey key, out object? result)
public bool TryRemove(TKey key)
{
return TryRemove(key, out _);
}
/// <summary>
/// Tries to remove a value with a specified key, if present.
/// </summary>
/// <param name="key">The key of the value to remove.</param>
/// <param name="result">The removed value, if it was present.</param>
/// <returns>Whether or not the key was present.</returns>
public bool TryRemove(TKey key, out TValue? result)
{
Entry[] entries = this.entries;
int bucketIndex = key.GetHashCode() & (this.buckets.Length - 1);
@ -218,13 +233,6 @@ namespace Microsoft.Collections.Extensions
return false;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Remove(TKey key)
{
return TryRemove(key, out _);
}
/// <summary>
/// Gets the value for the specified key, or, if the key is not present,
/// adds an entry and returns the value by ref. This makes it possible to

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

@ -14,18 +14,10 @@ namespace Microsoft.Collections.Extensions
where TKey : IEquatable<TKey>
{
/// <summary>
/// Tries to remove a value with a specified key.
/// Tries to remove a value with a specified key, if present.
/// </summary>
/// <param name="key">The key of the value to remove.</param>
/// <param name="result">The removed value, if it was present.</param>
/// <returns>.Whether or not the key was present.</returns>
bool TryRemove(TKey key, out object? result);
/// <summary>
/// Removes an item from the dictionary with the specified key, if present.
/// </summary>
/// <param name="key">The key of the item to remove.</param>
/// <returns>Whether or not an item was removed.</returns>
bool Remove(TKey key);
/// <returns>Whether or not the key was present.</returns>
bool TryRemove(TKey key);
}
}

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

@ -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 System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
{
/// <summary>
/// A simple type representing an immutable pair of types.
/// </summary>
/// <remarks>
/// This type replaces a simple <see cref="ValueTuple{T1,T2}"/> as it's faster in its
/// <see cref="GetHashCode"/> and <see cref="IEquatable{T}.Equals(T)"/> methods, and because
/// unlike a value tuple it exposes its fields as immutable. Additionally, the
/// <see cref="TMessage"/> and <see cref="TToken"/> fields provide additional clarity reading
/// the code compared to <see cref="ValueTuple{T1,T2}.Item1"/> and <see cref="ValueTuple{T1,T2}.Item2"/>.
/// </remarks>
internal readonly struct Type2 : IEquatable<Type2>
{
/// <summary>
/// The type of registered message.
/// </summary>
public readonly Type TMessage;
/// <summary>
/// The type of registration token.
/// </summary>
public readonly Type TToken;
/// <summary>
/// Initializes a new instance of the <see cref="Type2"/> struct.
/// </summary>
/// <param name="tMessage">The type of registered message.</param>
/// <param name="tToken">The type of registration token.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Type2(Type tMessage, Type tToken)
{
TMessage = tMessage;
TToken = tToken;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Type2 other)
{
// We can't just use reference equality, as that's technically not guaranteed
// to work and might fail in very rare cases (eg. with type forwarding between
// different assemblies). Instead, we can use the == operator to compare for
// equality, which still avoids the callvirt overhead of calling Type.Equals,
// and is also implemented as a JIT intrinsic on runtimes such as .NET Core.
return
TMessage == other.TMessage &&
TToken == other.TToken;
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is Type2 other && Equals(other);
}
/// <inheritdoc/>
[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);
hash = (hash << 5) + hash;
hash += RuntimeHelpers.GetHashCode(TToken);
return hash;
}
}
}
}

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

@ -0,0 +1,35 @@
// 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.Runtime.CompilerServices;
namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
{
/// <summary>
/// An empty type representing a generic token with no specific value.
/// </summary>
internal readonly struct Unit : IEquatable<Unit>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Unit other)
{
return true;
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is Unit;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
return 0;
}
}
}

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

@ -1,41 +0,0 @@
// 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.Runtime.CompilerServices;
namespace Microsoft.Toolkit.Mvvm.Messaging
{
/// <summary>
/// Extensions for the <see cref="IMessenger"/> type.
/// </summary>
public static partial class MessengerExtensions
{
/// <summary>
/// An empty type representing a generic token with no specific value.
/// </summary>
private readonly struct Unit : IEquatable<Unit>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Unit other)
{
return true;
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is Unit;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
return 0;
}
}
}
}

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

@ -8,71 +8,46 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Collections.Extensions;
using Microsoft.Toolkit.Mvvm.Messaging.Internals;
namespace Microsoft.Toolkit.Mvvm.Messaging
{
/// <summary>
/// A type that can be used to exchange messages between different objects.
/// This can be useful to decouple different modules of an application without having to keep strong
/// references to types being referenced. It is also possible to send messages to specific channels, uniquely
/// identified by a token, and to have different messengers in different sections of an applications.
/// In order to use the <see cref="IMessenger"/> functionalities, first define a message type, like so:
/// <code>
/// public sealed class LoginCompletedMessage { }
/// </code>
/// Then, register your a recipient for this message:
/// <code>
/// Messenger.Default.Register&lt;LoginCompletedMessage&gt;(this, m =>
/// {
/// // Handle the message here...
/// });
/// </code>
/// Finally, send a message when needed, like so:
/// <code>
/// Messenger.Default.Send&lt;LoginCompletedMessage&gt;();
/// </code>
/// Additionally, the method group syntax can also be used to specify the action
/// to invoke when receiving a message, if a method with the right signature is available
/// in the current scope. This is helpful to keep the registration and handling logic separate.
/// Following up from the previous example, consider a class having this method:
/// <code>
/// private void Receive(LoginCompletedMessage message)
/// {
/// // Handle the message there
/// }
/// </code>
/// The registration can then be performed in a single line like so:
/// <code>
/// Messenger.Default.Register&lt;LoginCompletedMessage&gt;(this, Receive);
/// </code>
/// The C# compiler will automatically convert that expression to an <see cref="Action{T}"/> instance
/// compatible with the <see cref="MessengerExtensions.Register{T}(IMessenger,object,Action{T})"/> method.
/// This will also work if multiple overloads of that method are available, each handling a different
/// message type: the C# compiler will automatically pick the right one for the current message type.
/// For info on the other available features, check the <see cref="IMessenger"/> interface.
/// A class providing a reference implementation for the <see cref="IMessenger"/> interface.
/// </summary>
public sealed class Messenger : IMessenger
/// <remarks>
/// This <see cref="IMessenger"/> implementation uses strong references to track the registered
/// recipients, so it is necessary to manually unregister them when they're no longer needed.
/// </remarks>
public sealed class StrongReferenceMessenger : IMessenger
{
// The Messenger class uses the following logic to link stored instances together:
// This messenger uses the following logic to link stored instances together:
// --------------------------------------------------------------------------------------------------------
// DictionarySlim<Recipient, HashSet<IMapping>> recipientsMap;
// | \________________[*]IDictionarySlim<Recipient, IDictionarySlim<TToken>>
// | \___ / / /
// | ________(recipients registrations)___________\________/ / __/
// | / _______(channel registrations)_____\___________________/ /
// | / / \ /
// DictionarySlim<Recipient, DictionarySlim<TToken, Action<TMessage>>> mapping = Mapping<TMessage, TToken>
// / / \ / /
// ___(Type2.tToken)____/ / \______/___________________/
// /________________(Type2.tMessage)____/ /
// / ________________________________________/
// | \____________/_________ /
// | ________(recipients registrations)____________________/ \ /
// | / ____(channel registrations)________________\____________/
// | / / \
// DictionarySlim<Recipient, DictionarySlim<TToken, MessageHandler<TRecipient, TMessage>>> mapping = Mapping<TMessage, TToken>
// / / /
// ___(Type2.TToken)____/ / /
// /________________(Type2.TMessage)________________________/ /
// / ____________________________________________________________/
// / /
// DictionarySlim<Type2, IMapping> typesMap;
// --------------------------------------------------------------------------------------------------------
// Each combination of <TMessage, TToken> results in a concrete Mapping<TMessage, TToken> type, which holds
// the references from registered recipients to handlers. The handlers are stored in a <TToken, Action<TMessage>>
// dictionary, so that each recipient can have up to one registered handler for a given token, for each
// message type. Each mapping is stored in the types map, which associates each pair of concrete types to its
// Each combination of <TMessage, TToken> results in a concrete Mapping<TMessage, TToken> type, which holds the references
// from registered recipients to handlers. The handlers are stored in a <TToken, MessageHandler<object, TMessage>> dictionary,
// so that each recipient can have up to one registered handler for a given token, for each message type.
// Note that the registered handlers are only stored as object references, even if they were actually of type
// MessageHandler<TRecipient, TMessage>, to avoid unnecessary unsafe casts. Each handler is also generic with respect to the
// recipient type, in order to allow the messenger to track and invoke type-specific handlers without using reflection and
// without having to capture the input handler in a proxy delegate, causing one extra memory allocations and adding overhead.
// This allows users to retain type information on each registered recipient, instead of having to manually cast each recipient
// to the right type within the handler. The type conversion is guaranteed to be respected due to how the messenger type
// itself works - as registered handlers are always invoked on their respective recipients.
// Each mapping is stored in the types map, which associates each pair of concrete types to its
// mapping instance. Mapping instances are exposed as IMapping items, as each will be a closed type over
// a different combination of TMessage and TToken generic type parameters. Each existing recipient is also stored in
// the main recipients map, along with a set of all the existing dictionaries of handlers for that recipient (for all
@ -109,9 +84,9 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
private readonly DictionarySlim<Type2, IMapping> typesMap = new DictionarySlim<Type2, IMapping>();
/// <summary>
/// Gets the default <see cref="Messenger"/> instance.
/// Gets the default <see cref="StrongReferenceMessenger"/> instance.
/// </summary>
public static Messenger Default { get; } = new Messenger();
public static StrongReferenceMessenger Default { get; } = new StrongReferenceMessenger();
/// <inheritdoc/>
public bool IsRegistered<TMessage, TToken>(object recipient, TToken token)
@ -132,7 +107,8 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
}
/// <inheritdoc/>
public void Register<TMessage, TToken>(object recipient, TToken token, Action<TMessage> action)
public void Register<TRecipient, TMessage, TToken>(TRecipient recipient, TToken token, MessageHandler<TRecipient, TMessage> handler)
where TRecipient : class
where TMessage : class
where TToken : IEquatable<TToken>
{
@ -141,22 +117,20 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
// Get the <TMessage, TToken> registration list for this recipient
Mapping<TMessage, TToken> mapping = GetOrAddMapping<TMessage, TToken>();
var key = new Recipient(recipient);
ref DictionarySlim<TToken, Action<TMessage>>? map = ref mapping.GetOrAddValueRef(key);
ref DictionarySlim<TToken, object>? map = ref mapping.GetOrAddValueRef(key);
map ??= new DictionarySlim<TToken, Action<TMessage>>();
map ??= new DictionarySlim<TToken, object>();
// Add the new registration entry
ref Action<TMessage>? handler = ref map.GetOrAddValueRef(token);
ref object? registeredHandler = ref map.GetOrAddValueRef(token);
if (!(handler is null))
if (!(registeredHandler is null))
{
ThrowInvalidOperationExceptionForDuplicateRegistration();
}
handler = action;
// Update the total counter for handlers for the current type parameters
mapping.TotalHandlersCount++;
// Treat the input delegate as if it was covariant (see comments below in the Send method)
registeredHandler = handler;
// Make sure this registration map is tracked for the current recipient
ref HashSet<IMapping>? set = ref this.recipientsMap.GetOrAddValueRef(key);
@ -183,37 +157,23 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
// Removes all the lists of registered handlers for the recipient
foreach (IMapping mapping in set!)
{
if (mapping.TryRemove(key, out object? handlersMap))
if (mapping.TryRemove(key) &&
mapping.Count == 0)
{
// If this branch is taken, it means the target recipient to unregister
// had at least one registered handler for the current <TToken, TMessage>
// pair of type parameters, which here is masked out by the IMapping interface.
// Before removing the handlers, we need to retrieve the count of how many handlers
// are being removed, in order to update the total counter for the mapping.
// Just casting the dictionary to the base interface and accessing the Count
// property directly gives us O(1) access time to retrieve this count.
// The handlers map is the IDictionary<TToken, TMessage> instance for the mapping.
int handlersCount = Unsafe.As<IDictionarySlim>(handlersMap).Count;
mapping.TotalHandlersCount -= handlersCount;
if (mapping.Count == 0)
{
// Maps here are really of type Mapping<,> and with unknown type arguments.
// If after removing the current recipient a given map becomes empty, it means
// that there are no registered recipients at all for a given pair of message
// and token types. In that case, we also remove the map from the types map.
// The reason for keeping a key in each mapping is that removing items from a
// dictionary (a hashed collection) only costs O(1) in the best case, while
// if we had tried to iterate the whole dictionary every time we would have
// paid an O(n) minimum cost for each single remove operation.
this.typesMap.Remove(mapping.TypeArguments);
}
// Maps here are really of type Mapping<,> and with unknown type arguments.
// If after removing the current recipient a given map becomes empty, it means
// that there are no registered recipients at all for a given pair of message
// and token types. In that case, we also remove the map from the types map.
// The reason for keeping a key in each mapping is that removing items from a
// dictionary (a hashed collection) only costs O(1) in the best case, while
// if we had tried to iterate the whole dictionary every time we would have
// paid an O(n) minimum cost for each single remove operation.
this.typesMap.TryRemove(mapping.TypeArguments, out _);
}
}
// Remove the associated set in the recipients map
this.recipientsMap.Remove(key);
this.recipientsMap.TryRemove(key, out _);
}
}
@ -222,7 +182,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
where TToken : IEquatable<TToken>
{
bool lockTaken = false;
IDictionarySlim<Recipient, IDictionarySlim<TToken>>[]? maps = null;
object[]? maps = null;
int i = 0;
// We use an explicit try/finally block here instead of the lock syntax so that we can use a single
@ -243,11 +203,16 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
return;
}
// Copy the candidate mappings for the target recipient to a local
// array, as we can't modify the contents of the set while iterating it.
// The rented buffer is oversized and will also include mappings for
// handlers of messages that are registered through a different token.
maps = ArrayPool<IDictionarySlim<Recipient, IDictionarySlim<TToken>>>.Shared.Rent(set!.Count);
// Copy the candidate mappings for the target recipient to a local array, as we can't modify the
// contents of the set while iterating it. The rented buffer is oversized and will also include
// mappings for handlers of messages that are registered through a different token. Note that
// we're using just an object array to minimize the number of total rented buffers, that would
// just remain in the shared pool unused, other than when they are rented here. Instead, we're
// using a type that would possibly also be used by the users of the library, which increases
// the opportunities to reuse existing buffers for both. When we need to reference an item
// stored in the buffer with the type we know it will have, we use Unsafe.As<T> to avoid the
// expensive type check in the cast, since we already know the assignment will be valid.
maps = ArrayPool<object>.Shared.Rent(set!.Count);
foreach (IMapping item in set)
{
@ -265,8 +230,10 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
// without having to know the concrete type in advance, and without having
// to deal with reflection: we can just check if the type of the closed interface
// matches with the token type currently in use, and operate on those instances.
foreach (IDictionarySlim<Recipient, IDictionarySlim<TToken>> map in maps.AsSpan(0, i))
foreach (object obj in maps.AsSpan(0, i))
{
var map = Unsafe.As<IDictionarySlim<Recipient, IDictionarySlim<TToken>>>(obj);
// We don't need whether or not the map contains the recipient, as the
// sequence of maps has already been copied from the set containing all
// the mappings for the target recipients: it is guaranteed to be here.
@ -274,36 +241,22 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
// Try to remove the registered handler for the input token,
// for the current message type (unknown from here).
if (holder.Remove(token))
if (holder.TryRemove(token) &&
holder.Count == 0)
{
// As above, we need to update the total number of registered handlers for the map.
// In this case we also know that the current TToken type parameter is of interest
// for the current method, as we're only unsubscribing handlers using that token.
// This is because we're already working on the final <TToken, TMessage> mapping,
// which associates a single handler with a given token, for a given recipient.
// This means that we don't have to retrieve the count to subtract in this case,
// we're just removing a single handler at a time. So, we just decrement the total.
Unsafe.As<IMapping>(map).TotalHandlersCount--;
// If the map is empty, remove the recipient entirely from its container
map.TryRemove(key);
if (holder.Count == 0)
// If no handlers are left at all for the recipient, across all
// message types and token types, remove the set of mappings
// entirely for the current recipient, and lost the strong
// reference to it as well. This is the same situation that
// would've been achieved by just calling UnregisterAll(recipient).
if (map.Count == 0 &&
set.Remove(Unsafe.As<IMapping>(map)) &&
set.Count == 0)
{
// If the map is empty, remove the recipient entirely from its container
map.Remove(key);
if (map.Count == 0)
{
// If no handlers are left at all for the recipient, across all
// message types and token types, remove the set of mappings
// entirely for the current recipient, and lost the strong
// reference to it as well. This is the same situation that
// would've been achieved by just calling UnregisterAll(recipient).
set.Remove(Unsafe.As<IMapping>(map));
if (set.Count == 0)
{
this.recipientsMap.Remove(key);
}
}
this.recipientsMap.TryRemove(key, out _);
}
}
}
@ -324,7 +277,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
{
maps.AsSpan(0, i).Clear();
ArrayPool<IDictionarySlim<Recipient, IDictionarySlim<TToken>>>.Shared.Return(maps);
ArrayPool<object>.Shared.Return(maps);
}
}
}
@ -344,84 +297,92 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
var key = new Recipient(recipient);
if (!mapping!.TryGetValue(key, out DictionarySlim<TToken, Action<TMessage>>? dictionary))
if (!mapping!.TryGetValue(key, out DictionarySlim<TToken, object>? dictionary))
{
return;
}
// Remove the target handler
if (dictionary!.Remove(token))
if (dictionary!.TryRemove(token, out _) &&
dictionary.Count == 0)
{
// Decrement the total count, as above
mapping.TotalHandlersCount--;
// If the map is empty, it means that the current recipient has no remaining
// registered handlers for the current <TMessage, TToken> combination, regardless,
// of the specific token value (ie. the channel used to receive messages of that type).
// We can remove the map entirely from this container, and remove the link to the map itself
// to the current mapping between existing registered recipients (or entire recipients too).
if (dictionary.Count == 0)
mapping.TryRemove(key, out _);
HashSet<IMapping> set = this.recipientsMap[key];
if (set.Remove(mapping) &&
set.Count == 0)
{
mapping.Remove(key);
HashSet<IMapping> set = this.recipientsMap[key];
set.Remove(mapping);
if (set.Count == 0)
{
this.recipientsMap.Remove(key);
}
this.recipientsMap.TryRemove(key, out _);
}
}
}
}
/// <inheritdoc/>
public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
public unsafe TMessage Send<TMessage, TToken>(TMessage message, TToken token)
where TMessage : class
where TToken : IEquatable<TToken>
{
Action<TMessage>[] entries;
object[] handlers;
object[] recipients;
ref object handlersRef = ref Unsafe.AsRef<object>(null);
ref object recipientsRef = ref Unsafe.AsRef<object>(null);
int i = 0;
lock (this.recipientsMap)
{
// Check whether there are any registered recipients
if (!TryGetMapping(out Mapping<TMessage, TToken>? mapping))
_ = TryGetMapping(out Mapping<TMessage, TToken>? mapping);
// We need to make a local copy of the currently registered handlers, since users might
// try to unregister (or register) new handlers from inside one of the currently existing
// handlers. We can use memory pooling to reuse arrays, to minimize the average memory
// usage. In practice, we usually just need to pay the small overhead of copying the items.
// The current mapping contains all the currently registered recipients and handlers for
// the <TMessage, TToken> combination in use. In the worst case scenario, all recipients
// will have a registered handler with a token matching the input one, meaning that we could
// have at worst a number of pending handlers to invoke equal to the total number of recipient
// in the mapping. This relies on the fact that tokens are unique, and that there is only
// one handler associated with a given token. We can use this upper bound as the requested
// size for each array rented from the pool, which guarantees that we'll have enough space.
int totalHandlersCount = mapping?.Count ?? 0;
if (totalHandlersCount == 0)
{
return message;
}
// We need to make a local copy of the currently registered handlers,
// since users might try to unregister (or register) new handlers from
// inside one of the currently existing handlers. We can use memory pooling
// to reuse arrays, to minimize the average memory usage. In practice,
// we usually just need to pay the small overhead of copying the items.
entries = ArrayPool<Action<TMessage>>.Shared.Rent(mapping!.TotalHandlersCount);
handlers = ArrayPool<object>.Shared.Rent(totalHandlersCount);
recipients = ArrayPool<object>.Shared.Rent(totalHandlersCount);
handlersRef = ref handlers[0];
recipientsRef = ref recipients[0];
// Copy the handlers to the local collection.
// Both types being enumerate expose a struct enumerator,
// so we're not actually allocating the enumerator here.
// The array is oversized at this point, since it also includes
// handlers for different tokens. We can reuse the same variable
// to count the number of matching handlers to invoke later on.
// This will be the array slice with valid actions in the rented buffer.
var mappingEnumerator = mapping.GetEnumerator();
// This will be the array slice with valid handler in the rented buffer.
var mappingEnumerator = mapping!.GetEnumerator();
// Explicit enumerator usage here as we're using a custom one
// that doesn't expose the single standard Current property.
while (mappingEnumerator.MoveNext())
{
var pairsEnumerator = mappingEnumerator.Value.GetEnumerator();
object recipient = mappingEnumerator.Key.Target;
while (pairsEnumerator.MoveNext())
// Pick the target handler, if the token is a match for the recipient
if (mappingEnumerator.Value.TryGetValue(token, out object? handler))
{
// Only select the ones with a matching token
if (pairsEnumerator.Key.Equals(token))
{
entries[i++] = pairsEnumerator.Value;
}
// We can manually offset here to skip the bounds checks in this inner loop when
// indexing the array (the size is already verified and guaranteed to be enough).
Unsafe.Add(ref handlersRef, (IntPtr)(void*)(uint)i) = handler!;
Unsafe.Add(ref recipientsRef, (IntPtr)(void*)(uint)i++) = recipient;
}
}
}
@ -429,23 +390,44 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
try
{
// Invoke all the necessary handlers on the local copy of entries
foreach (var entry in entries.AsSpan(0, i))
for (int j = 0; j < i; j++)
{
entry(message);
// We're doing an unsafe cast to skip the type checks again.
// See the comments in the UnregisterAll method for more info.
object handler = Unsafe.Add(ref handlersRef, (IntPtr)(void*)(uint)j);
object recipient = Unsafe.Add(ref recipientsRef, (IntPtr)(void*)(uint)j);
// Here we perform an unsafe cast to enable covariance for delegate types.
// We know that the input recipient will always respect the type constraints
// of each original input delegate, and doing so allows us to still invoke
// them all from here without worrying about specific generic type arguments.
Unsafe.As<MessageHandler<object, TMessage>>(handler)(recipient, message);
}
}
finally
{
// As before, we also need to clear it first to avoid having potentially long
// lasting memory leaks due to leftover references being stored in the pool.
entries.AsSpan(0, i).Clear();
handlers.AsSpan(0, i).Clear();
recipients.AsSpan(0, i).Clear();
ArrayPool<Action<TMessage>>.Shared.Return(entries);
ArrayPool<object>.Shared.Return(handlers);
ArrayPool<object>.Shared.Return(recipients);
}
return message;
}
/// <inheritdoc/>
void IMessenger.Cleanup()
{
// The current implementation doesn't require any kind of cleanup operation, as
// all the internal data structures are already kept in sync whenever a recipient
// is added or removed. This method is implemented through an explicit interface
// implementation so that developers using this type directly will not see it in
// the API surface (as it wouldn't be useful anyway, since it's a no-op here).
}
/// <inheritdoc/>
public void Reset()
{
@ -515,7 +497,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// This type is defined for simplicity and as a workaround for the lack of support for using type aliases
/// over open generic types in C# (using type aliases can only be used for concrete, closed types).
/// </remarks>
private sealed class Mapping<TMessage, TToken> : DictionarySlim<Recipient, DictionarySlim<TToken, Action<TMessage>>>, IMapping
private sealed class Mapping<TMessage, TToken> : DictionarySlim<Recipient, DictionarySlim<TToken, object>>, IMapping
where TMessage : class
where TToken : IEquatable<TToken>
{
@ -529,9 +511,6 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// <inheritdoc/>
public Type2 TypeArguments { get; }
/// <inheritdoc/>
public int TotalHandlersCount { get; set; }
}
/// <summary>
@ -544,11 +523,6 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// Gets the <see cref="Type2"/> instance representing the current type arguments.
/// </summary>
Type2 TypeArguments { get; }
/// <summary>
/// Gets or sets the total number of handlers in the current instance.
/// </summary>
int TotalHandlersCount { get; set; }
}
/// <summary>
@ -567,7 +541,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// <summary>
/// The registered recipient.
/// </summary>
private readonly object target;
public readonly object Target;
/// <summary>
/// Initializes a new instance of the <see cref="Recipient"/> struct.
@ -576,14 +550,14 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Recipient(object target)
{
this.target = target;
Target = target;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Recipient other)
{
return ReferenceEquals(this.target, other.target);
return ReferenceEquals(Target, other.Target);
}
/// <inheritdoc/>
@ -596,81 +570,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
return RuntimeHelpers.GetHashCode(this.target);
}
}
/// <summary>
/// A simple type representing an immutable pair of types.
/// </summary>
/// <remarks>
/// This type replaces a simple <see cref="ValueTuple{T1,T2}"/> as it's faster in its
/// <see cref="GetHashCode"/> and <see cref="IEquatable{T}.Equals(T)"/> methods, and because
/// unlike a value tuple it exposes its fields as immutable. Additionally, the
/// <see cref="tMessage"/> and <see cref="tToken"/> fields provide additional clarity reading
/// the code compared to <see cref="ValueTuple{T1,T2}.Item1"/> and <see cref="ValueTuple{T1,T2}.Item2"/>.
/// </remarks>
private readonly struct Type2 : IEquatable<Type2>
{
/// <summary>
/// The type of registered message.
/// </summary>
private readonly Type tMessage;
/// <summary>
/// The type of registration token.
/// </summary>
private readonly Type tToken;
/// <summary>
/// Initializes a new instance of the <see cref="Type2"/> struct.
/// </summary>
/// <param name="tMessage">The type of registered message.</param>
/// <param name="tToken">The type of registration token.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Type2(Type tMessage, Type tToken)
{
this.tMessage = tMessage;
this.tToken = tToken;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Type2 other)
{
// We can't just use reference equality, as that's technically not guaranteed
// to work and might fail in very rare cases (eg. with type forwarding between
// different assemblies). Instead, we can use the == operator to compare for
// equality, which still avoids the callvirt overhead of calling Type.Equals,
// and is also implemented as a JIT intrinsic on runtimes such as .NET Core.
return
this.tMessage == other.tMessage &&
this.tToken == other.tToken;
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is Type2 other && Equals(other);
}
/// <inheritdoc/>
[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(this.tMessage);
hash = (hash << 5) + hash;
hash += RuntimeHelpers.GetHashCode(this.tToken);
return hash;
}
return RuntimeHelpers.GetHashCode(this.Target);
}
}

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

@ -0,0 +1,488 @@
// 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.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Collections.Extensions;
using Microsoft.Toolkit.Mvvm.Messaging.Internals;
#if NETSTANDARD2_1
using RecipientsTable = System.Runtime.CompilerServices.ConditionalWeakTable<object, Microsoft.Collections.Extensions.IDictionarySlim>;
#else
using RecipientsTable = Microsoft.Toolkit.Mvvm.Messaging.WeakReferenceMessenger.ConditionalWeakTable<object, Microsoft.Collections.Extensions.IDictionarySlim>;
#endif
namespace Microsoft.Toolkit.Mvvm.Messaging
{
/// <summary>
/// A class providing a reference implementation for the <see cref="IMessenger"/> interface.
/// </summary>
/// <remarks>
/// This <see cref="IMessenger"/> implementation uses weak references to track the registered
/// recipients, so it is not necessary to manually unregister them when they're no longer needed.
/// </remarks>
public sealed class WeakReferenceMessenger : IMessenger
{
// This messenger uses the following logic to link stored instances together:
// --------------------------------------------------------------------------------------------------------
// DictionarySlim<TToken, MessageHandler<TRecipient, TMessage>> mapping
// / / /
// ___(Type2.TToken)___/ / /
// /_________________(Type2.TMessage)______________________/ /
// / ___________________________/
// / /
// DictionarySlim<Type2, ConditionalWeakTable<object, IDictionarySlim>> recipientsMap;
// --------------------------------------------------------------------------------------------------------
// Just like in the strong reference variant, each pair of message and token types is used as a key in the
// recipients map. In this case, the values in the dictionary are ConditionalWeakTable<,> instances, that
// link each registered recipient to a map of currently registered handlers, through a weak reference.
// The value in each conditional table is Dictionary<TToken, MessageHandler<TRecipient, TMessage>>, using
// the same unsafe cast as before to allow the generic handler delegates to be invoked without knowing
// what type each recipient was registered with, and without the need to use reflection.
/// <summary>
/// The map of currently registered recipients for all message types.
/// </summary>
private readonly DictionarySlim<Type2, RecipientsTable> recipientsMap = new DictionarySlim<Type2, RecipientsTable>();
/// <summary>
/// Gets the default <see cref="WeakReferenceMessenger"/> instance.
/// </summary>
public static WeakReferenceMessenger Default { get; } = new WeakReferenceMessenger();
/// <inheritdoc/>
public bool IsRegistered<TMessage, TToken>(object recipient, TToken token)
where TMessage : class
where TToken : IEquatable<TToken>
{
lock (this.recipientsMap)
{
Type2 type2 = new Type2(typeof(TMessage), typeof(TToken));
// Get the conditional table associated with the target recipient, for the current pair
// of token and message types. If it exists, check if there is a matching token.
return
this.recipientsMap.TryGetValue(type2, out RecipientsTable? table) &&
table!.TryGetValue(recipient, out IDictionarySlim? mapping) &&
Unsafe.As<DictionarySlim<TToken, object>>(mapping).ContainsKey(token);
}
}
/// <inheritdoc/>
public void Register<TRecipient, TMessage, TToken>(TRecipient recipient, TToken token, MessageHandler<TRecipient, TMessage> handler)
where TRecipient : class
where TMessage : class
where TToken : IEquatable<TToken>
{
lock (this.recipientsMap)
{
Type2 type2 = new Type2(typeof(TMessage), typeof(TToken));
// Get the conditional table for the pair of type arguments, or create it if it doesn't exist
ref RecipientsTable? mapping = ref this.recipientsMap.GetOrAddValueRef(type2);
mapping ??= new RecipientsTable();
// Get or create the handlers dictionary for the target recipient
var map = Unsafe.As<DictionarySlim<TToken, object>>(mapping.GetValue(recipient, _ => new DictionarySlim<TToken, object>()));
// Add the new registration entry
ref object? registeredHandler = ref map.GetOrAddValueRef(token);
if (!(registeredHandler is null))
{
ThrowInvalidOperationExceptionForDuplicateRegistration();
}
// Store the input handler
registeredHandler = handler;
}
}
/// <inheritdoc/>
public void UnregisterAll(object recipient)
{
lock (this.recipientsMap)
{
var enumerator = this.recipientsMap.GetEnumerator();
// Traverse all the existing conditional tables and remove all the ones
// with the target recipient as key. We don't perform a cleanup here,
// as that is responsability of a separate method defined below.
while (enumerator.MoveNext())
{
enumerator.Value.Remove(recipient);
}
}
}
/// <inheritdoc/>
public void UnregisterAll<TToken>(object recipient, TToken token)
where TToken : IEquatable<TToken>
{
lock (this.recipientsMap)
{
var enumerator = this.recipientsMap.GetEnumerator();
// Same as above, with the difference being that this time we only go through
// the conditional tables having a matching token type as key, and that we
// only try to remove handlers with a matching token, if any.
while (enumerator.MoveNext())
{
if (enumerator.Key.TToken == typeof(TToken) &&
enumerator.Value.TryGetValue(recipient, out IDictionarySlim mapping))
{
Unsafe.As<DictionarySlim<TToken, object>>(mapping).TryRemove(token, out _);
}
}
}
}
/// <inheritdoc/>
public void Unregister<TMessage, TToken>(object recipient, TToken token)
where TMessage : class
where TToken : IEquatable<TToken>
{
lock (this.recipientsMap)
{
var type2 = new Type2(typeof(TMessage), typeof(TToken));
var enumerator = this.recipientsMap.GetEnumerator();
// Traverse all the existing token and message pairs matching the current type
// arguments, and remove all the handlers with a matching token, as above.
while (enumerator.MoveNext())
{
if (enumerator.Key.Equals(type2) &&
enumerator.Value.TryGetValue(recipient, out IDictionarySlim mapping))
{
Unsafe.As<DictionarySlim<TToken, object>>(mapping).TryRemove(token, out _);
}
}
}
}
/// <inheritdoc/>
public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
where TMessage : class
where TToken : IEquatable<TToken>
{
ArrayPoolBufferWriter<object> recipients;
ArrayPoolBufferWriter<object> handlers;
lock (this.recipientsMap)
{
Type2 type2 = new Type2(typeof(TMessage), typeof(TToken));
// Try to get the target table
if (!this.recipientsMap.TryGetValue(type2, out RecipientsTable? table))
{
return message;
}
recipients = ArrayPoolBufferWriter<object>.Create();
handlers = ArrayPoolBufferWriter<object>.Create();
// We need a local, temporary copy of all the pending recipients and handlers to
// invoke, to avoid issues with handlers unregistering from messages while we're
// holding the lock. To do this, we can just traverse the conditional table in use
// to enumerate all the existing recipients for the token and message types pair
// corresponding to the generic arguments for this invocation, and then track the
// handlers with a matching token, and their corresponding recipients.
foreach (KeyValuePair<object, IDictionarySlim> pair in table!)
{
var map = Unsafe.As<DictionarySlim<TToken, object>>(pair.Value);
if (map.TryGetValue(token, out object? handler))
{
recipients.Add(pair.Key);
handlers.Add(handler!);
}
}
}
try
{
ReadOnlySpan<object>
recipientsSpan = recipients.Span,
handlersSpan = handlers.Span;
for (int i = 0; i < recipientsSpan.Length; i++)
{
// Just like in the other messenger, here we need an unsafe cast to be able to
// invoke a generic delegate with a contravariant input argument, with a less
// derived reference, without reflection. This is guaranteed to work by how the
// messenger tracks registered recipients and their associated handlers, so the
// type conversion will always be valid (the recipients are the rigth instances).
Unsafe.As<MessageHandler<object, TMessage>>(handlersSpan[i])(recipientsSpan[i], message);
}
}
finally
{
recipients.Dispose();
handlers.Dispose();
}
return message;
}
/// <inheritdoc/>
public void Cleanup()
{
lock (this.recipientsMap)
{
using ArrayPoolBufferWriter<Type2> type2s = ArrayPoolBufferWriter<Type2>.Create();
using ArrayPoolBufferWriter<object> emptyRecipients = ArrayPoolBufferWriter<object>.Create();
var enumerator = this.recipientsMap.GetEnumerator();
// First, we go through all the currently registered pairs of token and message types.
// These represents all the combinations of generic arguments with at least one registered
// handler, with the exception of those with recipients that have already been collected.
while (enumerator.MoveNext())
{
emptyRecipients.Reset();
bool hasAtLeastOneHandler = false;
// Go through the currently alive recipients to look for those with no handlers left. We track
// the ones we find to remove them outside of the loop (can't modify during enumeration).
foreach (KeyValuePair<object, IDictionarySlim> pair in enumerator.Value)
{
if (pair.Value.Count == 0)
{
emptyRecipients.Add(pair.Key);
}
else
{
hasAtLeastOneHandler = true;
}
}
// Remove the handler maps for recipients that are still alive but with no handlers
foreach (object recipient in emptyRecipients.Span)
{
enumerator.Value.Remove(recipient);
}
// Track the type combinations with no recipients or handlers left
if (!hasAtLeastOneHandler)
{
type2s.Add(enumerator.Key);
}
}
// Remove all the mappings with no handlers left
foreach (Type2 key in type2s.Span)
{
this.recipientsMap.TryRemove(key, out _);
}
}
}
/// <inheritdoc/>
public void Reset()
{
lock (this.recipientsMap)
{
this.recipientsMap.Clear();
}
}
#if !NETSTANDARD2_1
/// <summary>
/// A wrapper for <see cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}"/>
/// that backports the enumerable support to .NET Standard 2.0 through an auxiliary list.
/// </summary>
/// <typeparam name="TKey">Tke key of items to store in the table.</typeparam>
/// <typeparam name="TValue">The values to store in the table.</typeparam>
internal sealed class ConditionalWeakTable<TKey, TValue>
where TKey : class
where TValue : class?
{
/// <summary>
/// The underlying <see cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}"/> instance.
/// </summary>
private readonly System.Runtime.CompilerServices.ConditionalWeakTable<TKey, TValue> table;
/// <summary>
/// A supporting linked list to store keys in <see cref="table"/>. This is needed to expose
/// the ability to enumerate existing keys when there is no support for that in the BCL.
/// </summary>
private readonly LinkedList<WeakReference<TKey>> keys;
/// <summary>
/// Initializes a new instance of the <see cref="ConditionalWeakTable{TKey, TValue}"/> class.
/// </summary>
public ConditionalWeakTable()
{
this.table = new System.Runtime.CompilerServices.ConditionalWeakTable<TKey, TValue>();
this.keys = new LinkedList<WeakReference<TKey>>();
}
/// <inheritdoc cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}.TryGetValue"/>
public bool TryGetValue(TKey key, out TValue value)
{
return this.table.TryGetValue(key, out value);
}
/// <inheritdoc cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}.GetValue"/>
public TValue GetValue(TKey key, System.Runtime.CompilerServices.ConditionalWeakTable<TKey, TValue>.CreateValueCallback createValueCallback)
{
// Get or create the value. When this method returns, the key will be present in the table
TValue value = this.table.GetValue(key, createValueCallback);
// Check if the list of keys contains the given key.
// If it does, we can just stop here and return the result.
foreach (WeakReference<TKey> node in this.keys)
{
if (node.TryGetTarget(out TKey? target) &&
ReferenceEquals(target, key))
{
return value;
}
}
// Add the key to the list of weak references to track it
this.keys.AddFirst(new WeakReference<TKey>(key));
return value;
}
/// <inheritdoc cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}.Remove"/>
public bool Remove(TKey key)
{
return this.table.Remove(key);
}
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
for (LinkedListNode<WeakReference<TKey>>? node = this.keys.First; !(node is null);)
{
LinkedListNode<WeakReference<TKey>>? next = node.Next;
// Get the key and value for the current node
if (node.Value.TryGetTarget(out TKey? target) &&
this.table.TryGetValue(target!, out TValue value))
{
yield return new KeyValuePair<TKey, TValue>(target, value);
}
else
{
// If the current key has been collected, trim the list
this.keys.Remove(node);
}
node = next;
}
}
}
#endif
/// <summary>
/// A simple buffer writer implementation using pooled arrays.
/// </summary>
/// <typeparam name="T">The type of items to store in the list.</typeparam>
/// <remarks>
/// This type is a <see langword="ref"/> <see langword="struct"/> to avoid the object allocation and to
/// enable the pattern-based <see cref="IDisposable"/> support. We aren't worried with consumers not
/// using this type correctly since it's private and only accessible within the parent type.
/// </remarks>
private ref struct ArrayPoolBufferWriter<T>
{
/// <summary>
/// The default buffer size to use to expand empty arrays.
/// </summary>
private const int DefaultInitialBufferSize = 128;
/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
private T[] array;
/// <summary>
/// The starting offset within <see cref="array"/>.
/// </summary>
private int index;
/// <summary>
/// Creates a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> struct.
/// </summary>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ArrayPoolBufferWriter<T> Create()
{
return new ArrayPoolBufferWriter<T> { array = ArrayPool<T>.Shared.Rent(DefaultInitialBufferSize) };
}
/// <summary>
/// Gets a <see cref="ReadOnlySpan{T}"/> with the current items.
/// </summary>
public ReadOnlySpan<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.array.AsSpan(0, this.index);
}
/// <summary>
/// Adds a new item to the current collection.
/// </summary>
/// <param name="item">The item to add.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T item)
{
if (this.index == this.array.Length)
{
ResizeBuffer();
}
this.array[this.index++] = item;
}
/// <summary>
/// Resets the underlying array and the stored items.
/// </summary>
public void Reset()
{
Array.Clear(this.array, 0, this.index);
this.index = 0;
}
/// <summary>
/// Resizes <see cref="array"/> when there is no space left for new items.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private void ResizeBuffer()
{
T[] rent = ArrayPool<T>.Shared.Rent(this.index << 2);
Array.Copy(this.array, 0, rent, 0, this.index);
Array.Clear(this.array, 0, this.index);
ArrayPool<T>.Shared.Return(this.array);
this.array = rent;
}
/// <inheritdoc cref="IDisposable.Dispose"/>
public void Dispose()
{
Array.Clear(this.array, 0, this.index);
ArrayPool<T>.Shared.Return(this.array);
}
}
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when trying to add a duplicate handler.
/// </summary>
private static void ThrowInvalidOperationExceptionForDuplicateRegistration()
{
throw new InvalidOperationException("The target recipient has already subscribed to the target message");
}
}
}

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

@ -4,6 +4,7 @@
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Title>Windows Community Toolkit MVVM Toolkit</Title>
<Description>
This package includes a .NET Standard MVVM library with helpers such as:
@ -16,8 +17,9 @@
<PackageTags>UWP Toolkit Windows MVVM MVVMToolkit observable Ioc dependency injection services extensions helpers</PackageTags>
</PropertyGroup>
<!-- .NET Standard 2.0 doesn't have the Span<T> type -->
<!-- .NET Standard 2.0 doesn't have the Span<T> and IAsyncEnumerable<T> types -->
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.1" />
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
@ -27,7 +29,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.5" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
</ItemGroup>
</Project>

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

@ -1,7 +1,7 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFrameworks>uap10.0.16299;netstandard2.0;NET462</TargetFrameworks>
<TargetFrameworks>uap10.0.17763;netstandard2.0;NET462</TargetFrameworks>
<Title>Windows Community Toolkit .NET Standard Services</Title>
<Description>
This .NET standard library enables access to different data sources such as Microsoft Graph, OneDrive, Twitter, Microsoft Translator, and LinkedIn. It is part of the Windows Community Toolkit.
@ -12,7 +12,7 @@
<DeterministicSourcePaths Condition="'$(EnableSourceLink)' == ''">false</DeterministicSourcePaths>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0.16299'">
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0.17763'">
<DefineConstants Condition="'$(DisableImplicitFrameworkDefines)' != 'true'">$(DefineConstants);WINRT</DefineConstants>
</PropertyGroup>
@ -31,7 +31,7 @@
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0.16299'">
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0.17763'">
<ProjectReference Include="..\Microsoft.Toolkit.Uwp\Microsoft.Toolkit.Uwp.csproj" />
</ItemGroup>
@ -47,7 +47,7 @@
<PackageReference Include="Microsoft.Toolkit.Forms.UI.Controls.WebView" Version="[5.0.0-preview.gb86cb1c4cb,)" />
</ItemGroup>
<ItemGroup Condition="!('$(TargetFramework)'=='uap10.0.16299')">
<ItemGroup Condition="!('$(TargetFramework)'=='uap10.0.17763')">
<Compile Remove="PlatformSpecific\Uwp\**\*" />
</ItemGroup>
</Project>

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

@ -12,7 +12,6 @@ using Microsoft.Toolkit.Uwp.Helpers;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Enumeration;
using Windows.Foundation.Metadata;
using Windows.System;
namespace Microsoft.Toolkit.Uwp.Connectivity
@ -27,11 +26,6 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// </summary>
private const string BluetoothLeDeviceWatcherAqs = "(System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\")";
/// <summary>
/// Gets a value indicating whether the Bluetooth LE Helper is supported
/// </summary>
private static bool? _isBluetoothLESupported = null;
/// <summary>
/// We need to cache all DeviceInformation objects we get as they may
/// get updated in the future. The update may make them eligible to be put on
@ -82,12 +76,6 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// </summary>
public static BluetoothLEHelper Context { get; } = new BluetoothLEHelper();
/// <summary>
/// Gets a value indicating whether the Bluetooth LE Helper is supported.
/// </summary>
public static bool IsBluetoothLESupported => (bool)(_isBluetoothLESupported ??
(_isBluetoothLESupported = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 4)));
/// <summary>
/// Gets the list of available bluetooth devices
/// </summary>

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

@ -1,7 +1,7 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFramework>uap10.0.16299</TargetFramework>
<TargetFramework>uap10.0.17763</TargetFramework>
<Title>Windows Community Toolkit Devices</Title>
<Description>This library enables easier consumption of connectivity Devices/Peripherals and handle its connection to Windows devices. It contains BluetoothLE and Network connectivity helpers.</Description>
<PackageTags>UWP Toolkit Windows Devices Bluetooth BluetoothLE BLE Networking</PackageTags>

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

@ -1,7 +1,7 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFramework>uap10.0.16299</TargetFramework>
<TargetFramework>uap10.0.17763</TargetFramework>
<Title>Windows Community Toolkit Developer Tools</Title>
<Description>This library provides XAML user controls and services to help developers build their app. It is part of the Windows Community Toolkit.

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

@ -42,7 +42,7 @@
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
<AppContainerApplication>true</AppContainerApplication>
<ApplicationType>Windows Store</ApplicationType>
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.17134.0</WindowsTargetPlatformMinVersion>
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
<ProjectName>Microsoft.Toolkit.Uwp.Input.GazeInteraction</ProjectName>

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

@ -1,24 +0,0 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFramework>uap10.0</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
<Title>Windows Community Toolkit Notifications for JavaScript</Title>
<Description>
This project is used for packaging the WinMD to work for WinJS projects.
Generate tile, toast, and badge notifications for Windows 10 via code, with the help of IntelliSense, instead of directly using XML.
Supports adaptive tiles and adaptive/interactive toasts for Windows 10. It is part of the Windows Community Toolkit.
Supports C# and C++ UWP project types (see Microsoft.Toolkit.Uwp.Notifications).
Also works with C# portable class libraries and non-UWP C# projects like server projects.
</Description>
<PackageTags>notifications win10 windows-10 tile tiles toast toasts badge xml uwp javascript</PackageTags>
<ExtrasImplicitPlatformPackageIsPrivate>true</ExtrasImplicitPlatformPackageIsPrivate>
</PropertyGroup>
<ItemGroup>
<None Include="Microsoft.Toolkit.Uwp.Notifications.JavaScript.targets" PackagePath="build\Windows" Pack="true" />
<None Include="..\Microsoft.Toolkit.Uwp.Notifications\bin\$(Configuration)\native\*.*" PackagePath="lib\Windows" Pack="true" />
</ItemGroup>
</Project>

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

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Reference Include="Microsoft.Toolkit.Uwp.Notifications.Windows">
<HintPath>$(MSBuildThisFileDirectory)..\..\lib\Windows\Microsoft.Toolkit.Uwp.Notifications.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
</Reference>
</ItemGroup>
</Project>

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

@ -7,7 +7,7 @@
<Description>
Generate tile, toast, and badge notifications for Windows 10 via code, with the help of IntelliSense.
Adds Support for adaptive tiles and adaptive/interactive toasts for Windows 10. It is part of the Windows Community Toolkit.
Supports C# and C++ UWP project types (see Microsoft.Toolkit.Uwp.Notifications.JavaScript for the JS version).
Supports C# and C++ UWP project types.
Also works with C# portable class libraries and non-UWP C# projects like server projects.
This project contains outputs for netstandard1.4, uap10.0 and native for WinRT.
</Description>
@ -18,10 +18,10 @@
<Choose>
<!--Desktop Win32 apps-->
<When Condition="'$(TargetFramework)'=='net461' or '$(TargetFramework)'=='netcoreapp3.0'">
<When Condition="'$(TargetFramework)'=='net461' or '$(TargetFramework)'=='netcoreapp3.1'">
<ItemGroup>
<!--Reference Windows SDK NuGet of correct target platform version-->
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.17763.1000" />
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.1" />
</ItemGroup>
<PropertyGroup>
<!--Define the WINDOWS_UWP conditional symbol, since the Windows.Data.Xml and the Windows.UI.Notification namespaces are available-->
@ -40,7 +40,7 @@
</Choose>
<!--NET Core desktop apps also need the Registry NuGet package-->
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.0'">
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'">
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
</ItemGroup>
@ -65,7 +65,7 @@
<NugetTargetMoniker Condition="'$(DesignTimeBuild)' == 'true'">native</NugetTargetMoniker>
<NugetTargetMoniker Condition="'$(DesignTimeBuild)' != 'true'">UAP,Version=v10.0</NugetTargetMoniker>
<PackageTargetFallback>uap10.0</PackageTargetFallback>
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == '' ">10.0.18362.0</TargetPlatformVersion>
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == '' ">10.0.19041.0</TargetPlatformVersion>
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == '' ">10.0.10240.0</TargetPlatformMinVersion>
<DefineConstants Condition="'$(DisableImplicitFrameworkDefines)' != 'true'">$(DefineConstants);NETFX_CORE;WINDOWS_UWP;WINRT</DefineConstants>
<CopyLocalLockFileAssemblies Condition="'$(CopyLocalLockFileAssemblies)' == ''">false</CopyLocalLockFileAssemblies>

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

@ -6,22 +6,16 @@ Any code for generating notifications should be written in the Microsoft.Toolkit
If there's UWP-specific code, use the appropriate `#ifdef`, `WINDOWS_UWP` or `WINRT`.
## What are all the projects for?
There's two notification projects...
- Microsoft.Toolkit.Uwp.Notifications
- Microsoft.Toolkit.Uwp.Notifications.JavaScript
All the code is contained on the Microsoft.Toolkit.Uwp.Notifications project.
The first project is where all the code is contained.
The JavaScript project is just for packaging the `WinMD` to work for WinJS projects.
It outputs `netstandard1.4`, `uap10.0`, `native` for WinRT, and netcoreapp for .Net Core projects. The UWP library is only for C#, while the WinRT library is a Windows Runtime Component for C++.
The first project contains outputs for `netstandard1.4`, `uap10.0` and a `native` for WinRT. The UWP library is only for C#, while the WinRT library is a Windows Runtime Component for JavaScript and C++.
| C# | JavaScript/C++ |
| C# | C++ |
| ---------------- | ------------------- |
| NET Standard 1.4 | UWP WinRT Component |
| UWP C# DLL | |
| .Net Core DLL | |

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

@ -11,8 +11,8 @@
<AssemblyName>Microsoft.Toolkit.Uwp.SampleApp</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17134.0</TargetPlatformMinVersion>
<TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
@ -412,10 +412,7 @@
<Content Include="SamplePages\Incremental Loading Collection\IncrementalLoadingCollectionCode.bind" />
<Content Include="SamplePages\ImageCache\ImageCacheCode.bind" />
<Content Include="SamplePages\DropShadowPanel\DropShadowPanelXaml.bind" />
<Content Include="SamplePages\LiveTile\LiveTileCodeJavaScript.bind" />
<Content Include="SamplePages\Toast\ToastCodeJavaScript.bind" />
<Content Include="SamplePages\Object Storage\ObjectStorageCode.bind" />
<Content Include="SamplePages\WeatherLiveTileAndToast\WeatherLiveTileAndToastCodeJavaScript.bind" />
<Content Include="SamplePages\BackgroundTaskHelper\BackgroundTaskHelperCode.bind" />
<Content Include="SamplePages\MasterDetailsView\MasterDetailsView.bind" />
<Content Include="SamplePages\NetworkHelper\NetworkHelperCode.bind" />

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

@ -141,8 +141,6 @@ namespace Microsoft.Toolkit.Uwp.SampleApp
public string CodeFile { get; set; }
public string JavaScriptCodeFile { get; set; }
public string XamlCodeFile { get; set; }
public bool DisableXamlEditorRendering { get; set; }
@ -163,8 +161,6 @@ namespace Microsoft.Toolkit.Uwp.SampleApp
public bool HasCSharpCode => !string.IsNullOrEmpty(CodeFile);
public bool HasJavaScriptCode => !string.IsNullOrEmpty(JavaScriptCodeFile);
public bool HasDocumentation => !string.IsNullOrEmpty(DocumentationUrl);
public bool IsSupported
@ -191,17 +187,6 @@ namespace Microsoft.Toolkit.Uwp.SampleApp
}
}
public async Task<string> GetJavaScriptSourceAsync()
{
using (var codeStream = await StreamHelper.GetPackagedFileStreamAsync(JavaScriptCodeFile.StartsWith('/') ? JavaScriptCodeFile : $"SamplePages/{Name}/{JavaScriptCodeFile}"))
{
using (var streamReader = new StreamReader(codeStream.AsStream()))
{
return await streamReader.ReadToEndAsync();
}
}
}
#pragma warning disable SA1009 // Doesn't like ValueTuples.
public async Task<(string contents, string path)> GetDocumentationAsync()
#pragma warning restore SA1009 // Doesn't like ValueTuples.

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

@ -50,13 +50,12 @@
Margin="5,0,0,0"
Width="50">
<AppBarButton.Icon>
<PathIcon
Data="{StaticResource GithubIcon}"
Width="50"
Height="50"
Margin="-10">
<PathIcon Margin="-3"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Data="{StaticResource GithubIcon}">
<PathIcon.RenderTransform>
<CompositeTransform TranslateY="8" TranslateX="3" />
<CompositeTransform TranslateY="5" TranslateX="-5" />
</PathIcon.RenderTransform>
</PathIcon>
</AppBarButton.Icon>
@ -195,12 +194,6 @@
IsFocusEngagementEnabled="False" />
</PivotItem>
<PivotItem x:Name="JavaScriptPivotItem"
Padding="0, 10,0,0"
Header="Javascript">
<controlsLocal:CodeRenderer x:Name="JavaScriptCodeRenderer" />
</PivotItem>
<PivotItem x:Name="DocumentationPivotItem"
Padding="0, 10,0,0"
Header="Documentation">

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

@ -271,14 +271,6 @@ namespace Microsoft.Toolkit.Uwp.SampleApp
InfoAreaPivot.Items.Add(CSharpPivotItem);
}
if (CurrentSample.HasJavaScriptCode)
{
var code = await CurrentSample.GetJavaScriptSourceAsync();
JavaScriptCodeRenderer.SetCode(code, "js");
InfoAreaPivot.Items.Add(JavaScriptPivotItem);
}
if (CurrentSample.HasDocumentation)
{
#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly
@ -421,14 +413,6 @@ namespace Microsoft.Toolkit.Uwp.SampleApp
return;
}
if (CurrentSample.HasJavaScriptCode && InfoAreaPivot.SelectedItem == JavaScriptPivotItem)
{
var code = await CurrentSample.GetJavaScriptSourceAsync();
JavaScriptCodeRenderer.SetCode(code, "js");
return;
}
}
private async void XamlCodeEditor_UpdateRequested(object sender, EventArgs e)

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

@ -1,22 +1,18 @@
// Get a local copy of the context for easier reading
BluetoothLEHelper bluetoothLEHelper = BluetoothLEHelper.Context;
// check if BluetoothLE APIs are available
if (BluetoothLEHelper.IsBluetoothLESupported)
{
// Start the Enumeration
bluetoothLEHelper.StartEnumeration();
// Start the Enumeration
bluetoothLEHelper.StartEnumeration();
// At this point the user needs to select a device they want to connect to. This can be done by
// creating a ListView and binding the bluetoothLEHelper collection to it. Once a device is found,
// the Connect() method can be called to connect to the device and start interacting with its services
// At this point the user needs to select a device they want to connect to. This can be done by
// creating a ListView and binding the bluetoothLEHelper collection to it. Once a device is found,
// the Connect() method can be called to connect to the device and start interacting with its services
// Connect to a device if your choice
ObservableBluetoothLEDevice device = bluetoothLEHelper.BluetoothLeDevices[<Device you choose>];
await device.ConnectAsync();
// Connect to a device if your choice
ObservableBluetoothLEDevice device = bluetoothLEHelper.BluetoothLeDevices[<Device you choose>];
await device.ConnectAsync();
// At this point the device is connected and the Services property is populated.
// At this point the device is connected and the Services property is populated.
// See all the services
var services = device.Services;
}
// See all the services
var services = device.Services;

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

@ -6,89 +6,76 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<RelativePanel x:Name="MainContent"
Visibility="Collapsed">
<Button x:Name="BtEnumeration"
<RelativePanel>
<Button x:Name="BtEnumeration"
Margin="10"
Click="Enumeration_Click"
Content="Start enumeration" />
<ListView x:Name="LVDevices"
Height="200"
Margin="10"
Click="Enumeration_Click"
Content="Start enumeration" />
<ListView x:Name="LVDevices"
Height="200"
Margin="10"
VerticalAlignment="Top"
ItemsSource="{x:Bind bluetoothLEHelper.BluetoothLeDevices}"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.Below="BtEnumeration"
SelectionChanged="LVDevices_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate x:DataType="connectivity:ObservableBluetoothLEDevice">
<StackPanel>
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Name="SPDevice"
Margin="10"
RelativePanel.Below="LVDevices">
<TextBlock x:Name="TbDeviceName"
Text="" />
<TextBlock x:Name="TbDeviceBtAddr"
Text="" />
</StackPanel>
<ComboBox x:Name="CBServices"
Margin="10"
RelativePanel.Below="SPDevice"
SelectionChanged="CBServices_SelectionChanged"
Visibility="Collapsed">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="connectivity:ObservableGattDeviceService">
<TextBlock>
<Run Text="Service Name: " />
<Run Text="{x:Bind Name}" />
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox x:Name="CBCharacteristic"
Margin="10"
RelativePanel.Below="CBServices"
SelectionChanged="CBCharacteristic_SelectionChanged"
Visibility="Collapsed">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="connectivity:ObservableGattCharacteristics">
<TextBlock>
<Run Text="Characteristic Name: " />
<Run Text="{x:Bind Name}" />
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button x:Name="BtReadCharValue"
VerticalAlignment="Top"
ItemsSource="{x:Bind bluetoothLEHelper.BluetoothLeDevices}"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.Below="BtEnumeration"
SelectionChanged="LVDevices_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate x:DataType="connectivity:ObservableBluetoothLEDevice">
<StackPanel>
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Name="SPDevice"
Margin="10"
Click="ReadCharValue_Click"
Content="Read Value"
RelativePanel.Below="CBCharacteristic"
Visibility="Collapsed" />
RelativePanel.Below="LVDevices">
<TextBlock x:Name="TbDeviceName"
Text="" />
<TextBlock x:Name="TbDeviceBtAddr"
Text="" />
</StackPanel>
<TextBlock x:Name="TBCharValue"
Margin="10"
RelativePanel.Below="BtReadCharValue" />
</RelativePanel>
<TextBlock x:Name="NotAvailableMessage"
Margin="40"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="20"
Foreground="{ThemeResource SystemControlForegroundAccentBrush}"
Text="BluetoothLE requires the Windows 10 Creators Update"
TextAlignment="Center"
TextWrapping="WrapWholeWords"
Visibility="Collapsed" />
</Grid>
<ComboBox x:Name="CBServices"
Margin="10"
RelativePanel.Below="SPDevice"
SelectionChanged="CBServices_SelectionChanged"
Visibility="Collapsed">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="connectivity:ObservableGattDeviceService">
<TextBlock>
<Run Text="Service Name: " />
<Run Text="{x:Bind Name}" />
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox x:Name="CBCharacteristic"
Margin="10"
RelativePanel.Below="CBServices"
SelectionChanged="CBCharacteristic_SelectionChanged"
Visibility="Collapsed">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="connectivity:ObservableGattCharacteristics">
<TextBlock>
<Run Text="Characteristic Name: " />
<Run Text="{x:Bind Name}" />
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button x:Name="BtReadCharValue"
Margin="10"
Click="ReadCharValue_Click"
Content="Read Value"
RelativePanel.Below="CBCharacteristic"
Visibility="Collapsed" />
<TextBlock x:Name="TBCharValue"
Margin="10"
RelativePanel.Below="BtReadCharValue" />
</RelativePanel>
</Page>

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

@ -20,19 +20,6 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
this.InitializeComponent();
bluetoothLEHelper.EnumerationCompleted += BluetoothLEHelper_EnumerationCompleted;
Load();
}
private void Load()
{
if (BluetoothLEHelper.IsBluetoothLESupported)
{
MainContent.Visibility = Visibility.Visible;
}
else
{
NotAvailableMessage.Visibility = Visibility.Visible;
}
}
private async void BluetoothLEHelper_EnumerationCompleted(object sender, EventArgs e)
@ -46,11 +33,6 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
private void Enumeration_Click(object sender, RoutedEventArgs e)
{
if (!BluetoothLEHelper.IsBluetoothLESupported)
{
return;
}
if (!bluetoothLEHelper.IsEnumerating)
{
bluetoothLEHelper.StartEnumeration();

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

@ -22,20 +22,5 @@
</Style>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock x:Name="WarningText"
Margin="0,20"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Foreground="{ThemeResource Brush-Grey-03}"
Text="DropShadowPanel is only available on Windows 10 Anniversary Update or greater"
TextWrapping="Wrap"
Visibility="Collapsed" />
<Grid x:Name="XamlRoot" Grid.Row="1" />
</Grid>
<Grid x:Name="XamlRoot"/>
</Page>

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

@ -2,11 +2,7 @@
// 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.SampleApp.Models;
using Microsoft.Toolkit.Uwp.UI.Controls;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
@ -21,15 +17,6 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
public DropShadowPanelPage()
{
InitializeComponent();
Load();
}
private void Load()
{
if (!DropShadowPanel.IsSupported)
{
WarningText.Visibility = Visibility.Visible;
}
}
}
}

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

@ -69,25 +69,22 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
AddImage(false, false, true);
});
if (ImageExBase.IsLazyLoadingSupported)
SampleController.Current.RegisterNewCommand("Lazy loading sample", (sender, args) =>
{
SampleController.Current.RegisterNewCommand("Lazy loading sample (17763 or higher supported)", (sender, args) =>
var imageExLazyLoadingControl = new ImageExLazyLoadingControl();
imageExLazyLoadingControl.CloseButtonClick += (s, a) =>
{
var imageExLazyLoadingControl = new ImageExLazyLoadingControl();
imageExLazyLoadingControl.CloseButtonClick += (s, a) =>
{
if (lazyLoadingControlHost != null)
{
lazyLoadingControlHost.Child = null;
}
};
if (lazyLoadingControlHost != null)
{
lazyLoadingControlHost.Child = imageExLazyLoadingControl;
lazyLoadingControlHost.Child = null;
}
});
}
};
if (lazyLoadingControlHost != null)
{
lazyLoadingControlHost.Child = imageExLazyLoadingControl;
}
});
SampleController.Current.RegisterNewCommand("Clear image cache", async (sender, args) =>
{
@ -131,4 +128,4 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
}
}
}
}
}

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

@ -9,14 +9,6 @@
mc:Ignorable="d">
<Grid>
<TextBlock x:Name="WarningText"
Margin="0,20"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Foreground="Red"
Text="Light effects are not available on your version of Windows 10"
TextWrapping="Wrap"
Visibility="Collapsed" />
<Grid x:Name="XamlRoot"/>
<!-- Shallow Copy -->

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

@ -27,11 +27,6 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
this.InitializeComponent();
if (!AnimationExtensions.IsLightingSupported)
{
WarningText.Visibility = Visibility.Visible;
}
SampleController.Current.RegisterNewCommand("Apply", (s, e) =>
{
_lightBehavior?.StartAnimation();

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

@ -1,138 +0,0 @@
var square44x44Logo = new Windows.Foundation.Uri("ms-appx:///Assets/Square44x44Logo.png");
var square150x150Logo = new Windows.Foundation.Uri("ms-appx:///Assets/Square150x150Logo.png");
var wide310x150Logo = new Windows.Foundation.Uri("ms-appx:///Assets/Wide310x150Logo.png");
var square310x310Logo = new Windows.Foundation.Uri("ms-appx:///Assets/Square310x310Logo.png");
var generateTileBindingMedium = function (username, avatarLogoSource) {
var tileBinding = new Microsoft.Toolkit.Uwp.Notifications.TileBinding();
tileBinding.content = new Microsoft.Toolkit.Uwp.Notifications.TileBindingContentAdaptive();
tileBinding.content.peekImage = new Microsoft.Toolkit.Uwp.Notifications.TilePeekImage();
tileBinding.content.peekImage.source = avatarLogoSource;
tileBinding.content.peekImage.hintCrop = Microsoft.Toolkit.Uwp.Notifications.TilePeekImageCrop.Circle;
tileBinding.content.textStacking = Microsoft.Toolkit.Uwp.Notifications.TileTextStacking.center;
var adaptativeText1 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText1.text = "Hi,";
adaptativeText1.hintAlign = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextAlign.center;
adaptativeText1.hintStyle = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextStyle.base;
var adaptativeText2 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText2.text = username;
adaptativeText2.hintAlign = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextAlign.center;
adaptativeText2.hintStyle = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextStyle.captionSubtle;
tileBinding.content.children.push(adaptativeText1);
tileBinding.content.children.push(adaptativeText2);
return tileBinding;
};
var generateTileBindingWide = function (username, avatarLogoSource) {
var tileBinding = new Microsoft.Toolkit.Uwp.Notifications.TileBinding();
tileBinding.content = new Microsoft.Toolkit.Uwp.Notifications.TileBindingContentAdaptive();
var group = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveGroup();
var adaptativeSubgroup1 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveSubgroup();
adaptativeSubgroup1.hintWeight = 33;
var adaptativeImage = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveImage();
adaptativeImage.source = avatarLogoSource;
adaptativeImage.hintCrop = Microsoft.Toolkit.Uwp.Notifications.AdaptiveImageCrop.circle;
adaptativeSubgroup1.children.push(adaptativeImage);
var adaptativeSubgroup2 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveSubgroup();
adaptativeSubgroup2.hintTextStacking = Microsoft.Toolkit.Uwp.Notifications.AdaptiveSubgroupTextStacking.center;
var adaptativeText1 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText1.text = "Hi,";
adaptativeText1.hintStyle = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextStyle.title;
var adaptativeText2 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText2.text = username;
adaptativeText2.hintStyle = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextStyle.subtitleSubtle;
adaptativeSubgroup2.children.push(adaptativeText1);
adaptativeSubgroup2.children.push(adaptativeText2);
group.children.push(adaptativeSubgroup1);
group.children.push(adaptativeSubgroup2);
tileBinding.content.children.push(group);
return tileBinding;
};
var generateTileBindingLarge = function (username, avatarLogoSource) {
var tileBinding = new Microsoft.Toolkit.Uwp.Notifications.TileBinding();
tileBinding.content = new Microsoft.Toolkit.Uwp.Notifications.TileBindingContentAdaptive();
tileBinding.content.textStacking = Microsoft.Toolkit.Uwp.Notifications.TileTextStacking.center;
var group = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveGroup();
var adaptativeSubgroup1 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveSubgroup();
adaptativeSubgroup1.hintWeight = 1;
// we surround the image by two subgroups so that it doesn't take the full width
var adaptativeSubgroup2 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveSubgroup();
adaptativeSubgroup2.hintWeight = 2;
var adaptativeImage = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveImage();
adaptativeImage.source = avatarLogoSource;
adaptativeImage.hintCrop = Microsoft.Toolkit.Uwp.Notifications.AdaptiveImageCrop.circle;
adaptativeSubgroup2.children.push(adaptativeImage);
var adaptativeSubgroup3 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveSubgroup();
adaptativeSubgroup3.hintWeight = 1;
group.children.push(adaptativeSubgroup1);
group.children.push(adaptativeSubgroup2);
group.children.push(adaptativeSubgroup3);
var adaptativeText1 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText1.text = "Hi,";
adaptativeText1.hintAlign = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextAlign.center;
adaptativeText1.hintStyle = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextStyle.title;
var adaptativeText2 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText2.text = username;
adaptativeText2.hintAlign = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextAlign.center;
adaptativeText2.hintStyle = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextStyle.subtitleSubtle;
tileBinding.content.children.push(group);
tileBinding.content.children.push(adaptativeText1);
tileBinding.content.children.push(adaptativeText2);
return tileBinding;
};
var generateTileContent = function (username, avatarLogoSource) {
var tileContent = new Microsoft.Toolkit.Uwp.Notifications.TileContent();
tileContent.visual = new Microsoft.Toolkit.Uwp.Notifications.TileVisual();
tileContent.visual.tileMedium = generateTileBindingMedium(username, avatarLogoSource);
tileContent.visual.tileWide = generateTileBindingWide(username, avatarLogoSource);
tileContent.visual.tileLarge = generateTileBindingLarge(username, avatarLogoSource);
return tileContent;
};
var pinTile = function () {
var tile = new Windows.UI.StartScreen.SecondaryTile(new Date().getTime());
tile.displayName = "Xbox";
tile.arguments = "args";
tile.visualElements.square150x150Logo = square150x150Logo;
tile.visualElements.wide310x150Logo = wide310x150Logo;
tile.visualElements.square310x310Logo = square310x310Logo;
tile.visualElements.showNameOnSquare150x150Logo = true;
tile.visualElements.showNameOnSquare310x310Logo = true;
tile.visualElements.showNameOnWide310x150Logo = true;
tile.requestCreateAsync()
.then(function () {
// generate the tile notification content and update the tile
var content = generateTileContent("MasterHip", "Assets/Photos/Owl.jpg");
var tileNotification = new Windows.UI.Notifications.TileNotification(content.getXml());
Windows.UI.Notifications.TileUpdateManager.createTileUpdaterForSecondaryTile(tile.TileId).update(tileNotification);
});
};

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

@ -8,7 +8,6 @@
xmlns:textToolbarSamples="using:Microsoft.Toolkit.Uwp.SampleApp.SamplePages.TextToolbarSamples"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:contract7Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,7)"
mc:Ignorable="d">
<Page.Resources>
@ -58,7 +57,7 @@
BorderThickness="1"
BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
contract7Present:SelectionFlyout="{x:Null}"/>
SelectionFlyout="{x:Null}"/>
<Grid x:Name="MD"
Grid.Row="1"
Margin="0, 16"

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

@ -1,53 +0,0 @@
var generateToastContent = function () {
var toastButtonSnooze = new Microsoft.Toolkit.Uwp.Notifications.ToastButtonSnooze();
toastButtonSnooze.selectionBoxId = "snoozeTime";
var toastButtonDismiss = new Microsoft.Toolkit.Uwp.Notifications.ToastButtonDismiss();
var toastSelectionBox = new Microsoft.Toolkit.Uwp.Notifications.ToastSelectionBox("snoozeTime");
toastSelectionBox.defaultSelectionBoxItemId = "15";
toastSelectionBox.items.push(new Microsoft.Toolkit.Uwp.Notifications.ToastSelectionBoxItem("1", "1 minute"));
toastSelectionBox.items.push(new Microsoft.Toolkit.Uwp.Notifications.ToastSelectionBoxItem("15", "15 minutes"));
toastSelectionBox.items.push(new Microsoft.Toolkit.Uwp.Notifications.ToastSelectionBoxItem("60", "1 hour"));
toastSelectionBox.items.push(new Microsoft.Toolkit.Uwp.Notifications.ToastSelectionBoxItem("240", "4 hours"));
toastSelectionBox.items.push(new Microsoft.Toolkit.Uwp.Notifications.ToastSelectionBoxItem("1440", "1 day"));
var toastVisual = new Microsoft.Toolkit.Uwp.Notifications.ToastVisual();
var bindingGeneric = new Microsoft.Toolkit.Uwp.Notifications.ToastBindingGeneric();
var adaptiveText1 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptiveText1.text = "Adaptive Tiles Meeting";
var adaptiveText2 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptiveText2.text = "Conf Room 2001 / Building 135";
var adaptiveText3 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptiveText3.text = "10:00 AM - 10:30 AM";
bindingGeneric.children.push(adaptiveText1);
bindingGeneric.children.push(adaptiveText2);
bindingGeneric.children.push(adaptiveText3);
toastVisual.bindingGeneric = bindingGeneric;
var toastActions = new Microsoft.Toolkit.Uwp.Notifications.ToastActionsCustom();
toastActions.inputs.push(toastSelectionBox);
toastActions.buttons.push(toastButtonSnooze);
toastActions.buttons.push(toastButtonDismiss);
var content = new Microsoft.Toolkit.Uwp.Notifications.ToastContent();
content.launch = "action=viewEvent&eventId=1983";
content.scenario = Microsoft.Toolkit.Uwp.Notifications.ToastScenario.reminder;
content.visual = toastVisual;
content.actions = toastActions;
return content;
};
var popToast = function () {
// generate the toast notification content and pop the toast
var content = generateToastContent();
var toast = new Windows.UI.Notifications.ToastNotification(content.getXml());
Windows.UI.Notifications.ToastNotificationManager.createToastNotifier().show(toast);
};

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

@ -10,15 +10,11 @@
extensions:TitleBarExtensions.ForegroundColor="White"
extensions:TitleBarExtensions.ButtonBackgroundColor="CornflowerBlue"
extensions:TitleBarExtensions.ButtonForegroundColor="White"
extensions:StatusBarExtensions.BackgroundColor="CornflowerBlue"
extensions:StatusBarExtensions.BackgroundOpacity="0.8"
extensions:StatusBarExtensions.ForegroundColor="White"
extensions:StatusBarExtensions.IsVisible="False"
mc:Ignorable="d">
<Grid>
<TextBlock Margin="16">
Modify the XAML to change the Title and (mobile) Status bars.
Modify the XAML to change the Title.
</TextBlock>
</Grid>
</Page>

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

@ -5,10 +5,6 @@
xmlns:extensions="using:Microsoft.Toolkit.Uwp.UI.Extensions"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
extensions:ApplicationViewExtensions.Title="View Extensions"
extensions:StatusBarExtensions.BackgroundColor="Blue"
extensions:StatusBarExtensions.BackgroundOpacity="0.8"
extensions:StatusBarExtensions.ForegroundColor="White"
extensions:StatusBarExtensions.IsVisible="True"
extensions:TitleBarExtensions.BackgroundColor="Blue"
extensions:TitleBarExtensions.ForegroundColor="White"
mc:Ignorable="d">

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

@ -24,9 +24,6 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
base.OnNavigatedFrom(e);
// Reset app back to normal.
StatusBarExtensions.SetIsVisible(this, false);
ApplicationViewExtensions.SetTitle(this, string.Empty);
var lightGreyBrush = (Color)Application.Current.Resources["Grey-04"];

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

@ -121,7 +121,7 @@ private static bool IsAdaptiveToastSupported()
{
switch (AnalyticsInfo.VersionInfo.DeviceFamily)
{
// Desktop and Mobile started supporting adaptive toasts in API contract 3 (Anniversary Update)
// Desktop and Mobile started supporting adaptive toasts in API contract 3 (Anniversary Update)
case "Windows.Mobile":
case "Windows.Desktop":
return ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3);

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

@ -1,271 +0,0 @@
var square44x44Logo = new Windows.Foundation.Uri("ms-appx:///Assets/Square44x44Logo.png");
var square150x150Logo = new Windows.Foundation.Uri("ms-appx:///Assets/Square150x150Logo.png");
var wide310x150Logo = new Windows.Foundation.Uri("ms-appx:///Assets/Wide310x150Logo.png");
var square310x310Logo = new Windows.Foundation.Uri("ms-appx:///Assets/Square310x310Logo.png");
var generateLargeSubgroup = function (day, image, high, low) {
// generate the normal subgroup
var subgroup = generateSubgroup(day, image, high, low);
// allow there to be padding around the image
(subgroup.children[1]).hintRemoveMargin = null;
return subgroup;
};
var generateSubgroup = function (day, img, tempHi, tempLo) {
var adaptativeSubgroup = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveSubgroup();
adaptativeSubgroup.hintWeight = 1;
// Day
var dayText = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
dayText.text = day;
dayText.hintAlign = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextAlign.Center;
// Image
var adaptativeImage = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveImage();
adaptativeImage.source = img;
adaptativeImage.hintRemoveMargin = true;
// High temp
var highTempText = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
highTempText.text = tempHi + "<22>";
highTempText.hintAlign = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextAlign.Center;
// Low temp
var lowTempText = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
lowTempText.text = tempLo + "<22>";
lowTempText.hintAlign = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextAlign.Center;
lowTempText.hintStyle = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextStyle.CaptionSubtle;
adaptativeSubgroup.children.push(dayText);
adaptativeSubgroup.children.push(adaptativeImage);
adaptativeSubgroup.children.push(highTempText);
adaptativeSubgroup.children.push(lowTempText);
return adaptativeSubgroup;
};
var generateLegacyToastText = function (day, weatherEmoji, tempHi, tempLo) {
var adaptativeText = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText.text = `${day} ${weatherEmoji} ${tempHi}<7D> / ${tempLo}<7D>`;
return adaptativeText;
};
var isAdaptiveToastSupported = function () {
switch (Windows.System.Profile.AnalyticsInfo.versionInfo.deviceFamily) {
// desktop and Mobile started supporting adaptive toasts in API contract 3 (Anniversary Update)
case "Windows.Mobile":
case "Windows.Desktop":
return Windows.Foundation.Metadata.ApiInformation.isApiContractPresent("Windows.Foundation.UniversalApiContract", 3);
// other device families do not support adaptive toasts
default:
return false;
}
};
var generateToastContent = function () {
// start by constructing the visual portion of the toast
var binding = new Microsoft.Toolkit.Uwp.Notifications.ToastBindingGeneric();
// we'll always have this summary text on our toast notification
// (it is required that your toast starts with a text element)
var adaptativeText1 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText1.text = "Today will be mostly sunny with a high of 63 and a low of 42.";
binding.children.push(adaptativeText1);
// if Adaptive Toast Notifications are supported
if (isAdaptiveToastSupported()) {
var adaptativeGroup = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveGroup();
adaptativeGroup.children.push(generateSubgroup("Mon", "Mostly Cloudy.png", 63, 42));
adaptativeGroup.children.push(generateSubgroup("Tue", "Cloudy.png", 57, 38));
adaptativeGroup.children.push(generateSubgroup("Wed", "Sunny.png", 59, 43));
adaptativeGroup.children.push(generateSubgroup("Thu", "Sunny.png", 62, 42));
adaptativeGroup.children.push(generateSubgroup("Fri", "Sunny.png", 71, 66));
// use the rich Tile-like visual layout
binding.children.push(adaptativeGroup);
} else {
var adaptativeText2 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText2.text = "Monday ? 63<36> / 42<34>";
var adaptativeText3 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText3.text = "Tuesday ? 57<35> / 38<33>"
// we'll just add two simple lines of text
binding.children.push(adaptativeText2);
binding.children.push(adaptativeText3);
}
// construct the entire notification
var toastContent = new Microsoft.Toolkit.Uwp.Notifications.ToastContent();
toastContent.visual = new Microsoft.Toolkit.Uwp.Notifications.ToastVisual();
// use our binding from above
toastContent.visual.bindingGeneric = binding;
// set the base URI for the images, so we don't redundantly specify the entire path
toastContent.visual.baseUri = new Windows.Foundation.Uri("ms-appx:///Assets/NotificationAssets/");
// include launch string so we know what to open when user clicks toast
toastContent.launch = "action=viewForecast&zip=98008";
return toastContent;
};
var generateTileBindingSmall = function () {
var tileBinding = new Microsoft.Toolkit.Uwp.Notifications.TileBinding();
tileBinding.content = new Microsoft.Toolkit.Uwp.Notifications.TileBindingContentAdaptive();
tileBinding.content.textStacking = Microsoft.Toolkit.Uwp.Notifications.TileTextStacking.Center;
var adaptativeText1 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText1.text = "Mon";
adaptativeText1.hintStyle = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextStyle.Body;
adaptativeText1.hintAlign = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextAlign.Center;
var adaptativeText2 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText2.text = "63<36>";
adaptativeText2.hintStyle = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextStyle.Base;
adaptativeText2.hintAlign = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextAlign.Center;
tileBinding.content.children.push(adaptativeText1);
tileBinding.content.children.push(adaptativeText2);
return tileBinding;
};
var generateTileBindingMedium = function () {
var tileBinding = new Microsoft.Toolkit.Uwp.Notifications.TileBinding();
tileBinding.content = new Microsoft.Toolkit.Uwp.Notifications.TileBindingContentAdaptive();
var adaptativeGroup = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveGroup()
adaptativeGroup.children.push(generateSubgroup("Mon", "Mostly Cloudy.png", 63, 42));
adaptativeGroup.children.push(generateSubgroup("Tue", "Cloudy.png", 57, 38));
tileBinding.content.children.push(adaptativeGroup);
return tileBinding;
};
var generateTileBindingWide = function () {
var tileBinding = new Microsoft.Toolkit.Uwp.Notifications.TileBinding();
tileBinding.content = new Microsoft.Toolkit.Uwp.Notifications.TileBindingContentAdaptive();
var adaptativeGroup = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveGroup();
adaptativeGroup.children.push(generateSubgroup("Mon", "Mostly Cloudy.png", 63, 42));
adaptativeGroup.children.push(generateSubgroup("Tue", "Cloudy.png", 57, 38));
adaptativeGroup.children.push(generateSubgroup("Wed", "Sunny.png", 59, 43));
adaptativeGroup.children.push(generateSubgroup("Thu", "Sunny.png", 62, 42));
adaptativeGroup.children.push(generateSubgroup("Fri", "Sunny.png", 71, 66));
tileBinding.content.children.push(adaptativeGroup);
return tileBinding;
};
var generateTileBindingLarge = function () {
var tileBinding = new Microsoft.Toolkit.Uwp.Notifications.TileBinding();
tileBinding.content = new Microsoft.Toolkit.Uwp.Notifications.TileBindingContentAdaptive();
// 1. group
var adaptativeGroup1 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveGroup();
var adaptativeSubgroup1 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveSubgroup();
adaptativeSubgroup1.hintWeight = 30;
var adaptativeImage1 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveImage();
adaptativeImage1.source = "Mostly Cloudy.png";
adaptativeSubgroup1.children.push(adaptativeImage1);
var adaptativeSubgroup2 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveSubgroup();
var adaptativeText1 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText1.text = "Monday";
adaptativeText1.hintStyle = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextStyle.base;
var adaptativeText2 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText2.text = "63<36> / 42<34>";
var adaptativeText3 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText3.text = "20% chance of rain";
adaptativeText3.hintStyle = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextStyle.captionSubtle;
var adaptativeText4 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText();
adaptativeText4.text = "Winds 5 mph NE";
adaptativeText4.hintStyle = Microsoft.Toolkit.Uwp.Notifications.AdaptiveTextStyle.captionSubtle;
adaptativeSubgroup2.children.push(adaptativeText1);
adaptativeSubgroup2.children.push(adaptativeText2);
adaptativeSubgroup2.children.push(adaptativeText3);
adaptativeSubgroup2.children.push(adaptativeText4);
adaptativeGroup1.children.push(adaptativeSubgroup1);
adaptativeGroup1.children.push(adaptativeSubgroup2);
tileBinding.content.children.push(adaptativeGroup1);
// 2. for spacing
tileBinding.content.children.push(new Microsoft.Toolkit.Uwp.Notifications.AdaptiveText());
// 3. group
var adaptativeGroup2 = new Microsoft.Toolkit.Uwp.Notifications.AdaptiveGroup();
adaptativeGroup2.children.push(generateLargeSubgroup("Tue", "Cloudy.png", 57, 38));
adaptativeGroup2.children.push(generateLargeSubgroup("Wed", "Sunny.png", 59, 43));
adaptativeGroup2.children.push(generateLargeSubgroup("Thu", "Sunny.png", 62, 42));
adaptativeGroup2.children.push(generateLargeSubgroup("Fri", "Sunny.png", 71, 66));
tileBinding.content.children.push(adaptativeGroup2);
return tileBinding;
};
var generateTileContent = function () {
var tileContent = new Microsoft.Toolkit.Uwp.Notifications.TileContent();
tileContent.visual = new Microsoft.Toolkit.Uwp.Notifications.TileVisual();
tileContent.visual.tileSmall = generateTileBindingSmall();
tileContent.visual.tileMedium = generateTileBindingMedium();
tileContent.visual.tileWide = generateTileBindingWide();
tileContent.visual.tileLarge = generateTileBindingLarge();
// set the base URI for the images, so we don't redundantly specify the entire path
tileContent.visual.baseUri = new Windows.Foundation.Uri("ms-appx:///Assets/NotificationAssets/");
return tileContent;
};
var pinTile = function () {
var tile = new Windows.UI.StartScreen.SecondaryTile(new Date().getTime());
tile.displayName = "WeatherSample";
tile.arguments = "args";
tile.visualElements.square150x150Logo = square150x150Logo;
tile.visualElements.wide310x150Logo = wide310x150Logo;
tile.visualElements.square310x310Logo = square310x310Logo;
tile.visualElements.showNameOnSquare150x150Logo = true;
tile.visualElements.showNameOnSquare310x310Logo = true;
tile.visualElements.showNameOnWide310x150Logo = true;
tile.requestCreateAsync()
.then(function () {
// generate the tile notification content and update the tile
var content = generateTileContent();
var tileNotification = new Windows.UI.Notifications.TileNotification(content.getXml());
Windows.UI.Notifications.TileUpdateManager.createTileUpdaterForSecondaryTile(tile.TileId).update(tileNotification);
});
};
var popToast = function () {
// generate the toast notification content and pop the toast
var content = generateToastContent();
var toast = new Windows.UI.Notifications.ToastNotification(content.getXml());
Windows.UI.Notifications.ToastNotificationManager.createToastNotifier().show(toast);
};

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

@ -5,14 +5,11 @@
using System;
using Microsoft.Toolkit.Uwp.Notifications;
using Microsoft.Toolkit.Uwp.SampleApp.Common;
using Microsoft.Toolkit.Uwp.SampleApp.Models;
using NotificationsVisualizerLibrary;
using Windows.Foundation.Metadata;
using Windows.System.Profile;
using Windows.UI.Notifications;
using Windows.UI.StartScreen;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Navigation;
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
@ -119,10 +116,8 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
switch (AnalyticsInfo.VersionInfo.DeviceFamily)
{
// Desktop and Mobile started supporting adaptive toasts in API contract 3 (Anniversary Update)
case "Windows.Mobile":
case "Windows.Desktop":
return ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3);
return true;
// Other device families do not support adaptive toasts
default:

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

@ -882,7 +882,6 @@
"About": "This shows how to update a Live Tile with a rich Adaptive notification.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.Notifications",
"CodeFile": "LiveTileCode.bind",
"JavaScriptCodeFile": "LiveTileCodeJavaScript.bind",
"Icon": "/SamplePages/LiveTile/LiveTile.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/notifications/NotificationsOverview.md"
},
@ -893,7 +892,6 @@
"About": "This shows how to send a Toast notification.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.Notifications",
"CodeFile": "ToastCode.bind",
"JavaScriptCodeFile": "ToastCodeJavaScript.bind",
"Icon": "/SamplePages/Toast/Toast.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/notifications/NotificationsOverview.md"
},
@ -904,7 +902,6 @@
"About": "This shows how to send a Weather Live Tile and Toast notification, displaying the forecast.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.Notifications",
"CodeFile": "WeatherLiveTileAndToastCode.bind",
"JavaScriptCodeFile": "WeatherLiveTileAndToastCodeJavaScript.bind",
"Icon": "/SamplePages/WeatherLiveTileAndToast/WeatherLiveTileAndToast.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/notifications/NotificationsOverview.md"
},
@ -1139,7 +1136,7 @@
{
"Name": "ViewExtensions",
"Type": "ViewExtensionsPage",
"About": "View extensions to set StatusBar and TitleBar properties.",
"About": "View extensions to set TitleBar properties.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI/Extensions",
"XamlCodeFile": "ViewExtensionsCode.bind",
"Icon": "/SamplePages/ViewExtensions/ViewExtensions.png",

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

@ -6,14 +6,12 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Helpers;
using Microsoft.Toolkit.Uwp.SampleApp.Pages;
using Microsoft.Toolkit.Uwp.UI.Animations;
using Microsoft.Toolkit.Uwp.UI.Controls;
using Microsoft.Toolkit.Uwp.UI.Extensions;
using Windows.Foundation.Metadata;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
@ -331,10 +329,7 @@ namespace Microsoft.Toolkit.Uwp.SampleApp
if (MoreInfoImage != null && MoreInfoContent.DataContext != null)
{
var animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("sample_icon");
if (ApiInformation.IsTypePresent("Windows.UI.Xaml.Media.Animation.DirectConnectedAnimationConfiguration"))
{
animation.Configuration = new DirectConnectedAnimationConfiguration();
}
animation.Configuration = new DirectConnectedAnimationConfiguration();
_ = SamplePickerGridView.TryStartConnectedAnimationAsync(animation, MoreInfoContent.DataContext, "SampleIcon");
}

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

@ -10,7 +10,6 @@
xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Animations.Behaviors"
xmlns:animations="using:Microsoft.Toolkit.Uwp.UI.Animations"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
extensions:StatusBarExtensions.IsVisible="False"
extensions:TitleBarExtensions.BackgroundColor="{StaticResource Brand-Color}"
extensions:TitleBarExtensions.ButtonBackgroundColor="{StaticResource Brand-Color}"
extensions:TitleBarExtensions.ButtonForegroundColor="{StaticResource Titlebar-Foreground}"

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

@ -2,7 +2,6 @@
// 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.Foundation.Metadata;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
@ -21,46 +20,43 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.Styles
darkTheme = themes.ThemeDictionaries["Dark"] as ResourceDictionary;
lightTheme = themes.ThemeDictionaries["Light"] as ResourceDictionary;
if (ApiInformation.IsTypePresent("Windows.UI.Xaml.Media.AcrylicBrush"))
AddAcrylic(new ThemeAcrylic
{
AddAcrylic(new ThemeAcrylic
Name = "Background-AboutPage-SidePane",
DarkAcrylic = new AcrylicBrush
{
Name = "Background-AboutPage-SidePane",
DarkAcrylic = new AcrylicBrush
{
TintColor = Helpers.ColorHelper.ToColor("#FF333333"),
TintOpacity = 0.8,
BackgroundSource = AcrylicBackgroundSource.Backdrop,
FallbackColor = Helpers.ColorHelper.ToColor("#FF333333")
},
LightAcrylic = new AcrylicBrush
{
TintColor = Colors.White,
TintOpacity = 0.8,
BackgroundSource = AcrylicBackgroundSource.Backdrop,
FallbackColor = Colors.White
}
});
TintColor = Helpers.ColorHelper.ToColor("#FF333333"),
TintOpacity = 0.8,
BackgroundSource = AcrylicBackgroundSource.Backdrop,
FallbackColor = Helpers.ColorHelper.ToColor("#FF333333")
},
LightAcrylic = new AcrylicBrush
{
TintColor = Colors.White,
TintOpacity = 0.8,
BackgroundSource = AcrylicBackgroundSource.Backdrop,
FallbackColor = Colors.White
}
});
AddAcrylic(new ThemeAcrylic
AddAcrylic(new ThemeAcrylic
{
Names = new[] { "Commands-Background" },
DarkAcrylic = new AcrylicBrush
{
Names = new[] { "Commands-Background" },
DarkAcrylic = new AcrylicBrush
{
TintColor = Helpers.ColorHelper.ToColor("#FF111111"),
TintOpacity = 0.7,
BackgroundSource = AcrylicBackgroundSource.Backdrop,
FallbackColor = Helpers.ColorHelper.ToColor("#FF111111")
},
LightAcrylic = new AcrylicBrush
{
TintColor = Helpers.ColorHelper.ToColor("#FFDDDDDD"),
TintOpacity = 0.6,
BackgroundSource = AcrylicBackgroundSource.Backdrop,
FallbackColor = Helpers.ColorHelper.ToColor("#FFDDDDDD")
}
});
}
TintColor = Helpers.ColorHelper.ToColor("#FF111111"),
TintOpacity = 0.7,
BackgroundSource = AcrylicBackgroundSource.Backdrop,
FallbackColor = Helpers.ColorHelper.ToColor("#FF111111")
},
LightAcrylic = new AcrylicBrush
{
TintColor = Helpers.ColorHelper.ToColor("#FFDDDDDD"),
TintOpacity = 0.6,
BackgroundSource = AcrylicBackgroundSource.Backdrop,
FallbackColor = Helpers.ColorHelper.ToColor("#FFDDDDDD")
}
});
}
private static void AddAcrylic(ThemeAcrylic resource)

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

@ -168,7 +168,7 @@ The value is a string which is the fully-qualified typename to check for the pre
```json
{
//...
"About": "MySample needs 10.0.16299 or higher to work.",
"About": "MySample needs 10.0.18362 or higher to work.",
"ApiCheck": "Windows.UI.Xaml.Controls.NavigationView",
"BadgeUpdateVersionRequired": "Fall Creators Update required",
//...

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

@ -11,8 +11,8 @@
<AssemblyName>Microsoft.Toolkit.Uwp.Samples.BackgroundTasks</AssemblyName>
<DefaultLanguage>fr-FR</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
<TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>

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

@ -8,19 +8,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
{
internal class ApiInformationHelper
{
private static bool? _isAniversaryUpdateOrAbove;
private static bool? _isCreatorsUpdateOrAbove;
private static bool? _isFallCreatorsUpdateOrAbove;
public static bool IsAniversaryUpdateOrAbove => (bool)(_isAniversaryUpdateOrAbove ??
(_isAniversaryUpdateOrAbove = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3)));
public static bool IsCreatorsUpdateOrAbove => (bool)(_isCreatorsUpdateOrAbove ??
(_isCreatorsUpdateOrAbove = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 4)));
public static bool IsFallCreatorsUpdateOrAbove => (bool)(_isFallCreatorsUpdateOrAbove ??
(_isFallCreatorsUpdateOrAbove = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 5)));
public static bool IsXamlRootAvailable { get; } = ApiInformation.IsPropertyPresent("Windows.UI.Xaml.UIElement", "XamlRoot");
}
}

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

@ -9,17 +9,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations.Behaviors
/// <summary>
/// Performs an blur animation using composition.
/// </summary>
/// <remarks>
/// <para>
/// Blurs are only supported on Build 1607 and up. Assigning the blur behavior on an older
/// version of Windows will not add any effect. You can use the <see cref="AnimationExtensions.IsBlurSupported"/>
/// property to check for whether blurs are supported on the device at runtime.
/// </para>
/// </remarks>
/// <seealso>
/// <cref>Microsoft.Xaml.Interactivity.Behavior{Windows.UI.Xaml.UIElement}</cref>
/// </seealso>
/// <seealso cref="AnimationExtensions.IsBlurSupported"/>
public class Blur : CompositionBehaviorBase<FrameworkElement>
{
/// <summary>
@ -44,10 +36,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations.Behaviors
/// </summary>
public override void StartAnimation()
{
if (AnimationExtensions.BlurEffect.IsSupported)
{
AssociatedObject?.Blur(duration: Duration, delay: Delay, value: (float)Value, easingType: EasingType, easingMode: EasingMode)?.Start();
}
AssociatedObject?.Blur(duration: Duration, delay: Delay, value: (float)Value, easingType: EasingType, easingMode: EasingMode)?.Start();
}
}
}

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

@ -5,7 +5,6 @@
using Microsoft.Toolkit.Uwp.UI.Animations.Expressions;
using Microsoft.Toolkit.Uwp.UI.Behaviors;
using Microsoft.Toolkit.Uwp.UI.Extensions;
using Windows.Foundation.Metadata;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Hosting;
@ -91,13 +90,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations.Behaviors
/// <returns><c>true</c> if the assignment was successful; otherwise, <c>false</c>.</returns>
private bool AssignFadeAnimation()
{
// Confirm that Windows.UI.Xaml.Hosting.ElementCompositionPreview is available (Windows 10 10586 or later).
if (!ApiInformation.IsMethodPresent("Windows.UI.Xaml.Hosting.ElementCompositionPreview", nameof(ElementCompositionPreview.GetScrollViewerManipulationPropertySet)))
{
// Just return true since it's not supported
return true;
}
if (AssociatedObject == null)
{
return false;

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

@ -13,7 +13,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations.Behaviors
/// Applies a basic point light to a UIElement. You control the intensity by setting the distance of the light.
/// </summary>
/// <seealso cref="Microsoft.Toolkit.Uwp.UI.Animations.Behaviors.CompositionBehaviorBase" />
/// <seealso cref="AnimationExtensions.IsLightingSupported"/>
[Obsolete("The Light effect will be removed in a future major release. Please use XamlLight instead")]
public class Light : CompositionBehaviorBase<FrameworkElement>
@ -54,16 +53,13 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations.Behaviors
/// </summary>
public override void StartAnimation()
{
if (AnimationExtensions.IsLightingSupported)
{
AssociatedObject?.Light(
duration: Duration,
delay: Delay,
easingType: EasingType,
easingMode: EasingMode,
distance: (float)Distance,
color: ((SolidColorBrush)Color).Color)?.Start();
}
AssociatedObject?.Light(
duration: Duration,
delay: Delay,
easingType: EasingType,
easingMode: EasingMode,
distance: (float)Distance,
color: ((SolidColorBrush)Color).Color)?.Start();
}
}
}

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

@ -6,7 +6,6 @@ using Microsoft.Toolkit.Uwp.UI.Animations.Expressions;
using Microsoft.Toolkit.Uwp.UI.Behaviors;
using Microsoft.Toolkit.Uwp.UI.Extensions;
using Windows.Foundation;
using Windows.Foundation.Metadata;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -104,13 +103,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations.Behaviors
{
StopAnimation();
// Confirm that Windows.UI.Xaml.Hosting.ElementCompositionPreview is available (Windows 10 10586 or later).
if (!ApiInformation.IsMethodPresent("Windows.UI.Xaml.Hosting.ElementCompositionPreview", nameof(ElementCompositionPreview.GetScrollViewerManipulationPropertySet)))
{
// Just return true since it's not supported
return true;
}
if (AssociatedObject == null)
{
return false;

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

@ -38,15 +38,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations.Behaviors
/// </summary>
public override void StartAnimation()
{
if (AnimationExtensions.SaturationEffect.IsSupported)
{
_frameworkElement?.Saturation(
duration: Duration,
delay: Delay,
easingType: EasingType,
easingMode: EasingMode,
value: (float)Value)?.StartAsync();
}
_frameworkElement?.Saturation(
duration: Duration,
delay: Delay,
easingType: EasingType,
easingMode: EasingMode,
value: (float)Value)?.StartAsync();
}
/// <summary>

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

@ -6,7 +6,6 @@ using Microsoft.Toolkit.Uwp.UI.Animations.Expressions;
using Microsoft.Toolkit.Uwp.UI.Behaviors;
using Microsoft.Toolkit.Uwp.UI.Extensions;
using Windows.Foundation;
using Windows.Foundation.Metadata;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -106,13 +105,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations.Behaviors
{
StopAnimation();
// Confirm that Windows.UI.Xaml.Hosting.ElementCompositionPreview is available (Windows 10 10586 or later).
if (!ApiInformation.IsMethodPresent("Windows.UI.Xaml.Hosting.ElementCompositionPreview", nameof(ElementCompositionPreview.GetScrollViewerManipulationPropertySet)))
{
// Just return true since it's not supported
return true;
}
if (AssociatedObject == null)
{
return false;

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

@ -20,18 +20,5 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
{
Target = "Translation";
}
/// <inheritdoc/>
public override CompositionAnimation GetCompositionAnimation(Compositor compositor)
{
if (ApiInformationHelper.IsCreatorsUpdateOrAbove)
{
return base.GetCompositionAnimation(compositor);
}
else
{
return null;
}
}
}
}

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

@ -69,11 +69,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
animation.Target = Target;
animation.Duration = Duration;
animation.DelayTime = Delay;
if (ApiInformationHelper.IsCreatorsUpdateOrAbove)
{
animation.DelayBehavior = SetInitialValueBeforeDelay ? AnimationDelayBehavior.SetInitialValueBeforeDelay : AnimationDelayBehavior.SetInitialValueAfterDelay;
}
animation.DelayBehavior = SetInitialValueBeforeDelay ? AnimationDelayBehavior.SetInitialValueBeforeDelay : AnimationDelayBehavior.SetInitialValueAfterDelay;
if (KeyFrames.Count == 0)
{

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

@ -378,11 +378,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static void OnKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!ApiInformationHelper.IsCreatorsUpdateOrAbove)
{
return;
}
var element = d as FrameworkElement;
if (element == null)
{
@ -406,11 +401,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static void OnAnchorElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!ApiInformationHelper.IsCreatorsUpdateOrAbove)
{
return;
}
var element = d as FrameworkElement;
if (element == null)
{
@ -434,11 +424,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static void OnListItemKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!ApiInformationHelper.IsCreatorsUpdateOrAbove)
{
return;
}
var element = d as Windows.UI.Xaml.Controls.ListViewBase;
if (element == null)
{
@ -462,11 +447,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static void OnListItemElementNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!ApiInformationHelper.IsCreatorsUpdateOrAbove)
{
return;
}
var element = d as Windows.UI.Xaml.Controls.ListViewBase;
if (element == null)
{
@ -520,7 +500,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static void AddListViewBaseItemAnimationDetails(Page page, Windows.UI.Xaml.Controls.ListViewBase listViewBase)
{
if (ApiInformationHelper.IsCreatorsUpdateOrAbove && listViewBase != null)
if (listViewBase != null)
{
var elementName = GetListItemElementName(listViewBase);
var key = GetListItemKey(listViewBase);

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

@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Foundation.Metadata;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Animation;
@ -34,11 +33,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
throw new ArgumentNullException(nameof(frame));
}
if (!ApiInformationHelper.IsCreatorsUpdateOrAbove)
{
return;
}
frame.Navigating += Frame_Navigating;
frame.Navigated += Frame_Navigated;
}
@ -69,7 +63,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
{
ConnectedAnimation animation = null;
if (props.IsListAnimation && parameter != null && ApiInformationHelper.IsCreatorsUpdateOrAbove)
if (props.IsListAnimation && parameter != null)
{
foreach (var listAnimProperty in props.ListAnimProperties)
{
@ -97,8 +91,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
}
if (animation != null &&
e.NavigationMode == Windows.UI.Xaml.Navigation.NavigationMode.Back &&
ApiInformation.IsTypePresent("Windows.UI.Xaml.Media.Animation.DirectConnectedAnimationConfiguration"))
e.NavigationMode == Windows.UI.Xaml.Navigation.NavigationMode.Back)
{
UseDirectConnectedAnimationConfiguration(animation);
}
@ -147,7 +140,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
var animationHandled = false;
if (connectedAnimation != null)
{
if (props.IsListAnimation && parameter != null && ApiInformationHelper.IsCreatorsUpdateOrAbove)
if (props.IsListAnimation && parameter != null)
{
foreach (var listAnimProperty in props.ListAnimProperties)
{
@ -174,7 +167,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
}
else if (!props.IsListAnimation)
{
if (ApiInformationHelper.IsCreatorsUpdateOrAbove && coordinatedAnimationElements.TryGetValue(props.Element, out var coordinatedElements))
if (coordinatedAnimationElements.TryGetValue(props.Element, out var coordinatedElements))
{
connectedAnimation.TryStart(props.Element, coordinatedElements);
}

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

@ -19,14 +19,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations.Effects
{
private static string[] _effectProperties;
/// <summary>
/// Gets a value indicating whether this instance is supported.
/// </summary>
/// <value>
/// <c>true</c> if this instance is supported; otherwise, <c>false</c>.
/// </value>
public abstract bool IsSupported { get; }
/// <summary>
/// Gets the name of the effect.
/// </summary>
@ -80,11 +72,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations.Effects
return null;
}
if (!IsSupported)
{
return null;
}
var visual = animationSet.Visual;
var associatedObject = animationSet.Element as FrameworkElement;

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

@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using Microsoft.Graphics.Canvas.Effects;
using Windows.Foundation.Metadata;
using Windows.UI.Composition;
namespace Microsoft.Toolkit.Uwp.UI.Animations.Effects
@ -14,15 +13,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations.Effects
/// <seealso cref="Microsoft.Toolkit.Uwp.UI.Animations.Effects.AnimationEffect" />
public class Blur : AnimationEffect
{
/// <summary>
/// Gets a value indicating whether blur is supported.
/// </summary>
/// <value>
/// <c>true</c> if this instance is supported; otherwise, <c>false</c>.
/// </value>
public override bool IsSupported
=> ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3);
/// <summary>
/// Gets the name of the effect.
/// </summary>

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

@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using Microsoft.Graphics.Canvas.Effects;
using Windows.Foundation.Metadata;
using Windows.UI.Composition;
namespace Microsoft.Toolkit.Uwp.UI.Animations.Effects
@ -14,15 +13,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations.Effects
/// <seealso cref="Microsoft.Toolkit.Uwp.UI.Animations.Effects.AnimationEffect" />
public class Saturation : AnimationEffect
{
/// <summary>
/// Gets a value indicating whether Saturation is supported.
/// </summary>
/// <value>
/// <c>true</c> if this instance is supported; otherwise, <c>false</c>.
/// </value>
public override bool IsSupported
=> ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3);
/// <summary>
/// Gets the name of the effect.
/// </summary>

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

@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using Microsoft.Toolkit.Uwp.UI.Animations.Effects;
using Windows.Foundation.Metadata;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Animation;
@ -22,17 +21,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
/// </value>
public static Blur BlurEffect { get; } = new Blur();
/// <summary>
/// Gets a value indicating whether the platform supports blur.
/// </summary>
/// <remarks>
/// A check should always be made to IsBlurSupported prior to calling Blur,
/// since older operating systems will not support blurs.
/// </remarks>
/// <seealso cref="Blur(FrameworkElement, double, double, double, EasingType, EasingMode)"/>
public static bool IsBlurSupported =>
ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3); // SDK >= 14393
/// <summary>
/// Animates the Gaussian blur of the UIElement.
/// </summary>
@ -45,7 +33,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
/// <returns>
/// An Animation Set.
/// </returns>
/// <seealso cref="IsBlurSupported" />
public static AnimationSet Blur(
this FrameworkElement associatedObject,
double value = 0d,
@ -75,7 +62,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
/// <returns>
/// An Animation Set.
/// </returns>
/// <seealso cref="IsBlurSupported" />
public static AnimationSet Blur(
this AnimationSet animationSet,
double value = 0d,

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

@ -27,32 +27,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
/// </summary>
private static Dictionary<Visual, PointLight> pointLights = new Dictionary<Visual, PointLight>();
/// <summary>
/// Gets a value indicating whether this instance is lighting supported.
/// </summary>
/// <value>
/// <c>true</c> if this instance is lighting supported; otherwise, <c>false</c>.
/// </value>
public static bool IsLightingSupported
{
get
{
bool lightingSupported = true;
if (!Windows.Foundation.Metadata.ApiInformation.IsMethodPresent("Windows.UI.Xaml.Hosting.ElementCompositionPreview", "SetElementChildVisual"))
{
lightingSupported = false;
}
if (!Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.UI.Composition.CompositionSurfaceBrush"))
{
lightingSupported = false;
}
return lightingSupported;
}
}
/// <summary>
/// Animates a point light and it's distance.
/// </summary>
@ -93,7 +67,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
/// <param name="color">The color of the spotlight.</param>
/// <param name="easingType">The easing function</param>
/// <param name="easingMode">The easing mode</param>
/// <seealso cref="IsLightingSupported" />
/// <returns>
/// An Animation Set.
/// </returns>
@ -107,11 +80,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
EasingType easingType = EasingType.Default,
EasingMode easingMode = EasingMode.EaseOut)
{
if (!IsLightingSupported)
{
return null;
}
if (animationSet == null)
{
return null;

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

@ -118,11 +118,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static void ShowAnimationsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!ApiInformationHelper.IsCreatorsUpdateOrAbove)
{
return;
}
if (e.OldValue is AnimationCollection oldCollection)
{
oldCollection.AnimationCollectionChanged -= ShowCollectionChanged;
@ -139,11 +134,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static void HideAnimationsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!ApiInformationHelper.IsCreatorsUpdateOrAbove)
{
return;
}
if (e.OldValue is AnimationCollection oldCollection)
{
oldCollection.AnimationCollectionChanged -= HideCollectionChanged;
@ -176,11 +166,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static void ShowCollectionChanged(object sender, EventArgs e)
{
if (!ApiInformationHelper.IsCreatorsUpdateOrAbove)
{
return;
}
var collection = (AnimationCollection)sender;
if (collection.Parent == null)
{
@ -192,11 +177,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static void HideCollectionChanged(object sender, EventArgs e)
{
if (!ApiInformationHelper.IsCreatorsUpdateOrAbove)
{
return;
}
var collection = (AnimationCollection)sender;
if (collection.Parent == null)
{
@ -220,7 +200,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static CompositionAnimationGroup GetCompositionAnimationGroup(AnimationCollection collection, UIElement element)
{
if (ApiInformationHelper.IsCreatorsUpdateOrAbove && collection.ContainsTranslationAnimation)
if (collection.ContainsTranslationAnimation)
{
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
}
@ -230,7 +210,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static ImplicitAnimationCollection GetImplicitAnimationCollection(AnimationCollection collection, UIElement element)
{
if (ApiInformationHelper.IsCreatorsUpdateOrAbove && collection.ContainsTranslationAnimation)
if (collection.ContainsTranslationAnimation)
{
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
}

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

@ -1,7 +1,7 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFramework>uap10.0.16299</TargetFramework>
<TargetFramework>uap10.0.17763</TargetFramework>
<Title>Windows Community Toolkit Animations</Title>
<Description>
This library provides helpers and extensions on top of Windows Composition and XAML storyboards. It is part of the Windows Community Toolkit.

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

@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using Windows.Foundation.Metadata;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -19,16 +18,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static readonly DependencyProperty ReorderAnimationProperty =
DependencyProperty.RegisterAttached("ReorderAnimation", typeof(bool), typeof(ImplicitAnimationCollection), new PropertyMetadata(null));
/// <summary>
/// Gets a value indicating whether the platform supports the animation.
/// </summary>
/// <remarks>
/// On platforms not supporting the animation, this class has no effect.
/// </remarks>
public static bool IsSupported =>
Windows.ApplicationModel.DesignMode.DesignModeEnabled ? false :
ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3); // SDK >= 14393
/// <summary>
/// Identifies the Duration attached dependency property.
/// </summary>
@ -57,7 +46,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
private static void OnDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (IsSupported == false)
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
{
return;
}

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

@ -12,21 +12,21 @@
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<AssetTargetFallback>$(AssetTargetFallback);uap10.0.16299</AssetTargetFallback>
<AssetTargetFallback>$(AssetTargetFallback);uap10.0.17763</AssetTargetFallback>
</PropertyGroup>
<PropertyGroup>
<TargetPlatformVersion>8.1</TargetPlatformVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\Microsoft.Toolkit.Uwp.UI.Controls.DataGrid\bin\Debug\uap10.0.16299\Design\</OutputPath>
<OutputPath>..\Microsoft.Toolkit.Uwp.UI.Controls.DataGrid\bin\Debug\uap10.0.17763\Design\</OutputPath>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<OutputPath>..\Microsoft.Toolkit.Uwp.UI.Controls.DataGrid\bin\Release\uap10.0.16299\Design\</OutputPath>
<OutputPath>..\Microsoft.Toolkit.Uwp.UI.Controls.DataGrid\bin\Release\uap10.0.17763\Design\</OutputPath>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<Optimize>true</Optimize>
@ -113,7 +113,7 @@
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\Microsoft.Toolkit.Uwp.UI.Controls.DataGrid\bin\$(Configuration)\uap10.0.16299\Microsoft.Toolkit.Uwp.UI.Controls.DataGrid.xml">
<EmbeddedResource Include="..\Microsoft.Toolkit.Uwp.UI.Controls.DataGrid\bin\$(Configuration)\uap10.0.17763\Microsoft.Toolkit.Uwp.UI.Controls.DataGrid.xml">
<Link>Microsoft.Toolkit.Uwp.UI.Controls.DataGrid.xml</Link>
<SubType>Designer</SubType>
</EmbeddedResource>

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