Merge branch 'master' into jamesmcroft/3506-carousel-automation

This commit is contained in:
Kyaa Dost 2020-11-13 09:58:59 -08:00 коммит произвёл GitHub
Родитель c9825f1c8b 15edbed387
Коммит 662e5010e2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
112 изменённых файлов: 4536 добавлений и 494 удалений

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

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

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

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

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

@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces
{
/// <summary>
/// An interface for a <see cref="MemoryManager{T}"/> instance that can reinterpret its underlying data.
/// </summary>
internal interface IMemoryManager
{
/// <summary>
/// Creates a new <see cref="Memory{T}"/> that reinterprets the underlying data for the current instance.
/// </summary>
/// <typeparam name="T">The target type to cast the items to.</typeparam>
/// <param name="offset">The starting offset within the data store.</param>
/// <param name="length">The original used length for the data store.</param>
/// <returns>A new <see cref="Memory{T}"/> instance of the specified type, reinterpreting the current items.</returns>
Memory<T> GetMemory<T>(int offset, int length)
where T : unmanaged;
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -46,4 +46,20 @@
<Message Text="CSFiles: @(GeneratedCSFiles->'&quot;%(Identity)&quot;')" />
<Exec Command="for %%f in (@(GeneratedCSFiles->'&quot;%(Identity)&quot;')) do echo #pragma warning disable &gt; %%f.temp &amp;&amp; type %%f &gt;&gt; %%f.temp &amp;&amp; move /y %%f.temp %%f &gt; NUL" />
</Target>
<!--
Required workaround for ProjectReference inclusion of the Controls package
The UWP project system is including the Controls resources in the pri file because
it doesn't know it'll be an independent package later during packing.
Therefore, we need to remove these extra resources in the PRI pipeline so the
Markdown pri file is properly generated and doesn't include duplicate references to Control resources.
-->
<Target Name="RemoveUnwantedPri" AfterTargets="GetPackagingOutputs">
<!--<Message Text="Files Before: @(PackagingOutputs)" Importance="high" />-->
<ItemGroup>
<PackagingOutputs Remove="@(PackagingOutputs)" Condition="'%(PackagingOutputs.Filename)%(PackagingOutputs.Extension)' == 'Microsoft.Toolkit.Uwp.UI.Controls.pri'" />
<PackagingOutputs Remove="@(PackagingOutputs)" Condition="$([System.String]::new('%(PackagingOutputs.TargetPath)').StartsWith('Microsoft.Toolkit.Uwp.UI.Controls\'))" />
</ItemGroup>
<!--<Message Text="Files After: @(PackagingOutputs)" Importance="high" />-->
</Target>
</Project>

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

@ -7,9 +7,8 @@ using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Helpers;
using Microsoft.Toolkit.Uwp.Extensions;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.System;
using Windows.UI.Xaml.Media.Imaging;
@ -63,7 +62,7 @@ namespace Microsoft.Toolkit.Uwp.UI
throw new FileNotFoundException();
}
return await DispatcherQueue.ExecuteOnUIThreadAsync(async () =>
return await DispatcherQueue.EnqueueAsync(async () =>
{
BitmapImage image = new BitmapImage();

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

@ -4,7 +4,7 @@
using System;
using System.Threading;
using Microsoft.Toolkit.Uwp.Helpers;
using Microsoft.Toolkit.Uwp.Extensions;
using Windows.Devices.Input;
using Windows.Foundation;
using Windows.System;
@ -369,7 +369,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Extensions
private static async void RunInUIThread(DispatcherQueue dispatcherQueue, Action action)
{
await dispatcherQueue.ExecuteOnUIThreadAsync(action, DispatcherQueuePriority.Normal);
await dispatcherQueue.EnqueueAsync(action, DispatcherQueuePriority.Normal);
}
}
}

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

@ -3,10 +3,9 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Helpers;
using Microsoft.Toolkit.Uwp.Extensions;
using Windows.Foundation.Metadata;
using Windows.System;
using Windows.UI.ViewManagement;
@ -84,7 +83,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Helpers
private void Accessible_HighContrastChanged(AccessibilitySettings sender, object args)
{
#if DEBUG
Debug.WriteLine("HighContrast Changed");
System.Diagnostics.Debug.WriteLine("HighContrast Changed");
#endif
UpdateProperties();
@ -100,7 +99,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Helpers
internal Task OnColorValuesChanged()
{
// Getting called off thread, so we need to dispatch to request value.
return DispatcherQueue.ExecuteOnUIThreadAsync(
return DispatcherQueue.EnqueueAsync(
() =>
{
// TODO: This doesn't stop the multiple calls if we're in our faked 'White' HighContrast Mode below.
@ -108,7 +107,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Helpers
IsHighContrast != _accessible.HighContrast)
{
#if DEBUG
Debug.WriteLine("Color Values Changed");
System.Diagnostics.Debug.WriteLine("Color Values Changed");
#endif
UpdateProperties();
@ -122,7 +121,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Helpers
IsHighContrast != _accessible.HighContrast)
{
#if DEBUG
Debug.WriteLine("CoreWindow Activated Changed");
System.Diagnostics.Debug.WriteLine("CoreWindow Activated Changed");
#endif
UpdateProperties();

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

@ -3,8 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

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

@ -0,0 +1,257 @@
// 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.Tasks;
using Windows.Foundation.Metadata;
using Windows.System;
#nullable enable
namespace Microsoft.Toolkit.Uwp.Extensions
{
/// <summary>
/// Helpers for executing code in a <see cref="DispatcherQueue"/>.
/// </summary>
public static class DispatcherQueueExtensions
{
/// <summary>
/// Indicates whether or not <see cref="DispatcherQueue.HasThreadAccess"/> is available.
/// </summary>
private static readonly bool IsHasThreadAccessPropertyAvailable = ApiInformation.IsMethodPresent("Windows.System.DispatcherQueue", "HasThreadAccess");
/// <summary>
/// Invokes a given function on the target <see cref="DispatcherQueue"/> and returns a
/// <see cref="Task"/> that completes when the invocation of the function is completed.
/// </summary>
/// <param name="dispatcher">The target <see cref="DispatcherQueue"/> to invoke the code on.</param>
/// <param name="function">The <see cref="Action"/> to invoke.</param>
/// <param name="priority">The priority level for the function to invoke.</param>
/// <returns>A <see cref="Task"/> that completes when the invocation of <paramref name="function"/> is over.</returns>
/// <remarks>If the current thread has access to <paramref name="dispatcher"/>, <paramref name="function"/> will be invoked directly.</remarks>
public static Task EnqueueAsync(this DispatcherQueue dispatcher, Action function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
{
// Run the function directly when we have thread access.
// Also reuse Task.CompletedTask in case of success,
// to skip an unnecessary heap allocation for every invocation.
if (IsHasThreadAccessPropertyAvailable && dispatcher.HasThreadAccess)
{
try
{
function();
return Task.CompletedTask;
}
catch (Exception e)
{
return Task.FromException(e);
}
}
static Task TryEnqueueAsync(DispatcherQueue dispatcher, Action function, DispatcherQueuePriority priority)
{
var taskCompletionSource = new TaskCompletionSource<object?>();
if (!dispatcher.TryEnqueue(priority, () =>
{
try
{
function();
taskCompletionSource.SetResult(null);
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
}))
{
taskCompletionSource.SetException(new InvalidOperationException("Failed to enqueue the operation"));
}
return taskCompletionSource.Task;
}
return TryEnqueueAsync(dispatcher, function, priority);
}
/// <summary>
/// Invokes a given function on the target <see cref="DispatcherQueue"/> and returns a
/// <see cref="Task{TResult}"/> that completes when the invocation of the function is completed.
/// </summary>
/// <typeparam name="T">The return type of <paramref name="function"/> to relay through the returned <see cref="Task{TResult}"/>.</typeparam>
/// <param name="dispatcher">The target <see cref="DispatcherQueue"/> to invoke the code on.</param>
/// <param name="function">The <see cref="Func{TResult}"/> to invoke.</param>
/// <param name="priority">The priority level for the function to invoke.</param>
/// <returns>A <see cref="Task"/> that completes when the invocation of <paramref name="function"/> is over.</returns>
/// <remarks>If the current thread has access to <paramref name="dispatcher"/>, <paramref name="function"/> will be invoked directly.</remarks>
public static Task<T> EnqueueAsync<T>(this DispatcherQueue dispatcher, Func<T> function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
{
if (IsHasThreadAccessPropertyAvailable && dispatcher.HasThreadAccess)
{
try
{
return Task.FromResult(function());
}
catch (Exception e)
{
return Task.FromException<T>(e);
}
}
static Task<T> TryEnqueueAsync(DispatcherQueue dispatcher, Func<T> function, DispatcherQueuePriority priority)
{
var taskCompletionSource = new TaskCompletionSource<T>();
if (!dispatcher.TryEnqueue(priority, () =>
{
try
{
taskCompletionSource.SetResult(function());
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
}))
{
taskCompletionSource.SetException(new InvalidOperationException("Failed to enqueue the operation"));
}
return taskCompletionSource.Task;
}
return TryEnqueueAsync(dispatcher, function, priority);
}
/// <summary>
/// Invokes a given function on the target <see cref="DispatcherQueue"/> and returns a
/// <see cref="Task"/> that acts as a proxy for the one returned by the given function.
/// </summary>
/// <param name="dispatcher">The target <see cref="DispatcherQueue"/> to invoke the code on.</param>
/// <param name="function">The <see cref="Func{TResult}"/> to invoke.</param>
/// <param name="priority">The priority level for the function to invoke.</param>
/// <returns>A <see cref="Task"/> that acts as a proxy for the one returned by <paramref name="function"/>.</returns>
/// <remarks>If the current thread has access to <paramref name="dispatcher"/>, <paramref name="function"/> will be invoked directly.</remarks>
public static Task EnqueueAsync(this DispatcherQueue dispatcher, Func<Task> function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
{
// If we have thread access, we can retrieve the task directly.
// We don't use ConfigureAwait(false) in this case, in order
// to let the caller continue its execution on the same thread
// after awaiting the task returned by this function.
if (IsHasThreadAccessPropertyAvailable && dispatcher.HasThreadAccess)
{
try
{
if (function() is Task awaitableResult)
{
return awaitableResult;
}
return Task.FromException(new InvalidOperationException("The Task returned by function cannot be null."));
}
catch (Exception e)
{
return Task.FromException(e);
}
}
static Task TryEnqueueAsync(DispatcherQueue dispatcher, Func<Task> function, DispatcherQueuePriority priority)
{
var taskCompletionSource = new TaskCompletionSource<object?>();
if (!dispatcher.TryEnqueue(priority, async () =>
{
try
{
if (function() is Task awaitableResult)
{
await awaitableResult.ConfigureAwait(false);
taskCompletionSource.SetResult(null);
}
else
{
taskCompletionSource.SetException(new InvalidOperationException("The Task returned by function cannot be null."));
}
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
}))
{
taskCompletionSource.SetException(new InvalidOperationException("Failed to enqueue the operation"));
}
return taskCompletionSource.Task;
}
return TryEnqueueAsync(dispatcher, function, priority);
}
/// <summary>
/// Invokes a given function on the target <see cref="DispatcherQueue"/> and returns a
/// <see cref="Task{TResult}"/> that acts as a proxy for the one returned by the given function.
/// </summary>
/// <typeparam name="T">The return type of <paramref name="function"/> to relay through the returned <see cref="Task{TResult}"/>.</typeparam>
/// <param name="dispatcher">The target <see cref="DispatcherQueue"/> to invoke the code on.</param>
/// <param name="function">The <see cref="Func{TResult}"/> to invoke.</param>
/// <param name="priority">The priority level for the function to invoke.</param>
/// <returns>A <see cref="Task{TResult}"/> that relays the one returned by <paramref name="function"/>.</returns>
/// <remarks>If the current thread has access to <paramref name="dispatcher"/>, <paramref name="function"/> will be invoked directly.</remarks>
public static Task<T> EnqueueAsync<T>(this DispatcherQueue dispatcher, Func<Task<T>> function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
{
if (IsHasThreadAccessPropertyAvailable && dispatcher.HasThreadAccess)
{
try
{
if (function() is Task<T> awaitableResult)
{
return awaitableResult;
}
return Task.FromException<T>(new InvalidOperationException("The Task returned by function cannot be null."));
}
catch (Exception e)
{
return Task.FromException<T>(e);
}
}
static Task<T> TryEnqueueAsync(DispatcherQueue dispatcher, Func<Task<T>> function, DispatcherQueuePriority priority)
{
var taskCompletionSource = new TaskCompletionSource<T>();
if (!dispatcher.TryEnqueue(priority, async () =>
{
try
{
if (function() is Task<T> awaitableResult)
{
var result = await awaitableResult.ConfigureAwait(false);
taskCompletionSource.SetResult(result);
}
else
{
taskCompletionSource.SetException(new InvalidOperationException("The Task returned by function cannot be null."));
}
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
}))
{
taskCompletionSource.SetException(new InvalidOperationException("Failed to enqueue the operation"));
}
return taskCompletionSource.Task;
}
return TryEnqueueAsync(dispatcher, function, priority);
}
}
}

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

@ -4,7 +4,6 @@
using Windows.ApplicationModel.Resources;
using Windows.UI;
using Windows.UI.WindowManagement;
using Windows.UI.Xaml;
namespace Microsoft.Toolkit.Uwp.Extensions

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

@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.ApplicationModel.Activation;

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

@ -12,7 +12,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <summary>
/// This class provides static methods helper for executing code in UI thread of the main window.
/// </summary>
[Obsolete]
[Obsolete("Replace calls to APIs in this class with extensions for the Windows.System.DispatcherQueue type (see https://docs.microsoft.com/uwp/api/windows.system.dispatcherqueue).")]
public static class DispatcherHelper
{
/// <summary>
@ -22,6 +22,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="priority">Dispatcher execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
[Obsolete("This method should be replaced with dispatcherQueue.EnqueueAsync(function, priority), where dispatcherQueue is a DispatcherQueue instance that was retrieved from the UI thread and stored for later use.")]
public static Task ExecuteOnUIThreadAsync(Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority);
@ -35,6 +36,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="priority">Dispatcher execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task{T}"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
[Obsolete("This method should be replaced with dispatcherQueue.EnqueueAsync(function, priority), where dispatcherQueue is a DispatcherQueue instance that was retrieved from the UI thread and stored for later use.")]
public static Task<T> ExecuteOnUIThreadAsync<T>(Func<T> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority);
@ -47,6 +49,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="function">Asynchronous function to be executed asynchronously on UI thread.</param>
/// <param name="priority">Dispatcher execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task"/> for the operation.</returns>
[Obsolete("This method should be replaced with dispatcherQueue.EnqueueAsync(function, priority), where dispatcherQueue is a DispatcherQueue instance that was retrieved from the UI thread and stored for later use.")]
public static Task ExecuteOnUIThreadAsync(Func<Task> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority);
@ -60,6 +63,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="function">Asynchronous function to be executed asynchronously on UI thread.</param>
/// <param name="priority">Dispatcher execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task{T}"/> for the operation.</returns>
[Obsolete("This method should be replaced with dispatcherQueue.EnqueueAsync(function, priority), where dispatcherQueue is a DispatcherQueue instance that was retrieved from the UI thread and stored for later use.")]
public static Task<T> ExecuteOnUIThreadAsync<T>(Func<Task<T>> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority);
@ -73,6 +77,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="priority">Dispatcher execution priority, default is normal</param>
/// <returns>An awaitable <see cref="Task"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
[Obsolete("This method should be replaced with viewToExecuteOn.DispatcherQueue.EnqueueAsync(function, priority).")]
public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
if (viewToExecuteOn is null)
@ -92,6 +97,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="priority">Dispatcher execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task{T}"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
[Obsolete("This method should be replaced with viewToExecuteOn.DispatcherQueue.EnqueueAsync(function, priority).")]
public static Task<T> ExecuteOnUIThreadAsync<T>(this CoreApplicationView viewToExecuteOn, Func<T> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
if (viewToExecuteOn is null)
@ -110,6 +116,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="priority">Dispatcher execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
[Obsolete("This method should be replaced with viewToExecuteOn.DispatcherQueue.EnqueueAsync(function, priority).")]
public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func<Task> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
if (viewToExecuteOn is null)
@ -129,6 +136,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="priority">Dispatcher execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
[Obsolete("This method should be replaced with viewToExecuteOn.DispatcherQueue.EnqueueAsync(function, priority).")]
public static Task<T> ExecuteOnUIThreadAsync<T>(this CoreApplicationView viewToExecuteOn, Func<Task<T>> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
if (viewToExecuteOn is null)
@ -147,6 +155,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="priority">Dispatcher execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
[Obsolete("This method should be replaced with dispatcherQueue.EnqueueAsync(function, priority). A queue can be retrieved with DispatcherQueue.GetForCurrentThread().")]
public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
if (function is null)
@ -199,6 +208,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="priority">Dispatcher execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task{T}"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
[Obsolete("This method should be replaced with dispatcherQueue.EnqueueAsync(function, priority). A queue can be retrieved with DispatcherQueue.GetForCurrentThread().")]
public static Task<T> AwaitableRunAsync<T>(this CoreDispatcher dispatcher, Func<T> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
if (function is null)
@ -244,6 +254,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="priority">Dispatcher execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
[Obsolete("This method should be replaced with dispatcherQueue.EnqueueAsync(function, priority). A queue can be retrieved with DispatcherQueue.GetForCurrentThread().")]
public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func<Task> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
if (function is null)
@ -307,6 +318,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="priority">Dispatcher execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task{T}"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
[Obsolete("This method should be replaced with dispatcherQueue.EnqueueAsync(function, priority). A queue can be retrieved with DispatcherQueue.GetForCurrentThread().")]
public static Task<T> AwaitableRunAsync<T>(this CoreDispatcher dispatcher, Func<Task<T>> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
if (function is null)

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

@ -1,239 +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.Tasks;
using Windows.Foundation.Metadata;
using Windows.System;
namespace Microsoft.Toolkit.Uwp.Helpers
{
/// <summary>
/// This class provides static methods helper for executing code in a DispatcherQueue.
/// </summary>
public static class DispatcherQueueHelper
{
/// <summary>
/// Extension method for <see cref="DispatcherQueue"/>. Offering an actual awaitable <see cref="Task"/> with optional result that will be executed on the given dispatcher.
/// </summary>
/// <param name="dispatcher">DispatcherQueue of a thread to run <paramref name="function"/>.</param>
/// <param name="function"> Function to be executed on the given dispatcher.</param>
/// <param name="priority">DispatcherQueue execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
public static Task ExecuteOnUIThreadAsync(this DispatcherQueue dispatcher, Action function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
{
if (function is null)
{
throw new ArgumentNullException(nameof(function));
}
/* Run the function directly when we have thread access.
* Also reuse Task.CompletedTask in case of success,
* to skip an unnecessary heap allocation for every invocation. */
if (HasThreadAccess(dispatcher))
{
try
{
function();
return Task.CompletedTask;
}
catch (Exception e)
{
return Task.FromException(e);
}
}
var taskCompletionSource = new TaskCompletionSource<object>();
_ = dispatcher.TryEnqueue(priority, () =>
{
try
{
function();
taskCompletionSource.SetResult(null);
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
});
return taskCompletionSource.Task;
}
/// <summary>
/// Extension method for <see cref="DispatcherQueue"/>. Offering an actual awaitable <see cref="Task{T}"/> with optional result that will be executed on the given dispatcher.
/// </summary>
/// <typeparam name="T">Returned data type of the function.</typeparam>
/// <param name="dispatcher">DispatcherQueue of a thread to run <paramref name="function"/>.</param>
/// <param name="function"> Function to be executed on the given dispatcher.</param>
/// <param name="priority">DispatcherQueue execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task{T}"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
public static Task<T> ExecuteOnUIThreadAsync<T>(this DispatcherQueue dispatcher, Func<T> function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
{
if (function is null)
{
throw new ArgumentNullException(nameof(function));
}
if (HasThreadAccess(dispatcher))
{
try
{
return Task.FromResult(function());
}
catch (Exception e)
{
return Task.FromException<T>(e);
}
}
var taskCompletionSource = new TaskCompletionSource<T>();
_ = dispatcher.TryEnqueue(priority, () =>
{
try
{
taskCompletionSource.SetResult(function());
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
});
return taskCompletionSource.Task;
}
/// <summary>
/// Extension method for <see cref="DispatcherQueue"/>. Offering an actual awaitable <see cref="Task"/> with optional result that will be executed on the given dispatcher.
/// </summary>
/// <param name="dispatcher">DispatcherQueue of a thread to run <paramref name="function"/>.</param>
/// <param name="function">Asynchronous function to be executed on the given dispatcher.</param>
/// <param name="priority">DispatcherQueue execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
public static Task ExecuteOnUIThreadAsync(this DispatcherQueue dispatcher, Func<Task> function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
{
if (function is null)
{
throw new ArgumentNullException(nameof(function));
}
/* If we have thread access, we can retrieve the task directly.
* We don't use ConfigureAwait(false) in this case, in order
* to let the caller continue its execution on the same thread
* after awaiting the task returned by this function. */
if (HasThreadAccess(dispatcher))
{
try
{
if (function() is Task awaitableResult)
{
return awaitableResult;
}
return Task.FromException(new InvalidOperationException("The Task returned by function cannot be null."));
}
catch (Exception e)
{
return Task.FromException(e);
}
}
var taskCompletionSource = new TaskCompletionSource<object>();
_ = dispatcher.TryEnqueue(priority, async () =>
{
try
{
if (function() is Task awaitableResult)
{
await awaitableResult.ConfigureAwait(false);
taskCompletionSource.SetResult(null);
}
else
{
taskCompletionSource.SetException(new InvalidOperationException("The Task returned by function cannot be null."));
}
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
});
return taskCompletionSource.Task;
}
/// <summary>
/// Extension method for <see cref="DispatcherQueue"/>. Offering an actual awaitable <see cref="Task{T}"/> with optional result that will be executed on the given dispatcher.
/// </summary>
/// <typeparam name="T">Returned data type of the function.</typeparam>
/// <param name="dispatcher">DispatcherQueue of a thread to run <paramref name="function"/>.</param>
/// <param name="function">Asynchronous function to be executed Asynchronously on the given dispatcher.</param>
/// <param name="priority">DispatcherQueue execution priority, default is normal.</param>
/// <returns>An awaitable <see cref="Task{T}"/> for the operation.</returns>
/// <remarks>If the current thread has UI access, <paramref name="function"/> will be invoked directly.</remarks>
public static Task<T> ExecuteOnUIThreadAsync<T>(this DispatcherQueue dispatcher, Func<Task<T>> function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
{
if (function is null)
{
throw new ArgumentNullException(nameof(function));
}
if (HasThreadAccess(dispatcher))
{
try
{
if (function() is Task<T> awaitableResult)
{
return awaitableResult;
}
return Task.FromException<T>(new InvalidOperationException("The Task returned by function cannot be null."));
}
catch (Exception e)
{
return Task.FromException<T>(e);
}
}
var taskCompletionSource = new TaskCompletionSource<T>();
_ = dispatcher.TryEnqueue(priority, async () =>
{
try
{
if (function() is Task<T> awaitableResult)
{
var result = await awaitableResult.ConfigureAwait(false);
taskCompletionSource.SetResult(result);
}
else
{
taskCompletionSource.SetException(new InvalidOperationException("The Task returned by function cannot be null."));
}
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
});
return taskCompletionSource.Task;
}
private static bool HasThreadAccess(DispatcherQueue dispatcher)
{
return ApiInformation.IsMethodPresent("Windows.System.DispatcherQueue", "HasThreadAccess") && dispatcher.HasThreadAccess;
}
}
}

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

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Extensions;
using Windows.Graphics.Printing;
using Windows.System;
using Windows.UI.Xaml;
@ -205,7 +206,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
}
_printCanvas = null;
DispatcherQueue.ExecuteOnUIThreadAsync(() =>
DispatcherQueue.EnqueueAsync(() =>
{
_printDocument.Paginate -= CreatePrintPreviewPages;
_printDocument.GetPreviewPage -= GetPrintPreviewPage;
@ -229,7 +230,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
{
if (!_directPrint)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(() =>
await DispatcherQueue.EnqueueAsync(() =>
{
_canvasContainer.Children.Remove(_printCanvas);
_printCanvas.Children.Clear();
@ -262,7 +263,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
printTask.Completed += async (s, args) =>
{
// Notify the user when the print operation fails.
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
async () =>
{
foreach (var element in _stateBags.Keys)
@ -515,7 +516,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
element.Margin = new Thickness(marginWidth / 2, marginHeight / 2, marginWidth / 2, marginHeight / 2);
page.Content = element;
return DispatcherQueue.ExecuteOnUIThreadAsync(
return DispatcherQueue.EnqueueAsync(
() =>
{
// Add the (newly created) page to the print canvas which is part of the visual tree and force it to go
@ -531,7 +532,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
private Task ClearPageCache()
{
return DispatcherQueue.ExecuteOnUIThreadAsync(() =>
return DispatcherQueue.EnqueueAsync(() =>
{
if (!_directPrint)
{

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

@ -2,6 +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.Extensions;
using Windows.System;
using Windows.UI.Xaml;
@ -63,7 +64,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
/// <param name="element">Element to restore state to</param>
public void Restore(FrameworkElement element)
{
_dispatcherQueue.ExecuteOnUIThreadAsync(() =>
_dispatcherQueue.EnqueueAsync(() =>
{
element.HorizontalAlignment = HorizontalAlignment;
element.VerticalAlignment = VerticalAlignment;

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

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.Toolkit.Uwp.Extensions;
using Windows.System;
using Windows.System.RemoteSystems;
@ -84,7 +85,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
private async void RemoteSystemWatcher_RemoteSystemUpdated(RemoteSystemWatcher sender, RemoteSystemUpdatedEventArgs args)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(() =>
await DispatcherQueue.EnqueueAsync(() =>
{
RemoteSystems.Remove(RemoteSystems.First(a => a.Id == args.RemoteSystem.Id));
RemoteSystems.Add(args.RemoteSystem);
@ -93,7 +94,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
private async void RemoteSystemWatcher_RemoteSystemRemoved(RemoteSystemWatcher sender, RemoteSystemRemovedEventArgs args)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(() =>
await DispatcherQueue.EnqueueAsync(() =>
{
RemoteSystems.Remove(RemoteSystems.First(a => a.Id == args.RemoteSystemId));
});
@ -101,7 +102,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers
private async void RemoteSystemWatcher_RemoteSystemAdded(RemoteSystemWatcher sender, RemoteSystemAddedEventArgs args)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(() =>
await DispatcherQueue.EnqueueAsync(() =>
{
RemoteSystems.Add(args.RemoteSystem);
});

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

@ -5,6 +5,7 @@
<Title>Windows Community Toolkit</Title>
<Description>This package includes code only helpers such as Colors conversion tool, Storage file handling, a Stream helper class, etc.</Description>
<PackageTags>UWP Toolkit Windows</PackageTags>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>

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

@ -2,8 +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.System.Profile;
namespace Microsoft.Toolkit.Uwp.Helpers
{
/// <summary>

7
SmokeTests/App.xaml Normal file
Просмотреть файл

@ -0,0 +1,7 @@
<Application
x:Class="SmokeTest.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest">
</Application>

106
SmokeTests/App.xaml.cs Normal file
Просмотреть файл

@ -0,0 +1,106 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace SmokeTest
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public sealed partial class App : Application
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
// TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
if (e.PrelaunchActivated == false)
{
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
// Ensure the current window is active
Window.Current.Activate();
}
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
// TODO: Save application state and stop any background activity
deferral.Complete();
}
}
}

Двоичные данные
SmokeTests/Assets/LockScreenLogo.scale-200.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.4 KiB

Двоичные данные
SmokeTests/Assets/SplashScreen.scale-200.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.5 KiB

Двоичные данные
SmokeTests/Assets/Square150x150Logo.scale-200.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.9 KiB

Двоичные данные
SmokeTests/Assets/Square44x44Logo.scale-200.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.6 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Двоичные данные
SmokeTests/Assets/StoreLogo.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.4 KiB

Двоичные данные
SmokeTests/Assets/Wide310x150Logo.scale-200.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.1 KiB

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

@ -0,0 +1,17 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Width="300">
<Button Content="Click" Click="Button_Click" HorizontalAlignment="Center"/>
<TextBlock x:Name="textBlock" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
</Page>

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

@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Toolkit.HighPerformance.Helpers;
namespace SmokeTest
{
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
}
private void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
const uint value = 0xAAAA5555u;
textBlock.Text = BitHelper.HasLookupFlag(value, 0).ToString();
}
}
}

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

@ -0,0 +1,17 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Width="200">
<Button Content="Click" Command="{x:Bind TestCommand}" HorizontalAlignment="Center"/>
<TextBlock x:Name="textBlock"/>
</StackPanel>
</Grid>
</Page>

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

@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Toolkit.Mvvm.Input;
namespace SmokeTest
{
public sealed partial class MainPage
{
public RelayCommand TestCommand { get; }
public MainPage()
{
TestCommand = new RelayCommand(ExecuteRelayCommand);
InitializeComponent();
}
private void ExecuteRelayCommand()
{
textBlock.Text = "Clicked";
}
}
}

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

@ -0,0 +1,25 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ScrollViewer>
<StackPanel Padding="12">
<TextBox x:Name="RawMarkdown"
Header="Markdown"
Text="This is **Markdown**"
TextChanged="RawMarkdown_TextChanged"
AcceptsReturn="True"
MinHeight="60" />
<TextBlock
Text="Result:"
Margin="0,10,0,0" />
<TextBlock x:Name="MarkdownResult" />
</StackPanel>
</ScrollViewer>
</Page>

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

@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Toolkit.Parsers.Markdown;
using Windows.UI.Xaml.Controls;
namespace SmokeTest
{
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
Loaded += MarkdownParserPage_Loaded;
}
private void MarkdownParserPage_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
UpdateMDResult();
}
private void RawMarkdown_TextChanged(object sender, TextChangedEventArgs e)
{
UpdateMDResult();
}
private void UpdateMDResult()
{
var document = new MarkdownDocument();
document.Parse(RawMarkdown.Text);
MarkdownResult.Text = $"Root type is: {document.Type.ToString()}";
}
}
}

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

@ -0,0 +1,21 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Width="300">
<Button x:Name="ConnectButton"
Margin="0,10,0,0"
VerticalAlignment="Bottom"
Click="ConnectButton_OnClick"
Content="Connect" />
<TextBlock x:Name="textBlock"/>
</StackPanel>
</Grid>
</Page>

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

@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.Toolkit.Services.Twitter;
namespace SmokeTest
{
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
}
private void ConnectButton_OnClick(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
try
{
TwitterService.Instance.Initialize(string.Empty, string.Empty, string.Empty);
}
catch (Exception ex)
{
textBlock.Text = ex.ToString();
}
}
}
}

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

@ -0,0 +1,17 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Width="200">
<Button Content="Click" Click="Button_Click" HorizontalAlignment="Center"/>
<TextBlock x:Name="textBlock" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
</Page>

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

@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Toolkit.Uwp.Connectivity;
namespace SmokeTest
{
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
}
private void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
textBlock.Text = NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable.ToString();
}
}
}

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

@ -0,0 +1,27 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:developerTools="using:Microsoft.Toolkit.Uwp.DeveloperTools"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<developerTools:AlignmentGrid
Opacity="0.2"
LineBrush="Firebrick"
HorizontalStep="20"
VerticalStep="20" />
<StackPanel Margin="40" BorderThickness="1" BorderBrush="{ThemeResource SystemControlForegroundAccentBrush}">
<TextBlock Margin="0,0,0,30" Text="You can use the AlignmentGrid to check if your UI elements are corrected aligned" />
<TextBlock Margin="0,0,0,30" Text="The container of these texts has a margin of (40, 40, 40, 40)" />
<TextBlock Margin="20,0,0,20" Text="This text has a margin of (20, 0, 0, 20)" />
<TextBlock Margin="40,0,0,20" Text="This text has a margin of (40, 0, 0, 20)" />
<TextBlock Margin="20,0,0,20" Text="This text has a margin of (20, 0, 0, 20)" />
</StackPanel>
</Grid>
</Page>

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

@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace SmokeTest
{
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
}
}
}

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

@ -0,0 +1,35 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
RequestedTheme="Light"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ItemsControlItemTemplate1">
<Ellipse Fill="#FFAD0000" HorizontalAlignment="Left" Width="5" Height="5" Stroke="Black" VerticalAlignment="Top" RenderTransformOrigin="0.5,0.5" DataContext="{Binding DataContext, ElementName=Page, Mode=OneWay}">
<Ellipse.RenderTransform>
<TranslateTransform X="{Binding DataContext.X, RelativeSource={RelativeSource TemplatedParent}}" Y="{Binding DataContext.Y, RelativeSource={RelativeSource TemplatedParent}}"/>
</Ellipse.RenderTransform>
</Ellipse>
</DataTemplate>
<ItemsPanelTemplate x:Key="ItemsPanelTemplate1">
<Canvas ></Canvas>
</ItemsPanelTemplate>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid>
<ItemsControl x:Name="Points" ItemTemplate="{StaticResource ItemsControlItemTemplate1}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{x:Bind GazeHistory}" ItemsPanel="{StaticResource ItemsPanelTemplate1}" />
</Grid>
</Grid>
</Page>

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

@ -0,0 +1,67 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.ObjectModel;
using Windows.Devices.Input.Preview;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace SmokeTest
{
public sealed partial class MainPage
{
private GazeInputSourcePreview gazeInputSourcePreview;
public ObservableCollection<Point> GazeHistory { get; } = new ObservableCollection<Point>();
public int TracePointDiameter { get; set; }
public int MaxGazeHistorySize { get; set; }
public bool ShowIntermediatePoints { get; set; }
public MainPage()
{
InitializeComponent();
ShowIntermediatePoints = false;
MaxGazeHistorySize = 100;
gazeInputSourcePreview = GazeInputSourcePreview.GetForCurrentView();
gazeInputSourcePreview.GazeMoved += GazeInputSourcePreview_GazeMoved;
}
private void UpdateGazeHistory(GazePointPreview pt)
{
if (!pt.EyeGazePosition.HasValue)
{
return;
}
var transform = (Window.Current.Content as Frame).TransformToVisual(this);
var point = transform.TransformPoint(pt.EyeGazePosition.Value);
GazeHistory.Add(point);
if (GazeHistory.Count > MaxGazeHistorySize)
{
GazeHistory.RemoveAt(0);
}
}
private void GazeInputSourcePreview_GazeMoved(GazeInputSourcePreview sender, GazeMovedPreviewEventArgs args)
{
if (!ShowIntermediatePoints)
{
UpdateGazeHistory(args.CurrentPoint);
return;
}
var points = args.GetIntermediatePoints();
foreach (var pt in points)
{
UpdateGazeHistory(pt);
}
}
}
}

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

@ -0,0 +1,14 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Button Content="Click" Click="Button_Click"/>
</Grid>
</Page>

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

@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Toolkit.Uwp.Notifications;
using Windows.UI.Notifications;
namespace SmokeTest
{
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
}
private void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
ToastContent content = GenerateToastContent();
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(content.GetXml()));
}
public static ToastContent GenerateToastContent()
{
var builder = new ToastContentBuilder().SetToastScenario(ToastScenario.Reminder)
.AddToastActivationInfo("action=viewEvent&eventId=1983", ToastActivationType.Foreground)
.AddText("Adaptive Tiles Meeting")
.AddText("Conf Room 2001 / Building 135")
.AddText("10:00 AM - 10:30 AM")
.AddComboBox(
"snoozeTime",
"15",
("1", "1 minute"),
("15", "15 minutes"),
("60", "1 hour"),
("240", "4 hours"),
("1440", "1 day"))
.AddButton(new ToastButtonSnooze() { SelectionBoxId = "snoozeTime" })
.AddButton(new ToastButtonDismiss());
return builder.Content;
}
}
}

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

@ -0,0 +1,49 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:extensions="using:Microsoft.Toolkit.Uwp.UI.Extensions"
xmlns:animations="using:Microsoft.Toolkit.Uwp.UI.Animations"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Canvas>
<Border x:Name="Element"
Height="100"
Width="100"
Background="Red"
Canvas.Top="100"
Canvas.Left="100"
extensions:VisualExtensions.CenterPoint="50,50,0">
<animations:Implicit.ShowAnimations>
<animations:TranslationAnimation Duration="0:0:1" From="0, -200, 0" To="0" ></animations:TranslationAnimation>
<animations:OpacityAnimation Duration="0:0:1" From="0" To="1.0"></animations:OpacityAnimation>
</animations:Implicit.ShowAnimations>
<animations:Implicit.HideAnimations>
<animations:ScalarAnimation Target="Opacity" Duration="0:0:1" To="0.0"></animations:ScalarAnimation>
<animations:ScalarAnimation Target="Translation.Y" Duration="0:0:1" To="-200">
<animations:ScalarKeyFrame Key="0.1" Value="30"></animations:ScalarKeyFrame>
<animations:ScalarKeyFrame Key="0.5" Value="0.0"></animations:ScalarKeyFrame>
</animations:ScalarAnimation>
</animations:Implicit.HideAnimations>
<animations:Implicit.Animations>
<animations:Vector3Animation Target="Offset" Duration="0:0:1"></animations:Vector3Animation>
<animations:ScalarAnimation Target="RotationAngleInDegrees" ImplicitTarget="Offset" Duration="0:0:1.2" From="0" To="0">
<animations:ScalarKeyFrame Key="0.9" Value="80"></animations:ScalarKeyFrame>
</animations:ScalarAnimation>
<animations:Vector3Animation Target="Scale" Duration="0:0:1"></animations:Vector3Animation>
</animations:Implicit.Animations>
</Border>
</Canvas>
<Button Click="Button_Click" Content="Click"/>
</Grid>
</Page>

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

@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Windows.UI.Xaml.Controls;
namespace SmokeTest
{
public sealed partial class MainPage
{
private Random _random = new Random();
public MainPage()
{
InitializeComponent();
}
private void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
Canvas.SetTop(Element, _random.NextDouble() * this.ActualHeight);
Canvas.SetLeft(Element, _random.NextDouble() * this.ActualWidth);
}
}
}

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

@ -0,0 +1,97 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="RowDetailsTemplate">
<StackPanel>
<TextBlock Margin="20" Text="Here are the details for the selected mountain:" />
<Grid Margin="20,10" Padding="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="Coordinates: " FontWeight="SemiBold" FontSize="13" />
<TextBlock Grid.Row="1" Text="Prominence (m): " FontWeight="SemiBold" FontSize="13" />
<TextBlock Grid.Row="2" Text="First Ascent (year): " FontWeight="SemiBold" FontSize="13" />
<TextBlock Grid.Row="3" Text="No. of ascents: " FontWeight="SemiBold" FontSize="13" />
<TextBlock Grid.Column="1" FontSize="13" Text="{Binding Coordinates}" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="1" FontSize="13" Text="{Binding Prominence}" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="1" FontSize="13" Text="{Binding First_ascent}" HorizontalAlignment="Right" />
<TextBlock Grid.Row="3" Grid.Column="1" FontSize="13" Text="{Binding Ascents}" HorizontalAlignment="Right" />
</Grid>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="12">
<TextBlock Text="DataGrid Sample : Mountains" VerticalAlignment="Center" Margin="5,0" Style="{ThemeResource SubtitleTextBlockStyle}"></TextBlock>
<AppBarButton Icon="Filter" Label="Filter by">
<AppBarButton.Flyout>
<MenuFlyout>
<MenuFlyoutItem x:Name="rankLow" Text="Rank &lt; 50" />
<MenuFlyoutItem x:Name="rankHigh" Text="Rank &gt; 50" />
<MenuFlyoutSeparator />
<MenuFlyoutItem x:Name="heightLow" Text="Height &lt; 8000ft" />
<MenuFlyoutItem x:Name="heightHigh" Text="Height &gt; 8000ft" />
<MenuFlyoutSeparator />
<MenuFlyoutItem x:Name="clearFilter" Text="Remove Filter" />
</MenuFlyout>
</AppBarButton.Flyout>
</AppBarButton>
<AppBarButton x:Name="groupButton" Icon="List" Label="Group by" />
</StackPanel>
<controls:DataGrid
Grid.Row="1"
x:Name="dataGrid"
Margin="12"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
AlternatingRowBackground="Transparent"
AlternatingRowForeground="Gray"
AreRowDetailsFrozen="False"
AreRowGroupHeadersFrozen="True"
AutoGenerateColumns="False"
CanUserSortColumns="False"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
ColumnHeaderHeight="32"
MaxColumnWidth="400"
FrozenColumnCount="0"
GridLinesVisibility="None"
HeadersVisibility="Column"
IsReadOnly="False"
RowDetailsTemplate="{StaticResource RowDetailsTemplate}"
RowDetailsVisibilityMode="Collapsed"
SelectionMode="Extended"
RowGroupHeaderPropertyNameAlternative="Range"
Sorting="DataGrid_Sorting"
LoadingRowGroup="DataGrid_LoadingRowGroup">
<controls:DataGrid.Columns>
<controls:DataGridTextColumn Header="Rank" Binding="{Binding Rank}" Tag="Rank" />
<controls:DataGridComboBoxColumn Header="Mountain" Binding="{Binding Mountain}" Tag="Mountain" />
<controls:DataGridTextColumn Header="Height (m)" Binding="{Binding Height_m}" Tag="Height_m" />
<controls:DataGridTextColumn Header="Range" Binding="{Binding Range}" Tag="Range" />
<controls:DataGridTextColumn Header="Parent Mountain" Binding="{Binding Parent_mountain}" Tag="Parent_mountain" />
</controls:DataGrid.Columns>
</controls:DataGrid>
</Grid>
</Page>

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

@ -0,0 +1,499 @@
// 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.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using Microsoft.Toolkit.Uwp.UI.Controls;
using Windows.UI.Xaml.Data;
namespace SmokeTest
{
public sealed partial class MainPage
{
private DataGridDataSource viewModel = new DataGridDataSource();
public MainPage()
{
InitializeComponent();
dataGrid.ItemsSource = viewModel.GetData();
}
private void DataGrid_Sorting(object sender, Microsoft.Toolkit.Uwp.UI.Controls.DataGridColumnEventArgs e)
{
// Clear previous sorted column if we start sorting a different column
string previousSortedColumn = viewModel.CachedSortedColumn;
if (previousSortedColumn != string.Empty)
{
foreach (DataGridColumn dataGridColumn in dataGrid.Columns)
{
if (dataGridColumn.Tag != null && dataGridColumn.Tag.ToString() == previousSortedColumn &&
(e.Column.Tag == null || previousSortedColumn != e.Column.Tag.ToString()))
{
dataGridColumn.SortDirection = null;
}
}
}
// Toggle clicked column's sorting method
if (e.Column.Tag != null)
{
if (e.Column.SortDirection == null)
{
dataGrid.ItemsSource = viewModel.SortData(e.Column.Tag.ToString(), true);
e.Column.SortDirection = DataGridSortDirection.Ascending;
}
else if (e.Column.SortDirection == DataGridSortDirection.Ascending)
{
dataGrid.ItemsSource = viewModel.SortData(e.Column.Tag.ToString(), false);
e.Column.SortDirection = DataGridSortDirection.Descending;
}
else
{
dataGrid.ItemsSource = viewModel.FilterData(DataGridDataSource.FilterOptions.All);
e.Column.SortDirection = null;
}
}
}
private void DataGrid_LoadingRowGroup(object sender, DataGridRowGroupHeaderEventArgs e)
{
ICollectionViewGroup group = e.RowGroupHeader.CollectionViewGroup;
DataGridDataItem item = group.GroupItems[0] as DataGridDataItem;
e.RowGroupHeader.PropertyValue = item.Range;
}
}
public class DataGridDataItem : INotifyDataErrorInfo, IComparable
{
private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
private uint _rank;
private string _mountain;
private uint _height;
private string _range;
private string _parentMountain;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public uint Rank
{
get
{
return _rank;
}
set
{
if (_rank != value)
{
_rank = value;
}
}
}
public string Mountain
{
get
{
return _mountain;
}
set
{
if (_mountain != value)
{
_mountain = value;
bool isMountainValid = !_errors.Keys.Contains("Mountain");
if (_mountain == string.Empty && isMountainValid)
{
List<string> errors = new List<string>();
errors.Add("Mountain name cannot be empty");
_errors.Add("Mountain", errors);
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Mountain"));
}
else if (_mountain != string.Empty && !isMountainValid)
{
_errors.Remove("Mountain");
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Mountain"));
}
}
}
}
public uint Height_m
{
get
{
return _height;
}
set
{
if (_height != value)
{
_height = value;
}
}
}
public string Range
{
get
{
return _range;
}
set
{
if (_range != value)
{
_range = value;
bool isRangeValid = !_errors.Keys.Contains("Range");
if (_range == string.Empty && isRangeValid)
{
List<string> errors = new List<string>();
errors.Add("Range name cannot be empty");
_errors.Add("Range", errors);
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Range"));
}
else if (_range != string.Empty && !isRangeValid)
{
_errors.Remove("Range");
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Range"));
}
}
}
}
public string Parent_mountain
{
get
{
return _parentMountain;
}
set
{
if (_parentMountain != value)
{
_parentMountain = value;
bool isParentValid = !_errors.Keys.Contains("Parent_mountain");
if (_parentMountain == string.Empty && isParentValid)
{
List<string> errors = new List<string>();
errors.Add("Parent_mountain name cannot be empty");
_errors.Add("Parent_mountain", errors);
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Parent_mountain"));
}
else if (_parentMountain != string.Empty && !isParentValid)
{
_errors.Remove("Parent_mountain");
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Parent_mountain"));
}
}
}
}
public string Coordinates { get; set; }
public uint Prominence { get; set; }
public uint First_ascent { get; set; }
public string Ascents { get; set; }
bool INotifyDataErrorInfo.HasErrors
{
get
{
return _errors.Keys.Count > 0;
}
}
IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName)
{
if (propertyName == null)
{
propertyName = string.Empty;
}
if (_errors.Keys.Contains(propertyName))
{
return _errors[propertyName];
}
else
{
return null;
}
}
int IComparable.CompareTo(object obj)
{
int lnCompare = Range.CompareTo((obj as DataGridDataItem).Range);
if (lnCompare == 0)
{
return Parent_mountain.CompareTo((obj as DataGridDataItem).Parent_mountain);
}
else
{
return lnCompare;
}
}
}
public class DataGridDataSource
{
private const string File = @"1,Mount Everest,8848,Mahalangur Himalaya,27d59m17sN 86d55m31sE,8848,none,1953,>>145 (121)
2, K2/Qogir,8611,Baltoro Karakoram,35d52m53sN 76d30m48sE,4017,Mount Everest,1954,45 (44)
3,Kangchenjunga,8586,Kangchenjunga Himalaya,27d42m12sN 88d08m51sE*,3922,Mount Everest,1955,38 (24)
4,Lhotse,8516,Mahalangur Himalaya,27d57m42sN 86d55m59sE,610,Mount Everest,1956,26 (26)
5,Makalu,8485,Mahalangur Himalaya,27d53m23sN 87d5m20sE,2386,Mount Everest,1955,45 (52)
6,Cho Oyu,8188, Mahalangur Himalaya,28d05m39sN 86d39m39sE,2340,Mount Everest,1954,79 (28)
7,Dhaulagiri I,8167, Dhaulagiri Himalaya,28d41m48sN 83d29m35sE,3357,K2,1960,51 (39)
8,Manaslu,8163,Manaslu Himalaya,28d33m00sN 84d33m35sE,3092,Cho Oyu,1956,49 (45)
9,Nanga Parbat,8126, Nanga Parbat Himalaya,35d14m14sN 74d35m21sE,4608,Dhaulagiri,1953,52 (67)
10,Annapurna I,8091, Annapurna Himalaya,28d35m44sN 83d49m13sE,2984,Cho Oyu,1950,36 (47)
11,Gasherbrum I,8080, Baltoro Karakoram,35d43m28sN 76d41m47sE,2155,K2,1958,31 (16)
12,Broad Peak/K3,8051,Baltoro Karakoram,35d48m38sN 76d34m06sE,1701,Gasherbrum I,1957,39 (19)
13,Gasherbrum II/K4,8034,Baltoro Karakoram,35d45m28sN 76d39m12sE,1523,Gasherbrum I,1956,54 (12)
14,Shishapangma,8027,Jugal Himalaya,28d21m12sN 85d46m43sE,2897,Cho Oyu,1964,43 (19)
15,Gyachung Kang,7952, Mahalangur Himalaya,28d05m53sN 86d44m42sE,700,Cho Oyu,1964,5 (3)
15,Gasherbrum III,7946, Baltoro Karakoram,35d45m33sN 76d38m30sE,355,Gasherbrum II,1975,2 (2)
16,Annapurna II,7937, Annapurna Himalaya,28d32m05sN 84d07m19sE,2437,Annapurna I,1960,6 (19)
17,Gasherbrum IV,7932, Baltoro Karakoram,35d45m38sN 76d36m58sE,715,Gasherbrum III,1958,4 (11)
18,Himalchuli,7893,Manaslu Himalaya,28d26m12sN 84d38m23sE*,1633,Manaslu,1960,6 (12)
19,Distaghil Sar,7884, Hispar Karakoram,36d19m33sN 75d11m16sE,2525,K2,1960,3 (5)
20,Ngadi Chuli,7871, Manaslu Himalaya,28d30m12sN 84d34m00sE,1020,Manaslu,1970,2 (6)";
private static ObservableCollection<DataGridDataItem> _items;
private static List<string> _mountains;
private static CollectionViewSource groupedItems;
private string _cachedSortedColumn = string.Empty;
// Loading data
public IEnumerable<DataGridDataItem> GetData()
{
_items = new ObservableCollection<DataGridDataItem>();
using (StringReader sr = new StringReader(File))
{
string line;
while ((line = sr.ReadLine()) != null)
{
string[] values = line.Split(',');
_items.Add(
new DataGridDataItem()
{
Rank = uint.Parse(values[0]),
Mountain = values[1],
Height_m = uint.Parse(values[2]),
Range = values[3],
Coordinates = values[4],
Prominence = uint.Parse(values[5]),
Parent_mountain = values[6],
First_ascent = uint.Parse(values[7]),
Ascents = values[8]
});
}
}
return _items;
}
// Load mountains into separate collection for use in combobox column
public IEnumerable<string> GetMountains()
{
if (_items == null || !_items.Any())
{
GetData();
}
_mountains = _items?.OrderBy(x => x.Mountain).Select(x => x.Mountain).Distinct().ToList();
return _mountains;
}
// Sorting implementation using LINQ
public string CachedSortedColumn
{
get
{
return _cachedSortedColumn;
}
set
{
_cachedSortedColumn = value;
}
}
public ObservableCollection<DataGridDataItem> SortData(string sortBy, bool ascending)
{
_cachedSortedColumn = sortBy;
switch (sortBy)
{
case "Rank":
if (ascending)
{
return new ObservableCollection<DataGridDataItem>(from item in _items
orderby item.Rank ascending
select item);
}
else
{
return new ObservableCollection<DataGridDataItem>(from item in _items
orderby item.Rank descending
select item);
}
case "Parent_mountain":
if (ascending)
{
return new ObservableCollection<DataGridDataItem>(from item in _items
orderby item.Parent_mountain ascending
select item);
}
else
{
return new ObservableCollection<DataGridDataItem>(from item in _items
orderby item.Parent_mountain descending
select item);
}
case "Mountain":
if (ascending)
{
return new ObservableCollection<DataGridDataItem>(from item in _items
orderby item.Mountain ascending
select item);
}
else
{
return new ObservableCollection<DataGridDataItem>(from item in _items
orderby item.Mountain descending
select item);
}
case "Height_m":
if (ascending)
{
return new ObservableCollection<DataGridDataItem>(from item in _items
orderby item.Height_m ascending
select item);
}
else
{
return new ObservableCollection<DataGridDataItem>(from item in _items
orderby item.Height_m descending
select item);
}
case "Range":
if (ascending)
{
return new ObservableCollection<DataGridDataItem>(from item in _items
orderby item.Range ascending
select item);
}
else
{
return new ObservableCollection<DataGridDataItem>(from item in _items
orderby item.Range descending
select item);
}
}
return _items;
}
// Grouping implementation using LINQ
public CollectionViewSource GroupData()
{
ObservableCollection<GroupInfoCollection<DataGridDataItem>> groups = new ObservableCollection<GroupInfoCollection<DataGridDataItem>>();
var query = from item in _items
orderby item
group item by item.Range into g
select new { GroupName = g.Key, Items = g };
foreach (var g in query)
{
GroupInfoCollection<DataGridDataItem> info = new GroupInfoCollection<DataGridDataItem>();
info.Key = g.GroupName;
foreach (var item in g.Items)
{
info.Add(item);
}
groups.Add(info);
}
groupedItems = new CollectionViewSource();
groupedItems.IsSourceGrouped = true;
groupedItems.Source = groups;
return groupedItems;
}
public class GroupInfoCollection<T> : ObservableCollection<T>
{
public object Key { get; set; }
public new IEnumerator<T> GetEnumerator()
{
return (IEnumerator<T>)base.GetEnumerator();
}
}
// Filtering implementation using LINQ
public enum FilterOptions
{
All = -1,
Rank_Low = 0,
Rank_High = 1,
Height_Low = 2,
Height_High = 3
}
public ObservableCollection<DataGridDataItem> FilterData(FilterOptions filterBy)
{
switch (filterBy)
{
case FilterOptions.All:
return new ObservableCollection<DataGridDataItem>(_items);
case FilterOptions.Rank_Low:
return new ObservableCollection<DataGridDataItem>(from item in _items
where item.Rank < 50
select item);
case FilterOptions.Rank_High:
return new ObservableCollection<DataGridDataItem>(from item in _items
where item.Rank > 50
select item);
case FilterOptions.Height_High:
return new ObservableCollection<DataGridDataItem>(from item in _items
where item.Height_m > 8000
select item);
case FilterOptions.Height_Low:
return new ObservableCollection<DataGridDataItem>(from item in _items
where item.Height_m < 8000
select item);
}
return _items;
}
}
}

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

@ -0,0 +1,41 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:winui="using:Microsoft.UI.Xaml.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<DataTemplate x:Key="WrapTemplate">
<Border Width="{Binding Width}" Height="50">
<Border.Background>
<SolidColorBrush Color="{Binding Color}"/>
</Border.Background>
<TextBlock Text="{Binding Index}" FontSize="20"/>
</Border>
</DataTemplate>
</Page.Resources>
<Grid Padding="48">
<winui:ItemsRepeaterScrollHost>
<!-- Needed for 1803 and below -->
<ScrollViewer x:Name="WrapScrollParent">
<winui:ItemsRepeater x:Name="WrapRepeater"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
ItemsSource="{x:Bind Items}"
ItemTemplate="{StaticResource WrapTemplate}">
<winui:ItemsRepeater.Layout>
<controls:WrapLayout x:Name="Wrap"
VerticalSpacing="5"
HorizontalSpacing="5"/>
</winui:ItemsRepeater.Layout>
</winui:ItemsRepeater>
</ScrollViewer>
</winui:ItemsRepeaterScrollHost>
</Grid>
</Page>

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

@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.ObjectModel;
using Windows.UI;
namespace SmokeTest
{
public sealed partial class MainPage
{
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();
private readonly Random _random;
public class Item
{
public int Index { get; internal set; }
public int Width { get; internal set; }
public int Height { get; internal set; }
public Color Color { get; internal set; }
}
public MainPage()
{
InitializeComponent();
_random = new Random(DateTime.Now.Millisecond);
for (int i = 0; i < _random.Next(1000, 5000); i++)
{
var item = new Item { Index = i, Width = _random.Next(50, 250), Height = _random.Next(50, 250), Color = Color.FromArgb(255, (byte)_random.Next(0, 255), (byte)_random.Next(0, 255), (byte)_random.Next(0, 255)) };
Items.Add(item);
}
}
}
}

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

@ -0,0 +1,50 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Margin="12,12,12,0"
FontSize="18"
Text="Regular Text" />
<TextBox x:Name="UnformattedText"
Grid.Row="1"
Margin="12"
AcceptsReturn="True"
Text="**Try it live!** Type in the *unformatted text box* above!"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap" />
<TextBlock Grid.Row="2"
Margin="12,12,12,0"
FontSize="18"
Text="Markdown Text" />
<ScrollViewer Grid.Row="3"
Margin="12"
BorderBrush="{ThemeResource AppBarBorderThemeBrush}"
BorderThickness="2"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Visible">
<controls:MarkdownTextBlock x:Name="MarkdownText"
Margin="6"
Header1Foreground="{ThemeResource SystemControlForegroundAccentBrush}"
Text="{Binding ElementName=UnformattedText, Path=Text}"
SchemeList="companyportal,randomscheme"
UriPrefix="ms-appx://" />
</ScrollViewer>
</Grid>
</Page>

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

@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace SmokeTest
{
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
}
}
}

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

@ -0,0 +1,123 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<Style x:Key="BladeStyle" TargetType="controls:BladeItem">
<Setter Property="Header" Value="Default Blade" />
<Setter Property="Width" Value="400" />
<Setter Property="IsOpen" Value="True" />
</Style>
</Page.Resources>
<Grid>
<controls:BladeView x:Name="BladeView"
Margin="0"
Padding="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BladeMode="Normal"
AutoCollapseCountThreshold="4">
<controls:BladeItem x:Name="FirstBlade"
Header="Test"
Style="{StaticResource BladeStyle}"
TitleBarVisibility="Collapsed">
<StackPanel Margin="24">
<TextBlock Text="This first blade has a hidden TitleBar, so you can't close it."
TextWrapping="WrapWholeWords" />
<TextBlock Margin="0,12,0,0"
Text="The buttons below toggle more blades on and off. The blades appear in the order that they're opened."
TextWrapping="WrapWholeWords" />
<ToggleButton Margin="0,24,0,0"
Content="Default Blade"
IsChecked="{Binding IsOpen, Mode=TwoWay, ElementName=SecondBlade}" />
<ToggleButton Margin="0,24,0,0"
Content="Custom Titlebar"
IsChecked="{Binding IsOpen, Mode=TwoWay, ElementName=ThirdBlade}" />
<ToggleButton Margin="0,24,0,0"
Content="Custom Close Button"
IsChecked="{Binding IsOpen, Mode=TwoWay, ElementName=FourthBlade}" />
<Button x:Name="AddBlade"
Margin="0,24,0,0"
Content="Add Blade" />
</StackPanel>
</controls:BladeItem>
<controls:BladeItem x:Name="SecondBlade"
Header="Default blade"
IsOpen="False"
Style="{StaticResource BladeStyle}">
<TextBlock Margin="24"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="This is a blade with all settings set to default." />
</controls:BladeItem>
<controls:BladeItem x:Name="ThirdBlade"
Header="Custom title bar"
Style="{StaticResource BladeStyle}"
IsOpen="False"
TitleBarBackground="DarkSlateGray"
CloseButtonForeground="White">
<controls:BladeItem.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Foreground="White" />
</DataTemplate>
</controls:BladeItem.HeaderTemplate>
<TextBlock Margin="24"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="This is a blade with custom titlebar colors." />
</controls:BladeItem>
<controls:BladeItem x:Name="FourthBlade"
Header="Custom close button color"
Style="{StaticResource BladeStyle}"
CloseButtonBackground="DarkSlateGray"
CloseButtonForeground="White"
IsOpen="False">
<TextBlock Margin="24"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="This is a blade with a custom close button color." />
</controls:BladeItem>
</controls:BladeView>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Full">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="600" />
</VisualState.StateTriggers>
</VisualState>
<VisualState x:Name="Small">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="BladeView.Padding" Value="12" />
<Setter Target="FirstBlade.Width" Value="280" />
<Setter Target="FirstBlade.FontSize" Value="12" />
<Setter Target="SecondBlade.Width" Value="280" />
<Setter Target="SecondBlade.FontSize" Value="12" />
<Setter Target="ThirdBlade.Width" Value="280" />
<Setter Target="ThirdBlade.FontSize" Value="12" />
<Setter Target="FourthBlade.Width" Value="280" />
<Setter Target="FourthBlade.FontSize" Value="12" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</Page>

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

@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace SmokeTest
{
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
}
}
}

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

@ -0,0 +1,31 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:media="using:Microsoft.Toolkit.Uwp.UI.Media"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Image Source="ms-appx:///Assets/SplashScreen.png"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ProgressRing IsActive="True" Grid.ColumnSpan="2"
VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Red" Width="200" Height="200"/>
<Border BorderBrush="Black" BorderThickness="1"
Grid.Column="2"
Height="400">
<Border.Background>
<media:BackdropBlurBrush Amount="3" />
</Border.Background>
</Border>
</Grid>
</Grid>
</Page>

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

@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace SmokeTest
{
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
}
}
}

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

@ -0,0 +1,35 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<ScrollViewer>
<Grid Height="2200">
<Border x:Name="EffectElementHost"
Width="200"
Height="200"
Background="Gray">
<interactivity:Interaction.Behaviors>
<behaviors:ViewportBehavior x:Name="ViewportBehavior" />
</interactivity:Interaction.Behaviors>
<Image x:Name="EffectElement"
Width="100"
Height="100" />
</Border>
</Grid>
</ScrollViewer>
<TextBlock HorizontalAlignment="Left"
VerticalAlignment="Top"
Foreground="OrangeRed"
IsHitTestVisible="False"
Text="Please scroll down to see the effect." />
</Grid>
</Page>

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

@ -0,0 +1,60 @@
// 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.Diagnostics;
using System.Linq;
using Microsoft.Toolkit.Uwp.UI.Behaviors;
using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml.Media.Imaging;
namespace SmokeTest
{
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
Loaded += this.MainPage_Loaded;
}
private void MainPage_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
var behaviors = Interaction.GetBehaviors(EffectElementHost);
var viewportBehavior = behaviors.OfType<ViewportBehavior>().FirstOrDefault();
if (viewportBehavior != null)
{
viewportBehavior.EnteredViewport += EffectElementHost_EnteredViewport;
viewportBehavior.EnteringViewport += EffectElementHost_EnteringViewport;
viewportBehavior.ExitedViewport += EffectElementHost_ExitedViewport;
viewportBehavior.ExitingViewport += EffectElementHost_ExitingViewport;
}
}
private void EffectElementHost_EnteredViewport(object sender, EventArgs e)
{
Debug.WriteLine("Entered viewport");
}
private void EffectElementHost_EnteringViewport(object sender, EventArgs e)
{
Debug.WriteLine("Entering viewport");
EffectElement.Source = new BitmapImage(new Uri("ms-appx:///Assets/ToolkitLogo.png"));
}
private void EffectElementHost_ExitedViewport(object sender, EventArgs e)
{
Debug.WriteLine("Exited viewport");
EffectElement.Source = null;
}
private void EffectElementHost_ExitingViewport(object sender, EventArgs e)
{
Debug.WriteLine("Exiting viewport");
}
}
}

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

@ -0,0 +1,15 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Image x:Name="image"/>
<Button Content="Click" Click="Button_Click"/>
</Grid>
</Page>

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

@ -0,0 +1,67 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.Toolkit.Uwp.Helpers;
using Windows.Graphics.Imaging;
using Windows.Media;
using Windows.System;
using Windows.UI.Xaml.Media.Imaging;
namespace SmokeTest
{
public sealed partial class MainPage
{
private CameraHelper _cameraHelper;
private DispatcherQueue _dispatcherQueue;
public MainPage()
{
InitializeComponent();
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
image.Source = new SoftwareBitmapSource();
}
private async void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
if (_cameraHelper != null)
{
_cameraHelper.FrameArrived -= CameraHelper_FrameArrived;
await _cameraHelper.CleanUpAsync();
}
_cameraHelper = new CameraHelper();
var result = await _cameraHelper.InitializeAndStartCaptureAsync();
if (result == CameraHelperResult.Success)
{
_cameraHelper.FrameArrived += CameraHelper_FrameArrived;
}
else
{
var errorMessage = result.ToString();
}
}
private void CameraHelper_FrameArrived(object sender, FrameEventArgs e)
{
VideoFrame currentVideoFrame = e.VideoFrame;
SoftwareBitmap softwareBitmap = currentVideoFrame.SoftwareBitmap;
if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 || softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
{
softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
_dispatcherQueue.TryEnqueue(async () =>
{
if (image.Source is SoftwareBitmapSource softwareBitmapSource)
{
await softwareBitmapSource.SetBitmapAsync(softwareBitmap);
}
});
}
}
}

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

@ -0,0 +1,18 @@
<Page
x:Class="SmokeTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SmokeTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Width="200">
<TextBox x:Name="textBox"/>
<Button Content="Is Email?" Click="Button_Click" HorizontalAlignment="Center"/>
<TextBlock x:Name="textBlock"/>
</StackPanel>
</Grid>
</Page>

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

@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Toolkit.Extensions;
namespace SmokeTest
{
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
}
private void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
textBlock.Text = textBox.Text?.IsEmail().ToString() ?? "False";
}
}
}

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

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
IgnorableNamespaces="uap mp uap3">
<Identity
Name="96554a00-b926-4130-b09f-f2a0777f3d09"
Publisher="CN=alzollin"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="96554a00-b926-4130-b09f-f2a0777f3d09" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>SmokeTest</DisplayName>
<PublisherDisplayName>alzollin</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="SmokeTest.App">
<uap:VisualElements
DisplayName="SmokeTest"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png"
Description="SmokeTest"
BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
<Capability Name="privateNetworkClientServer" />
<uap3:Capability Name="remoteSystem" />
<DeviceCapability Name="location" />
<DeviceCapability Name="bluetooth" />
<DeviceCapability Name="webcam" />
<DeviceCapability Name="gazeInput" />
</Capabilities>
</Package>

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

@ -0,0 +1,31 @@
// 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.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SmokeTest")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SmokeTest")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: ComVisible(false)]

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

@ -0,0 +1,31 @@
<!--
This file contains Runtime Directives used by .NET Native. The defaults here are suitable for most
developers. However, you can modify these parameters to modify the behavior of the .NET Native
optimizer.
Runtime Directives are documented at https://go.microsoft.com/fwlink/?LinkID=391919
To fully enable reflection for App1.MyClass and all of its public/private members
<Type Name="App1.MyClass" Dynamic="Required All"/>
To enable dynamic creation of the specific instantiation of AppClass<T> over System.Int32
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
Using the Namespace directive to apply reflection policy to all the types in a particular namespace
<Namespace Name="DataClasses.ViewModels" Serialize="All" />
-->
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<!--
An Assembly element with Name="*Application*" applies to all assemblies in
the application package. The asterisks are not wildcards.
-->
<Assembly Name="*Application*" Dynamic="Required All" />
<!-- Add your application specific runtime directives here. -->
</Application>
</Directives>

129
SmokeTests/SmokeTest.csproj Normal file
Просмотреть файл

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<!-- - - - - - Don't check-in changes in between this lines. Used for development. - - - - - -->
<PropertyGroup Condition="'$(CurrentProject)' == ''">
<!-- When writting the SmokeTests, change this to whichever Toolkit project you want to build a test to, then reload the project -->
<CurrentProject>Microsoft.Toolkit.Uwp.UI.Controls</CurrentProject>
</PropertyGroup>
<!-- - - - - - Don't check-in changes in between this lines. Used for development. - - - - - -->
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}</ProjectGuid>
<OutputType>AppContainerExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SmokeTest</RootNamespace>
<AssemblyName>SmokeTest.$(CurrentProject)</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.$(DefaultTargetPlatformVersion).0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.$(DefaultTargetPlatformMinVersion).0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WindowsXamlEnableOverview>true</WindowsXamlEnableOverview>
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
<AppxBundlePlatforms>$(Platform)</AppxBundlePlatforms>
<AppxBundle>Always</AppxBundle>
<UapAppxPackageBuildMode>StoreUpload</UapAppxPackageBuildMode>
<PlatformTarget>$(Platform)</PlatformTarget>
<NoWarn>;2008;SA0001;SA1601</NoWarn>
<Prefer32Bit>true</Prefer32Bit>
<ErrorReport>prompt</ErrorReport>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition="'$(CurrentProject)' != ''">
<AppxPackageName>SmokeTest_$(CurrentProject)</AppxPackageName>
<IntermediateOutputPath>obj\$(CurrentProject)\</IntermediateOutputPath>
<OutputPath>bin\$(CurrentProject)\$(Platform)\$(Configuration)\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<!-- These empty PropertyGroup just make VS happy -->
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|ARM' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|ARM' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|ARM64' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|ARM64' ">
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<Content Include="Properties\Default.rd.xml" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
</ItemGroup>
<ItemGroup Condition="'$(CurrentProject)' != ''">
<Compile Include="$(CurrentProject)\*.cs" />
<Page Include="$(CurrentProject)\*.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.10</Version>
</PackageReference>
</ItemGroup>
<!-- Only the Layout package have a dependency on WinUI -->
<ItemGroup Condition="$(CurrentProject) == 'Microsoft.Toolkit.Uwp.UI.Controls.Layout'">
<PackageReference Include="Microsoft.UI.Xaml">
<Version>2.4.3</Version>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(CurrentProject)' != ''">
<PackageReference Include="$(CurrentProject)" Version="7.*-*" />
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<Target Name="BeforeBuild">
<ItemGroup>
<ToolkitNugets Include="../bin/nupkg/$(CurrentProject).*.nupkg"/>
<ToolkitNuget Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(Identity)', `$(CurrentProject).([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?.nupkg`))" Include="@(ToolkitNugets)"/>
</ItemGroup>
<Error Condition="'@(ToolkitNuget)' == ''" Text="NuGet $(CurrentProject).[SEMVER].nupkg doesn't exist!"/>
</Target>
</Project>

51
SmokeTests/SmokeTest.sln Normal file
Просмотреть файл

@ -0,0 +1,51 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30413.136
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmokeTest", "SmokeTest.csproj", "{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM = Release|ARM
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Debug|ARM.ActiveCfg = Debug|ARM
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Debug|ARM.Build.0 = Debug|ARM
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Debug|ARM.Deploy.0 = Debug|ARM
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Debug|ARM64.Build.0 = Debug|ARM64
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Debug|ARM64.Deploy.0 = Debug|ARM64
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Debug|x64.ActiveCfg = Debug|x64
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Debug|x64.Build.0 = Debug|x64
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Debug|x64.Deploy.0 = Debug|x64
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Debug|x86.ActiveCfg = Debug|x86
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Debug|x86.Build.0 = Debug|x86
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Debug|x86.Deploy.0 = Debug|x86
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Release|ARM.ActiveCfg = Release|ARM
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Release|ARM.Build.0 = Release|ARM
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Release|ARM.Deploy.0 = Release|ARM
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Release|ARM64.ActiveCfg = Release|ARM64
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Release|ARM64.Build.0 = Release|ARM64
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Release|ARM64.Deploy.0 = Release|ARM64
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Release|x64.ActiveCfg = Release|x64
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Release|x64.Build.0 = Release|x64
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Release|x64.Deploy.0 = Release|x64
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Release|x86.ActiveCfg = Release|x86
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Release|x86.Build.0 = Release|x86
{A6E4CB52-1025-4BBA-9C65-BB871D1FB53F}.Release|x86.Deploy.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8A672016-FF33-4C67-9309-5230BCE67EF6}
EndGlobalSection
EndGlobal

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

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="Current" DefaultTargets="Build">
<PropertyGroup>
<BuildPlatforms>x86</BuildPlatforms>
<BuildConfigurations>Release</BuildConfigurations>
<ToolkitPackages>Microsoft.Toolkit;Microsoft.Toolkit.HighPerformance;Microsoft.Toolkit.Parsers;Microsoft.Toolkit.Mvvm;Microsoft.Toolkit.Services;Microsoft.Toolkit.Uwp;Microsoft.Toolkit.Uwp.Connectivity;Microsoft.Toolkit.Uwp.DeveloperTools;Microsoft.Toolkit.Uwp.Input.GazeInteraction;Microsoft.Toolkit.Uwp.Notifications;Microsoft.Toolkit.Uwp.UI;Microsoft.Toolkit.Uwp.UI.Animations;Microsoft.Toolkit.Uwp.UI.Controls;Microsoft.Toolkit.Uwp.UI.Controls.DataGrid;Microsoft.Toolkit.Uwp.UI.Controls.Layout;Microsoft.Toolkit.Uwp.UI.Media;Microsoft.Toolkit.Uwp.UI.Controls.Markdown</ToolkitPackages>
</PropertyGroup>
<Target Name="Build"
DependsOnTargets="ChooseProjectsToBuild"
Inputs="@(ProjectsToBuild)"
Outputs="%(Filename)">
<Message Importance="High" Text="Building project %(ProjectsToBuild.Filename): (%(ProjectsToBuild.Configuration)|%(ProjectsToBuild.Platform))" />
<MSBuild Projects="SmokeTest.csproj"
Targets="restore;build"
Properties="CurrentProject=%(ProjectsToBuild.Identity);Configuration=%(ProjectsToBuild.Configuration);Platform=%(ProjectsToBuild.Platform)"/>
</Target>
<Target Name="ChooseProjectsToBuild" DependsOnTargets="CheckNugets">
<ItemGroup>
<BuildPlatform Include="$(BuildPlatforms)" />
<BuildConfiguration Include="$(BuildConfigurations)" />
<ToolkitPackage Include="$(ToolkitPackages)" />
<ToolkitProject Include="@(ToolkitPackage)">
<Platforms>x86;x64;ARM;ARM64</Platforms>
<BinDir>bin</BinDir>
<AssemblyName>%(ToolkitPackage.Identity)</AssemblyName>
</ToolkitProject>
<CandidateProjects Include="@(ToolkitProject);@(AnyCPUProject)">
<Platform>%(BuildPlatform.Identity)</Platform>
</CandidateProjects>
<FilteredProjects Include="@(CandidateProjects)" Condition="$([System.String]::new('%(CandidateProjects.Platforms)').Contains('%(CandidateProjects.Platform)'))" />
<ProjectsPerConfig Include="@(FilteredProjects)">
<Configuration>%(BuildConfiguration.Identity)</Configuration>
</ProjectsPerConfig>
<ProjectsToBuild Include="@(ProjectsPerConfig)">
<AdditionalProperties>Platform=%(ProjectsPerConfig.Platform);Configuration=%(ProjectsPerConfig.Configuration)</AdditionalProperties>
</ProjectsToBuild>
</ItemGroup>
</Target>
<Target Name="CheckNugets">
<PropertyGroup>
<ToolkitNugets>../bin/nupkg/*.nupkg</ToolkitNugets>
</PropertyGroup>
<ItemGroup>
<ToolkitNugets Include="$(ToolkitNugets)" />
</ItemGroup>
<Error Condition="'@(ToolkitNugets)' == ''" Text="Directory '$(ToolkitNugets)' is empty"/>
</Target>
</Project>

8
SmokeTests/nuget.config Normal file
Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="Local" value="..\bin\nupkg" />
</packageSources>
</configuration>

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

@ -6,6 +6,7 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Toolkit.HighPerformance.Buffers;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -17,6 +18,44 @@ namespace UnitTests.HighPerformance.Buffers
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_ArrayPoolBufferWriterOfT
{
[TestCategory("ArrayPoolBufferWriterOfT")]
[TestMethod]
[DataRow(0, 256)] // 256 is the default initial size for ArrayPoolBufferWriter<T>
[DataRow(4, 256)]
[DataRow(7, 256)]
[DataRow(27, 256)]
[DataRow(188, 256)]
[DataRow(257, 512)]
[DataRow(358, 512)]
[DataRow(799, 1024)]
[DataRow(1024, 1024)]
[DataRow(1025, 2048)]
[DataRow((1024 * 1024) - 1, 1024 * 1024)]
[DataRow(1024 * 1024, 1024 * 1024)]
[DataRow((1024 * 1024) + 1, 2 * 1024 * 1024)]
[DataRow(2 * 1024 * 1024, 2 * 1024 * 1024)]
[DataRow((2 * 1024 * 1024) + 1, 4 * 1024 * 1024)]
[DataRow(3 * 1024 * 1024, 4 * 1024 * 1024)]
public void Test_ArrayPoolBufferWriterOfT_BufferSize(int request, int expected)
{
using var writer = new ArrayPoolBufferWriter<byte>();
// Request a Span<T> of a specified size and discard it. We're just invoking this
// method to force the ArrayPoolBufferWriter<T> instance to internally resize the
// buffer to ensure it can contain at least this number of items. After this, we
// can use reflection to get the internal array and ensure the size equals the
// expected one, which matches the "round up to power of 2" logic we need. This
// is documented within the resize method in ArrayPoolBufferWriter<T>, and it's
// done to prevent repeated allocations of arrays in some scenarios.
_ = writer.GetSpan(request);
var arrayFieldInfo = typeof(ArrayPoolBufferWriter<byte>).GetField("array", BindingFlags.Instance | BindingFlags.NonPublic);
byte[] array = (byte[])arrayFieldInfo!.GetValue(writer);
Assert.AreEqual(array!.Length, expected);
}
[TestCategory("ArrayPoolBufferWriterOfT")]
[TestMethod]
public void Test_ArrayPoolBufferWriterOfT_AllocateAndGetMemoryAndSpan()

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

@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Toolkit.HighPerformance.Buffers;
@ -105,7 +104,7 @@ namespace UnitTests.HighPerformance.Buffers
// by accident doesn't cause issues, and just does nothing.
}
[TestCategory("HashCodeOfT")]
[TestCategory("MemoryOwnerOfT")]
[TestMethod]
public void Test_MemoryOwnerOfT_PooledBuffersAndClear()
{
@ -124,5 +123,44 @@ namespace UnitTests.HighPerformance.Buffers
Assert.IsTrue(buffer.Span.ToArray().All(i => i == 0));
}
}
[TestCategory("MemoryOwnerOfT")]
[TestMethod]
public void Test_MemoryOwnerOfT_AllocateAndGetArray()
{
var buffer = MemoryOwner<int>.Allocate(127);
// Here we allocate a MemoryOwner<T> instance with a requested size of 127, which means it
// internally requests an array of size 127 from ArrayPool<T>.Shared. We then get the array
// segment, so we need to verify that (since buffer is not disposed) the returned array is
// not null, is of size >= the requested one (since ArrayPool<T> by definition returns an
// array that is at least of the requested size), and that the offset and count properties
// match our input values (same length, and offset at 0 since the buffer was not sliced).
var segment = buffer.DangerousGetArray();
Assert.IsNotNull(segment.Array);
Assert.IsTrue(segment.Array.Length >= buffer.Length);
Assert.AreEqual(segment.Offset, 0);
Assert.AreEqual(segment.Count, buffer.Length);
var second = buffer.Slice(10, 80);
// The original buffer instance is disposed here, because calling Slice transfers
// the ownership of the internal buffer to the new instance (this is documented in
// XML docs for the MemoryOwner<T>.Slice method).
Assert.ThrowsException<ObjectDisposedException>(() => buffer.DangerousGetArray());
segment = second.DangerousGetArray();
// Same as before, but we now also verify the initial offset != 0, as we used Slice
Assert.IsNotNull(segment.Array);
Assert.IsTrue(segment.Array.Length >= second.Length);
Assert.AreEqual(segment.Offset, 10);
Assert.AreEqual(segment.Count, second.Length);
second.Dispose();
Assert.ThrowsException<ObjectDisposedException>(() => second.DangerousGetArray());
}
}
}

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

@ -60,7 +60,7 @@ namespace UnitTests.HighPerformance.Buffers
Assert.Fail("You shouldn't be here");
}
[TestCategory("HashCodeOfT")]
[TestCategory("SpanOwnerOfT")]
[TestMethod]
public void Test_SpanOwnerOfT_PooledBuffersAndClear()
{
@ -79,5 +79,23 @@ namespace UnitTests.HighPerformance.Buffers
Assert.IsTrue(buffer.Span.ToArray().All(i => i == 0));
}
}
[TestCategory("SpanOwnerOfT")]
[TestMethod]
public void Test_SpanOwnerOfT_AllocateAndGetArray()
{
using var buffer = SpanOwner<int>.Allocate(127);
var segment = buffer.DangerousGetArray();
// See comments in the MemoryOwner<T> tests about this. The main difference
// here is that we don't do the disposed checks, as SpanOwner<T> is optimized
// with the assumption that usages after dispose are undefined behavior. This
// is all documented in the XML docs for the SpanOwner<T> type.
Assert.IsNotNull(segment.Array);
Assert.IsTrue(segment.Array.Length >= buffer.Length);
Assert.AreEqual(segment.Offset, 0);
Assert.AreEqual(segment.Count, buffer.Length);
}
}
}

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

@ -3,7 +3,10 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -12,12 +15,565 @@ namespace UnitTests.HighPerformance.Extensions
[TestClass]
public class Test_MemoryExtensions
{
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_Cast_Empty()
{
// Casting an empty memory of any size should always be valid
// and result in another empty memory, regardless of types.
Memory<byte> m1 = default;
Memory<byte> mc1 = m1.Cast<byte, byte>();
Assert.IsTrue(mc1.IsEmpty);
Memory<byte> m2 = default;
Memory<float> mc2 = m2.Cast<byte, float>();
Assert.IsTrue(mc2.IsEmpty);
Memory<short> m3 = default;
Memory<Guid> mc3 = m3.Cast<short, Guid>();
Assert.IsTrue(mc3.IsEmpty);
// Same as above, but with a sliced memory (length 12, slide from 12, so length of 0)
Memory<byte> m4 = new byte[12].AsMemory(12);
Memory<int> mc4 = m4.Cast<byte, int>();
Assert.IsTrue(mc4.IsEmpty);
// Same as above, but slicing to 12 in two steps
Memory<byte> m5 = new byte[12].AsMemory().Slice(4).Slice(8);
Memory<int> mc5 = m5.Cast<byte, int>();
Assert.IsTrue(mc5.IsEmpty);
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_Cast_TooShort()
{
// One int is 4 bytes, so casting from 3 rounds down to 0
Memory<byte> m1 = new byte[3];
Memory<int> mc1 = m1.Cast<byte, int>();
Assert.IsTrue(mc1.IsEmpty);
// Same as above, 13 / sizeof(int) == 3
Memory<byte> m2 = new byte[13];
Memory<float> mc2 = m2.Cast<byte, float>();
Assert.AreEqual(mc2.Length, 3);
// 16 - 5 = 11 ---> 11 / sizeof(int) = 2
Memory<byte> m3 = new byte[16].AsMemory(5);
Memory<float> mc3 = m3.Cast<byte, float>();
Assert.AreEqual(mc3.Length, 2);
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromArray_CastFromByte()
{
// Test for a standard cast from bytes with an evenly divisible length
Memory<byte> memoryOfBytes = new byte[128];
Memory<float> memoryOfFloats = memoryOfBytes.Cast<byte, float>();
Assert.AreEqual(memoryOfFloats.Length, 128 / sizeof(float));
Span<byte> spanOfBytes = memoryOfBytes.Span;
Span<float> spanOfFloats = memoryOfFloats.Span;
// We also need to check that the Span<T> returned from the caast memory
// actually has the initial reference pointing to the same location as
// the one to the same item in the span from the original memory.
Assert.AreEqual(memoryOfFloats.Length, spanOfFloats.Length);
Assert.IsTrue(Unsafe.AreSame(
ref spanOfBytes[0],
ref Unsafe.As<float, byte>(ref spanOfFloats[0])));
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromArray_CastToByte()
{
// Cast from float to bytes to verify casting works when the target type
// as a smaller byte size as well (so the resulting length will be larger).
Memory<float> memoryOfFloats = new float[128];
Memory<byte> memoryOfBytes = memoryOfFloats.Cast<float, byte>();
Assert.AreEqual(memoryOfBytes.Length, 128 * sizeof(float));
Span<float> spanOfFloats = memoryOfFloats.Span;
Span<byte> spanOfBytes = memoryOfBytes.Span;
// Same as above, we need to verify that the resulting span has matching
// starting references with the one produced by the original memory.
Assert.AreEqual(memoryOfBytes.Length, spanOfBytes.Length);
Assert.IsTrue(Unsafe.AreSame(
ref spanOfFloats[0],
ref Unsafe.As<byte, float>(ref spanOfBytes[0])));
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromArray_CastToShort()
{
// Similar test as above, just with different types to double check
Memory<float> memoryOfFloats = new float[128];
Memory<short> memoryOfShorts = memoryOfFloats.Cast<float, short>();
Assert.AreEqual(memoryOfShorts.Length, 128 * sizeof(float) / sizeof(short));
Span<float> spanOfFloats = memoryOfFloats.Span;
Span<short> spanOfShorts = memoryOfShorts.Span;
Assert.AreEqual(memoryOfShorts.Length, spanOfShorts.Length);
Assert.IsTrue(Unsafe.AreSame(
ref spanOfFloats[0],
ref Unsafe.As<short, float>(ref spanOfShorts[0])));
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromArray_CastFromByteAndBack()
{
// Here we start from a byte array, get a memory, then cast to float and then
// back to byte. We want to verify that the final memory is both valid and
// consistent, as well that our internal optimization works and that the final
// memory correctly skipped the indirect memory managed and just wrapped the original
// array instead. This is documented in the custom array memory manager in the package.
var data = new byte[128];
Memory<byte> memoryOfBytes = data;
Memory<float> memoryOfFloats = memoryOfBytes.Cast<byte, float>();
Memory<byte> memoryBack = memoryOfFloats.Cast<float, byte>();
Assert.AreEqual(memoryOfBytes.Length, memoryBack.Length);
// Here we get the array from the final memory and check that it does exist and
// the associated parameters match the ones we'd expect here (same length, offset of 0).
Assert.IsTrue(MemoryMarshal.TryGetArray<byte>(memoryBack, out var segment));
Assert.AreSame(segment.Array!, data);
Assert.AreEqual(segment.Offset, 0);
Assert.AreEqual(segment.Count, data.Length);
Assert.IsTrue(memoryOfBytes.Equals(memoryBack));
Span<byte> span1 = memoryOfBytes.Span;
Span<byte> span2 = memoryBack.Span;
// Also validate the initial and final spans for reference equality
Assert.IsTrue(span1 == span2);
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_Cast_TooShort_WithSlice()
{
// Like we did above, we have some more tests where we slice an initial memory and
// validate the length of the resulting, accounting for the expected rounding down.
Memory<byte> m1 = new byte[8].AsMemory().Slice(4, 3);
Memory<int> mc1 = m1.Cast<byte, int>();
Assert.IsTrue(mc1.IsEmpty);
Memory<byte> m2 = new byte[20].AsMemory().Slice(4, 13);
Memory<float> mc2 = m2.Cast<byte, float>();
Assert.AreEqual(mc2.Length, 3);
Memory<byte> m3 = new byte[16].AsMemory().Slice(5);
Memory<float> mc3 = m3.Cast<byte, float>();
Assert.AreEqual(mc3.Length, 2);
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromArray_CastFromByte_WithSlice()
{
// Same exact test as the cast from byte to float did above, but with a slice. This is done
// to ensure the cast still works correctly when the memory is internally storing an offset.
Memory<byte> memoryOfBytes = new byte[512].AsMemory().Slice(128, 128);
Memory<float> memoryOfFloats = memoryOfBytes.Cast<byte, float>();
Assert.AreEqual(memoryOfFloats.Length, 128 / sizeof(float));
Span<byte> spanOfBytes = memoryOfBytes.Span;
Span<float> spanOfFloats = memoryOfFloats.Span;
Assert.AreEqual(memoryOfFloats.Length, spanOfFloats.Length);
Assert.IsTrue(Unsafe.AreSame(
ref spanOfBytes[0],
ref Unsafe.As<float, byte>(ref spanOfFloats[0])));
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromArray_CastToByte_WithSlice()
{
// Same as above, just with inverted source and destination types
Memory<float> memoryOfFloats = new float[512].AsMemory().Slice(128, 128);
Memory<byte> memoryOfBytes = memoryOfFloats.Cast<float, byte>();
Assert.AreEqual(memoryOfBytes.Length, 128 * sizeof(float));
Span<float> spanOfFloats = memoryOfFloats.Span;
Span<byte> spanOfBytes = memoryOfBytes.Span;
Assert.AreEqual(memoryOfBytes.Length, spanOfBytes.Length);
Assert.IsTrue(Unsafe.AreSame(
ref spanOfFloats[0],
ref Unsafe.As<byte, float>(ref spanOfBytes[0])));
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromArray_CastToShort_WithSlice()
{
// Once again the same test but with types both different in size than 1. We're mostly
// just testing the rounding logic in a number of different case to ensure it's correct.
Memory<float> memoryOfFloats = new float[512].AsMemory().Slice(128, 128);
Memory<short> memoryOfShorts = memoryOfFloats.Cast<float, short>();
Assert.AreEqual(memoryOfShorts.Length, 128 * sizeof(float) / sizeof(short));
Span<float> spanOfFloats = memoryOfFloats.Span;
Span<short> spanOfShorts = memoryOfShorts.Span;
Assert.AreEqual(memoryOfShorts.Length, spanOfShorts.Length);
Assert.IsTrue(Unsafe.AreSame(
ref spanOfFloats[0],
ref Unsafe.As<short, float>(ref spanOfShorts[0])));
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromArray_CastFromByteAndBack_WithSlice()
{
// Just like the equivalent test above, but with a slice thrown in too
var data = new byte[512];
Memory<byte> memoryOfBytes = data.AsMemory().Slice(128, 128);
Memory<float> memoryOfFloats = memoryOfBytes.Cast<byte, float>();
Memory<byte> memoryBack = memoryOfFloats.Cast<float, byte>();
Assert.AreEqual(memoryOfBytes.Length, memoryBack.Length);
// Here we now also have to validate the starting offset from the extracted array
Assert.IsTrue(MemoryMarshal.TryGetArray<byte>(memoryBack, out var segment));
Assert.AreSame(segment.Array!, data);
Assert.AreEqual(segment.Offset, 128);
Assert.AreEqual(segment.Count, 128);
Assert.IsTrue(memoryOfBytes.Equals(memoryBack));
Span<byte> span1 = memoryOfBytes.Span;
Span<byte> span2 = memoryBack.Span;
Assert.IsTrue(span1 == span2);
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromMemoryManager_CastFromByte()
{
// This test is just like the ones above, but this time we're casting a memory
// that wraps a custom memory manager and not an array. This is done to ensure
// the casting logic works correctly in all cases, as it'll use a different
// memory manager internally (a memory can wrap a string, an array or a manager).
Memory<byte> memoryOfBytes = new ArrayMemoryManager<byte>(128);
Memory<float> memoryOfFloats = memoryOfBytes.Cast<byte, float>();
Assert.AreEqual(memoryOfFloats.Length, 128 / sizeof(float));
Span<byte> spanOfBytes = memoryOfBytes.Span;
Span<float> spanOfFloats = memoryOfFloats.Span;
Assert.AreEqual(memoryOfFloats.Length, spanOfFloats.Length);
Assert.IsTrue(Unsafe.AreSame(
ref spanOfBytes[0],
ref Unsafe.As<float, byte>(ref spanOfFloats[0])));
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromMemoryManager_CastToByte()
{
// Same as above, but with inverted types
Memory<float> memoryOfFloats = new ArrayMemoryManager<float>(128);
Memory<byte> memoryOfBytes = memoryOfFloats.Cast<float, byte>();
Assert.AreEqual(memoryOfBytes.Length, 128 * sizeof(float));
Span<float> spanOfFloats = memoryOfFloats.Span;
Span<byte> spanOfBytes = memoryOfBytes.Span;
Assert.AreEqual(memoryOfBytes.Length, spanOfBytes.Length);
Assert.IsTrue(Unsafe.AreSame(
ref spanOfFloats[0],
ref Unsafe.As<byte, float>(ref spanOfBytes[0])));
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromMemoryManager_CastToShort()
{
// Same as above, but with types different in size than 1, just in case
Memory<float> memoryOfFloats = new ArrayMemoryManager<float>(128);
Memory<short> memoryOfShorts = memoryOfFloats.Cast<float, short>();
Assert.AreEqual(memoryOfShorts.Length, 128 * sizeof(float) / sizeof(short));
Span<float> spanOfFloats = memoryOfFloats.Span;
Span<short> spanOfShorts = memoryOfShorts.Span;
Assert.AreEqual(memoryOfShorts.Length, spanOfShorts.Length);
Assert.IsTrue(Unsafe.AreSame(
ref spanOfFloats[0],
ref Unsafe.As<short, float>(ref spanOfShorts[0])));
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromMemoryManager_CastFromByteAndBack()
{
// Equivalent to the one with an array, but with a memory manager
var data = new ArrayMemoryManager<byte>(128);
Memory<byte> memoryOfBytes = data;
Memory<float> memoryOfFloats = memoryOfBytes.Cast<byte, float>();
Memory<byte> memoryBack = memoryOfFloats.Cast<float, byte>();
Assert.AreEqual(memoryOfBytes.Length, memoryBack.Length);
// Here we expect to get back the original memory manager, due to the same optimization we
// checked for when using an array. We need to check they're the same, and the other parameters.
Assert.IsTrue(MemoryMarshal.TryGetMemoryManager<byte, ArrayMemoryManager<byte>>(memoryBack, out var manager, out var start, out var length));
Assert.AreSame(manager!, data);
Assert.AreEqual(start, 0);
Assert.AreEqual(length, 128);
Assert.IsTrue(memoryOfBytes.Equals(memoryBack));
Span<byte> span1 = memoryOfBytes.Span;
Span<byte> span2 = memoryBack.Span;
Assert.IsTrue(span1 == span2);
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromMemoryManager_CastFromByte_WithSlice()
{
// Same as the ones with an array, but with an extra slice
Memory<byte> memoryOfBytes = new ArrayMemoryManager<byte>(512).Memory.Slice(128, 128);
Memory<float> memoryOfFloats = memoryOfBytes.Cast<byte, float>();
Assert.AreEqual(memoryOfFloats.Length, 128 / sizeof(float));
Span<byte> spanOfBytes = memoryOfBytes.Span;
Span<float> spanOfFloats = memoryOfFloats.Span;
Assert.AreEqual(memoryOfFloats.Length, spanOfFloats.Length);
Assert.IsTrue(Unsafe.AreSame(
ref spanOfBytes[0],
ref Unsafe.As<float, byte>(ref spanOfFloats[0])));
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromMemoryManager_CastToByte_WithSlice()
{
// Same as above, but with inverted types
Memory<float> memoryOfFloats = new ArrayMemoryManager<float>(512).Memory.Slice(128, 128);
Memory<byte> memoryOfBytes = memoryOfFloats.Cast<float, byte>();
Assert.AreEqual(memoryOfBytes.Length, 128 * sizeof(float));
Span<float> spanOfFloats = memoryOfFloats.Span;
Span<byte> spanOfBytes = memoryOfBytes.Span;
Assert.AreEqual(memoryOfBytes.Length, spanOfBytes.Length);
Assert.IsTrue(Unsafe.AreSame(
ref spanOfFloats[0],
ref Unsafe.As<byte, float>(ref spanOfBytes[0])));
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromMemoryManager_CastToShort_WithSlice()
{
// Same as above but with different types
Memory<float> memoryOfFloats = new ArrayMemoryManager<float>(512).Memory.Slice(128, 128);
Memory<short> memoryOfShorts = memoryOfFloats.Cast<float, short>();
Assert.AreEqual(memoryOfShorts.Length, 128 * sizeof(float) / sizeof(short));
Span<float> spanOfFloats = memoryOfFloats.Span;
Span<short> spanOfShorts = memoryOfShorts.Span;
Assert.AreEqual(memoryOfShorts.Length, spanOfShorts.Length);
Assert.IsTrue(Unsafe.AreSame(
ref spanOfFloats[0],
ref Unsafe.As<short, float>(ref spanOfShorts[0])));
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromMemoryManager_CastFromByteAndBack_WithSlice()
{
// Just like the one above, but with the slice
var data = new ArrayMemoryManager<byte>(512);
Memory<byte> memoryOfBytes = data.Memory.Slice(128, 128);
Memory<float> memoryOfFloats = memoryOfBytes.Cast<byte, float>();
Memory<byte> memoryBack = memoryOfFloats.Cast<float, byte>();
Assert.AreEqual(memoryOfBytes.Length, memoryBack.Length);
// Here we also need to validate that the offset was maintained
Assert.IsTrue(MemoryMarshal.TryGetMemoryManager<byte, ArrayMemoryManager<byte>>(memoryBack, out var manager, out var start, out var length));
Assert.AreSame(manager!, data);
Assert.AreEqual(start, 128);
Assert.AreEqual(length, 128);
Assert.IsTrue(memoryOfBytes.Equals(memoryBack));
Span<byte> span1 = memoryOfBytes.Span;
Span<byte> span2 = memoryBack.Span;
Assert.IsTrue(span1 == span2);
}
[TestCategory("MemoryExtensions")]
[TestMethod]
[DataRow(64, 0, 0)]
[DataRow(64, 4, 0)]
[DataRow(64, 0, 4)]
[DataRow(64, 4, 4)]
[DataRow(64, 4, 0)]
[DataRow(256, 16, 0)]
[DataRow(256, 4, 16)]
[DataRow(256, 64, 0)]
[DataRow(256, 64, 8)]
public unsafe void Test_MemoryExtensions_FromArray_CastFromByte_Pin(int size, int preOffset, int postOffset)
{
// Here we need to validate that pinning works correctly in a number of cases. First we allocate
// an array of the requested size, then get a memory after slicing to a target position, then cast
// and then slice again. We do so to ensure that pinning correctly tracks the correct index with
// respect to the original array through a number of internal offsets. As in, when pinning the
// final memory, our internal custom memory manager should be able to pin the item in the original
// array at offset preOffset + (postOffset * sizeof(float)), accounting for the cast as well.
var data = new byte[size];
Memory<byte> memoryOfBytes = data.AsMemory(preOffset);
Memory<float> memoryOfFloats = memoryOfBytes.Cast<byte, float>().Slice(postOffset);
using var handle = memoryOfFloats.Pin();
void* p1 = handle.Pointer;
void* p2 = Unsafe.AsPointer(ref data[preOffset + (postOffset * sizeof(float))]);
Assert.IsTrue(p1 == p2);
}
[TestCategory("MemoryExtensions")]
[TestMethod]
[DataRow(64, 0, 0)]
[DataRow(64, 4, 0)]
[DataRow(64, 0, 4)]
[DataRow(64, 4, 4)]
[DataRow(64, 4, 0)]
[DataRow(256, 16, 0)]
[DataRow(256, 4, 16)]
[DataRow(256, 64, 0)]
[DataRow(256, 64, 8)]
public unsafe void Test_MemoryExtensions_FromMemoryManager_CastFromByte_Pin(int size, int preOffset, int postOffset)
{
// Just like the test above, but this type the initial memory wraps a memory manager
var data = new ArrayMemoryManager<byte>(size);
Memory<byte> memoryOfBytes = data.Memory.Slice(preOffset);
Memory<float> memoryOfFloats = memoryOfBytes.Cast<byte, float>().Slice(postOffset);
using var handle = memoryOfFloats.Pin();
void* p1 = handle.Pointer;
void* p2 = Unsafe.AsPointer(ref data.GetSpan()[preOffset + (postOffset * sizeof(float))]);
Assert.IsTrue(p1 == p2);
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_FromString_CastFromByteAndBack()
{
// This is the same as the tests above, but here we're testing the
// other remaining case, that is when a memory is wrapping a string.
var data = new string('a', 128);
Memory<char> memoryOfChars = MemoryMarshal.AsMemory(data.AsMemory());
Memory<float> memoryOfFloats = memoryOfChars.Cast<char, float>();
Memory<char> memoryBack = memoryOfFloats.Cast<float, char>();
Assert.AreEqual(memoryOfChars.Length, memoryBack.Length);
// Get the original string back (to validate the optimization too) and check the params
Assert.IsTrue(MemoryMarshal.TryGetString(memoryOfChars, out var text, out int start, out int length));
Assert.AreSame(text!, data);
Assert.AreEqual(start, 0);
Assert.AreEqual(length, data.Length);
Assert.IsTrue(memoryOfChars.Equals(memoryBack));
Span<char> span1 = memoryOfChars.Span;
Span<char> span2 = memoryBack.Span;
Assert.IsTrue(span1 == span2);
}
[TestCategory("MemoryExtensions")]
[TestMethod]
[DataRow(64, 0, 0)]
[DataRow(64, 4, 0)]
[DataRow(64, 0, 4)]
[DataRow(64, 4, 4)]
[DataRow(64, 4, 0)]
[DataRow(256, 16, 0)]
[DataRow(256, 4, 16)]
[DataRow(256, 64, 0)]
[DataRow(256, 64, 8)]
public unsafe void Test_MemoryExtensions_FromString_CastAndPin(int size, int preOffset, int postOffset)
{
// Same test as before to validate pinning, but starting from a string
var data = new string('a', size);
Memory<char> memoryOfChars = MemoryMarshal.AsMemory(data.AsMemory()).Slice(preOffset);
Memory<byte> memoryOfBytes = memoryOfChars.Cast<char, byte>().Slice(postOffset);
using (var handle1 = memoryOfBytes.Pin())
{
void* p1 = handle1.Pointer;
void* p2 = Unsafe.AsPointer(ref data.DangerousGetReferenceAt(preOffset + (postOffset * sizeof(byte) / sizeof(char))));
Assert.IsTrue(p1 == p2);
}
// Here we also add an extra test just like the one above, but casting to a type
// that is bigger in byte size than char. Just to double check the casting logic.
Memory<int> memoryOfInts = memoryOfChars.Cast<char, int>().Slice(postOffset);
using (var handle2 = memoryOfInts.Pin())
{
void* p3 = handle2.Pointer;
void* p4 = Unsafe.AsPointer(ref data.DangerousGetReferenceAt(preOffset + (postOffset * sizeof(int) / sizeof(char))));
Assert.IsTrue(p3 == p4);
}
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_EmptyMemoryStream()
{
Memory<byte> memory = default;
// Creating a stream from a default memory is valid, it's just empty
Stream stream = memory.AsStream();
Assert.IsNotNull(stream);
@ -37,5 +593,43 @@ namespace UnitTests.HighPerformance.Extensions
Assert.AreEqual(stream.Length, memory.Length);
Assert.IsTrue(stream.CanWrite);
}
private sealed class ArrayMemoryManager<T> : MemoryManager<T>
where T : unmanaged
{
private readonly T[] array;
public ArrayMemoryManager(int size)
{
this.array = new T[size];
}
public override Span<T> GetSpan()
{
return this.array;
}
public override unsafe MemoryHandle Pin(int elementIndex = 0)
{
GCHandle handle = GCHandle.Alloc(this.array, GCHandleType.Pinned);
ref T r0 = ref this.array[elementIndex];
void* p = Unsafe.AsPointer(ref r0);
return new MemoryHandle(p, handle);
}
public override void Unpin()
{
}
protected override void Dispose(bool disposing)
{
}
public static implicit operator Memory<T>(ArrayMemoryManager<T> memoryManager)
{
return memoryManager.Memory;
}
}
}
}

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

@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using Microsoft.Toolkit.Mvvm.Input;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -124,26 +126,57 @@ namespace UnitTests.Mvvm
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
// We need to test the cancellation support here, so we use the overload with an input
// parameter, which is a cancellation token. The token is the one that is internally managed
// by the AsyncRelayCommand instance, and canceled when using IAsyncRelayCommand.Cancel().
var command = new AsyncRelayCommand(token => tcs.Task);
List<PropertyChangedEventArgs> args = new List<PropertyChangedEventArgs>();
command.PropertyChanged += (s, e) => args.Add(e);
// We have no canExecute parameter, so the command can always be invoked
Assert.IsTrue(command.CanExecute(null));
Assert.IsTrue(command.CanExecute(new object()));
// The command isn't running, so it can't be canceled yet
Assert.IsFalse(command.CanBeCanceled);
Assert.IsFalse(command.IsCancellationRequested);
// Start the command, which will return the token from our task completion source.
// We can use that to easily keep the command running while we do our tests, and then
// stop the processing by completing the source when we need (see below).
command.Execute(null);
// The command is running, so it can be canceled, as we used the token overload
Assert.IsTrue(command.CanBeCanceled);
Assert.IsFalse(command.IsCancellationRequested);
command.Execute(null);
Assert.IsFalse(command.IsCancellationRequested);
// Validate the various event args for all the properties that were updated when executing the command
Assert.AreEqual(args.Count, 4);
Assert.AreEqual(args[0].PropertyName, nameof(IAsyncRelayCommand.IsCancellationRequested));
Assert.AreEqual(args[1].PropertyName, nameof(IAsyncRelayCommand.ExecutionTask));
Assert.AreEqual(args[2].PropertyName, nameof(IAsyncRelayCommand.IsRunning));
Assert.AreEqual(args[3].PropertyName, nameof(IAsyncRelayCommand.CanBeCanceled));
command.Cancel();
// Verify that these two properties raised notifications correctly when canceling the command too.
// We need to ensure all command properties support notifications so that users can bind to them.
Assert.AreEqual(args.Count, 6);
Assert.AreEqual(args[4].PropertyName, nameof(IAsyncRelayCommand.IsCancellationRequested));
Assert.AreEqual(args[5].PropertyName, nameof(IAsyncRelayCommand.CanBeCanceled));
Assert.IsTrue(command.IsCancellationRequested);
// Complete the source, which will mark the command as completed too (as it returned the same task)
tcs.SetResult(null);
await command.ExecutionTask!;
// Verify that the command can no longer be canceled, and that the cancellation is
// instead still true, as that's reset when executing a command and not on completion.
Assert.IsFalse(command.CanBeCanceled);
Assert.IsTrue(command.IsCancellationRequested);
}
}

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

@ -19,16 +19,28 @@ namespace UnitTests.Mvvm
public void Test_ObservableValidator_HasErrors()
{
var model = new Person();
var args = new List<PropertyChangedEventArgs>();
model.PropertyChanged += (s, e) => args.Add(e);
Assert.IsFalse(model.HasErrors);
model.Name = "No";
// Verify that errors were correctly reported as changed, and that all the relevant
// properties were broadcast as well (both the changed property and HasErrors). We need
// this last one to raise notifications too so that users can bind to that in the UI.
Assert.IsTrue(model.HasErrors);
Assert.AreEqual(args.Count, 2);
Assert.AreEqual(args[0].PropertyName, nameof(Person.Name));
Assert.AreEqual(args[1].PropertyName, nameof(INotifyDataErrorInfo.HasErrors));
model.Name = "Valid";
Assert.IsFalse(model.HasErrors);
Assert.AreEqual(args.Count, 4);
Assert.AreEqual(args[2].PropertyName, nameof(Person.Name));
Assert.AreEqual(args[3].PropertyName, nameof(INotifyDataErrorInfo.HasErrors));
}
[TestCategory("Mvvm")]
@ -119,7 +131,6 @@ namespace UnitTests.Mvvm
[TestCategory("Mvvm")]
[TestMethod]
[DataRow(null, false)]
[DataRow("", false)]
[DataRow("No", false)]
[DataRow("This text is really, really too long for the target property", false)]
@ -142,6 +153,60 @@ namespace UnitTests.Mvvm
}
}
[TestCategory("Mvvm")]
[TestMethod]
public void Test_ObservableValidator_TrySetProperty()
{
var model = new Person();
var events = new List<DataErrorsChangedEventArgs>();
model.ErrorsChanged += (s, e) => events.Add(e);
// Set a correct value, this should update the property
Assert.IsTrue(model.TrySetName("Hello", out var errors));
Assert.IsTrue(errors.Count == 0);
Assert.IsTrue(events.Count == 0);
Assert.AreEqual(model.Name, "Hello");
Assert.IsFalse(model.HasErrors);
// Invalid value #1, this should be ignored
Assert.IsFalse(model.TrySetName(null, out errors));
Assert.IsTrue(errors.Count > 0);
Assert.IsTrue(events.Count == 0);
Assert.AreEqual(model.Name, "Hello");
Assert.IsFalse(model.HasErrors);
// Invalid value #2, same as above
Assert.IsFalse(model.TrySetName("This string is too long for the target property in this model and should fail", out errors));
Assert.IsTrue(errors.Count > 0);
Assert.IsTrue(events.Count == 0);
Assert.AreEqual(model.Name, "Hello");
Assert.IsFalse(model.HasErrors);
// Correct value, this should update the property
Assert.IsTrue(model.TrySetName("Hello world", out errors));
Assert.IsTrue(errors.Count == 0);
Assert.IsTrue(events.Count == 0);
Assert.AreEqual(model.Name, "Hello world");
Assert.IsFalse(model.HasErrors);
// Actually set an invalid value to show some errors
model.Name = "No";
// Errors should now be present
Assert.IsTrue(model.HasErrors);
Assert.IsTrue(events.Count == 1);
Assert.IsTrue(model.GetErrors(nameof(Person.Name)).Cast<ValidationResult>().Any());
Assert.IsTrue(model.HasErrors);
// Trying to set a correct property should clear the errors
Assert.IsTrue(model.TrySetName("This is fine", out errors));
Assert.IsTrue(errors.Count == 0);
Assert.IsTrue(events.Count == 2);
Assert.IsFalse(model.HasErrors);
Assert.AreEqual(model.Name, "This is fine");
}
public class Person : ObservableValidator
{
private string name;
@ -155,6 +220,11 @@ namespace UnitTests.Mvvm
set => SetProperty(ref this.name, value, true);
}
public bool TrySetName(string value, out IReadOnlyCollection<ValidationResult> errors)
{
return TrySetProperty(ref name, value, out errors, nameof(Name));
}
private int age;
[Range(0, 100)]

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

@ -3,38 +3,37 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;
using System.Linq;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;
using Microsoft.Toolkit.Uwp.Helpers;
using Windows.ApplicationModel.Core;
using Windows.UI.Core;
using Windows.System;
using Microsoft.Toolkit.Uwp.Extensions;
namespace UnitTests.Helpers
namespace UnitTests.Extensions
{
[TestClass]
[Ignore("Ignored until issue on .Net Native is fixed. These are working.")]
public class Test_DispatcherQueueHelper
public class Test_DispatcherQueueExtensions
{
private const int TIME_OUT = 5000;
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[UITestMethod]
[ExpectedException(typeof(ArgumentNullException))]
[ExpectedException(typeof(NullReferenceException))]
public void Test_DispatcherQueueHelper_Action_Null()
{
DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), default(Action));
DispatcherQueue.GetForCurrentThread().EnqueueAsync(default(Action)!);
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[UITestMethod]
public void Test_DispatcherQueueHelper_Action_Ok_UIThread()
{
DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), () =>
DispatcherQueue.GetForCurrentThread().EnqueueAsync(() =>
{
var textBlock = new TextBlock { Text = nameof(Test_DispatcherQueueHelper_Action_Ok_UIThread) };
@ -42,7 +41,7 @@ namespace UnitTests.Helpers
}).Wait();
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[TestMethod]
public async Task Test_DispatcherQueueHelper_Action_Ok_NonUIThread()
{
@ -55,7 +54,7 @@ namespace UnitTests.Helpers
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
await Task.Run(async () =>
{
await DispatcherQueueHelper.ExecuteOnUIThreadAsync(dispatcherQueue, () =>
await dispatcherQueue.EnqueueAsync(() =>
{
var textBlock = new TextBlock { Text = nameof(Test_DispatcherQueueHelper_Action_Ok_NonUIThread) };
@ -73,11 +72,11 @@ namespace UnitTests.Helpers
await taskSource.Task;
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[UITestMethod]
public void Test_DispatcherQueueHelper_Action_Exception()
{
var task = DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), () =>
var task = DispatcherQueue.GetForCurrentThread().EnqueueAsync(() =>
{
throw new ArgumentException(nameof(this.Test_DispatcherQueueHelper_Action_Exception));
});
@ -88,19 +87,19 @@ namespace UnitTests.Helpers
Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException));
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[UITestMethod]
[ExpectedException(typeof(ArgumentNullException))]
[ExpectedException(typeof(NullReferenceException))]
public void Test_DispatcherQueueHelper_FuncOfT_Null()
{
DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), default(Func<int>));
DispatcherQueue.GetForCurrentThread().EnqueueAsync(default(Func<int>)!);
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[UITestMethod]
public void Test_DispatcherQueueHelper_FuncOfT_Ok_UIThread()
{
var textBlock = DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), () =>
var textBlock = DispatcherQueue.GetForCurrentThread().EnqueueAsync(() =>
{
return new TextBlock { Text = nameof(Test_DispatcherQueueHelper_FuncOfT_Ok_UIThread) };
}).Result;
@ -108,7 +107,7 @@ namespace UnitTests.Helpers
Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherQueueHelper_FuncOfT_Ok_UIThread));
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[TestMethod]
public async Task Test_DispatcherQueueHelper_FuncOfT_Ok_NonUIThread()
{
@ -121,11 +120,11 @@ namespace UnitTests.Helpers
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
await Task.Run(async () =>
{
var textBlock = await DispatcherQueueHelper.ExecuteOnUIThreadAsync(dispatcherQueue, () =>
var textBlock = await dispatcherQueue.EnqueueAsync(() =>
{
return new TextBlock { Text = nameof(Test_DispatcherQueueHelper_FuncOfT_Ok_NonUIThread) };
});
await DispatcherQueueHelper.ExecuteOnUIThreadAsync(dispatcherQueue, () =>
await dispatcherQueue.EnqueueAsync(() =>
{
Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherQueueHelper_FuncOfT_Ok_NonUIThread));
taskSource.SetResult(null);
@ -140,11 +139,11 @@ namespace UnitTests.Helpers
await taskSource.Task;
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[UITestMethod]
public void Test_DispatcherQueueHelper_FuncOfT_Exception()
{
var task = DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), new Func<int>(() =>
var task = DispatcherQueue.GetForCurrentThread().EnqueueAsync(new Func<int>(() =>
{
throw new ArgumentException(nameof(this.Test_DispatcherQueueHelper_FuncOfT_Exception));
}));
@ -155,20 +154,19 @@ namespace UnitTests.Helpers
Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException));
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[UITestMethod]
[ExpectedException(typeof(ArgumentNullException))]
[SuppressMessage("Style", "IDE0034", Justification = "Explicit overload for clarity")]
[ExpectedException(typeof(NullReferenceException))]
public void Test_DispatcherQueueHelper_FuncOfTask_Null()
{
DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), default(Func<Task>));
DispatcherQueue.GetForCurrentThread().EnqueueAsync(default(Func<Task>)!);
}
[TestCategory("Helpers")]
[UITestMethod]
public void Test_DispatcherQueueHelper_FuncOfTask_Ok_UIThread()
{
DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), () =>
DispatcherQueue.GetForCurrentThread().EnqueueAsync(() =>
{
var textBlock = new TextBlock { Text = nameof(Test_DispatcherQueueHelper_FuncOfTask_Ok_UIThread) };
@ -178,7 +176,7 @@ namespace UnitTests.Helpers
}).Wait();
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[TestMethod]
public async Task Test_DispatcherQueueHelper_FuncOfTask_Ok_NonUIThread()
{
@ -188,7 +186,7 @@ namespace UnitTests.Helpers
{
try
{
await DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), async () =>
await DispatcherQueue.GetForCurrentThread().EnqueueAsync(async () =>
{
await Task.Yield();
@ -207,11 +205,11 @@ namespace UnitTests.Helpers
await taskSource.Task;
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[UITestMethod]
public void Test_DispatcherQueueHelper_FuncOfTask_Exception()
{
var task = DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), new Func<Task>(() =>
var task = DispatcherQueue.GetForCurrentThread().EnqueueAsync(new Func<Task>(() =>
{
throw new ArgumentException(nameof(this.Test_DispatcherQueueHelper_FuncOfTask_Exception));
}));
@ -222,19 +220,19 @@ namespace UnitTests.Helpers
Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException));
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[UITestMethod]
[ExpectedException(typeof(ArgumentNullException))]
[ExpectedException(typeof(NullReferenceException))]
public void Test_DispatcherQueueHelper_FuncOfTaskOfT_Null()
{
DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), default(Func<Task<int>>));
DispatcherQueue.GetForCurrentThread().EnqueueAsync(default(Func<Task<int>>)!);
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[UITestMethod]
public void Test_DispatcherQueueHelper_FuncOfTaskOfT_Ok_UIThread()
{
DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), () =>
DispatcherQueue.GetForCurrentThread().EnqueueAsync(() =>
{
var textBlock = new TextBlock { Text = nameof(Test_DispatcherQueueHelper_FuncOfTaskOfT_Ok_UIThread) };
@ -244,7 +242,7 @@ namespace UnitTests.Helpers
}).Wait();
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[TestMethod]
public async Task Test_DispatcherQueueHelper_FuncOfTaskOfT_Ok_NonUIThread()
{
@ -254,7 +252,7 @@ namespace UnitTests.Helpers
{
try
{
await DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), async () =>
await DispatcherQueue.GetForCurrentThread().EnqueueAsync(async () =>
{
await Task.Yield();
@ -275,11 +273,11 @@ namespace UnitTests.Helpers
await taskSource.Task;
}
[TestCategory("Helpers")]
[TestCategory("DispatcherQueueExtensions")]
[UITestMethod]
public void Test_DispatcherQueueHelper_FuncOfTaskOfT_Exception()
{
var task = DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), new Func<Task<int>>(() =>
var task = DispatcherQueue.GetForCurrentThread().EnqueueAsync(new Func<Task<int>>(() =>
{
throw new ArgumentException(nameof(this.Test_DispatcherQueueHelper_FuncOfTaskOfT_Exception));
}));

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

@ -13,7 +13,7 @@ using Microsoft.Toolkit.Uwp.Helpers;
namespace UnitTests.Helpers
{
#pragma warning disable CS0612 // Type or member is obsolete
#pragma warning disable CS0612, CS0618 // Type or member is obsolete
[TestClass]
public class Test_DispatcherHelper
{

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

@ -16,6 +16,7 @@ using Windows.Globalization;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Markup;
using Microsoft.Toolkit.Uwp.Extensions;
namespace UnitTests.UI.Controls
{
@ -78,7 +79,7 @@ namespace UnitTests.UI.Controls
[TestMethod]
public async Task Test_TextToolbar_Localization_Override_Fr()
{
await CoreApplication.MainView.DispatcherQueue.ExecuteOnUIThreadAsync(async () =>
await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(async () =>
{
// Just double-check we've got the right environment setup in our tests.
CollectionAssert.AreEquivalent(new string[] { "en-US", "fr" }, ApplicationLanguages.ManifestLanguages.ToArray(), "Missing locales for test");

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