diff --git a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml index 3e50bf8a08..2fe16ba8e3 100644 --- a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml +++ b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml @@ -1,5 +1,7 @@ A control for selecting dates with a calendar drop-down @@ -39,6 +41,9 @@ + + + diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 4b3cfa9c9d..2b0c30f311 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -5,6 +5,7 @@ using Avalonia.Controls.Notifications; using Avalonia.Dialogs; using Avalonia.Platform; using System; +using System.ComponentModel.DataAnnotations; using MiniMvvm; namespace ControlCatalog.ViewModels @@ -164,5 +165,17 @@ namespace ControlCatalog.ViewModels public MiniCommand ExitCommand { get; } public MiniCommand ToggleMenuItemCheckedCommand { get; } + + private DateTime? _validatedDateExample; + + /// + /// A required DateTime which should demonstrate validation for the DateTimePicker + /// + [Required] + public DateTime? ValidatedDateExample + { + get => _validatedDateExample; + set => this.RaiseAndSetIfChanged(ref _validatedDateExample, value); + } } } diff --git a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs index a856ee071c..cd9c80d3e0 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs @@ -185,7 +185,8 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect( nameof(SelectedDate), o => o.SelectedDate, - (o, v) => o.SelectedDate = v); + (o, v) => o.SelectedDate = v, + enableDataValidation: true); public static readonly StyledProperty SelectedDateFormatProperty = AvaloniaProperty.Register( @@ -533,13 +534,11 @@ namespace Avalonia.Controls } } - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) { - base.OnPropertyChanged(change); - - if (change.Property == SelectedDateProperty) + if (property == SelectedDateProperty) { - DataValidationErrors.SetError(this, change.NewValue.Error); + DataValidationErrors.SetError(this, value.Error); } } diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml index 6c4e94caf1..26c3bbc19f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml @@ -33,6 +33,7 @@ + @@ -107,7 +108,6 @@ Padding="{TemplateBinding Padding}" Watermark="{TemplateBinding Watermark}" UseFloatingWatermark="{TemplateBinding UseFloatingWatermark}" - DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Grid.Column="0"/> @@ -136,8 +136,12 @@ DisplayDateEnd="{TemplateBinding DisplayDateEnd}" /> + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml index 47704c12bd..1b4d66405b 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml @@ -20,6 +20,7 @@ (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..65ba851100 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,10 @@ namespace Avalonia.Media public static readonly StyledProperty MiterLimitProperty = AvaloniaProperty.Register(nameof(MiterLimit), 10.0); + private EventHandler? _invalidated; + private IAffectsRender? _subscribedToBrush; + private IAffectsRender? _subscribedToDashes; + /// /// Initializes a new instance of the class. /// @@ -96,17 +100,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 +109,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 +163,19 @@ namespace Avalonia.Media /// /// Raised when the pen changes. /// - public event EventHandler? Invalidated; + public event EventHandler? Invalidated + { + add + { + _invalidated += value; + UpdateSubscriptions(); + } + remove + { + _invalidated -= value; + UpdateSubscriptions(); + } + } /// /// Creates an immutable clone of the brush. @@ -182,68 +192,42 @@ 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) + UpdateSubscription(ref _subscribedToBrush, Brush); + if(change.Property == DashStyleProperty) + UpdateSubscription(ref _subscribedToDashes, DashStyle); + base.OnPropertyChanged(change); + } - static void InvalidateAndSubscribe(AvaloniaPropertyChangedEventArgs e) + + void UpdateSubscription(ref IAffectsRender? field, object? value) + { + if ((_invalidated == null || field != value) && field != 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(field, this); + field = null; } - foreach (var property in properties) + if (_invalidated != null && field != value && value is IAffectsRender affectsRender) { - if (property.CanValueAffectRender()) - { - property.Changed.Subscribe(e => InvalidateAndSubscribe(e)); - } - else - { - property.Changed.Subscribe(e => Invalidate(e)); - } + InvalidatedWeakEvent.Subscribe(affectsRender, this); + field = 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 UpdateSubscriptions() + { + UpdateSubscription(ref _subscribedToBrush, Brush); + UpdateSubscription(ref _subscribedToDashes, DashStyle); + } + + void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, EventArgs e) + { + if (ev == InvalidatedWeakEvent) + _invalidated?.Invoke(this, EventArgs.Empty); + } } }