From ae48a9a13b22df8bd2cecfff923e9a5374f31cdd Mon Sep 17 00:00:00 2001 From: Foaltin Dorin Date: Fri, 12 Nov 2021 15:37:59 +0200 Subject: [PATCH 01/21] Fix DataGrid selection broken after 3 right clicks --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 10c7c16488..95ee73be4e 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -5751,6 +5751,7 @@ namespace Avalonia.Controls return true; } // Unselect everything except the row that was clicked on + _noSelectionChangeCount++; try { UpdateSelectionAndCurrency(columnIndex, slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false); From ffac3eb0278ea1281aea3aa281d910663c6f010d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Dec 2021 14:24:46 +0100 Subject: [PATCH 02/21] Added nullable annotations to Avalonia.Animation. --- src/Avalonia.Animation/Animatable.cs | 16 +++---- src/Avalonia.Animation/Animation.cs | 46 +++++++++++++------ src/Avalonia.Animation/AnimationInstance`1.cs | 33 +++++++------ src/Avalonia.Animation/AnimatorKeyFrame.cs | 20 ++++---- .../Animators/Animator`1.cs | 9 ++-- .../Avalonia.Animation.csproj | 4 ++ src/Avalonia.Animation/Clock.cs | 2 +- src/Avalonia.Animation/Cue.cs | 8 ++-- .../DisposeAnimationInstanceSubject.cs | 8 ++-- src/Avalonia.Animation/Easing/Easing.cs | 4 +- .../Easing/EasingTypeConverter.cs | 4 +- src/Avalonia.Animation/IAnimation.cs | 2 +- src/Avalonia.Animation/IAnimationSetter.cs | 4 +- src/Avalonia.Animation/IAnimator.cs | 4 +- src/Avalonia.Animation/ITransition.cs | 2 +- src/Avalonia.Animation/IterationCount.cs | 2 +- .../IterationCountTypeConverter.cs | 4 +- src/Avalonia.Animation/KeyFrame.cs | 4 +- .../KeySplineTypeConverter.cs | 4 +- src/Avalonia.Animation/Transition.cs | 21 +++++++-- src/Avalonia.Animation/TransitionInstance.cs | 6 +-- .../Animation/Animators/BaseBrushAnimator.cs | 7 ++- .../Animators/GradientBrushAnimator.cs | 5 ++ .../Animators/SolidColorBrushAnimator.cs | 5 ++ .../Animation/Animators/TransformAnimator.cs | 7 ++- 25 files changed, 142 insertions(+), 89 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 4811028f85..50fc5ac73b 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -157,7 +157,7 @@ namespace Avalonia.Animation state.Instance?.Dispose(); state.Instance = transition.Apply( this, - Clock ?? AvaloniaLocator.Current.GetService(), + Clock ?? AvaloniaLocator.Current.GetRequiredService(), oldValue, newValue); return; @@ -169,7 +169,7 @@ namespace Avalonia.Animation base.OnPropertyChangedCore(change); } - private void TransitionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void TransitionsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (!_transitionsEnabled) { @@ -179,14 +179,14 @@ namespace Avalonia.Animation switch (e.Action) { case NotifyCollectionChangedAction.Add: - AddTransitions(e.NewItems); + AddTransitions(e.NewItems!); break; case NotifyCollectionChangedAction.Remove: - RemoveTransitions(e.OldItems); + RemoveTransitions(e.OldItems!); break; case NotifyCollectionChangedAction.Replace: - RemoveTransitions(e.OldItems); - AddTransitions(e.NewItems); + RemoveTransitions(e.OldItems!); + AddTransitions(e.NewItems!); break; case NotifyCollectionChangedAction.Reset: throw new NotSupportedException("Transitions collection cannot be reset."); @@ -204,7 +204,7 @@ namespace Avalonia.Animation for (var i = 0; i < items.Count; ++i) { - var t = (ITransition)items[i]; + var t = (ITransition)items[i]!; _transitionState.Add(t, new TransitionState { @@ -222,7 +222,7 @@ namespace Avalonia.Animation for (var i = 0; i < items.Count; ++i) { - var t = (ITransition)items[i]; + var t = (ITransition)items[i]!; if (_transitionState.TryGetValue(t, out var state)) { diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index a4515db514..03b2d17e44 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -203,7 +203,7 @@ namespace Avalonia.Animation /// /// The animation setter. /// The property animator type. - public static Type GetAnimator(IAnimationSetter setter) + public static Type? GetAnimator(IAnimationSetter setter) { if (s_animators.TryGetValue(setter, out var type)) { @@ -254,7 +254,7 @@ namespace Avalonia.Animation Animators.Insert(0, (condition, typeof(TAnimator))); } - private static Type GetAnimatorType(AvaloniaProperty property) + private static Type? GetAnimatorType(AvaloniaProperty property) { foreach (var (condition, type) in Animators) { @@ -276,6 +276,11 @@ namespace Avalonia.Animation { foreach (var setter in keyframe.Setters) { + if (setter.Property is null) + { + throw new InvalidOperationException("No Setter property assigned."); + } + var handler = Animation.GetAnimator(setter) ?? GetAnimatorType(setter.Property); if (handler == null) @@ -305,7 +310,7 @@ namespace Avalonia.Animation foreach (var (handlerType, property) in handlerList) { - var newInstance = (IAnimator)Activator.CreateInstance(handlerType); + var newInstance = (IAnimator)Activator.CreateInstance(handlerType)!; newInstance.Property = property; newAnimatorInstances.Add(newInstance); } @@ -321,32 +326,43 @@ namespace Avalonia.Animation } /// - public IDisposable Apply(Animatable control, IClock clock, IObservable match, Action onComplete) + public IDisposable Apply(Animatable control, IClock? clock, IObservable match, Action? onComplete) { var (animators, subscriptions) = InterpretKeyframes(control); if (animators.Count == 1) { - subscriptions.Add(animators[0].Apply(this, control, clock, match, onComplete)); + var subscription = animators[0].Apply(this, control, clock, match, onComplete); + + if (subscription is not null) + { + subscriptions.Add(subscription); + } } else { var completionTasks = onComplete != null ? new List() : null; foreach (IAnimator animator in animators) { - Action animatorOnComplete = null; + Action? animatorOnComplete = null; if (onComplete != null) { - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); animatorOnComplete = () => tcs.SetResult(null); - completionTasks.Add(tcs.Task); + completionTasks!.Add(tcs.Task); + } + + var subscription = animator.Apply(this, control, clock, match, animatorOnComplete); + + if (subscription is not null) + { + subscriptions.Add(subscription); } - subscriptions.Add(animator.Apply(this, control, clock, match, animatorOnComplete)); } if (onComplete != null) { - Task.WhenAll(completionTasks).ContinueWith( - (_, state) => ((Action)state).Invoke(), + Task.WhenAll(completionTasks!).ContinueWith( + (_, state) => ((Action)state!).Invoke(), onComplete); } } @@ -354,25 +370,25 @@ namespace Avalonia.Animation } /// - public Task RunAsync(Animatable control, IClock clock = null) + public Task RunAsync(Animatable control, IClock? clock = null) { return RunAsync(control, clock, default); } /// - public Task RunAsync(Animatable control, IClock clock = null, CancellationToken cancellationToken = default) + public Task RunAsync(Animatable control, IClock? clock = null, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { return Task.CompletedTask; } - var run = new TaskCompletionSource(); + var run = new TaskCompletionSource(); if (this.IterationCount == IterationCount.Infinite) run.SetException(new InvalidOperationException("Looping animations must not use the Run method.")); - IDisposable subscriptions = null, cancellation = null; + IDisposable? subscriptions = null, cancellation = null; subscriptions = this.Apply(control, clock, Observable.Return(true), () => { run.TrySetResult(null); diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index cf79640150..52cd4b324f 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -31,15 +31,15 @@ namespace Avalonia.Animation private TimeSpan _initialDelay; private TimeSpan _iterationDelay; private TimeSpan _duration; - private Easings.Easing _easeFunc; - private Action _onCompleteAction; + private Easings.Easing? _easeFunc; + private Action? _onCompleteAction; private Func _interpolator; - private IDisposable _timerSub; + private IDisposable? _timerSub; private readonly IClock _baseClock; - private IClock _clock; - private EventHandler _propertyChangedDelegate; + private IClock? _clock; + private EventHandler? _propertyChangedDelegate; - public AnimationInstance(Animation animation, Animatable control, Animator animator, IClock baseClock, Action OnComplete, Func Interpolator) + public AnimationInstance(Animation animation, Animatable control, Animator animator, IClock baseClock, Action? OnComplete, Func Interpolator) { _animator = animator; _animation = animation; @@ -47,6 +47,9 @@ namespace Avalonia.Animation _onCompleteAction = OnComplete; _interpolator = Interpolator; _baseClock = baseClock; + _lastInterpValue = default!; + _firstKFValue = default!; + _neutralValue = default!; FetchProperties(); } @@ -82,7 +85,7 @@ namespace Avalonia.Animation _targetControl.PropertyChanged -= _propertyChangedDelegate; _timerSub?.Dispose(); - _clock.PlayState = PlayState.Stop; + _clock!.PlayState = PlayState.Stop; } protected override void Subscribed() @@ -108,6 +111,8 @@ namespace Avalonia.Animation private void ApplyFinalFill() { + if (_animator.Property is null) + throw new InvalidOperationException("Animator has no property specified."); if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both) _targetControl.SetValue(_animator.Property, _lastInterpValue, BindingPriority.LocalValue); } @@ -130,12 +135,12 @@ namespace Avalonia.Animation private void DoPlayStates() { - if (_clock.PlayState == PlayState.Stop || _baseClock.PlayState == PlayState.Stop) + if (_clock!.PlayState == PlayState.Stop || _baseClock.PlayState == PlayState.Stop) DoComplete(); if (!_gotFirstKFValue) { - _firstKFValue = (T)_animator.First().Value; + _firstKFValue = (T)_animator.First().Value!; _gotFirstKFValue = true; } } @@ -169,7 +174,7 @@ namespace Avalonia.Animation // and snap the last iteration value to exact values. if ((_currentIteration + 1) > _iterationCount) { - var easedTime = _easeFunc.Ease(_playbackReversed ? 0.0 : 1.0); + var easedTime = _easeFunc!.Ease(_playbackReversed ? 0.0 : 1.0); _lastInterpValue = _interpolator(easedTime, _neutralValue); DoComplete(); } @@ -203,7 +208,7 @@ namespace Avalonia.Animation normalizedTime = 1 - normalizedTime; // Ease and interpolate - var easedTime = _easeFunc.Ease(normalizedTime); + var easedTime = _easeFunc!.Ease(normalizedTime); _lastInterpValue = _interpolator(easedTime, _neutralValue); PublishNext(_lastInterpValue); @@ -223,14 +228,14 @@ namespace Avalonia.Animation private void UpdateNeutralValue() { - var property = _animator.Property; + var property = _animator.Property ?? throw new InvalidOperationException("Animator has no property specified."); var baseValue = _targetControl.GetBaseValue(property, BindingPriority.LocalValue); _neutralValue = baseValue != AvaloniaProperty.UnsetValue ? - (T)baseValue : (T)_targetControl.GetValue(property); + (T)baseValue! : (T)_targetControl.GetValue(property)!; } - private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + private void ControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { if (e.Property == _animator.Property && e.Priority > BindingPriority.Animation) { diff --git a/src/Avalonia.Animation/AnimatorKeyFrame.cs b/src/Avalonia.Animation/AnimatorKeyFrame.cs index f6a0c12be4..8af31f2948 100644 --- a/src/Avalonia.Animation/AnimatorKeyFrame.cs +++ b/src/Avalonia.Animation/AnimatorKeyFrame.cs @@ -12,22 +12,22 @@ namespace Avalonia.Animation /// public class AnimatorKeyFrame : AvaloniaObject { - public static readonly DirectProperty ValueProperty = - AvaloniaProperty.RegisterDirect(nameof(Value), k => k.Value, (k, v) => k.Value = v); + public static readonly DirectProperty ValueProperty = + AvaloniaProperty.RegisterDirect(nameof(Value), k => k.Value, (k, v) => k.Value = v); public AnimatorKeyFrame() { } - public AnimatorKeyFrame(Type animatorType, Cue cue) + public AnimatorKeyFrame(Type? animatorType, Cue cue) { AnimatorType = animatorType; Cue = cue; KeySpline = null; } - public AnimatorKeyFrame(Type animatorType, Cue cue, KeySpline keySpline) + public AnimatorKeyFrame(Type? animatorType, Cue cue, KeySpline? keySpline) { AnimatorType = animatorType; Cue = cue; @@ -35,14 +35,14 @@ namespace Avalonia.Animation } internal bool isNeutral; - public Type AnimatorType { get; } + public Type? AnimatorType { get; } public Cue Cue { get; } - public KeySpline KeySpline { get; } - public AvaloniaProperty Property { get; private set; } + public KeySpline? KeySpline { get; } + public AvaloniaProperty? Property { get; private set; } - private object _value; + private object? _value; - public object Value + public object? Value { get => _value; set => SetAndRaise(ValueProperty, ref _value, value); @@ -80,7 +80,7 @@ namespace Avalonia.Animation throw new InvalidCastException($"KeyFrame value doesnt match property type."); } - return (T)typeConv.ConvertTo(Value, typeof(T)); + return (T)typeConv.ConvertTo(Value, typeof(T))!; } } } diff --git a/src/Avalonia.Animation/Animators/Animator`1.cs b/src/Avalonia.Animation/Animators/Animator`1.cs index 23afa76bf6..248ca61c1d 100644 --- a/src/Avalonia.Animation/Animators/Animator`1.cs +++ b/src/Avalonia.Animation/Animators/Animator`1.cs @@ -24,7 +24,7 @@ namespace Avalonia.Animation.Animators /// /// Gets or sets the target property for the keyframe. /// - public AvaloniaProperty Property { get; set; } + public AvaloniaProperty? Property { get; set; } public Animator() { @@ -33,7 +33,7 @@ namespace Avalonia.Animation.Animators } /// - public virtual IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete) + public virtual IDisposable? Apply(Animation animation, Animatable control, IClock? clock, IObservable match, Action? onComplete) { if (!_isVerifiedAndConverted) VerifyConvertKeyFrames(); @@ -106,13 +106,16 @@ namespace Avalonia.Animation.Animators public virtual IDisposable BindAnimation(Animatable control, IObservable instance) { + if (Property is null) + throw new InvalidOperationException("Animator has no property specified."); + return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); } /// /// Runs the KeyFrames Animation. /// - internal IDisposable Run(Animation animation, Animatable control, IClock clock, Action onComplete) + internal IDisposable Run(Animation animation, Animatable control, IClock? clock, Action? onComplete) { var instance = new AnimationInstance( animation, diff --git a/src/Avalonia.Animation/Avalonia.Animation.csproj b/src/Avalonia.Animation/Avalonia.Animation.csproj index 85938ad958..9e3758658c 100644 --- a/src/Avalonia.Animation/Avalonia.Animation.csproj +++ b/src/Avalonia.Animation/Avalonia.Animation.csproj @@ -2,9 +2,13 @@ netstandard2.0;net6.0 + + + + diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index 5c2b7ce0dd..5afd2ae705 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/src/Avalonia.Animation/Clock.cs @@ -4,7 +4,7 @@ namespace Avalonia.Animation { public class Clock : ClockBase { - public static IClock GlobalClock => AvaloniaLocator.Current.GetService(); + public static IClock GlobalClock => AvaloniaLocator.Current.GetRequiredService(); private readonly IDisposable _parentSubscription; diff --git a/src/Avalonia.Animation/Cue.cs b/src/Avalonia.Animation/Cue.cs index 7da7a9382b..6578148b07 100644 --- a/src/Avalonia.Animation/Cue.cs +++ b/src/Avalonia.Animation/Cue.cs @@ -30,7 +30,7 @@ namespace Avalonia.Animation /// /// Parses a string to a object. /// - public static Cue Parse(string value, CultureInfo culture) + public static Cue Parse(string value, CultureInfo? culture) { string v = value; @@ -72,14 +72,14 @@ namespace Avalonia.Animation public class CueTypeConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { return Cue.Parse((string)value, culture); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs b/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs index 696f43d006..7283eaeedf 100644 --- a/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs +++ b/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs @@ -8,15 +8,15 @@ namespace Avalonia.Animation /// internal class DisposeAnimationInstanceSubject : IObserver, IDisposable { - private IDisposable _lastInstance; + private IDisposable? _lastInstance; private bool _lastMatch; private Animator _animator; private Animation _animation; private Animatable _control; - private Action _onComplete; - private IClock _clock; + private Action? _onComplete; + private IClock? _clock; - public DisposeAnimationInstanceSubject(Animator animator, Animation animation, Animatable control, IClock clock, Action onComplete) + public DisposeAnimationInstanceSubject(Animator animator, Animation animation, Animatable control, IClock? clock, Action? onComplete) { this._animator = animator; this._animation = animation; diff --git a/src/Avalonia.Animation/Easing/Easing.cs b/src/Avalonia.Animation/Easing/Easing.cs index e006459652..2f4b93dab1 100644 --- a/src/Avalonia.Animation/Easing/Easing.cs +++ b/src/Avalonia.Animation/Easing/Easing.cs @@ -15,7 +15,7 @@ namespace Avalonia.Animation.Easings /// public abstract double Ease(double progress); - static Dictionary _easingTypes; + static Dictionary? _easingTypes; static readonly Type s_thisType = typeof(Easing); @@ -48,7 +48,7 @@ namespace Avalonia.Animation.Easings if (_easingTypes.ContainsKey(e)) { var type = _easingTypes[e]; - return (Easing)Activator.CreateInstance(type); + return (Easing)Activator.CreateInstance(type)!; } else { diff --git a/src/Avalonia.Animation/Easing/EasingTypeConverter.cs b/src/Avalonia.Animation/Easing/EasingTypeConverter.cs index 6613f6d393..3d67d54a6f 100644 --- a/src/Avalonia.Animation/Easing/EasingTypeConverter.cs +++ b/src/Avalonia.Animation/Easing/EasingTypeConverter.cs @@ -6,12 +6,12 @@ namespace Avalonia.Animation.Easings { public class EasingTypeConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { return Easing.Parse((string)value); } diff --git a/src/Avalonia.Animation/IAnimation.cs b/src/Avalonia.Animation/IAnimation.cs index d037834630..436a765d27 100644 --- a/src/Avalonia.Animation/IAnimation.cs +++ b/src/Avalonia.Animation/IAnimation.cs @@ -12,7 +12,7 @@ namespace Avalonia.Animation /// /// Apply the animation to the specified control and run it when produces true. /// - IDisposable Apply(Animatable control, IClock clock, IObservable match, Action onComplete = null); + IDisposable Apply(Animatable control, IClock? clock, IObservable match, Action? onComplete = null); /// /// Run the animation on the specified control. diff --git a/src/Avalonia.Animation/IAnimationSetter.cs b/src/Avalonia.Animation/IAnimationSetter.cs index 2d22377286..6a1d3539e2 100644 --- a/src/Avalonia.Animation/IAnimationSetter.cs +++ b/src/Avalonia.Animation/IAnimationSetter.cs @@ -2,7 +2,7 @@ namespace Avalonia.Animation { public interface IAnimationSetter { - AvaloniaProperty Property { get; set; } - object Value { get; set; } + AvaloniaProperty? Property { get; set; } + object? Value { get; set; } } } diff --git a/src/Avalonia.Animation/IAnimator.cs b/src/Avalonia.Animation/IAnimator.cs index d0fb173c54..f64ac9f913 100644 --- a/src/Avalonia.Animation/IAnimator.cs +++ b/src/Avalonia.Animation/IAnimator.cs @@ -11,11 +11,11 @@ namespace Avalonia.Animation /// /// The target property. /// - AvaloniaProperty Property {get; set;} + AvaloniaProperty? Property {get; set;} /// /// Applies the current KeyFrame group to the specified control. /// - IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete); + IDisposable? Apply(Animation animation, Animatable control, IClock? clock, IObservable match, Action? onComplete); } } diff --git a/src/Avalonia.Animation/ITransition.cs b/src/Avalonia.Animation/ITransition.cs index ade2ec8b9e..241ca208d1 100644 --- a/src/Avalonia.Animation/ITransition.cs +++ b/src/Avalonia.Animation/ITransition.cs @@ -10,7 +10,7 @@ namespace Avalonia.Animation /// /// Applies the transition to the specified . /// - IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue); + IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue); /// /// Gets the property to be animated. diff --git a/src/Avalonia.Animation/IterationCount.cs b/src/Avalonia.Animation/IterationCount.cs index 9463718608..3b52cdab49 100644 --- a/src/Avalonia.Animation/IterationCount.cs +++ b/src/Avalonia.Animation/IterationCount.cs @@ -97,7 +97,7 @@ namespace Avalonia.Animation /// /// The object with which to test equality. /// True if the objects are equal, otherwise false. - public override bool Equals(object o) + public override bool Equals(object? o) { if (o == null) { diff --git a/src/Avalonia.Animation/IterationCountTypeConverter.cs b/src/Avalonia.Animation/IterationCountTypeConverter.cs index 1c63f8cdf1..f64972ff5c 100644 --- a/src/Avalonia.Animation/IterationCountTypeConverter.cs +++ b/src/Avalonia.Animation/IterationCountTypeConverter.cs @@ -6,12 +6,12 @@ namespace Avalonia.Animation { public class IterationCountTypeConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { return IterationCount.Parse((string)value); } diff --git a/src/Avalonia.Animation/KeyFrame.cs b/src/Avalonia.Animation/KeyFrame.cs index c2cc1aa051..3ab7a70d90 100644 --- a/src/Avalonia.Animation/KeyFrame.cs +++ b/src/Avalonia.Animation/KeyFrame.cs @@ -19,7 +19,7 @@ namespace Avalonia.Animation { private TimeSpan _ktimeSpan; private Cue _kCue; - private KeySpline _kKeySpline; + private KeySpline? _kKeySpline; public KeyFrame() { @@ -79,7 +79,7 @@ namespace Avalonia.Animation /// Gets or sets the KeySpline of this . /// /// The key spline. - public KeySpline KeySpline + public KeySpline? KeySpline { get { diff --git a/src/Avalonia.Animation/KeySplineTypeConverter.cs b/src/Avalonia.Animation/KeySplineTypeConverter.cs index b026206e5f..eecad3c3ac 100644 --- a/src/Avalonia.Animation/KeySplineTypeConverter.cs +++ b/src/Avalonia.Animation/KeySplineTypeConverter.cs @@ -12,12 +12,12 @@ namespace Avalonia.Animation /// public class KeySplineTypeConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { return KeySpline.Parse((string)value, CultureInfo.InvariantCulture); } diff --git a/src/Avalonia.Animation/Transition.cs b/src/Avalonia.Animation/Transition.cs index 4115c95c0f..d307f348c4 100644 --- a/src/Avalonia.Animation/Transition.cs +++ b/src/Avalonia.Animation/Transition.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Avalonia.Animation.Easings; namespace Avalonia.Animation @@ -8,7 +9,7 @@ namespace Avalonia.Animation /// public abstract class Transition : AvaloniaObject, ITransition { - private AvaloniaProperty _prop; + private AvaloniaProperty? _prop; /// /// Gets or sets the duration of the transition. @@ -26,7 +27,8 @@ namespace Avalonia.Animation public Easing Easing { get; set; } = new LinearEasing(); /// - public AvaloniaProperty Property + [DisallowNull] + public AvaloniaProperty? Property { get { @@ -42,16 +44,25 @@ namespace Avalonia.Animation } } + AvaloniaProperty ITransition.Property + { + get => Property ?? throw new InvalidOperationException("Transition has no property specified."); + set => Property = value; + } + /// /// Apply interpolation to the property. /// public abstract IObservable DoTransition(IObservable progress, T oldValue, T newValue); /// - public virtual IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue) + public virtual IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue) { - var transition = DoTransition(new TransitionInstance(clock, Delay, Duration), (T)oldValue, (T)newValue); + if (Property is null) + throw new InvalidOperationException("Transition has no property specified."); + + var transition = DoTransition(new TransitionInstance(clock, Delay, Duration), (T)oldValue!, (T)newValue!); return control.Bind((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index b522d1961e..9c9494ff87 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -10,11 +10,11 @@ namespace Avalonia.Animation /// internal class TransitionInstance : SingleSubscriberObservableBase, IObserver { - private IDisposable _timerSubscription; + private IDisposable? _timerSubscription; private TimeSpan _delay; private TimeSpan _duration; private readonly IClock _baseClock; - private TransitionClock _clock; + private TransitionClock? _clock; public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration) { @@ -67,7 +67,7 @@ namespace Avalonia.Animation protected override void Unsubscribed() { _timerSubscription?.Dispose(); - _clock.PlayState = PlayState.Stop; + _clock!.PlayState = PlayState.Stop; } protected override void Subscribed() diff --git a/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs index a2c4b0313b..5f22254fb5 100644 --- a/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs @@ -38,8 +38,8 @@ namespace Avalonia.Animation.Animators } /// - public override IDisposable Apply(Animation animation, Animatable control, IClock clock, - IObservable match, Action onComplete) + public override IDisposable? Apply(Animation animation, Animatable control, IClock? clock, + IObservable match, Action? onComplete) { if (TryCreateCustomRegisteredAnimator(out var animator) || TryCreateGradientAnimator(out animator) @@ -135,9 +135,8 @@ namespace Avalonia.Animation.Animators private bool TryCreateCustomRegisteredAnimator([NotNullWhen(true)] out IAnimator? animator) { - if (_brushAnimators.Count > 0) + if (_brushAnimators.Count > 0 && this[0].Value?.GetType() is Type firstKeyType) { - var firstKeyType = this[0].Value.GetType(); foreach (var (match, animatorType) in _brushAnimators) { if (!match(firstKeyType)) diff --git a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs index 864e12413f..0979de16d0 100644 --- a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs @@ -58,6 +58,11 @@ namespace Avalonia.Animation.Animators public override IDisposable BindAnimation(Animatable control, IObservable instance) { + if (Property is null) + { + throw new InvalidOperationException("Animator has no property specified."); + } + return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); } diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 7c6372aae2..57f9f3c1a5 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -24,6 +24,11 @@ namespace Avalonia.Animation.Animators public override IDisposable BindAnimation(Animatable control, IObservable instance) { + if (Property is null) + { + throw new InvalidOperationException("Animator has no property specified."); + } + return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); } } diff --git a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs index 1d7bfd3748..34ec8ac503 100644 --- a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs @@ -14,10 +14,15 @@ namespace Avalonia.Animation.Animators DoubleAnimator? _doubleAnimator; /// - public override IDisposable? Apply(Animation animation, Animatable control, IClock clock, IObservable obsMatch, Action onComplete) + public override IDisposable? Apply(Animation animation, Animatable control, IClock? clock, IObservable obsMatch, Action? onComplete) { var ctrl = (Visual)control; + if (Property is null) + { + throw new InvalidOperationException("Animator has no property specified."); + } + // Check if the Target Property is Transform derived. if (typeof(Transform).IsAssignableFrom(Property.OwnerType)) { From 98d061ec30b65b63ec98d9c45e52332d65a6db21 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Dec 2021 15:53:12 +0100 Subject: [PATCH 03/21] Fix failing unit tests. --- .../AnimatableTests.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs index 26d8059eec..1d5296bebd 100644 --- a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs +++ b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs @@ -36,7 +36,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Transition_Is_Not_Applied_To_Initial_Style() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + using (Start()) { var target = CreateTarget(); var control = new Control @@ -74,6 +74,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Transition_Is_Applied_When_Local_Value_Changes() { + using var app = Start(); var target = CreateTarget(); var control = CreateControl(target.Object); @@ -170,6 +171,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Transition_Is_Not_Applied_When_StyleTrigger_Changes_With_LocalValue_Present() { + using var app = Start(); var target = CreateTarget(); var control = CreateControl(target.Object); @@ -195,6 +197,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Transition_Is_Disposed_When_Local_Value_Changes() { + using var app = Start(); var target = CreateTarget(); var control = CreateControl(target.Object); var sub = new Mock(); @@ -211,6 +214,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void New_Transition_Is_Applied_When_Local_Value_Changes() { + using var app = Start(); var target = CreateTarget(); var control = CreateControl(target.Object); @@ -239,6 +243,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Transition_Is_Not_Applied_When_Removed_From_Visual_Tree() { + using var app = Start(); var target = CreateTarget(); var control = CreateControl(target.Object); @@ -266,6 +271,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Animation_Is_Cancelled_When_Transition_Removed() { + using var app = Start(); var target = CreateTarget(); var control = CreateControl(target.Object); var sub = new Mock(); @@ -285,7 +291,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Animation_Is_Cancelled_When_New_Style_Activates() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + using (Start()) { var target = CreateTarget(); var control = CreateStyledControl(target.Object); @@ -301,7 +307,7 @@ namespace Avalonia.Animation.UnitTests target.Verify(x => x.Apply( control, - It.IsAny(), + It.IsAny(), 1.0, 0.5), Times.Once); @@ -315,7 +321,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Transition_From_Style_Trigger_Is_Applied() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + using (Start()) { var target = CreateTransition(Control.WidthProperty); var control = CreateStyledControl(transition2: target.Object); @@ -326,7 +332,7 @@ namespace Avalonia.Animation.UnitTests target.Verify(x => x.Apply( control, - It.IsAny(), + It.IsAny(), double.NaN, 100.0), Times.Once); @@ -337,7 +343,7 @@ namespace Avalonia.Animation.UnitTests public void Replacing_Transitions_During_Animation_Does_Not_Throw_KeyNotFound() { // Issue #4059 - using (UnitTestApplication.Start(TestServices.RealStyler)) + using (Start()) { Border target; var clock = new TestClock(); @@ -428,6 +434,13 @@ namespace Avalonia.Animation.UnitTests control.EndBatchUpdate(); } + private static IDisposable Start() + { + var clock = new MockGlobalClock(); + var services = TestServices.RealStyler.With(globalClock: clock); + return UnitTestApplication.Start(services); + } + private static Mock CreateTarget() { return CreateTransition(Visual.OpacityProperty); From dfd0523e36e3f5227305a6da73cd61d6a0943b93 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 3 Jan 2022 22:12:51 +0300 Subject: [PATCH 04/21] Optimize Pen's subscriptions to IAffects render --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 5 +- src/Avalonia.Visuals/Media/Pen.cs | 110 ++++++++------------- 2 files changed, 46 insertions(+), 69 deletions(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index dcb3246a63..68e3673cfe 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -7,6 +7,9 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Task MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.GlyphRun..ctor()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.GlyphRun.GlyphTypeface.set(Avalonia.Media.GlyphTypeface)' does not exist in the implementation but it does exist in the contract. +CannotSealType : Type 'Avalonia.Media.Pen' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +MembersMustExist : Member 'protected void Avalonia.Media.Pen.AffectsRender(Avalonia.AvaloniaProperty[])' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.Media.Pen.RaiseInvalidated(System.EventArgs)' does not exist in the implementation but it does exist in the contract. TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract. @@ -83,4 +86,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avaloni InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime.get()' is present in the implementation but not in the contract. -Total Issues: 84 +Total Issues: 87 diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index 7c966a35cf..f4ae58eff3 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -7,7 +7,7 @@ namespace Avalonia.Media /// /// Describes how a stroke is drawn. /// - public class Pen : AvaloniaObject, IPen + public sealed class Pen : AvaloniaObject, IPen, IWeakEventSubscriber { /// /// Defines the property. @@ -45,6 +45,9 @@ namespace Avalonia.Media public static readonly StyledProperty MiterLimitProperty = AvaloniaProperty.Register(nameof(MiterLimit), 10.0); + private EventHandler? _invalidated; + private IAffectsRender? _subscribedTo; + /// /// Initializes a new instance of the class. /// @@ -96,17 +99,6 @@ namespace Avalonia.Media DashStyle = dashStyle; } - static Pen() - { - AffectsRender( - BrushProperty, - ThicknessProperty, - DashStyleProperty, - LineCapProperty, - LineJoinProperty, - MiterLimitProperty); - } - /// /// Gets or sets the brush used to draw the stroke. /// @@ -116,6 +108,11 @@ namespace Avalonia.Media set => SetValue(BrushProperty, value); } + private static readonly WeakEvent InvalidatedWeakEvent = + WeakEvent.Register( + (s, h) => s.Invalidated += h, + (s, h) => s.Invalidated -= h); + /// /// Gets or sets the stroke thickness. /// @@ -165,7 +162,19 @@ namespace Avalonia.Media /// /// Raised when the pen changes. /// - public event EventHandler? Invalidated; + public event EventHandler? Invalidated + { + add + { + _invalidated += value; + UpdateBrushSubscription(); + } + remove + { + _invalidated -= value; + UpdateBrushSubscription(); + } + } /// /// Creates an immutable clone of the brush. @@ -182,68 +191,33 @@ namespace Avalonia.Media MiterLimit); } - /// - /// Marks a property as affecting the pen's visual representation. - /// - /// The properties. - /// - /// After a call to this method in a pen's static constructor, any change to the - /// property will cause the event to be raised on the pen. - /// - protected static void AffectsRender(params AvaloniaProperty[] properties) - where T : Pen + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - static void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - if (e.Sender is T sender) - { - sender.RaiseInvalidated(EventArgs.Empty); - } - } + _invalidated?.Invoke(this, EventArgs.Empty); + if(change.Property == BrushProperty) + UpdateBrushSubscription(); + base.OnPropertyChanged(change); + } - static void InvalidateAndSubscribe(AvaloniaPropertyChangedEventArgs e) + void UpdateBrushSubscription() + { + if ((_invalidated == null || _subscribedTo != Brush) && _subscribedTo != null) { - if (e.Sender is T sender) - { - if (e.OldValue is IAffectsRender oldValue) - { - WeakEventHandlerManager.Unsubscribe( - oldValue, - nameof(oldValue.Invalidated), - sender.AffectsRenderInvalidated); - } - - if (e.NewValue is IAffectsRender newValue) - { - WeakEventHandlerManager.Subscribe( - newValue, - nameof(newValue.Invalidated), - sender.AffectsRenderInvalidated); - } - - sender.RaiseInvalidated(EventArgs.Empty); - } + InvalidatedWeakEvent.Unsubscribe(_subscribedTo, this); + _subscribedTo = null; } - foreach (var property in properties) + if (_invalidated != null && _subscribedTo != Brush && Brush is IAffectsRender affectsRender) { - if (property.CanValueAffectRender()) - { - property.Changed.Subscribe(e => InvalidateAndSubscribe(e)); - } - else - { - property.Changed.Subscribe(e => Invalidate(e)); - } + InvalidatedWeakEvent.Subscribe(affectsRender, this); + _subscribedTo = affectsRender; } } - - /// - /// Raises the event. - /// - /// The event args. - protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e); - - private void AffectsRenderInvalidated(object? sender, EventArgs e) => RaiseInvalidated(EventArgs.Empty); + + void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, EventArgs e) + { + if (ev == InvalidatedWeakEvent) + _invalidated?.Invoke(this, EventArgs.Empty); + } } } From 7e45ee1e537e5f0b67c23cba159638dfcf5241a7 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 4 Jan 2022 01:19:14 +0300 Subject: [PATCH 05/21] Subscribe to DashStyle too --- src/Avalonia.Visuals/Media/Pen.cs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index f4ae58eff3..65ba851100 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -46,7 +46,8 @@ namespace Avalonia.Media AvaloniaProperty.Register(nameof(MiterLimit), 10.0); private EventHandler? _invalidated; - private IAffectsRender? _subscribedTo; + private IAffectsRender? _subscribedToBrush; + private IAffectsRender? _subscribedToDashes; /// /// Initializes a new instance of the class. @@ -167,12 +168,12 @@ namespace Avalonia.Media add { _invalidated += value; - UpdateBrushSubscription(); + UpdateSubscriptions(); } remove { _invalidated -= value; - UpdateBrushSubscription(); + UpdateSubscriptions(); } } @@ -195,24 +196,33 @@ namespace Avalonia.Media { _invalidated?.Invoke(this, EventArgs.Empty); if(change.Property == BrushProperty) - UpdateBrushSubscription(); + UpdateSubscription(ref _subscribedToBrush, Brush); + if(change.Property == DashStyleProperty) + UpdateSubscription(ref _subscribedToDashes, DashStyle); base.OnPropertyChanged(change); } - void UpdateBrushSubscription() + + void UpdateSubscription(ref IAffectsRender? field, object? value) { - if ((_invalidated == null || _subscribedTo != Brush) && _subscribedTo != null) + if ((_invalidated == null || field != value) && field != null) { - InvalidatedWeakEvent.Unsubscribe(_subscribedTo, this); - _subscribedTo = null; + InvalidatedWeakEvent.Unsubscribe(field, this); + field = null; } - if (_invalidated != null && _subscribedTo != Brush && Brush is IAffectsRender affectsRender) + if (_invalidated != null && field != value && value is IAffectsRender affectsRender) { InvalidatedWeakEvent.Subscribe(affectsRender, this); - _subscribedTo = affectsRender; + field = affectsRender; } } + + void UpdateSubscriptions() + { + UpdateSubscription(ref _subscribedToBrush, Brush); + UpdateSubscription(ref _subscribedToDashes, DashStyle); + } void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, EventArgs e) { From 88db01532f70f209af9a1cb90f8bd13ad36fc6bf Mon Sep 17 00:00:00 2001 From: Tim U Date: Tue, 4 Jan 2022 16:34:41 +0100 Subject: [PATCH 06/21] Implement CellEditingTemplate --- .../DataGridTemplateColumn.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs index 7e95dd100c..fbdad0a8ad 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs @@ -30,6 +30,20 @@ namespace Avalonia.Controls set { SetAndRaise(CellTemplateProperty, ref _cellTemplate, value); } } + private IDataTemplate _cellEditingCellTemplate; + + public static readonly DirectProperty CellEditingTemplateProperty = + AvaloniaProperty.RegisterDirect( + nameof(CellEditingTemplate), + o => o.CellEditingTemplate, + (o, v) => o.CellEditingTemplate = v); + + public IDataTemplate CellEditingTemplate + { + get => _cellEditingCellTemplate; + set => SetAndRaise(CellEditingTemplateProperty, ref _cellEditingCellTemplate, value); + } + private void OnCellTemplateChanged(AvaloniaPropertyChangedEventArgs e) { var oldValue = (IDataTemplate)e.OldValue; @@ -38,7 +52,7 @@ namespace Avalonia.Controls public DataGridTemplateColumn() { - IsReadOnly = true; + // IsReadOnly = true; } protected override IControl GenerateElement(DataGridCell cell, object dataItem) @@ -60,7 +74,18 @@ namespace Avalonia.Controls protected override IControl GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding binding) { binding = null; - return GenerateElement(cell, dataItem); + if(CellEditingTemplate != null) + { + return CellEditingTemplate.Build(dataItem); + } + if (Design.IsDesignMode) + { + return null; + } + else + { + throw DataGridError.DataGridTemplateColumn.MissingTemplateForType(typeof(DataGridTemplateColumn)); + } } protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs) @@ -70,7 +95,8 @@ namespace Avalonia.Controls protected internal override void RefreshCellContent(IControl element, string propertyName) { - if(propertyName == nameof(CellTemplate) && element.Parent is DataGridCell cell) + var cell = element.Parent as DataGridCell; + if(propertyName == nameof(CellTemplate) && cell is not null) { cell.Content = GenerateElement(cell, cell.DataContext); } From 66a02a37d09b226127b3d7636e18f0d84fbd0f02 Mon Sep 17 00:00:00 2001 From: Tim U Date: Tue, 4 Jan 2022 17:27:28 +0100 Subject: [PATCH 07/21] Handle IsReadOnly correct for DataGridTemplateColumn --- .../DataGridColumn.cs | 2 +- .../DataGridTemplateColumn.cs | 25 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 6b515503aa..8501ce3896 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -448,7 +448,7 @@ namespace Avalonia.Controls internal set; } - public bool IsReadOnly + public virtual bool IsReadOnly { get { diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs index fbdad0a8ad..e8ccb7df34 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls { public class DataGridTemplateColumn : DataGridColumn { - IDataTemplate _cellTemplate; + private IDataTemplate _cellTemplate; public static readonly DirectProperty CellTemplateProperty = AvaloniaProperty.RegisterDirect( @@ -54,7 +54,7 @@ namespace Avalonia.Controls { // IsReadOnly = true; } - + protected override IControl GenerateElement(DataGridCell cell, object dataItem) { if(CellTemplate != null) @@ -78,6 +78,10 @@ namespace Avalonia.Controls { return CellEditingTemplate.Build(dataItem); } + else if (CellTemplate != null) + { + return CellTemplate.Build(dataItem); + } if (Design.IsDesignMode) { return null; @@ -103,5 +107,22 @@ namespace Avalonia.Controls base.RefreshCellContent(element, propertyName); } + + public override bool IsReadOnly + { + get + { + if (CellEditingTemplate is null) + { + return true; + } + + return base.IsReadOnly; + } + set + { + base.IsReadOnly = value; + } + } } } From cf6c0991f8f52bcd683328dafa011550df184698 Mon Sep 17 00:00:00 2001 From: Tim U Date: Tue, 4 Jan 2022 17:27:57 +0100 Subject: [PATCH 08/21] Update Demo --- samples/ControlCatalog/Models/Person.cs | 15 +++++++++++++++ samples/ControlCatalog/Pages/DataGridPage.xaml | 12 ++++++++++++ samples/ControlCatalog/Pages/DataGridPage.xaml.cs | 6 +++--- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/Models/Person.cs b/samples/ControlCatalog/Models/Person.cs index 47f41bc584..cd70fa3959 100644 --- a/samples/ControlCatalog/Models/Person.cs +++ b/samples/ControlCatalog/Models/Person.cs @@ -16,6 +16,7 @@ namespace ControlCatalog.Models string _firstName; string _lastName; bool _isBanned; + private int _age; public string FirstName { @@ -59,6 +60,20 @@ namespace ControlCatalog.Models } } + + /// + /// Gets or sets the age of the person + /// + public int Age + { + get => _age; + set + { + _age = value; + OnPropertyChanged(nameof(Age)); + } + } + Dictionary> _errorLookup = new Dictionary>(); void SetError(string propertyName, string error) diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index 63e873d9b5..451a774cb4 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -64,6 +64,18 @@ + + + + + + + + + + + +