Merge branch 'main' into winui

# Conflicts:
#	CommunityToolkit.WinUI.SampleApp/SamplePages/Implicit Animations/ImplicitAnimationsPage.xaml.cs
#	version.json
This commit is contained in:
Alexandre Zollinger Chohfi 2021-10-12 11:23:09 -07:00
Родитель be7f299f61 84adf91186
Коммит 862b43a39c
16 изменённых файлов: 253 добавлений и 54 удалений

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

@ -42,10 +42,11 @@ namespace CommunityToolkit.Mvvm.SourceGenerators
return;
}
// Validate the language version
if (context.ParseOptions is not CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp9 })
// Validate the language version (this needs at least C# 8.0 due to static local functions being used).
// If a lower C# version is set, just skip the execution silently. The fallback path will be used just fine.
if (context.ParseOptions is not CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp8 })
{
context.ReportDiagnostic(Diagnostic.Create(UnsupportedCSharpLanguageVersionError, null));
return;
}
// Get the symbol for the required attributes

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

@ -39,10 +39,10 @@ namespace CommunityToolkit.Mvvm.SourceGenerators
return;
}
// Validate the language version
if (context.ParseOptions is not CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp9 })
// Like in the ObservableValidator.ValidateALlProperties generator, execution is skipped if C# >= 8.0 isn't available
if (context.ParseOptions is not CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp8 })
{
context.ReportDiagnostic(Diagnostic.Create(UnsupportedCSharpLanguageVersionError, null));
return;
}
// Get the symbol for the IRecipient<T> interface type

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

@ -488,6 +488,21 @@ namespace CommunityToolkit.Mvvm.ComponentModel
// Fallback method to create the delegate with a compiled LINQ expression
static Action<object> GetValidationActionFallback(Type type)
{
// Get the collection of all properties to validate
(string Name, MethodInfo GetMethod)[] validatableProperties = (
from property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
where property.GetIndexParameters().Length == 0 &&
property.GetCustomAttributes<ValidationAttribute>(true).Any()
let getMethod = property.GetMethod
where getMethod is not null
select (property.Name, getMethod)).ToArray();
// Short path if there are no properties to validate
if (validatableProperties.Length == 0)
{
return static _ => { };
}
// MyViewModel inst0 = (MyViewModel)arg0;
ParameterExpression arg0 = Expression.Parameter(typeof(object));
UnaryExpression inst0 = Expression.Convert(arg0, type);
@ -513,14 +528,10 @@ namespace CommunityToolkit.Mvvm.ComponentModel
// ObservableValidator externally, but that is fine because IL doesn't really have
// a concept of member visibility, that's purely a C# build-time feature.
BlockExpression body = Expression.Block(
from property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
where property.GetIndexParameters().Length == 0 &&
property.GetCustomAttributes<ValidationAttribute>(true).Any()
let getter = property.GetMethod
where getter is not null
from property in validatableProperties
select Expression.Call(inst0, validateMethod, new Expression[]
{
Expression.Convert(Expression.Call(inst0, getter), typeof(object)),
Expression.Convert(Expression.Call(inst0, property.GetMethod), typeof(object)),
Expression.Constant(property.Name)
}));

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

@ -153,6 +153,20 @@ namespace CommunityToolkit.Mvvm.Messaging
// The LINQ codegen bloat is not really important for the same reason.
static Action<IMessenger, object, TToken> LoadRegistrationMethodsForTypeFallback(Type recipientType)
{
// Get the collection of validation methods
MethodInfo[] registrationMethods = (
from interfaceType in recipientType.GetInterfaces()
where interfaceType.IsGenericType &&
interfaceType.GetGenericTypeDefinition() == typeof(IRecipient<>)
let messageType = interfaceType.GenericTypeArguments[0]
select MethodInfos.RegisterIRecipient.MakeGenericMethod(messageType, typeof(TToken))).ToArray();
// Short path if there are no message handlers to register
if (registrationMethods.Length == 0)
{
return static (_, _, _) => { };
}
// Input parameters (IMessenger instance, non-generic recipient, token)
ParameterExpression
arg0 = Expression.Parameter(typeof(IMessenger)),
@ -178,11 +192,7 @@ namespace CommunityToolkit.Mvvm.Messaging
// We also add an explicit object conversion to cast the input recipient type to
// the actual specific type, so that the exposed message handlers are accessible.
BlockExpression body = Expression.Block(
from interfaceType in recipientType.GetInterfaces()
where interfaceType.IsGenericType &&
interfaceType.GetGenericTypeDefinition() == typeof(IRecipient<>)
let messageType = interfaceType.GenericTypeArguments[0]
let registrationMethod = MethodInfos.RegisterIRecipient.MakeGenericMethod(messageType, typeof(TToken))
from registrationMethod in registrationMethods
select Expression.Call(registrationMethod, new Expression[]
{
arg0,

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

@ -18,7 +18,7 @@
<Border Grid.Column="0">
<Border.Background>
<media:ImageBlendBrush
Source="/SamplePages/DropShadowPanel/Unicorn.png"
Source="/SamplePages/Shadows/Unicorn.png"
Stretch="@[Unicorn Stretch:Enum:Stretch.None]"
Mode="@[Unicorn Blend Mode:Enum:ImageBlendMode.ColorBurn]"
/>
@ -27,7 +27,7 @@
<Border Grid.Column="1">
<Border.Background>
<media:ImageBlendBrush
Source="/SamplePages/DropShadowPanel/Trex.png"
Source="/SamplePages/Shadows/Trex.png"
Stretch="@[Trex Stretch:Enum:Stretch.None]"
Mode="@[Trex Blend Mode:Enum:ImageBlendMode.Subtract]"
/>

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

@ -5,6 +5,7 @@
using System;
using System.Numerics;
using CommunityToolkit.WinUI.UI;
using CommunityToolkit.WinUI.UI.Animations;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Hosting;
@ -18,6 +19,8 @@ namespace CommunityToolkit.WinUI.SampleApp.SamplePages
{
private Random _random = new Random();
private UIElement _element;
private ImplicitAnimationSet _animationSet;
private bool _areAnimationsToggled;
public ImplicitAnimationsPage()
{
@ -28,6 +31,8 @@ namespace CommunityToolkit.WinUI.SampleApp.SamplePages
public void OnXamlRendered(FrameworkElement control)
{
_element = control.FindChild("Element");
_animationSet = Implicit.GetAnimations(_element);
_areAnimationsToggled = true;
}
private void Load()
@ -60,6 +65,16 @@ namespace CommunityToolkit.WinUI.SampleApp.SamplePages
1);
}
});
SampleController.Current.RegisterNewCommand("Toggle animations", (sender, args) =>
{
if (_element != null)
{
Implicit.SetAnimations(_element, _areAnimationsToggled ? null : _animationSet);
_areAnimationsToggled = !_areAnimationsToggled;
}
});
}
}
}

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

@ -26,7 +26,7 @@
<media:BlurEffect Amount="16"/>
<media:ShadeEffect Color="#FF222222" Intensity="0.2"/>
<media:BlendEffect Mode="Overlay" Source="{media:TileSource Uri=ms-appx:///Assets/BrushAssets/NoiseTexture.png}"/>
<media:BlendEffect Mode="Overlay" Source="{media:ImageSource Uri=ms-appx:///SamplePages/DropShadowPanel/Unicorn.png}"/>
<media:BlendEffect Mode="Overlay" Source="{media:ImageSource Uri=ms-appx:///SamplePages/Shadows/Unicorn.png}"/>
</media:PipelineBrush>
</Border.Background>
</Border>

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

