Merge remote-tracking branch 'origin/master' into wrapPanel.verticalAlignment
This commit is contained in:
Коммит
1952e16977
|
@ -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<int> myTask;
|
||||
///
|
||||
/// public Task<int> 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<ILogger, Logger>();
|
||||
/// });
|
||||
/// </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<ILogger>().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<MyRecipientType, LoginCompletedMessage>(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<LoginCompletedMessage>();
|
||||
/// </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<LoginCompletedMessage>(this, m =>
|
||||
/// {
|
||||
/// // Handle the message here...
|
||||
/// });
|
||||
/// </code>
|
||||
/// Finally, send a message when needed, like so:
|
||||
/// <code>
|
||||
/// Messenger.Default.Send<LoginCompletedMessage>();
|
||||
/// </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<LoginCompletedMessage>(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>
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче