Merge branch 'master' into jamesmcroft/3506-carousel-automation
|
@ -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<ILogger, Logger>()
|
||||
/// .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<ILogger>().Log("Hello world!");
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public sealed class Ioc : IServiceProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default <see cref="Ioc"/> instance.
|
||||
/// </summary>
|
||||
public static Ioc Default { get; } = new Ioc();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="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->'"%(Identity)"')" />
|
||||
<Exec Command="for %%f in (@(GeneratedCSFiles->'"%(Identity)"')) do echo #pragma warning disable > %%f.temp && type %%f >> %%f.temp && move /y %%f.temp %%f > NUL" />
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Required workaround for ProjectReference inclusion of the Controls package
|
||||
The UWP project system is including the Controls resources in the pri file because
|
||||
it doesn't know it'll be an independent package later during packing.
|
||||
Therefore, we need to remove these extra resources in the PRI pipeline so the
|
||||
Markdown pri file is properly generated and doesn't include duplicate references to Control resources.
|
||||
-->
|
||||
<Target Name="RemoveUnwantedPri" AfterTargets="GetPackagingOutputs">
|
||||
<!--<Message Text="Files Before: @(PackagingOutputs)" Importance="high" />-->
|
||||
<ItemGroup>
|
||||
<PackagingOutputs Remove="@(PackagingOutputs)" Condition="'%(PackagingOutputs.Filename)%(PackagingOutputs.Extension)' == 'Microsoft.Toolkit.Uwp.UI.Controls.pri'" />
|
||||
<PackagingOutputs Remove="@(PackagingOutputs)" Condition="$([System.String]::new('%(PackagingOutputs.TargetPath)').StartsWith('Microsoft.Toolkit.Uwp.UI.Controls\'))" />
|
||||
</ItemGroup>
|
||||
<!--<Message Text="Files After: @(PackagingOutputs)" Importance="high" />-->
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<Application
|
||||
x:Class="SmokeTest.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:SmokeTest">
|
||||
|
||||
</Application>
|
|
@ -0,0 +1,106 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
|
||||
namespace SmokeTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public sealed partial class App : Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.Suspending += OnSuspending;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched normally by the end user. Other entry points
|
||||
/// will be used such as when the application is launched to open a specific file.
|
||||
/// </summary>
|
||||
/// <param name="e">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs e)
|
||||
{
|
||||
Frame rootFrame = Window.Current.Content as Frame;
|
||||
|
||||
// Do not repeat app initialization when the Window already has content,
|
||||
// just ensure that the window is active
|
||||
if (rootFrame == null)
|
||||
{
|
||||
// Create a Frame to act as the navigation context and navigate to the first page
|
||||
rootFrame = new Frame();
|
||||
|
||||
rootFrame.NavigationFailed += OnNavigationFailed;
|
||||
|
||||
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
|
||||
{
|
||||
// TODO: Load state from previously suspended application
|
||||
}
|
||||
|
||||
// Place the frame in the current Window
|
||||
Window.Current.Content = rootFrame;
|
||||
}
|
||||
|
||||
if (e.PrelaunchActivated == false)
|
||||
{
|
||||
if (rootFrame.Content == null)
|
||||
{
|
||||
// When the navigation stack isn't restored navigate to the first page,
|
||||
// configuring the new page by passing required information as a navigation
|
||||
// parameter
|
||||
rootFrame.Navigate(typeof(MainPage), e.Arguments);
|
||||
}
|
||||
|
||||
// Ensure the current window is active
|
||||
Window.Current.Activate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when Navigation to a certain page fails
|
||||
/// </summary>
|
||||
/// <param name="sender">The Frame which failed navigation</param>
|
||||
/// <param name="e">Details about the navigation failure</param>
|
||||
private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
{
|
||||
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when application execution is being suspended. Application state is saved
|
||||
/// without knowing whether the application will be terminated or resumed with the contents
|
||||
/// of memory still intact.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the suspend request.</param>
|
||||
/// <param name="e">Details about the suspend request.</param>
|
||||
private void OnSuspending(object sender, SuspendingEventArgs e)
|
||||
{
|
||||
var deferral = e.SuspendingOperation.GetDeferral();
|
||||
|
||||
// TODO: Save application state and stop any background activity
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
}
|
После Ширина: | Высота: | Размер: 1.4 KiB |
После Ширина: | Высота: | Размер: 7.5 KiB |
После Ширина: | Высота: | Размер: 2.9 KiB |
После Ширина: | Высота: | Размер: 1.6 KiB |
После Ширина: | Высота: | Размер: 1.2 KiB |
После Ширина: | Высота: | Размер: 1.4 KiB |
После Ширина: | Высота: | Размер: 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 < 50" />
|
||||
<MenuFlyoutItem x:Name="rankHigh" Text="Rank > 50" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem x:Name="heightLow" Text="Height < 8000ft" />
|
||||
<MenuFlyoutItem x:Name="heightHigh" Text="Height > 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>
|
|
@ -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)' < '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>
|
|
@ -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>
|
|
@ -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");
|
||||
|
|