@ -25,7 +25,7 @@
<media:BlurEffect Amount="16"/>
<media:ShadeEffect Color="#FF222222" Intensity="0.2"/>
<media:BlendEffect Mode="Overlay" Source="{media:TileSource Uri=ms-appx:///Assets/BrushAssets/NoiseTexture.png}"/>
<media:BlendEffect Mode="Overlay" Source="{media:ImageSource Uri=ms-appx:///SamplePages/DropShadowPanel/Unicorn.png}"/>
<media:BlendEffect Mode="Overlay" Source="{media:ImageSource Uri=ms-appx:///SamplePages/Shadows/Unicorn.png}"/>
</media:PipelineVisualFactory>
</media:UIElementExtensions.VisualFactory>
</Border>

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

@ -53,7 +53,7 @@ namespace CommunityToolkit.WinUI.UI.Animations
if (collection is null)
{
element.SetValue(ShowAnimationsProperty, collection = new());
element.SetValue(ShowAnimationsProperty, collection = new ImplicitAnimationSet());
}
return collection;
@ -80,7 +80,7 @@ namespace CommunityToolkit.WinUI.UI.Animations
if (collection is null)
{
element.SetValue(HideAnimationsProperty, collection = new());
element.SetValue(HideAnimationsProperty, collection = new ImplicitAnimationSet());
}
return collection;
@ -107,7 +107,7 @@ namespace CommunityToolkit.WinUI.UI.Animations
if (collection is null)
{
element.SetValue(AnimationsProperty, collection = new());
element.SetValue(AnimationsProperty, collection = new ImplicitAnimationSet());
}
return collection;
@ -145,15 +145,21 @@ namespace CommunityToolkit.WinUI.UI.Animations
oldCollection.AnimationsChanged -= OnAnimationsChanged;
}
if (d is UIElement element &&
e.NewValue is ImplicitAnimationSet collection)
if (d is UIElement element)
{
collection.ParentReference = new(element);
collection.AnimationsChanged -= OnAnimationsChanged;
collection.AnimationsChanged += OnAnimationsChanged;
if (e.NewValue is ImplicitAnimationSet collection)
{
collection.ParentReference = new(element);
collection.AnimationsChanged -= OnAnimationsChanged;
collection.AnimationsChanged += OnAnimationsChanged;
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
ElementCompositionPreview.SetImplicitShowAnimation(element, collection.GetCompositionAnimationGroup(element));
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
ElementCompositionPreview.SetImplicitShowAnimation(element, collection.GetCompositionAnimationGroup(element));
}
else
{
ElementCompositionPreview.SetImplicitShowAnimation(element, null);
}
}
}
@ -179,15 +185,21 @@ namespace CommunityToolkit.WinUI.UI.Animations
oldCollection.AnimationsChanged -= OnAnimationsChanged;
}
if (d is UIElement element &&
e.NewValue is ImplicitAnimationSet collection)
if (d is UIElement element)
{
collection.ParentReference = new(element);
collection.AnimationsChanged -= OnAnimationsChanged;
collection.AnimationsChanged += OnAnimationsChanged;
if (e.NewValue is ImplicitAnimationSet collection)
{
collection.ParentReference = new(element);
collection.AnimationsChanged -= OnAnimationsChanged;
collection.AnimationsChanged += OnAnimationsChanged;
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
ElementCompositionPreview.SetImplicitHideAnimation(element, collection.GetCompositionAnimationGroup(element));
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
ElementCompositionPreview.SetImplicitHideAnimation(element, collection.GetCompositionAnimationGroup(element));
}
else
{
ElementCompositionPreview.SetImplicitHideAnimation(element, null);
}
}
}
@ -213,15 +225,21 @@ namespace CommunityToolkit.WinUI.UI.Animations
oldCollection.AnimationsChanged -= OnAnimationsChanged;
}
if (d is UIElement element &&
e.NewValue is ImplicitAnimationSet collection)
if (d is UIElement element)
{
collection.ParentReference = new(element);
collection.AnimationsChanged -= OnAnimationsChanged;
collection.AnimationsChanged += OnAnimationsChanged;
if (e.NewValue is ImplicitAnimationSet collection)
{
collection.ParentReference = new(element);
collection.AnimationsChanged -= OnAnimationsChanged;
collection.AnimationsChanged += OnAnimationsChanged;
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
ElementCompositionPreview.GetElementVisual(element).ImplicitAnimations = collection.GetImplicitAnimationCollection(element);
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
ElementCompositionPreview.GetElementVisual(element).ImplicitAnimations = collection.GetImplicitAnimationCollection(element);
}
else
{
ElementCompositionPreview.GetElementVisual(element).ImplicitAnimations = null;
}
}
}
}

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

@ -62,7 +62,7 @@ namespace CommunityToolkit.WinUI.UI
timer.Tick += Timer_Tick;
// Store/Update function
_debounceInstances.AddOrUpdate(timer, action, (k, v) => v);
_debounceInstances.AddOrUpdate(timer, action, (k, v) => action);
}
// Start the timer to keep track of the last call here.

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

@ -12,7 +12,7 @@ using System.Reflection;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
#pragma warning disable SA1124
#pragma warning disable SA1124, SA1307, SA1401
#nullable enable
@ -371,7 +371,7 @@ namespace UnitTests.Mvvm
[MinLength(5)]
private string? value;
}
public partial class ViewModelWithValidatableGeneratedProperties : ObservableValidator
{
[Required]

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

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -335,6 +336,54 @@ namespace UnitTests.Mvvm
model.Age = -10;
model.ValidateAllProperties();
Assert.IsTrue(model.HasErrors);
Assert.IsTrue(events.Count == 1);
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Age)));
}
[TestCategory("Mvvm")]
[TestMethod]
public void Test_ObservableValidator_ValidateAllProperties_WithFallback()
{
var model = new PersonWithDeferredValidation();
var events = new List<DataErrorsChangedEventArgs>();
MethodInfo[] staticMethods = typeof(ObservableValidator).GetMethods(BindingFlags.Static | BindingFlags.NonPublic);
MethodInfo validationMethod = staticMethods.Single(static m => m.Name.Contains("GetValidationActionFallback"));
Func<Type, Action<object>> validationFunc = (Func<Type, Action<object>>)validationMethod.CreateDelegate(typeof(Func<Type, Action<object>>));
Action<object> validationAction = validationFunc(model.GetType());
model.ErrorsChanged += (s, e) => events.Add(e);
validationAction(model);
Assert.IsTrue(model.HasErrors);
Assert.IsTrue(events.Count == 2);
// Note: we can't use an index here because the order used to return properties
// from reflection APIs is an implementation detail and might change at any time.
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Name)));
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Age)));
events.Clear();
model.Name = "James";
model.Age = 42;
validationAction(model);
Assert.IsFalse(model.HasErrors);
Assert.IsTrue(events.Count == 2);
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Name)));
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Age)));
events.Clear();
model.Age = -10;
validationAction(model);
Assert.IsTrue(model.HasErrors);
Assert.IsTrue(events.Count == 1);
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Age)));
@ -414,6 +463,34 @@ namespace UnitTests.Mvvm
Assert.AreEqual(allErrors[1].ErrorMessage, $"SECOND: {nameof(ValidationWithDisplayName.AnotherRequiredField)}.");
}
// See: https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/4272
[TestCategory("Mvvm")]
[TestMethod]
[DataRow(typeof(MyBase))]
[DataRow(typeof(MyDerived2))]
public void Test_ObservableRecipient_ValidationOnNonValidatableProperties(Type type)
{
MyBase viewmodel = (MyBase)Activator.CreateInstance(type);
viewmodel.ValidateAll();
}
// See: https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/4272
[TestCategory("Mvvm")]
[TestMethod]
[DataRow(typeof(MyBase))]
[DataRow(typeof(MyDerived2))]
public void Test_ObservableRecipient_ValidationOnNonValidatableProperties_WithFallback(Type type)
{
MyBase viewmodel = (MyBase)Activator.CreateInstance(type);
MethodInfo[] staticMethods = typeof(ObservableValidator).GetMethods(BindingFlags.Static | BindingFlags.NonPublic);
MethodInfo validationMethod = staticMethods.Single(static m => m.Name.Contains("GetValidationActionFallback"));
Func<Type, Action<object>> validationFunc = (Func<Type, Action<object>>)validationMethod.CreateDelegate(typeof(Func<Type, Action<object>>));
validationFunc(viewmodel.GetType())(viewmodel);
}
public class Person : ObservableValidator
{
private string name;
@ -631,5 +708,22 @@ namespace UnitTests.Mvvm
set => SetProperty(ref this.anotherRequiredField, value, true);
}
}
public class MyBase : ObservableValidator
{
public int? MyDummyInt { get; set; } = 0;
public void ValidateAll()
{
ValidateAllProperties();
}
}
public class MyDerived2 : MyBase
{
public string Name { get; set; }
public int SomeRandomproperty { get; set; }
}
}
}

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

@ -319,9 +319,9 @@ namespace UnitTests.Mvvm
}
}";
// This is explicitly allowed in C# < 9.0, as it doesn't use any new features
VerifyGeneratedDiagnostics<ObservableValidatorValidateAllPropertiesGenerator>(
CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)),
"MVVMTK0013");
CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)));
}
[TestCategory("Mvvm")]
@ -368,9 +368,9 @@ namespace UnitTests.Mvvm
}
}";
// This is explicitly allowed in C# < 9.0, as it doesn't use any new features
VerifyGeneratedDiagnostics<IMessengerRegisterAllGenerator>(
CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)),
"MVVMTK0013");
CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)));
}
/// <summary>

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

@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Toolkit.Uwp.UI;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading.Tasks;
namespace UnitTests.Extensions
{
[TestClass]
public class Test_DispatcherQueueTimerExtensions
{
[TestCategory("DispatcherQueueTimerExtensions")]
[TestMethod]
public async Task Test_DispatcherQueueTimerExtensions_Debounce()
{
var debounceTimer = App.DispatcherQueue.CreateTimer();
var triggeredCount = 0;
string triggeredValue = null;
var value = "He";
debounceTimer.Debounce(
() =>
{
triggeredCount++;
triggeredValue = value;
},
TimeSpan.FromMilliseconds(60));
var value2 = "Hello";
debounceTimer.Debounce(
() =>
{
triggeredCount++;
triggeredValue = value2;
},
TimeSpan.FromMilliseconds(60));
await Task.Delay(TimeSpan.FromMilliseconds(110));
Assert.AreEqual(false, debounceTimer.IsRunning, "Expected to stop the timer.");
Assert.AreEqual(value2, triggeredValue, "Expected to execute the last action.");
Assert.AreEqual(1, triggeredCount, "Expected to postpone execution.");
}
}
}

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

@ -66,6 +66,7 @@
<Compile Include="Converters\Test_StringFormatConverter.cs" />
<Compile Include="Converters\Test_TypeToObjectConverter.cs" />
<Compile Include="Extensions\Helpers\ObjectWithNullableBoolProperty.cs" />
<Compile Include="Extensions\Test_DispatcherQueueTimerExtensions.cs" />
<Compile Include="Extensions\Test_StringExtensions.cs" />
<Compile Include="Extensions\Test_UIElementExtensions_Coordinates.cs" />
<Compile Include="Extensions\Test_VisualTreeExtensions.cs" />

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

@ -1,5 +1,5 @@
{
"version": "7.0.3-build.{height}",
"version": "7.1.0-build.{height}",
"publicReleaseRefSpec": [
"^refs/heads/main$", // we release out of main
"^refs/heads/dev$", // we release out of dev
@ -20,4 +20,4 @@
"versionIncrement" : "build",
"firstUnstableTag" : "preview"
}
}
}