From 55da255d86e55c9c4a01a6c21e3e387c1a5b8722 Mon Sep 17 00:00:00 2001 From: CommonGuy Date: Wed, 16 May 2018 10:43:26 +0200 Subject: [PATCH 01/16] Improve KeyDown behavior in DropDown --- src/Avalonia.Controls/DropDown.cs | 80 ++++++++++++++------------- src/Avalonia.Controls/DropDownItem.cs | 35 +++++++++++- 2 files changed, 76 insertions(+), 39 deletions(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index 932179028e..5b7213257d 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -6,7 +6,6 @@ using Avalonia.Controls.Generators; using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Input; -using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.VisualTree; @@ -51,6 +50,7 @@ namespace Avalonia.Controls { FocusableProperty.OverrideDefaultValue(true); SelectedItemProperty.Changed.AddClassHandler(x => x.SelectedItemChanged); + KeyDownEvent.AddClassHandler(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel); } /// @@ -96,54 +96,46 @@ namespace Avalonia.Controls this.UpdateSelectionBoxItem(this.SelectedItem); } - protected override void OnGotFocus(GotFocusEventArgs e) - { - base.OnGotFocus(e); - - if (!e.Handled && e.NavigationMethod == NavigationMethod.Directional) - { - e.Handled = UpdateSelectionFromEventSource(e.Source); - } - } - /// protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); - if (!e.Handled) + if (e.Handled) + return; + + if (e.Key == Key.F4 || + ((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0))) { - if (e.Key == Key.F4 || - ((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0))) + IsDropDownOpen = !IsDropDownOpen; + e.Handled = true; + } + else if (IsDropDownOpen && e.Key == Key.Escape) + { + IsDropDownOpen = false; + e.Handled = true; + } + else if (IsDropDownOpen && e.Key == Key.Enter) + { + SelectFocusedItem(); + IsDropDownOpen = false; + e.Handled = true; + } + else if (!IsDropDownOpen) + { + if (e.Key == Key.Down) { - IsDropDownOpen = !IsDropDownOpen; + if (++SelectedIndex >= ItemCount) + SelectedIndex = 0; + e.Handled = true; } - else if (IsDropDownOpen && (e.Key == Key.Escape || e.Key == Key.Enter)) + else if (e.Key == Key.Up) { - IsDropDownOpen = false; - e.Handled = true; - } + if (--SelectedIndex < 0) + SelectedIndex = ItemCount - 1; - if (!IsDropDownOpen) - { - if (e.Key == Key.Down) - { - if (SelectedIndex == -1) - SelectedIndex = 0; - - if (++SelectedIndex >= ItemCount) - SelectedIndex = 0; - - e.Handled = true; - } - else if (e.Key == Key.Up) - { - if (--SelectedIndex < 0) - SelectedIndex = ItemCount - 1; - - e.Handled = true; - } + e.Handled = true; } } } @@ -230,5 +222,17 @@ namespace Avalonia.Controls SelectionBoxItem = item; } } + + private void SelectFocusedItem() + { + foreach (ItemContainerInfo dropdownItem in ItemContainerGenerator.Containers) + { + if (dropdownItem.ContainerControl.IsFocused) + { + SelectedIndex = dropdownItem.Index; + break; + } + } + } } } diff --git a/src/Avalonia.Controls/DropDownItem.cs b/src/Avalonia.Controls/DropDownItem.cs index 1a5cbb5014..3fd80c4562 100644 --- a/src/Avalonia.Controls/DropDownItem.cs +++ b/src/Avalonia.Controls/DropDownItem.cs @@ -1,12 +1,45 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; + namespace Avalonia.Controls { /// /// A selectable item in a . /// - public class DropDownItem : ListBoxItem + public class DropDownItem : ContentControl, ISelectable { + /// + /// Defines the property. + /// + public static readonly StyledProperty IsSelectedProperty = + AvaloniaProperty.Register(nameof(IsSelected)); + + /// + /// Initializes static members of the class. + /// + static DropDownItem() + { + FocusableProperty.OverrideDefaultValue(true); + IsFocusedProperty.Changed.Subscribe(x => + { + var sender = x.Sender as IControl; + + if (sender != null) + { + ((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue); + } + }); + } + + /// + /// Gets or sets the selection state of the item. + /// + public bool IsSelected + { + get { return GetValue(IsSelectedProperty); } + set { SetValue(IsSelectedProperty, value); } + } } } From c2c581e524441dc76a8729a912b71995cb64725c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sun, 13 May 2018 16:03:37 -0500 Subject: [PATCH 02/16] Refactor away dependencies between Avalonia.Markup and Avalonia.Controls. Now Avalonia.Markup only depends on Avalonia.Base. --- src/Avalonia.Controls/ControlExtensions.cs | 15 ----- .../Controls}/ControlLocator.cs | 65 ++++++++----------- .../Controls/NameScopeExtensions.cs | 13 ++++ .../Avalonia.Markup.Xaml/Data/Binding.cs | 23 ++++++- .../Avalonia.Markup/Avalonia.Markup.csproj | 6 -- 5 files changed, 63 insertions(+), 59 deletions(-) rename src/{Markup/Avalonia.Markup => Avalonia.Styling/Controls}/ControlLocator.cs (65%) diff --git a/src/Avalonia.Controls/ControlExtensions.cs b/src/Avalonia.Controls/ControlExtensions.cs index 60a940627f..45a7554f1c 100644 --- a/src/Avalonia.Controls/ControlExtensions.cs +++ b/src/Avalonia.Controls/ControlExtensions.cs @@ -66,21 +66,6 @@ namespace Avalonia.Controls return nameScope.Find(name); } - /// - /// Finds the name scope for a control by searching up the logical tree. - /// - /// The control. - /// The control's name scope, or null if not found. - public static INameScope FindNameScope(this IControl control) - { - Contract.Requires(control != null); - - return control.GetSelfAndLogicalAncestors() - .OfType() - .Select(x => (x as INameScope) ?? NameScope.GetNameScope(x)) - .FirstOrDefault(x => x != null); - } - /// /// Adds or removes a pseudoclass depending on a boolean value. /// diff --git a/src/Markup/Avalonia.Markup/ControlLocator.cs b/src/Avalonia.Styling/Controls/ControlLocator.cs similarity index 65% rename from src/Markup/Avalonia.Markup/ControlLocator.cs rename to src/Avalonia.Styling/Controls/ControlLocator.cs index 1a82c0a4fd..e03ec722ad 100644 --- a/src/Markup/Avalonia.Markup/ControlLocator.cs +++ b/src/Avalonia.Styling/Controls/ControlLocator.cs @@ -9,7 +9,7 @@ using Avalonia.Controls; using Avalonia.LogicalTree; using Avalonia.VisualTree; -namespace Avalonia.Markup +namespace Avalonia.Controls { /// /// The type of tree via which to track a control. @@ -38,12 +38,12 @@ namespace Avalonia.Markup /// The control relative from which the other control should be found. /// /// The name of the control to find. - public static IObservable Track(IControl relativeTo, string name) + public static IObservable Track(ILogical relativeTo, string name) { var attached = Observable.FromEventPattern( x => relativeTo.AttachedToLogicalTree += x, x => relativeTo.AttachedToLogicalTree -= x) - .Select(x => ((IControl)x.Sender).FindNameScope()) + .Select(x => ((ILogical)x.Sender).FindNameScope()) .StartWith(relativeTo.FindNameScope()); var detached = Observable.FromEventPattern( @@ -60,53 +60,32 @@ namespace Avalonia.Markup x => nameScope.Registered -= x) .Where(x => x.EventArgs.Name == name) .Select(x => x.EventArgs.Element) - .OfType(); + .OfType(); var unregistered = Observable.FromEventPattern( x => nameScope.Unregistered += x, x => nameScope.Unregistered -= x) .Where(x => x.EventArgs.Name == name) - .Select(_ => (IControl)null); + .Select(_ => (ILogical)null); return registered - .StartWith(nameScope.Find(name)) + .StartWith(nameScope.Find(name)) .Merge(unregistered); } else { - return Observable.Return(null); + return Observable.Return(null); } }).Switch(); } - /// - /// Tracks a typed visual ancestor control. - /// - /// - /// The control relative from which the other control should be found. - /// - /// The tree via which to track the control. - /// - /// The level of ancestor control to look for. Use 0 for the first ancestor of the - /// requested type. - /// - /// The type of the ancestor to find. - public static IObservable Track(IControl relativeTo, TreeType tree, int ancestorLevel, Type ancestorType = null) + public static IObservable Track(ILogical relativeTo, int ancestorLevel, Type ancestorType = null) { - return TrackAttachmentToTree(relativeTo, tree).Select(isAttachedToTree => + return TrackAttachmentToTree(relativeTo).Select(isAttachedToTree => { if (isAttachedToTree) { - if (tree == TreeType.Visual) - { - return relativeTo.GetVisualAncestors() - .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true) - .ElementAtOrDefault(ancestorLevel) as IControl; - } - else - { - return relativeTo.GetLogicalAncestors() - .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true) - .ElementAtOrDefault(ancestorLevel) as IControl; - } + return relativeTo.GetLogicalAncestors() + .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true) + .ElementAtOrDefault(ancestorLevel); } else { @@ -115,12 +94,24 @@ namespace Avalonia.Markup }); } - private static IObservable TrackAttachmentToTree(IControl relativeTo, TreeType tree) + public static IObservable Track(IVisual relativeTo, int ancestorLevel, Type ancestorType = null) { - return tree == TreeType.Visual ? TrackAttachmentToVisualTree(relativeTo) : TrackAttachmentToLogicalTree(relativeTo); + return TrackAttachmentToTree(relativeTo).Select(isAttachedToTree => + { + if (isAttachedToTree) + { + return relativeTo.GetVisualAncestors() + .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true) + .ElementAtOrDefault(ancestorLevel); + } + else + { + return null; + } + }); } - private static IObservable TrackAttachmentToVisualTree(IControl relativeTo) + private static IObservable TrackAttachmentToTree(IVisual relativeTo) { var attached = Observable.FromEventPattern( x => relativeTo.AttachedToVisualTree += x, @@ -137,7 +128,7 @@ namespace Avalonia.Markup return attachmentStatus; } - private static IObservable TrackAttachmentToLogicalTree(IControl relativeTo) + private static IObservable TrackAttachmentToTree(ILogical relativeTo) { var attached = Observable.FromEventPattern( x => relativeTo.AttachedToLogicalTree += x, diff --git a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs index b8c26c8d49..49be842ffc 100644 --- a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs +++ b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs @@ -3,6 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; +using Avalonia.LogicalTree; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -64,5 +67,15 @@ namespace Avalonia.Controls return (T)result; } + + public static INameScope FindNameScope(this ILogical control) + { + Contract.Requires(control != null); + + return control.GetSelfAndLogicalAncestors() + .OfType() + .Select(x => (x as INameScope) ?? NameScope.GetNameScope(x)) + .FirstOrDefault(x => x != null); + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs index 7f750144df..c440a94935 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs @@ -8,6 +8,7 @@ using System.Reactive.Linq; using System.Reflection; using Avalonia.Controls; using Avalonia.Data; +using Avalonia.LogicalTree; using Avalonia.Markup.Data; using Avalonia.VisualTree; @@ -234,8 +235,28 @@ namespace Avalonia.Markup.Xaml.Data { Contract.Requires(target != null); + IObservable controlLocator; + + switch (relativeSource.Tree) + { + case TreeType.Logical: + controlLocator = ControlLocator.Track( + (ILogical)target, + relativeSource.AncestorLevel - 1, + relativeSource.AncestorType); + break; + case TreeType.Visual: + controlLocator = ControlLocator.Track( + (IVisual)target, + relativeSource.AncestorLevel - 1, + relativeSource.AncestorType); + break; + default: + throw new InvalidOperationException("Invalid tree to traverse."); + } + return new ExpressionObserver( - ControlLocator.Track(target, relativeSource.Tree, relativeSource.AncestorLevel - 1, relativeSource.AncestorType), + controlLocator, path, enableDataValidation); } diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index a3b7d07784..56b8d46b64 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -4,12 +4,6 @@ - - - - - - From cf8244b37ab60aea01e91c5632b068bcaad4d3c1 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 26 May 2018 22:15:42 -0500 Subject: [PATCH 03/16] Move current contents of Avalonia.Markup to Avalonia.Base. Move Avalonia.Markup.Xaml.Data/Parser classes to Avalonia.Markup since they're not Xaml-specific. Use pattern-matching switch. Fix bug from cleanup. --- Avalonia.sln | 2 + build/Binding.props | 8 + .../Pages/AutoCompleteBoxPage.xaml.cs | 3 +- .../Resources/Resource.Designer.cs | 7 +- src/Avalonia.Base/Avalonia.Base.csproj | 1 + .../Collections/AvaloniaDictionary.cs | 5 +- src/Avalonia.Base/Data/BindingNotification.cs | 3 +- .../AlwaysEnabledDelegateCommand.cs | 2 +- .../Data/Converters}/BoolConverters.cs | 2 +- .../Data/Converters}/DefaultValueConverter.cs | 2 +- .../Converters}/FuncMultiValueConverter.cs | 2 +- .../Data/Converters}/FuncValueConverter.cs | 2 +- .../Data/Converters}/IMultiValueConverter.cs | 2 +- .../Data/Converters}/IValueConverter.cs | 2 +- .../Data/Converters}/StringConverters.cs | 2 +- .../Data/Core}/BindingExpression.cs | 4 +- .../Data/Core}/CommonPropertyNames.cs | 2 +- .../Data/Core}/EmptyExpressionNode.cs | 2 +- .../Data/Core}/ExpressionNode.cs | 2 +- .../Data/Core}/ExpressionNodeBuilder.cs | 4 +- .../Data/Core}/ExpressionObserver.cs | 45 +- .../Data/Core}/ExpressionParseException.cs | 4 +- .../Data/Core}/ISettableNode.cs | 2 +- .../Data/Core}/ITransformNode.cs | 2 +- .../Data/Core}/IndexerNode.cs | 2 +- .../Data/Core}/LogicalNotNode.cs | 2 +- .../Data/Core}/MarkupBindingChainException.cs | 3 +- .../Data/Core}/Parsers/ArgumentListParser.cs | 2 +- .../Data/Core}/Parsers/ExpressionParser.cs | 2 +- .../Data/Core}/Parsers/IdentifierParser.cs | 2 +- .../Data/Core}/Parsers/Reader.cs | 2 +- .../Plugins/AvaloniaPropertyAccessorPlugin.cs | 2 +- .../DataAnnotationsValidationPlugin.cs | 2 +- .../Data/Core}/Plugins/DataValidatiorBase.cs | 2 +- .../Plugins/ExceptionValidationPlugin.cs | 2 +- .../Core}/Plugins/IDataValidationPlugin.cs | 2 +- .../Data/Core}/Plugins/IPropertyAccessor.cs | 2 +- .../Core}/Plugins/IPropertyAccessorPlugin.cs | 2 +- .../Data/Core}/Plugins/IStreamPlugin.cs | 2 +- .../Core}/Plugins/IndeiValidationPlugin.cs | 2 +- .../Plugins/InpcPropertyAccessorPlugin.cs | 2 +- .../Core}/Plugins/MethodAccessorPlugin.cs | 2 +- .../Core}/Plugins/ObservableStreamPlugin.cs | 2 +- .../Core}/Plugins/PropertyAccessorBase.cs | 2 +- .../Data/Core}/Plugins/PropertyError.cs | 2 +- .../Data/Core}/Plugins/TaskStreamPlugin.cs | 2 +- .../Data/Core}/PropertyAccessorNode.cs | 4 +- .../Data/Core}/StreamNode.cs | 2 +- src/Avalonia.Base/Properties/AssemblyInfo.cs | 3 +- .../Avalonia.Markup.Xaml.csproj | 6 +- .../AvaloniaXamlLoader.cs | 2 +- .../AvaloniaPropertyTypeConverter.cs | 2 +- .../Converters/SelectorTypeConverter.cs | 4 +- .../MarkupExtensions/BindingExtension.cs | 3 +- .../RelativeSourceExtension.cs | 2 +- .../ResourceInclude.cs | 2 +- .../StaticResourceExtension.cs | 2 +- .../TemplateBindingExtension.cs | 5 +- .../AvaloniaRuntimeTypeProvider.cs | 3 +- .../PortableXaml/AvaloniaXamlSchemaContext.cs | 2 +- .../PortableXaml/AvaloniaXamlType.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 - .../Templates/MemberSelector.cs | 2 +- .../Templates/TreeDataTemplate.cs | 7 +- .../Avalonia.Markup/Avalonia.Markup.csproj | 6 +- .../Data/Binding.cs | 7 +- .../Data/DelayedBinding.cs | 2 +- .../Data/MultiBinding.cs | 3 +- .../Data/RelativeSource.cs | 3 +- .../Parsers/SelectorGrammar.cs | 2 +- .../Parsers/SelectorParser.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../AvaloniaObjectTests_Binding.cs | 2 +- .../AvaloniaObjectTests_Direct.cs | 2 +- .../Collections/AvaloniaDictionaryTests.cs | 3 +- .../Data/Core}/BindingExpressionTests.cs | 5 +- .../Data/Core}/ExpressionNodeBuilderTests.cs | 4 +- .../ExpressionNodeBuilderTests_Errors.cs | 4 +- ...xpressionObserverTests_AttachedProperty.cs | 4 +- ...xpressionObserverTests_AvaloniaProperty.cs | 4 +- .../ExpressionObserverTests_DataValidation.cs | 4 +- .../Core}/ExpressionObserverTests_Indexer.cs | 4 +- .../Core}/ExpressionObserverTests_Lifetime.cs | 4 +- .../Core}/ExpressionObserverTests_Method.cs | 4 +- .../Core}/ExpressionObserverTests_Negation.cs | 4 +- .../ExpressionObserverTests_Observable.cs | 4 +- .../Core}/ExpressionObserverTests_Property.cs | 6 +- .../Core}/ExpressionObserverTests_SetValue.cs | 4 +- .../Core}/ExpressionObserverTests_Task.cs | 4 +- .../Data/Core}/IndeiBase.cs | 2 +- .../DataAnnotationsValidationPluginTests.cs | 2 +- .../Plugins/ExceptionValidationPluginTests.cs | 4 +- .../Plugins/IndeiValidationPluginTests.cs | 4 +- .../Data}/DefaultValueConverterTests.cs | 3 +- .../Data}/UnitTestSynchronizationContext.cs | 2 +- .../AutoCompleteBoxTests.cs | 2 +- .../ButtonTests.cs | 2 +- .../ContentControlTests.cs | 2 +- .../DatePickerTests.cs | 2 +- .../ListBoxTests_Single.cs | 2 +- .../Primitives/RangeBaseTests.cs | 2 +- .../Primitives/SelectingItemsControlTests.cs | 2 +- .../SelectingItemsControlTests_Multiple.cs | 2 +- .../Primitives/ToggleButtonTests.cs | 2 +- .../RadioButtonTests.cs | 2 +- .../TextBoxTests.cs | 2 +- .../TextBoxTests_DataValidation.cs | 2 +- .../TreeViewTests.cs | 1 + tests/Avalonia.LeakTests/ControlTests.cs | 2 +- .../ExpressionObserverTests.cs | 1 + .../Data/BindingTests.cs | 548 ++++++++++++++++++ .../Data/BindingTests_DataValidation.cs | 4 +- .../Data/BindingTests_ElementName.cs | 4 +- .../Data/BindingTests_RelativeSource.cs | 4 +- .../Data/BindingTests_Self.cs | 4 +- .../Data/BindingTests_Source.cs | 3 +- .../Data/BindingTests_TemplatedParent.cs | 76 +++ .../Data/MultiBindingTests.cs | 5 +- .../Parsers/SelectorGrammarTests.cs | 4 +- .../Parsers/SelectorParserTests.cs | 2 +- .../Data/BindingTests.cs | 532 +---------------- .../Data/BindingTests_TemplatedParent.cs | 55 -- .../DynamicResourceExtensionTests.cs | 2 +- .../ResourceIncludeTests.cs | 2 +- .../MarkupExtensions/TestValueConverter.cs | 1 + .../StyleTests.cs | 2 +- .../Xaml/BasicTests.cs | 3 +- .../Xaml/StyleTests.cs | 2 +- .../Xaml/TreeDataTemplateTests.cs | 2 +- .../ControlLocatorTests.cs | 9 +- .../Avalonia.Styling.UnitTests/SetterTests.cs | 3 +- 131 files changed, 841 insertions(+), 776 deletions(-) create mode 100644 build/Binding.props rename src/{Markup/Avalonia.Markup => Avalonia.Base/Data/Converters}/AlwaysEnabledDelegateCommand.cs (97%) rename src/{Markup/Avalonia.Markup => Avalonia.Base/Data/Converters}/BoolConverters.cs (94%) rename src/{Markup/Avalonia.Markup => Avalonia.Base/Data/Converters}/DefaultValueConverter.cs (98%) rename src/{Markup/Avalonia.Markup => Avalonia.Base/Data/Converters}/FuncMultiValueConverter.cs (97%) rename src/{Markup/Avalonia.Markup => Avalonia.Base/Data/Converters}/FuncValueConverter.cs (97%) rename src/{Markup/Avalonia.Markup => Avalonia.Base/Data/Converters}/IMultiValueConverter.cs (97%) rename src/{Markup/Avalonia.Markup => Avalonia.Base/Data/Converters}/IValueConverter.cs (98%) rename src/{Markup/Avalonia.Markup => Avalonia.Base/Data/Converters}/StringConverters.cs (96%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/BindingExpression.cs (99%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/CommonPropertyNames.cs (89%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/EmptyExpressionNode.cs (94%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/ExpressionNode.cs (99%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/ExpressionNodeBuilder.cs (92%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/ExpressionObserver.cs (90%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/ExpressionParseException.cs (93%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/ISettableNode.cs (90%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/ITransformNode.cs (83%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/IndexerNode.cs (99%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/LogicalNotNode.cs (98%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/MarkupBindingChainException.cs (95%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Parsers/ArgumentListParser.cs (97%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Parsers/ExpressionParser.cs (99%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Parsers/IdentifierParser.cs (97%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Parsers/Reader.cs (95%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/AvaloniaPropertyAccessorPlugin.cs (99%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/DataAnnotationsValidationPlugin.cs (98%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/DataValidatiorBase.cs (98%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/ExceptionValidationPlugin.cs (97%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/IDataValidationPlugin.cs (97%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/IPropertyAccessor.cs (97%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/IPropertyAccessorPlugin.cs (97%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/IStreamPlugin.cs (96%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/IndeiValidationPlugin.cs (99%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/InpcPropertyAccessorPlugin.cs (99%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/MethodAccessorPlugin.cs (98%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/ObservableStreamPlugin.cs (96%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/PropertyAccessorBase.cs (98%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/PropertyError.cs (96%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/Plugins/TaskStreamPlugin.cs (98%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/PropertyAccessorNode.cs (96%) rename src/{Markup/Avalonia.Markup/Data => Avalonia.Base/Data/Core}/StreamNode.cs (96%) rename src/Markup/Avalonia.Markup.Xaml/{Data => MarkupExtensions}/ResourceInclude.cs (97%) rename src/Markup/{Avalonia.Markup.Xaml => Avalonia.Markup}/Data/Binding.cs (98%) rename src/Markup/{Avalonia.Markup.Xaml => Avalonia.Markup}/Data/DelayedBinding.cs (99%) rename src/Markup/{Avalonia.Markup.Xaml => Avalonia.Markup}/Data/MultiBinding.cs (98%) rename src/Markup/{Avalonia.Markup.Xaml => Avalonia.Markup}/Data/RelativeSource.cs (98%) rename src/Markup/{Avalonia.Markup.Xaml => Avalonia.Markup}/Parsers/SelectorGrammar.cs (99%) rename src/Markup/{Avalonia.Markup.Xaml => Avalonia.Markup}/Parsers/SelectorParser.cs (99%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/BindingExpressionTests.cs (99%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionNodeBuilderTests.cs (98%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionNodeBuilderTests_Errors.cs (96%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionObserverTests_AttachedProperty.cs (98%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionObserverTests_AvaloniaProperty.cs (97%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionObserverTests_DataValidation.cs (99%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionObserverTests_Indexer.cs (99%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionObserverTests_Lifetime.cs (98%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionObserverTests_Method.cs (97%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionObserverTests_Negation.cs (98%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionObserverTests_Observable.cs (98%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionObserverTests_Property.cs (99%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionObserverTests_SetValue.cs (97%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/ExpressionObserverTests_Task.cs (98%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/IndeiBase.cs (96%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/Plugins/DataAnnotationsValidationPluginTests.cs (99%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/Plugins/ExceptionValidationPluginTests.cs (96%) rename tests/{Avalonia.Markup.UnitTests/Data => Avalonia.Base.UnitTests/Data/Core}/Plugins/IndeiValidationPluginTests.cs (98%) rename tests/{Avalonia.Markup.UnitTests => Avalonia.Base.UnitTests/Data}/DefaultValueConverterTests.cs (98%) rename tests/{Avalonia.Markup.UnitTests => Avalonia.Base.UnitTests/Data}/UnitTestSynchronizationContext.cs (98%) create mode 100644 tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs rename tests/{Avalonia.Markup.Xaml.UnitTests => Avalonia.Markup.UnitTests}/Data/BindingTests_DataValidation.cs (96%) rename tests/{Avalonia.Markup.Xaml.UnitTests => Avalonia.Markup.UnitTests}/Data/BindingTests_ElementName.cs (98%) rename tests/{Avalonia.Markup.Xaml.UnitTests => Avalonia.Markup.UnitTests}/Data/BindingTests_RelativeSource.cs (98%) rename tests/{Avalonia.Markup.Xaml.UnitTests => Avalonia.Markup.UnitTests}/Data/BindingTests_Self.cs (96%) rename tests/{Avalonia.Markup.Xaml.UnitTests => Avalonia.Markup.UnitTests}/Data/BindingTests_Source.cs (94%) create mode 100644 tests/Avalonia.Markup.UnitTests/Data/BindingTests_TemplatedParent.cs rename tests/{Avalonia.Markup.Xaml.UnitTests => Avalonia.Markup.UnitTests}/Data/MultiBindingTests.cs (96%) rename tests/{Avalonia.Markup.Xaml.UnitTests => Avalonia.Markup.UnitTests}/Parsers/SelectorGrammarTests.cs (98%) rename tests/{Avalonia.Markup.Xaml.UnitTests => Avalonia.Markup.UnitTests}/Parsers/SelectorParserTests.cs (91%) rename tests/Avalonia.Markup.Xaml.UnitTests/{Data => MarkupExtensions}/ResourceIncludeTests.cs (96%) rename tests/{Avalonia.Markup.UnitTests => Avalonia.Styling.UnitTests}/ControlLocatorTests.cs (95%) diff --git a/Avalonia.sln b/Avalonia.sln index 47ee4c1ad9..91413fc5ee 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -138,6 +138,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{74487168 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}" ProjectSection(SolutionItems) = preProject + build\Base.props = build\Base.props + build\Binding.props = build\Binding.props build\JetBrains.Annotations.props = build\JetBrains.Annotations.props build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props diff --git a/build/Binding.props b/build/Binding.props new file mode 100644 index 0000000000..a512ee1d9c --- /dev/null +++ b/build/Binding.props @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs index 6f3b8361cd..65be8c801e 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs @@ -2,12 +2,13 @@ using Avalonia.Controls; using Avalonia.LogicalTree; using Avalonia.Markup; using Avalonia.Markup.Xaml; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Avalonia.Data.Converters; namespace ControlCatalog.Pages { diff --git a/src/Android/Avalonia.Android/Resources/Resource.Designer.cs b/src/Android/Avalonia.Android/Resources/Resource.Designer.cs index 80cbbc51ec..e66c2800d3 100644 --- a/src/Android/Avalonia.Android/Resources/Resource.Designer.cs +++ b/src/Android/Avalonia.Android/Resources/Resource.Designer.cs @@ -40,11 +40,14 @@ namespace Avalonia.Android public partial class String { + // aapt resource value: 0x7f020002 + public static int ApplicationName = 2130837506; + // aapt resource value: 0x7f020001 - public static int ApplicationName = 2130837505; + public static int Hello = 2130837505; // aapt resource value: 0x7f020000 - public static int Hello = 2130837504; + public static int library_name = 2130837504; static String() { diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 35adcbeb92..26397a6f32 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -5,6 +5,7 @@ Avalonia + \ No newline at end of file diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs index 84ac85d3db..e8dc2a5ed7 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs +++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; +using Avalonia.Data.Core; namespace Avalonia.Collections { @@ -116,8 +117,8 @@ namespace Avalonia.Collections _inner = new Dictionary(); - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(CommonPropertyNames.IndexerName)); if (CollectionChanged != null) diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs index 5510a73b91..5d3e6b26f4 100644 --- a/src/Avalonia.Base/Data/BindingNotification.cs +++ b/src/Avalonia.Base/Data/BindingNotification.cs @@ -171,8 +171,7 @@ namespace Avalonia.Data /// public static object ExtractError(object o) { - var notification = o as BindingNotification; - return notification != null ? notification.Error : o; + return o is BindingNotification notification ? notification.Error : o; } /// diff --git a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs b/src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs similarity index 97% rename from src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs rename to src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs index 48db99b43c..d2fb48ffb8 100644 --- a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs +++ b/src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs @@ -6,7 +6,7 @@ using System.Reflection; using System.Text; using System.Windows.Input; -namespace Avalonia.Markup +namespace Avalonia.Data.Converters { class AlwaysEnabledDelegateCommand : ICommand { diff --git a/src/Markup/Avalonia.Markup/BoolConverters.cs b/src/Avalonia.Base/Data/Converters/BoolConverters.cs similarity index 94% rename from src/Markup/Avalonia.Markup/BoolConverters.cs rename to src/Avalonia.Base/Data/Converters/BoolConverters.cs index e049dccc06..6b429e1087 100644 --- a/src/Markup/Avalonia.Markup/BoolConverters.cs +++ b/src/Avalonia.Base/Data/Converters/BoolConverters.cs @@ -3,7 +3,7 @@ using System.Linq; -namespace Avalonia.Markup +namespace Avalonia.Data.Converters { /// /// Provides a set of useful s for working with string values. diff --git a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs similarity index 98% rename from src/Markup/Avalonia.Markup/DefaultValueConverter.cs rename to src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs index 28d64eb561..ec75076892 100644 --- a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs @@ -7,7 +7,7 @@ using Avalonia.Data; using Avalonia.Utilities; using System.Windows.Input; -namespace Avalonia.Markup +namespace Avalonia.Data.Converters { /// /// Provides a default set of value conversions for bindings that do not specify a value diff --git a/src/Markup/Avalonia.Markup/FuncMultiValueConverter.cs b/src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs similarity index 97% rename from src/Markup/Avalonia.Markup/FuncMultiValueConverter.cs rename to src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs index 75aace0bd9..6e1c4cb0e3 100644 --- a/src/Markup/Avalonia.Markup/FuncMultiValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -namespace Avalonia.Markup +namespace Avalonia.Data.Converters { /// /// A general purpose that uses a diff --git a/src/Markup/Avalonia.Markup/FuncValueConverter.cs b/src/Avalonia.Base/Data/Converters/FuncValueConverter.cs similarity index 97% rename from src/Markup/Avalonia.Markup/FuncValueConverter.cs rename to src/Avalonia.Base/Data/Converters/FuncValueConverter.cs index 109de9371d..b747587b4a 100644 --- a/src/Markup/Avalonia.Markup/FuncValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/FuncValueConverter.cs @@ -5,7 +5,7 @@ using System; using System.Globalization; using Avalonia.Utilities; -namespace Avalonia.Markup +namespace Avalonia.Data.Converters { /// /// A general purpose that uses a diff --git a/src/Markup/Avalonia.Markup/IMultiValueConverter.cs b/src/Avalonia.Base/Data/Converters/IMultiValueConverter.cs similarity index 97% rename from src/Markup/Avalonia.Markup/IMultiValueConverter.cs rename to src/Avalonia.Base/Data/Converters/IMultiValueConverter.cs index be75dabb1c..3f84fcb3e7 100644 --- a/src/Markup/Avalonia.Markup/IMultiValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/IMultiValueConverter.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; -namespace Avalonia.Markup +namespace Avalonia.Data.Converters { /// /// Converts multi-binding inputs to a final value. diff --git a/src/Markup/Avalonia.Markup/IValueConverter.cs b/src/Avalonia.Base/Data/Converters/IValueConverter.cs similarity index 98% rename from src/Markup/Avalonia.Markup/IValueConverter.cs rename to src/Avalonia.Base/Data/Converters/IValueConverter.cs index 10d5c626c2..b55a2c4fe8 100644 --- a/src/Markup/Avalonia.Markup/IValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/IValueConverter.cs @@ -5,7 +5,7 @@ using System; using System.Globalization; using Avalonia.Data; -namespace Avalonia.Markup +namespace Avalonia.Data.Converters { /// /// Converts a binding value. diff --git a/src/Markup/Avalonia.Markup/StringConverters.cs b/src/Avalonia.Base/Data/Converters/StringConverters.cs similarity index 96% rename from src/Markup/Avalonia.Markup/StringConverters.cs rename to src/Avalonia.Base/Data/Converters/StringConverters.cs index fda79c76a3..470f0d2289 100644 --- a/src/Markup/Avalonia.Markup/StringConverters.cs +++ b/src/Avalonia.Base/Data/Converters/StringConverters.cs @@ -5,7 +5,7 @@ using System; using System.Globalization; using Avalonia.Utilities; -namespace Avalonia.Markup +namespace Avalonia.Data.Converters { /// /// Provides a set of useful s for working with string values. diff --git a/src/Markup/Avalonia.Markup/Data/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/BindingExpression.cs rename to src/Avalonia.Base/Data/Core/BindingExpression.cs index 5b9959e42e..4b41d1568c 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -5,11 +5,11 @@ using System; using System.Globalization; using System.Reactive.Linq; using System.Reactive.Subjects; -using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Logging; using Avalonia.Utilities; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { /// /// Binds to an expression on an object using a type value converter to convert the values diff --git a/src/Markup/Avalonia.Markup/Data/CommonPropertyNames.cs b/src/Avalonia.Base/Data/Core/CommonPropertyNames.cs similarity index 89% rename from src/Markup/Avalonia.Markup/Data/CommonPropertyNames.cs rename to src/Avalonia.Base/Data/Core/CommonPropertyNames.cs index f91940baf7..6760c3f259 100644 --- a/src/Markup/Avalonia.Markup/Data/CommonPropertyNames.cs +++ b/src/Avalonia.Base/Data/Core/CommonPropertyNames.cs @@ -1,7 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { public static class CommonPropertyNames { diff --git a/src/Markup/Avalonia.Markup/Data/EmptyExpressionNode.cs b/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs similarity index 94% rename from src/Markup/Avalonia.Markup/Data/EmptyExpressionNode.cs rename to src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs index 02ecd817da..93e0d5947a 100644 --- a/src/Markup/Avalonia.Markup/Data/EmptyExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs @@ -4,7 +4,7 @@ using System; using System.Reactive.Linq; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { internal class EmptyExpressionNode : ExpressionNode { diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/ExpressionNode.cs rename to src/Avalonia.Base/Data/Core/ExpressionNode.cs index 56c0072eaa..ae70cacdba 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -7,7 +7,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using Avalonia.Data; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { internal abstract class ExpressionNode : ISubject { diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionNodeBuilder.cs b/src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs similarity index 92% rename from src/Markup/Avalonia.Markup/Data/ExpressionNodeBuilder.cs rename to src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs index 013299c1d7..8e9e9fc3c1 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionNodeBuilder.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs @@ -2,9 +2,9 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using Avalonia.Markup.Data.Parsers; +using Avalonia.Data.Core.Parsers; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { internal static class ExpressionNodeBuilder { diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs similarity index 90% rename from src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs rename to src/Avalonia.Base/Data/Core/ExpressionObserver.cs index dd9718a0f6..7719f93a02 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs @@ -8,9 +8,9 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; using Avalonia.Data; -using Avalonia.Markup.Data.Plugins; +using Avalonia.Data.Core.Plugins; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { /// /// Observes and sets the value of an expression on an object. @@ -245,40 +245,35 @@ namespace Avalonia.Markup.Data private object Translate(object o) { - var weak = o as WeakReference; - - if (weak != null) + if (o is WeakReference weak) { return weak.Target; } - else + else if (BindingNotification.ExtractError(o) is MarkupBindingChainException broken) { - var broken = BindingNotification.ExtractError(o) as MarkupBindingChainException; - - if (broken != null) - { - broken.Commit(Description); - } - return o; + broken.Commit(Description); } + + return o; } private IDisposable StartRoot() { - var observable = _root as IObservable; - - if (observable != null) + switch (_root) { - return observable.Subscribe( - x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null), - _ => _finished.OnNext(Unit.Default), - () => _finished.OnNext(Unit.Default)); - } - else - { - _node.Target = (WeakReference)_root; - return Disposable.Empty; + case IObservable observable: + return observable.Subscribe( + x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null), + _ => _finished.OnNext(Unit.Default), + () => _finished.OnNext(Unit.Default)); + case WeakReference weak: + _node.Target = weak; + break; + default: + throw new AvaloniaInternalException("The ExpressionObserver._root member should only be either an observable or WeakReference."); } + + return Disposable.Empty; } } } diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionParseException.cs b/src/Avalonia.Base/Data/Core/ExpressionParseException.cs similarity index 93% rename from src/Markup/Avalonia.Markup/Data/ExpressionParseException.cs rename to src/Avalonia.Base/Data/Core/ExpressionParseException.cs index d06bdd1e52..3d7bce4080 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionParseException.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionParseException.cs @@ -2,9 +2,9 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using Avalonia.Markup.Data.Parsers; +using Avalonia.Data.Core.Parsers; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { /// /// Exception thrown when could not parse the provided diff --git a/src/Markup/Avalonia.Markup/Data/ISettableNode.cs b/src/Avalonia.Base/Data/Core/ISettableNode.cs similarity index 90% rename from src/Markup/Avalonia.Markup/Data/ISettableNode.cs rename to src/Avalonia.Base/Data/Core/ISettableNode.cs index 8ee4f1de20..7788407833 100644 --- a/src/Markup/Avalonia.Markup/Data/ISettableNode.cs +++ b/src/Avalonia.Base/Data/Core/ISettableNode.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { interface ISettableNode { diff --git a/src/Markup/Avalonia.Markup/Data/ITransformNode.cs b/src/Avalonia.Base/Data/Core/ITransformNode.cs similarity index 83% rename from src/Markup/Avalonia.Markup/Data/ITransformNode.cs rename to src/Avalonia.Base/Data/Core/ITransformNode.cs index f33ecd3722..7638db8302 100644 --- a/src/Markup/Avalonia.Markup/Data/ITransformNode.cs +++ b/src/Avalonia.Base/Data/Core/ITransformNode.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { interface ITransformNode { diff --git a/src/Markup/Avalonia.Markup/Data/IndexerNode.cs b/src/Avalonia.Base/Data/Core/IndexerNode.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/IndexerNode.cs rename to src/Avalonia.Base/Data/Core/IndexerNode.cs index 4e2914a148..47e82fa2d3 100644 --- a/src/Markup/Avalonia.Markup/Data/IndexerNode.cs +++ b/src/Avalonia.Base/Data/Core/IndexerNode.cs @@ -13,7 +13,7 @@ using System.Reflection; using System.Reactive.Linq; using Avalonia.Data; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { internal class IndexerNode : ExpressionNode, ISettableNode { diff --git a/src/Markup/Avalonia.Markup/Data/LogicalNotNode.cs b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs similarity index 98% rename from src/Markup/Avalonia.Markup/Data/LogicalNotNode.cs rename to src/Avalonia.Base/Data/Core/LogicalNotNode.cs index ae68867e82..f277005cec 100644 --- a/src/Markup/Avalonia.Markup/Data/LogicalNotNode.cs +++ b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs @@ -5,7 +5,7 @@ using System; using System.Globalization; using Avalonia.Data; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { internal class LogicalNotNode : ExpressionNode, ITransformNode { diff --git a/src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs b/src/Avalonia.Base/Data/Core/MarkupBindingChainException.cs similarity index 95% rename from src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs rename to src/Avalonia.Base/Data/Core/MarkupBindingChainException.cs index ddfcf531eb..a9b31c7617 100644 --- a/src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs +++ b/src/Avalonia.Base/Data/Core/MarkupBindingChainException.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using Avalonia.Data; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { internal class MarkupBindingChainException : BindingChainException { diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs b/src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs similarity index 97% rename from src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs rename to src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs index 05b3b29be0..17200a62b1 100644 --- a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs +++ b/src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Text; -namespace Avalonia.Markup.Data.Parsers +namespace Avalonia.Data.Core.Parsers { internal static class ArgumentListParser { diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs b/src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs rename to src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs index f31f6eccb7..5c74c5cd13 100644 --- a/src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs +++ b/src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace Avalonia.Markup.Data.Parsers +namespace Avalonia.Data.Core.Parsers { internal class ExpressionParser { diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/IdentifierParser.cs b/src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs similarity index 97% rename from src/Markup/Avalonia.Markup/Data/Parsers/IdentifierParser.cs rename to src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs index fa9051af06..b0a9ff4df2 100644 --- a/src/Markup/Avalonia.Markup/Data/Parsers/IdentifierParser.cs +++ b/src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs @@ -4,7 +4,7 @@ using System.Globalization; using System.Text; -namespace Avalonia.Markup.Data.Parsers +namespace Avalonia.Data.Core.Parsers { internal static class IdentifierParser { diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/Reader.cs b/src/Avalonia.Base/Data/Core/Parsers/Reader.cs similarity index 95% rename from src/Markup/Avalonia.Markup/Data/Parsers/Reader.cs rename to src/Avalonia.Base/Data/Core/Parsers/Reader.cs index 414648a10c..14187c769a 100644 --- a/src/Markup/Avalonia.Markup/Data/Parsers/Reader.cs +++ b/src/Avalonia.Base/Data/Core/Parsers/Reader.cs @@ -3,7 +3,7 @@ using System; -namespace Avalonia.Markup.Data.Parsers +namespace Avalonia.Data.Core.Parsers { internal class Reader { diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs index ac64459dd7..8cbcaa8233 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Reactive.Linq; using Avalonia.Data; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Reads a property from a . diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/DataAnnotationsValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs similarity index 98% rename from src/Markup/Avalonia.Markup/Data/Plugins/DataAnnotationsValidationPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs index 859438636a..47e4d91e13 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/DataAnnotationsValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs @@ -8,7 +8,7 @@ using System.Linq; using System.Reflection; using Avalonia.Data; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Validates properties on that have s. diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/DataValidatiorBase.cs b/src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs similarity index 98% rename from src/Markup/Avalonia.Markup/Data/Plugins/DataValidatiorBase.cs rename to src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs index 95d269f437..bd429f04d6 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/DataValidatiorBase.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs @@ -4,7 +4,7 @@ using System; using Avalonia.Data; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Base class for data validators. diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/ExceptionValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs similarity index 97% rename from src/Markup/Avalonia.Markup/Data/Plugins/ExceptionValidationPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs index e0b6bcfd7c..35f9f7e59a 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/ExceptionValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs @@ -5,7 +5,7 @@ using Avalonia.Data; using System; using System.Reflection; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Validates properties that report errors by throwing exceptions. diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/IDataValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs similarity index 97% rename from src/Markup/Avalonia.Markup/Data/Plugins/IDataValidationPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs index 0952e2edab..c55917b088 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/IDataValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs @@ -4,7 +4,7 @@ using System; using Avalonia.Data; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Defines how data validation is observed by an . diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessor.cs b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs similarity index 97% rename from src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessor.cs rename to src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs index 9e686baf10..d7dda57a72 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessor.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs @@ -4,7 +4,7 @@ using System; using Avalonia.Data; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Defines an accessor to a property on an object returned by a diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs similarity index 97% rename from src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessorPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs index ebfdf6ebe4..539f518083 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs @@ -3,7 +3,7 @@ using System; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Defines how a member is read, written and observed by an diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/IStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs similarity index 96% rename from src/Markup/Avalonia.Markup/Data/Plugins/IStreamPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs index efb2e2d93a..b80d9d75c8 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/IStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs @@ -3,7 +3,7 @@ using System; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Defines a plugin that handles the '^' stream binding operator. diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/IndeiValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/Plugins/IndeiValidationPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs index 82bc87c207..436046f3fa 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/IndeiValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs @@ -8,7 +8,7 @@ using System.Linq; using Avalonia.Data; using Avalonia.Utilities; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Validates properties on objects that implement . diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index 3bdaba6fd9..ba4e60eb74 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -10,7 +10,7 @@ using Avalonia.Data; using Avalonia.Logging; using Avalonia.Utilities; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Reads a property from a standard C# object that optionally supports the diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs similarity index 98% rename from src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index db0d3e0299..b2b3a107fa 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs @@ -5,7 +5,7 @@ using Avalonia.Data; using System.Reflection; using System.Linq; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { class MethodAccessorPlugin : IPropertyAccessorPlugin { diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/ObservableStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs similarity index 96% rename from src/Markup/Avalonia.Markup/Data/Plugins/ObservableStreamPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs index a1da42d28f..14ca8ee79e 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/ObservableStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs @@ -3,7 +3,7 @@ using System; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Handles binding to s for the '^' stream binding operator. diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/PropertyAccessorBase.cs b/src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs similarity index 98% rename from src/Markup/Avalonia.Markup/Data/Plugins/PropertyAccessorBase.cs rename to src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs index 9aa858e0eb..9cc78369a7 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/PropertyAccessorBase.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs @@ -4,7 +4,7 @@ using System; using Avalonia.Data; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Defines a default base implementation for a . diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/PropertyError.cs b/src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs similarity index 96% rename from src/Markup/Avalonia.Markup/Data/Plugins/PropertyError.cs rename to src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs index b351ef39bd..647adc36cb 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/PropertyError.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs @@ -2,7 +2,7 @@ using System; using System.Reactive.Disposables; using Avalonia.Data; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// An that represents an error. diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs similarity index 98% rename from src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs index 02fe8104b8..cc9b3abd56 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs @@ -9,7 +9,7 @@ using System.Reflection; using System.Threading.Tasks; using Avalonia.Data; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Handles binding to s for the '^' stream binding operator. diff --git a/src/Markup/Avalonia.Markup/Data/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs similarity index 96% rename from src/Markup/Avalonia.Markup/Data/PropertyAccessorNode.cs rename to src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs index f6040d3f15..4dbff4602f 100644 --- a/src/Markup/Avalonia.Markup/Data/PropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs @@ -6,9 +6,9 @@ using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia.Data; -using Avalonia.Markup.Data.Plugins; +using Avalonia.Data.Core.Plugins; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { internal class PropertyAccessorNode : ExpressionNode, ISettableNode { diff --git a/src/Markup/Avalonia.Markup/Data/StreamNode.cs b/src/Avalonia.Base/Data/Core/StreamNode.cs similarity index 96% rename from src/Markup/Avalonia.Markup/Data/StreamNode.cs rename to src/Avalonia.Base/Data/Core/StreamNode.cs index ebcbfc9598..187c79af49 100644 --- a/src/Markup/Avalonia.Markup/Data/StreamNode.cs +++ b/src/Avalonia.Base/Data/Core/StreamNode.cs @@ -6,7 +6,7 @@ using System.Globalization; using Avalonia.Data; using System.Reactive.Linq; -namespace Avalonia.Markup.Data +namespace Avalonia.Data.Core { internal class StreamNode : ExpressionNode { diff --git a/src/Avalonia.Base/Properties/AssemblyInfo.cs b/src/Avalonia.Base/Properties/AssemblyInfo.cs index 0a276aa2aa..75d58f45d5 100644 --- a/src/Avalonia.Base/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Base/Properties/AssemblyInfo.cs @@ -1,9 +1,10 @@ -// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Reflection; using System.Runtime.CompilerServices; +using Avalonia.Metadata; +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Data.Converters")] [assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.UnitTests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 8bfe8c25bd..1690ad97ae 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -32,6 +32,7 @@ + @@ -51,11 +52,11 @@ + + - - @@ -86,6 +87,5 @@ - \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index f3fac3faf7..c361372820 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Controls; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Markup.Xaml.PortableXaml; using Avalonia.Platform; using Portable.Xaml; diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs index bc3caff3b9..131cb5a20b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs @@ -10,7 +10,7 @@ namespace Avalonia.Markup.Xaml.Converters using Avalonia.Styling; using Portable.Xaml; using Portable.Xaml.ComponentModel; - using System.ComponentModel; + using System.ComponentModel; using Portable.Xaml.Markup; public class AvaloniaPropertyTypeConverter : TypeConverter diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs index 29ddcccd08..fb0131a9b4 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs @@ -3,12 +3,12 @@ using System; using System.Globalization; -using Avalonia.Markup.Xaml.Parsers; +using Avalonia.Markup.Parsers; namespace Avalonia.Markup.Xaml.Converters { using Portable.Xaml.ComponentModel; - using System.ComponentModel; + using System.ComponentModel; public class SelectorTypeConverter : TypeConverter { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs index 6e8fe1e4c0..98203deebe 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs @@ -2,12 +2,13 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Data; -using Avalonia.Markup.Xaml.Data; using System; namespace Avalonia.Markup.Xaml.MarkupExtensions { using Avalonia.Controls; + using Avalonia.Data.Converters; + using Avalonia.Markup.Data; using Avalonia.Styling; using Portable.Xaml; using Portable.Xaml.ComponentModel; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs index 0b3345d6db..c9ec973f20 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs @@ -1,7 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; namespace Avalonia.Markup.Xaml.MarkupExtensions { diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs similarity index 97% rename from src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs rename to src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs index 035765fae0..75fac8aa92 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs @@ -4,7 +4,7 @@ using Avalonia.Controls; using Portable.Xaml.ComponentModel; using Portable.Xaml.Markup; -namespace Avalonia.Markup.Xaml.Data +namespace Avalonia.Markup.Xaml.MarkupExtensions { /// /// Loads a resource dictionary from a specified URL. diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index 8e71c5f81b..dd1d697224 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Reflection; using Avalonia.Controls; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Portable.Xaml; using Portable.Xaml.ComponentModel; using Portable.Xaml.Markup; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs index c5fe83977f..c8fc8a41d1 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs @@ -2,14 +2,15 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Data; -using Avalonia.Markup.Xaml.Data; namespace Avalonia.Markup.Xaml.MarkupExtensions { using System; + using Avalonia.Data.Converters; + using Avalonia.Markup.Data; using Portable.Xaml.Markup; - [MarkupExtensionReturnType(typeof(Binding))] + [MarkupExtensionReturnType(typeof(IBinding))] public class TemplateBindingExtension : MarkupExtension { public TemplateBindingExtension() diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaRuntimeTypeProvider.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaRuntimeTypeProvider.cs index b6ffac69ab..9cb2cc6cd8 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaRuntimeTypeProvider.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaRuntimeTypeProvider.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Avalonia.Controls; +using Avalonia.Markup.Data; using Avalonia.Markup.Xaml.Templates; using Avalonia.Media; using Avalonia.Metadata; @@ -35,7 +36,7 @@ namespace Avalonia.Markup.Xaml.Context typeof(Style).GetTypeInfo().Assembly, typeof(DataTemplate).GetTypeInfo().Assembly, typeof(SolidColorBrush).GetTypeInfo().Assembly, - typeof(IValueConverter).GetTypeInfo().Assembly, + typeof(Binding).GetTypeInfo().Assembly, }; private Dictionary> _namespaces = new Dictionary>(); diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs index fda5da902a..70402aa764 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs @@ -1,6 +1,6 @@ using Avalonia.Data; using Avalonia.Markup.Xaml.Context; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Markup.Xaml.Styling; using Portable.Xaml; diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs index 59dbba7084..2194223cb7 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs @@ -1,6 +1,6 @@ using Avalonia.Controls; using Avalonia.Data; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Metadata; using Avalonia.Styling; using Portable.Xaml; diff --git a/src/Markup/Avalonia.Markup.Xaml/Properties/AssemblyInfo.cs b/src/Markup/Avalonia.Markup.Xaml/Properties/AssemblyInfo.cs index 1b0e056baf..e67e994123 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Properties/AssemblyInfo.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Properties/AssemblyInfo.cs @@ -5,10 +5,8 @@ using System.Reflection; using Avalonia.Metadata; using System.Runtime.CompilerServices; -[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.Data")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.MarkupExtensions")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.Styling")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.Templates")] -[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml", "Avalonia.Markup.Xaml.MarkupExtensions.Standard")] [assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests")] diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs index 1581765132..aa3c359953 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs @@ -3,7 +3,7 @@ using Avalonia.Controls.Templates; using Avalonia.Data; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using System; using System.Reactive.Linq; diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs index 883e177f8a..a733ef761c 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs @@ -6,8 +6,8 @@ using System.Reflection; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Data.Core; using Avalonia.Markup.Data; -using Avalonia.Markup.Xaml.Data; using Avalonia.Metadata; namespace Avalonia.Markup.Xaml.Templates @@ -48,11 +48,6 @@ namespace Avalonia.Markup.Xaml.Templates return null; } - public bool IsExpanded(object item) - { - return true; - } - public IControl Build(object data) { var visualTreeForItem = TemplateContent.Load(Content); diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index 56b8d46b64..02e49a4d73 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -4,7 +4,11 @@ + + + + - + \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs similarity index 98% rename from src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs rename to src/Markup/Avalonia.Markup/Data/Binding.cs index c440a94935..bf44912bba 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -8,11 +8,12 @@ using System.Reactive.Linq; using System.Reflection; using Avalonia.Controls; using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.Data.Core; using Avalonia.LogicalTree; -using Avalonia.Markup.Data; using Avalonia.VisualTree; -namespace Avalonia.Markup.Xaml.Data +namespace Avalonia.Markup.Data { /// /// A XAML binding. @@ -84,7 +85,7 @@ namespace Avalonia.Markup.Xaml.Data /// public object Source { get; set; } - internal WeakReference DefaultAnchor { get; set; } + public WeakReference DefaultAnchor { get; set; } /// public InstancedBinding Initiate( diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs b/src/Markup/Avalonia.Markup/Data/DelayedBinding.cs similarity index 99% rename from src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs rename to src/Markup/Avalonia.Markup/Data/DelayedBinding.cs index 5ec40fe702..df16c02dab 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/DelayedBinding.cs @@ -9,7 +9,7 @@ using Avalonia.Controls; using Avalonia.Data; using Avalonia.Logging; -namespace Avalonia.Markup.Xaml.Data +namespace Avalonia.Markup.Data { /// /// Provides delayed bindings for controls. diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs similarity index 98% rename from src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs rename to src/Markup/Avalonia.Markup/Data/MultiBinding.cs index 3c29ed3a39..4c7d9396ac 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -9,9 +9,10 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using Avalonia.Controls; using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Metadata; -namespace Avalonia.Markup.Xaml.Data +namespace Avalonia.Markup.Data { /// /// A XAML binding that calculates an aggregate value from multiple child . diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs b/src/Markup/Avalonia.Markup/Data/RelativeSource.cs similarity index 98% rename from src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs rename to src/Markup/Avalonia.Markup/Data/RelativeSource.cs index 825d3b8ba5..ffddb7ee03 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs +++ b/src/Markup/Avalonia.Markup/Data/RelativeSource.cs @@ -2,8 +2,9 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using Avalonia.Controls; -namespace Avalonia.Markup.Xaml.Data +namespace Avalonia.Markup.Data { /// /// Defines the mode of a object. diff --git a/src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Parsers/SelectorGrammar.cs similarity index 99% rename from src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorGrammar.cs rename to src/Markup/Avalonia.Markup/Parsers/SelectorGrammar.cs index 671fdfff30..014f08d995 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Parsers/SelectorGrammar.cs @@ -9,7 +9,7 @@ using Sprache; // only reason they have overridden Equals methods is for unit testing. #pragma warning disable 659 -namespace Avalonia.Markup.Xaml.Parsers +namespace Avalonia.Markup.Parsers { internal class SelectorGrammar { diff --git a/src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Parsers/SelectorParser.cs similarity index 99% rename from src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorParser.cs rename to src/Markup/Avalonia.Markup/Parsers/SelectorParser.cs index 1cecb21f17..e50056ddef 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup/Parsers/SelectorParser.cs @@ -7,7 +7,7 @@ using Avalonia.Styling; using Avalonia.Utilities; using Sprache; -namespace Avalonia.Markup.Xaml.Parsers +namespace Avalonia.Markup.Parsers { /// /// Parses a from text. diff --git a/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs b/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs index 15c0be673a..28f0fd48bd 100644 --- a/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs +++ b/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs @@ -5,5 +5,5 @@ using System.Reflection; using Avalonia.Metadata; using System.Runtime.CompilerServices; -[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Data")] [assembly: InternalsVisibleTo("Avalonia.Markup.UnitTests")] diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 80cd52d529..02fb1f11ad 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Logging; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Platform; using Avalonia.Threading; using Avalonia.UnitTests; diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 5cc5bae8b0..980cbfaaf8 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -12,7 +12,7 @@ using Avalonia.Data; using Avalonia.Logging; using Avalonia.Platform; using Avalonia.Threading; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.UnitTests; using Moq; using Xunit; diff --git a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaDictionaryTests.cs b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaDictionaryTests.cs index 0f08c856fe..b6ab7dac34 100644 --- a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaDictionaryTests.cs +++ b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaDictionaryTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using Avalonia.Collections; +using Avalonia.Data.Core; using Xunit; namespace Avalonia.Base.UnitTests.Collections @@ -145,7 +146,7 @@ namespace Avalonia.Base.UnitTests.Collections var tracker = new PropertyChangedTracker(target); target.Clear(); - Assert.Equal(new[] { "Count", "Item[]" }, tracker.Names); + Assert.Equal(new[] { "Count", CommonPropertyNames.IndexerName }, tracker.Names); } } } diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs similarity index 99% rename from tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs index 2e0f048e21..6b71d28e22 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs @@ -8,12 +8,13 @@ using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using Avalonia.Data; -using Avalonia.Markup.Data; +using Avalonia.Data.Converters; +using Avalonia.Data.Core; using Avalonia.UnitTests; using Moq; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class BindingExpressionTests : IClassFixture { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionNodeBuilderTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests.cs similarity index 98% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionNodeBuilderTests.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests.cs index 4e583191a5..146b7cace1 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionNodeBuilderTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Linq; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionNodeBuilderTests { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionNodeBuilderTests_Errors.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests_Errors.cs similarity index 96% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionNodeBuilderTests_Errors.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests_Errors.cs index ed52d3e2a9..1bf1ce132a 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionNodeBuilderTests_Errors.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionNodeBuilderTests_Errors.cs @@ -1,10 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionNodeBuilderTests_Errors { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AttachedProperty.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AttachedProperty.cs similarity index 98% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AttachedProperty.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AttachedProperty.cs index 5ddff63a0c..750e691103 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AttachedProperty.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AttachedProperty.cs @@ -6,10 +6,10 @@ using System.Collections.Generic; using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Diagnostics; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionObserverTests_AttachedProperty { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AvaloniaProperty.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AvaloniaProperty.cs similarity index 97% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AvaloniaProperty.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AvaloniaProperty.cs index 6d1e9d94a9..bf2b6cbcb2 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AvaloniaProperty.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_AvaloniaProperty.cs @@ -6,10 +6,10 @@ using System.Collections.Generic; using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Diagnostics; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionObserverTests_AvaloniaProperty { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs similarity index 99% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs index 9bf1e4711b..3732569753 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs @@ -7,11 +7,11 @@ using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; using Avalonia.Data; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using Avalonia.UnitTests; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionObserverTests_DataValidation : IClassFixture { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs similarity index 99% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs index 1bdbf0d895..8a54f968b1 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs @@ -8,11 +8,11 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Collections; using Avalonia.Diagnostics; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using Avalonia.UnitTests; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionObserverTests_Indexer { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs similarity index 98% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs index 04a8e30d16..b88bf2c427 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs @@ -7,10 +7,10 @@ using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; using Microsoft.Reactive.Testing; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionObserverTests_Lifetime { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Method.cs similarity index 97% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Method.cs index 439e9dc542..ef89c2b4bd 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Method.cs @@ -1,5 +1,5 @@ using Avalonia.Data; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using System; using System.Collections.Generic; using System.Linq; @@ -8,7 +8,7 @@ using System.Text; using System.Threading.Tasks; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionObserverTests_Method { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Negation.cs similarity index 98% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Negation.cs index f4aa85c393..556352f6ca 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Negation.cs @@ -5,10 +5,10 @@ using System; using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Data; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionObserverTests_Negation { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs similarity index 98% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs index aa78c100c1..f1c39617eb 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs @@ -6,11 +6,11 @@ using System.Collections.Generic; using System.Reactive.Linq; using System.Reactive.Subjects; using Avalonia.Data; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using Avalonia.UnitTests; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionObserverTests_Observable { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs similarity index 99% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index 60174e65e6..a3cb11114a 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -8,12 +8,12 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using Microsoft.Reactive.Testing; using Avalonia.Data; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using Avalonia.UnitTests; using Xunit; using System.Threading.Tasks; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionObserverTests_Property { @@ -363,7 +363,7 @@ namespace Avalonia.Markup.UnitTests.Data { "bar", new BindingNotification( - new MissingMemberException("Could not find CLR property 'Bar' on 'Avalonia.Markup.UnitTests.Data.ExpressionObserverTests_Property+WithoutBar'"), + new MissingMemberException("Could not find CLR property 'Bar' on 'Avalonia.Base.UnitTests.Data.Core.ExpressionObserverTests_Property+WithoutBar'"), BindingErrorType.Error), "baz", }, diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_SetValue.cs similarity index 97% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_SetValue.cs index 0705ae9c5a..a163229e26 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_SetValue.cs @@ -4,11 +4,11 @@ using System; using System.Reactive.Linq; using System.Reactive.Subjects; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using Avalonia.UnitTests; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionObserverTests_SetValue { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Task.cs similarity index 98% rename from tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Task.cs index 87a8be045b..3b9a23f846 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Task.cs @@ -6,11 +6,11 @@ using System.Collections.Generic; using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Data; -using Avalonia.Markup.Data; +using Avalonia.Data.Core; using Avalonia.UnitTests; using Xunit; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { public class ExpressionObserverTests_Task { diff --git a/tests/Avalonia.Markup.UnitTests/Data/IndeiBase.cs b/tests/Avalonia.Base.UnitTests/Data/Core/IndeiBase.cs similarity index 96% rename from tests/Avalonia.Markup.UnitTests/Data/IndeiBase.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/IndeiBase.cs index bd0ab71626..6edcdc820d 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/IndeiBase.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/IndeiBase.cs @@ -7,7 +7,7 @@ using System.ComponentModel; using System.Runtime.CompilerServices; using Avalonia.UnitTests; -namespace Avalonia.Markup.UnitTests.Data +namespace Avalonia.Base.UnitTests.Data.Core { internal abstract class IndeiBase : NotifyingBase, INotifyDataErrorInfo { diff --git a/tests/Avalonia.Markup.UnitTests/Data/Plugins/DataAnnotationsValidationPluginTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs similarity index 99% rename from tests/Avalonia.Markup.UnitTests/Data/Plugins/DataAnnotationsValidationPluginTests.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs index 6663457add..2bffb7b84a 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/Plugins/DataAnnotationsValidationPluginTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Avalonia.Data; -using Avalonia.Markup.Data.Plugins; +using Avalonia.Data.Core.Plugins; using Avalonia.UnitTests; using Xunit; diff --git a/tests/Avalonia.Markup.UnitTests/Data/Plugins/ExceptionValidationPluginTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/ExceptionValidationPluginTests.cs similarity index 96% rename from tests/Avalonia.Markup.UnitTests/Data/Plugins/ExceptionValidationPluginTests.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/Plugins/ExceptionValidationPluginTests.cs index eb529a3b13..2a307f9a61 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/Plugins/ExceptionValidationPluginTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/ExceptionValidationPluginTests.cs @@ -5,11 +5,11 @@ using System; using System.Collections.Generic; using System.Reactive.Linq; using Avalonia.Data; -using Avalonia.Markup.Data.Plugins; +using Avalonia.Data.Core.Plugins; using Avalonia.UnitTests; using Xunit; -namespace Avalonia.Markup.UnitTests.Data.Plugins +namespace Avalonia.Base.UnitTests.Data.Core.Plugins { public class ExceptionValidationPluginTests { diff --git a/tests/Avalonia.Markup.UnitTests/Data/Plugins/IndeiValidationPluginTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs similarity index 98% rename from tests/Avalonia.Markup.UnitTests/Data/Plugins/IndeiValidationPluginTests.cs rename to tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs index 788bc25a34..45c084014b 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/Plugins/IndeiValidationPluginTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs @@ -6,10 +6,10 @@ using System.Collections; using System.Collections.Generic; using System.Reactive.Linq; using Avalonia.Data; -using Avalonia.Markup.Data.Plugins; +using Avalonia.Data.Core.Plugins; using Xunit; -namespace Avalonia.Markup.UnitTests.Data.Plugins +namespace Avalonia.Base.UnitTests.Data.Core.Plugins { public class IndeiValidationPluginTests { diff --git a/tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs b/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs similarity index 98% rename from tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs rename to tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs index 0aa2e00c0f..eeb502d730 100644 --- a/tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs @@ -7,8 +7,9 @@ using Avalonia.Data; using Xunit; using System.Windows.Input; using System; +using Avalonia.Data.Converters; -namespace Avalonia.Markup.UnitTests +namespace Avalonia.Base.UnitTests.Data.Converters { public class DefaultValueConverterTests { diff --git a/tests/Avalonia.Markup.UnitTests/UnitTestSynchronizationContext.cs b/tests/Avalonia.Base.UnitTests/Data/UnitTestSynchronizationContext.cs similarity index 98% rename from tests/Avalonia.Markup.UnitTests/UnitTestSynchronizationContext.cs rename to tests/Avalonia.Base.UnitTests/Data/UnitTestSynchronizationContext.cs index b5068c511d..838aee6528 100644 --- a/tests/Avalonia.Markup.UnitTests/UnitTestSynchronizationContext.cs +++ b/tests/Avalonia.Base.UnitTests/Data/UnitTestSynchronizationContext.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Reactive.Disposables; using System.Threading; -namespace Avalonia.Markup.UnitTests +namespace Avalonia.Base.UnitTests.Data { internal sealed class UnitTestSynchronizationContext : SynchronizationContext { diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index f9da2ab6f3..b10929cbdc 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -10,7 +10,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Platform; using Avalonia.Threading; using Avalonia.UnitTests; diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 71bf9104f7..a194da98a8 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -1,6 +1,6 @@ using System; using System.Windows.Input; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Xunit; namespace Avalonia.Controls.UnitTests diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs index 653ab17b63..c17893604c 100644 --- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs @@ -12,7 +12,7 @@ using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Data; using System.Collections.Generic; diff --git a/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs index fe4f1ea06e..936d700ad0 100644 --- a/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs @@ -10,7 +10,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Platform; using Avalonia.UnitTests; using Moq; diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index fee4994ee3..70d59e82c8 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -9,7 +9,7 @@ using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.LogicalTree; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Styling; using Avalonia.VisualTree; using Xunit; diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs index 2dfb30a9f0..42578c61ac 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs @@ -6,7 +6,7 @@ using System.ComponentModel; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Styling; using Xunit; diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index a60074fa43..0f742f5a35 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -10,7 +10,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.UnitTests; using Xunit; diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index 642f594e4d..3c6b278e07 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -10,7 +10,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Xunit; namespace Avalonia.Controls.UnitTests.Primitives diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs index db60d05a34..377f861668 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs @@ -1,4 +1,4 @@ -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.UnitTests; using Xunit; diff --git a/tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs b/tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs index 37fb52beeb..2d9dca93f5 100644 --- a/tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs @@ -1,4 +1,4 @@ -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.UnitTests; using Xunit; diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 5ddc8e71e7..af461f07a4 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -8,7 +8,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs index 851d741eae..4d79dd557e 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs @@ -9,7 +9,7 @@ using System.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Platform; using Avalonia.UnitTests; using Moq; diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 625d9eb26e..a7263cacbd 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -8,6 +8,7 @@ using Avalonia.Collections; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Data.Core; using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Markup.Data; diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index fe3ad59bb4..f00dd6d5d0 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -185,7 +185,7 @@ namespace Avalonia.LeakTests Content = new TextBox() }; - var binding = new Avalonia.Markup.Xaml.Data.Binding + var binding = new Avalonia.Markup.Data.Binding { Path = "Name" }; diff --git a/tests/Avalonia.LeakTests/ExpressionObserverTests.cs b/tests/Avalonia.LeakTests/ExpressionObserverTests.cs index d0f892bebb..96f9e37897 100644 --- a/tests/Avalonia.LeakTests/ExpressionObserverTests.cs +++ b/tests/Avalonia.LeakTests/ExpressionObserverTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Avalonia.Collections; +using Avalonia.Data.Core; using Avalonia.Markup.Data; using Avalonia.UnitTests; using JetBrains.dotMemoryUnit; diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs new file mode 100644 index 0000000000..6c95a465b3 --- /dev/null +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -0,0 +1,548 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Markup.Data; +using Moq; +using Xunit; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using Avalonia.UnitTests; +using Avalonia.Data.Converters; +using Avalonia.Data.Core; + +namespace Avalonia.Markup.UnitTests.Data +{ + public class BindingTests + { + [Fact] + public void OneWay_Binding_Should_Be_Set_Up() + { + var source = new Source { Foo = "foo" }; + var target = new TextBlock { DataContext = source }; + var binding = new Binding + { + Path = "Foo", + Mode = BindingMode.OneWay, + }; + + target.Bind(TextBox.TextProperty, binding); + + Assert.Equal("foo", target.Text); + source.Foo = "bar"; + Assert.Equal("bar", target.Text); + target.Text = "baz"; + Assert.Equal("bar", source.Foo); + } + + [Fact] + public void TwoWay_Binding_Should_Be_Set_Up() + { + var source = new Source { Foo = "foo" }; + var target = new TextBlock { DataContext = source }; + var binding = new Binding + { + Path = "Foo", + Mode = BindingMode.TwoWay, + }; + + target.Bind(TextBox.TextProperty, binding); + + Assert.Equal("foo", target.Text); + source.Foo = "bar"; + Assert.Equal("bar", target.Text); + target.Text = "baz"; + Assert.Equal("baz", source.Foo); + } + + [Fact] + public void OneTime_Binding_Should_Be_Set_Up() + { + var source = new Source { Foo = "foo" }; + var target = new TextBlock { DataContext = source }; + var binding = new Binding + { + Path = "Foo", + Mode = BindingMode.OneTime, + }; + + target.Bind(TextBox.TextProperty, binding); + + Assert.Equal("foo", target.Text); + source.Foo = "bar"; + Assert.Equal("foo", target.Text); + target.Text = "baz"; + Assert.Equal("bar", source.Foo); + } + + [Fact] + public void OneWayToSource_Binding_Should_Be_Set_Up() + { + var source = new Source { Foo = "foo" }; + var target = new TextBlock { DataContext = source, Text = "bar" }; + var binding = new Binding + { + Path = "Foo", + Mode = BindingMode.OneWayToSource, + }; + + target.Bind(TextBox.TextProperty, binding); + + Assert.Equal("bar", source.Foo); + target.Text = "baz"; + Assert.Equal("baz", source.Foo); + source.Foo = "quz"; + Assert.Equal("baz", target.Text); + } + + [Fact] + public void Default_BindingMode_Should_Be_Used() + { + var source = new Source { Foo = "foo" }; + var target = new TwoWayBindingTest { DataContext = source }; + var binding = new Binding + { + Path = "Foo", + }; + + target.Bind(TwoWayBindingTest.TwoWayProperty, binding); + + Assert.Equal("foo", target.TwoWay); + source.Foo = "bar"; + Assert.Equal("bar", target.TwoWay); + target.TwoWay = "baz"; + Assert.Equal("baz", source.Foo); + } + + [Fact] + public void DataContext_Binding_Should_Use_Parent_DataContext() + { + var parentDataContext = Mock.Of(x => x.Header == (object)"Foo"); + + var parent = new Decorator + { + Child = new Control(), + DataContext = parentDataContext, + }; + + var binding = new Binding + { + Path = "Header", + }; + + parent.Child.Bind(Control.DataContextProperty, binding); + + Assert.Equal("Foo", parent.Child.DataContext); + + parentDataContext = Mock.Of(x => x.Header == (object)"Bar"); + parent.DataContext = parentDataContext; + Assert.Equal("Bar", parent.Child.DataContext); + } + + [Fact] + public void DataContext_Binding_Should_Track_Parent() + { + var parent = new Decorator + { + DataContext = new { Foo = "foo" }, + }; + + var child = new Control(); + + var binding = new Binding + { + Path = "Foo", + }; + + child.Bind(Control.DataContextProperty, binding); + + Assert.Null(child.DataContext); + parent.Child = child; + Assert.Equal("foo", child.DataContext); + } + + [Fact] + public void DataContext_Binding_Should_Produce_Correct_Results() + { + var viewModel = new { Foo = "bar" }; + var root = new Decorator + { + DataContext = viewModel, + }; + + var child = new Control(); + var values = new List(); + + child.GetObservable(Control.DataContextProperty).Subscribe(x => values.Add(x)); + child.Bind(Control.DataContextProperty, new Binding("Foo")); + + // When binding to DataContext and the target isn't found, the binding should produce + // null rather than UnsetValue in order to not propagate incorrect DataContexts from + // parent controls while things are being set up. This logic is implemented in + // `Avalonia.Markup.Xaml.Binding.Initiate`. + Assert.True(child.IsSet(Control.DataContextProperty)); + + root.Child = child; + + Assert.Equal(new[] { null, "bar" }, values); + } + + [Fact] + public void Should_Use_DefaultValueConverter_When_No_Converter_Specified() + { + var target = new TextBlock(); ; + var binding = new Binding + { + Path = "Foo", + }; + + var result = binding.Initiate(target, TextBox.TextProperty).Subject; + + Assert.IsType(((BindingExpression)result).Converter); + } + + [Fact] + public void Should_Use_Supplied_Converter() + { + var target = new TextBlock(); + var converter = new Mock(); + var binding = new Binding + { + Converter = converter.Object, + Path = "Foo", + }; + + var result = binding.Initiate(target, TextBox.TextProperty).Subject; + + Assert.Same(converter.Object, ((BindingExpression)result).Converter); + } + + [Fact] + public void Should_Pass_ConverterParameter_To_Supplied_Converter() + { + var target = new TextBlock(); + var converter = new Mock(); + var binding = new Binding + { + Converter = converter.Object, + ConverterParameter = "foo", + Path = "Bar", + }; + + var result = binding.Initiate(target, TextBox.TextProperty).Subject; + + Assert.Same("foo", ((BindingExpression)result).ConverterParameter); + } + + [Fact] + public void Should_Return_FallbackValue_When_Path_Not_Resolved() + { + var target = new TextBlock(); + var source = new Source(); + var binding = new Binding + { + Source = source, + Path = "BadPath", + FallbackValue = "foofallback", + }; + + target.Bind(TextBlock.TextProperty, binding); + + Assert.Equal("foofallback", target.Text); + } + + [Fact] + public void Should_Return_FallbackValue_When_Invalid_Source_Type() + { + var target = new ProgressBar(); + var source = new Source { Foo = "foo" }; + var binding = new Binding + { + Source = source, + Path = "Foo", + FallbackValue = 42, + }; + + target.Bind(ProgressBar.ValueProperty, binding); + + Assert.Equal(42, target.Value); + } + + /// + /// Tests a problem discovered with ListBox with selection. + /// + /// + /// - Items is bound to DataContext first, followed by say SelectedIndex + /// - When the ListBox is removed from the logical tree, DataContext becomes null (as it's + /// inherited) + /// - This changes Items to null, which changes SelectedIndex to null as there are no + /// longer any items + /// - However, the news that DataContext is now null hasn't yet reached the SelectedIndex + /// binding and so the unselection is sent back to the ViewModel + /// + [Fact] + public void Should_Not_Write_To_Old_DataContext() + { + var vm = new OldDataContextViewModel(); + var target = new OldDataContextTest(); + + var fooBinding = new Binding + { + Path = "Foo", + Mode = BindingMode.TwoWay, + }; + + var barBinding = new Binding + { + Path = "Bar", + Mode = BindingMode.TwoWay, + }; + + // Bind Foo and Bar to the VM. + target.Bind(OldDataContextTest.FooProperty, fooBinding); + target.Bind(OldDataContextTest.BarProperty, barBinding); + target.DataContext = vm; + + // Make sure the control's Foo and Bar properties are read from the VM + Assert.Equal(1, target.GetValue(OldDataContextTest.FooProperty)); + Assert.Equal(2, target.GetValue(OldDataContextTest.BarProperty)); + + // Set DataContext to null. + target.DataContext = null; + + // Foo and Bar are no longer bound so they return 0, their default value. + Assert.Equal(0, target.GetValue(OldDataContextTest.FooProperty)); + Assert.Equal(0, target.GetValue(OldDataContextTest.BarProperty)); + + // The problem was here - DataContext is now null, setting Foo to 0. Bar is bound to + // Foo so Bar also gets set to 0. However the Bar binding still had a reference to + // the VM and so vm.Bar was set to 0 erroneously. + Assert.Equal(1, vm.Foo); + Assert.Equal(2, vm.Bar); + } + + [Fact] + public void AvaloniaObject_this_Operator_Accepts_Binding() + { + var target = new ContentControl + { + DataContext = new { Foo = "foo" } + }; + + target[!ContentControl.ContentProperty] = new Binding("Foo"); + + Assert.Equal("foo", target.Content); + } + + [Fact] + public void StyledProperty_SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() + { + var viewModel = new TestStackOverflowViewModel() + { + Value = 50 + }; + + var target = new StyledPropertyClass(); + + target.Bind(StyledPropertyClass.DoubleValueProperty, + new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel }); + + var child = new StyledPropertyClass(); + + child.Bind(StyledPropertyClass.DoubleValueProperty, + new Binding("DoubleValue") + { + Mode = BindingMode.TwoWay, + Source = target + }); + + Assert.Equal(1, viewModel.SetterInvokedCount); + + //here in real life stack overflow exception is thrown issue #855 and #824 + target.DoubleValue = 51.001; + + Assert.Equal(2, viewModel.SetterInvokedCount); + + double expected = 51; + + Assert.Equal(expected, viewModel.Value); + Assert.Equal(expected, target.DoubleValue); + Assert.Equal(expected, child.DoubleValue); + } + + [Fact] + public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() + { + var viewModel = new TestStackOverflowViewModel() + { + Value = 50 + }; + + var target = new DirectPropertyClass(); + + target.Bind(DirectPropertyClass.DoubleValueProperty, new Binding("Value") + { + Mode = BindingMode.TwoWay, + Source = viewModel + }); + + var child = new DirectPropertyClass(); + + child.Bind(DirectPropertyClass.DoubleValueProperty, + new Binding("DoubleValue") + { + Mode = BindingMode.TwoWay, + Source = target + }); + + Assert.Equal(1, viewModel.SetterInvokedCount); + + //here in real life stack overflow exception is thrown issue #855 and #824 + target.DoubleValue = 51.001; + + Assert.Equal(2, viewModel.SetterInvokedCount); + + double expected = 51; + + Assert.Equal(expected, viewModel.Value); + Assert.Equal(expected, target.DoubleValue); + Assert.Equal(expected, child.DoubleValue); + } + + private class StyledPropertyClass : AvaloniaObject + { + public static readonly StyledProperty DoubleValueProperty = + AvaloniaProperty.Register(nameof(DoubleValue)); + + public double DoubleValue + { + get { return GetValue(DoubleValueProperty); } + set { SetValue(DoubleValueProperty, value); } + } + } + + private class DirectPropertyClass : AvaloniaObject + { + public static readonly DirectProperty DoubleValueProperty = + AvaloniaProperty.RegisterDirect( + nameof(DoubleValue), + o => o.DoubleValue, + (o, v) => o.DoubleValue = v); + + private double _doubleValue; + public double DoubleValue + { + get { return _doubleValue; } + set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); } + } + } + + private class TestStackOverflowViewModel : INotifyPropertyChanged + { + public int SetterInvokedCount { get; private set; } + + public const int MaxInvokedCount = 1000; + + private double _value; + + public event PropertyChangedEventHandler PropertyChanged; + + public double Value + { + get { return _value; } + set + { + if (_value != value) + { + SetterInvokedCount++; + if (SetterInvokedCount < MaxInvokedCount) + { + _value = (int)value; + if (_value > 75) _value = 75; + if (_value < 25) _value = 25; + } + else + { + _value = value; + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); + } + } + } + } + + private class TwoWayBindingTest : Control + { + public static readonly StyledProperty TwoWayProperty = + AvaloniaProperty.Register( + "TwoWay", + defaultBindingMode: BindingMode.TwoWay); + + public string TwoWay + { + get { return GetValue(TwoWayProperty); } + set { SetValue(TwoWayProperty, value); } + } + } + + public class Source : INotifyPropertyChanged + { + private string _foo; + + public string Foo + { + get { return _foo; } + set + { + _foo = value; + RaisePropertyChanged(); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + private void RaisePropertyChanged([CallerMemberName] string prop = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); + } + } + + private class OldDataContextViewModel + { + public int Foo { get; set; } = 1; + public int Bar { get; set; } = 2; + } + + private class OldDataContextTest : Control + { + public static readonly StyledProperty FooProperty = + AvaloniaProperty.Register("Foo"); + + public static readonly StyledProperty BarProperty = + AvaloniaProperty.Register("Bar"); + + public OldDataContextTest() + { + Bind(BarProperty, this.GetObservable(FooProperty)); + } + } + + private class InheritanceTest : Decorator + { + public static readonly StyledProperty BazProperty = + AvaloniaProperty.Register(nameof(Baz), defaultValue: 6, inherits: true); + + public int Baz + { + get { return GetValue(BazProperty); } + set { SetValue(BazProperty, value); } + } + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_DataValidation.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs similarity index 96% rename from tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_DataValidation.cs rename to tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs index 5dd8d0cdf9..04b91b5f6a 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_DataValidation.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs @@ -5,11 +5,11 @@ using System; using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Data; +using Avalonia.Data.Core; using Avalonia.Markup.Data; -using Avalonia.Markup.Xaml.Data; using Xunit; -namespace Avalonia.Markup.Xaml.UnitTests.Data +namespace Avalonia.Markup.UnitTests.Data { public class BindingTests_DataValidation { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_ElementName.cs similarity index 98% rename from tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs rename to tests/Avalonia.Markup.UnitTests/Data/BindingTests_ElementName.cs index d582964987..a05b121a42 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_ElementName.cs @@ -2,11 +2,11 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Controls; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.UnitTests; using Xunit; -namespace Avalonia.Markup.Xaml.UnitTests.Data +namespace Avalonia.Markup.UnitTests.Data { public class BindingTests_ElementName { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs similarity index 98% rename from tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs rename to tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs index c46fb6fce2..36e801d22d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs @@ -2,11 +2,11 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Controls; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.UnitTests; using Xunit; -namespace Avalonia.Markup.Xaml.UnitTests.Data +namespace Avalonia.Markup.UnitTests.Data { public class BindingTests_RelativeSource { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Self.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Self.cs similarity index 96% rename from tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Self.cs rename to tests/Avalonia.Markup.UnitTests/Data/BindingTests_Self.cs index e0d16a9563..7b899472bb 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Self.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Self.cs @@ -5,12 +5,12 @@ using System; using Moq; using Avalonia.Controls; using Avalonia.Data; -using Avalonia.Markup.Xaml.Data; using Avalonia.Styling; using Xunit; using System.Reactive.Disposables; +using Avalonia.Markup.Data; -namespace Avalonia.Markup.Xaml.UnitTests.Data +namespace Avalonia.Markup.UnitTests.Data { public class BindingTests_Self { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Source.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Source.cs similarity index 94% rename from tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Source.cs rename to tests/Avalonia.Markup.UnitTests/Data/BindingTests_Source.cs index 1fbf8b679d..9cc02539ce 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Source.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Source.cs @@ -5,12 +5,11 @@ using Moq; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Markup.Data; -using Avalonia.Markup.Xaml.Data; using Xunit; using System.ComponentModel; using System.Runtime.CompilerServices; -namespace Avalonia.Markup.Xaml.UnitTests.Data +namespace Avalonia.Markup.UnitTests.Data { public class BindingTests_Source { diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_TemplatedParent.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_TemplatedParent.cs new file mode 100644 index 0000000000..e9c3da5160 --- /dev/null +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_TemplatedParent.cs @@ -0,0 +1,76 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Moq; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Styling; +using Xunit; +using System.Reactive.Disposables; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using System.Linq; +using Avalonia.Markup.Data; + +namespace Avalonia.Markup.UnitTests.Data +{ + public class BindingTests_TemplatedParent + { + [Fact] + public void OneWay_Binding_Should_Be_Set_Up() + { + var target = CreateTarget(); + var binding = new Binding + { + Mode = BindingMode.OneWay, + RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), + Priority = BindingPriority.TemplatedParent, + Path = "Foo", + }; + + target.Object.Bind(TextBox.TextProperty, binding); + + target.Verify(x => x.Bind( + TextBox.TextProperty, + It.IsAny>(), + BindingPriority.TemplatedParent)); + } + + [Fact] + public void TwoWay_Binding_Should_Be_Set_Up() + { + var target = CreateTarget(); + var binding = new Binding + { + Mode = BindingMode.TwoWay, + RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), + Priority = BindingPriority.TemplatedParent, + Path = "Foo", + }; + + target.Object.Bind(TextBox.TextProperty, binding); + + target.Verify(x => x.Bind( + TextBox.TextProperty, + It.IsAny>(), + BindingPriority.TemplatedParent)); + } + + private Mock CreateTarget( + ITemplatedControl templatedParent = null, + string text = null) + { + var result = new Mock(); + + result.Setup(x => x.GetValue(Control.TemplatedParentProperty)).Returns(templatedParent); + result.Setup(x => x.GetValue((AvaloniaProperty)Control.TemplatedParentProperty)).Returns(templatedParent); + result.Setup(x => x.GetValue((AvaloniaProperty)TextBox.TextProperty)).Returns(text); + result.Setup(x => x.Bind(It.IsAny(), It.IsAny>(), It.IsAny())) + .Returns(Disposable.Empty); + return result; + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs similarity index 96% rename from tests/Avalonia.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs rename to tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs index 4450cc0c90..b341af2810 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs @@ -8,11 +8,12 @@ using System.Linq; using System.Reactive.Linq; using Moq; using Avalonia.Controls; -using Avalonia.Markup.Xaml.Data; using Xunit; using System.Threading.Tasks; +using Avalonia.Data.Converters; +using Avalonia.Markup.Data; -namespace Avalonia.Markup.Xaml.UnitTests.Data +namespace Avalonia.Markup.UnitTests.Data { public class MultiBindingTests { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Parsers/SelectorGrammarTests.cs b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs similarity index 98% rename from tests/Avalonia.Markup.Xaml.UnitTests/Parsers/SelectorGrammarTests.cs rename to tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs index ad2c1bf8d3..8cb2639125 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Parsers/SelectorGrammarTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs @@ -2,11 +2,11 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Linq; -using Avalonia.Markup.Xaml.Parsers; +using Avalonia.Markup.Parsers; using Sprache; using Xunit; -namespace Avalonia.Xaml.Base.UnitTest.Parsers +namespace Avalonia.Markup.UnitTest.Parsers { public class SelectorGrammarTests { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Parsers/SelectorParserTests.cs b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs similarity index 91% rename from tests/Avalonia.Markup.Xaml.UnitTests/Parsers/SelectorParserTests.cs rename to tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs index 8c0b043907..360be7f909 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Parsers/SelectorParserTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs @@ -1,6 +1,6 @@ using System; using Avalonia.Controls; -using Avalonia.Markup.Xaml.Parsers; +using Avalonia.Markup.Parsers; using Xunit; namespace Avalonia.Markup.Xaml.UnitTests.Parsers diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs index c6f89e07a6..0a00b9d39d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs @@ -8,475 +8,18 @@ using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Markup.Data; -using Avalonia.Markup.Xaml.Data; using Moq; using Xunit; using System.ComponentModel; using System.Runtime.CompilerServices; using Avalonia.UnitTests; +using Avalonia.Data.Converters; +using Avalonia.Data.Core; namespace Avalonia.Markup.Xaml.UnitTests.Data { public class BindingTests { - [Fact] - public void OneWay_Binding_Should_Be_Set_Up() - { - var source = new Source { Foo = "foo" }; - var target = new TextBlock { DataContext = source }; - var binding = new Binding - { - Path = "Foo", - Mode = BindingMode.OneWay, - }; - - target.Bind(TextBox.TextProperty, binding); - - Assert.Equal("foo", target.Text); - source.Foo = "bar"; - Assert.Equal("bar", target.Text); - target.Text = "baz"; - Assert.Equal("bar", source.Foo); - } - - [Fact] - public void TwoWay_Binding_Should_Be_Set_Up() - { - var source = new Source { Foo = "foo" }; - var target = new TextBlock { DataContext = source }; - var binding = new Binding - { - Path = "Foo", - Mode = BindingMode.TwoWay, - }; - - target.Bind(TextBox.TextProperty, binding); - - Assert.Equal("foo", target.Text); - source.Foo = "bar"; - Assert.Equal("bar", target.Text); - target.Text = "baz"; - Assert.Equal("baz", source.Foo); - } - - [Fact] - public void OneTime_Binding_Should_Be_Set_Up() - { - var source = new Source { Foo = "foo" }; - var target = new TextBlock { DataContext = source }; - var binding = new Binding - { - Path = "Foo", - Mode = BindingMode.OneTime, - }; - - target.Bind(TextBox.TextProperty, binding); - - Assert.Equal("foo", target.Text); - source.Foo = "bar"; - Assert.Equal("foo", target.Text); - target.Text = "baz"; - Assert.Equal("bar", source.Foo); - } - - [Fact] - public void OneWayToSource_Binding_Should_Be_Set_Up() - { - var source = new Source { Foo = "foo" }; - var target = new TextBlock { DataContext = source, Text = "bar" }; - var binding = new Binding - { - Path = "Foo", - Mode = BindingMode.OneWayToSource, - }; - - target.Bind(TextBox.TextProperty, binding); - - Assert.Equal("bar", source.Foo); - target.Text = "baz"; - Assert.Equal("baz", source.Foo); - source.Foo = "quz"; - Assert.Equal("baz", target.Text); - } - - [Fact] - public void Default_BindingMode_Should_Be_Used() - { - var source = new Source { Foo = "foo" }; - var target = new TwoWayBindingTest { DataContext = source }; - var binding = new Binding - { - Path = "Foo", - }; - - target.Bind(TwoWayBindingTest.TwoWayProperty, binding); - - Assert.Equal("foo", target.TwoWay); - source.Foo = "bar"; - Assert.Equal("bar", target.TwoWay); - target.TwoWay = "baz"; - Assert.Equal("baz", source.Foo); - } - - [Fact] - public void DataContext_Binding_Should_Use_Parent_DataContext() - { - var parentDataContext = Mock.Of(x => x.Header == (object)"Foo"); - - var parent = new Decorator - { - Child = new Control(), - DataContext = parentDataContext, - }; - - var binding = new Binding - { - Path = "Header", - }; - - parent.Child.Bind(Control.DataContextProperty, binding); - - Assert.Equal("Foo", parent.Child.DataContext); - - parentDataContext = Mock.Of(x => x.Header == (object)"Bar"); - parent.DataContext = parentDataContext; - Assert.Equal("Bar", parent.Child.DataContext); - } - - [Fact] - public void DataContext_Binding_Should_Track_Parent() - { - var parent = new Decorator - { - DataContext = new { Foo = "foo" }, - }; - - var child = new Control(); - - var binding = new Binding - { - Path = "Foo", - }; - - child.Bind(Control.DataContextProperty, binding); - - Assert.Null(child.DataContext); - parent.Child = child; - Assert.Equal("foo", child.DataContext); - } - - [Fact] - public void DataContext_Binding_Should_Produce_Correct_Results() - { - var viewModel = new { Foo = "bar" }; - var root = new Decorator - { - DataContext = viewModel, - }; - - var child = new Control(); - var values = new List(); - - child.GetObservable(Control.DataContextProperty).Subscribe(x => values.Add(x)); - child.Bind(Control.DataContextProperty, new Binding("Foo")); - - // When binding to DataContext and the target isn't found, the binding should produce - // null rather than UnsetValue in order to not propagate incorrect DataContexts from - // parent controls while things are being set up. This logic is implemented in - // `Avalonia.Markup.Xaml.Binding.Initiate`. - Assert.True(child.IsSet(Control.DataContextProperty)); - - root.Child = child; - - Assert.Equal(new[] { null, "bar" }, values); - } - - [Fact] - public void Should_Use_DefaultValueConverter_When_No_Converter_Specified() - { - var target = new TextBlock(); ; - var binding = new Binding - { - Path = "Foo", - }; - - var result = binding.Initiate(target, TextBox.TextProperty).Subject; - - Assert.IsType(((BindingExpression)result).Converter); - } - - [Fact] - public void Should_Use_Supplied_Converter() - { - var target = new TextBlock(); - var converter = new Mock(); - var binding = new Binding - { - Converter = converter.Object, - Path = "Foo", - }; - - var result = binding.Initiate(target, TextBox.TextProperty).Subject; - - Assert.Same(converter.Object, ((BindingExpression)result).Converter); - } - - [Fact] - public void Should_Pass_ConverterParameter_To_Supplied_Converter() - { - var target = new TextBlock(); - var converter = new Mock(); - var binding = new Binding - { - Converter = converter.Object, - ConverterParameter = "foo", - Path = "Bar", - }; - - var result = binding.Initiate(target, TextBox.TextProperty).Subject; - - Assert.Same("foo", ((BindingExpression)result).ConverterParameter); - } - - [Fact] - public void Should_Return_FallbackValue_When_Path_Not_Resolved() - { - var target = new TextBlock(); - var source = new Source(); - var binding = new Binding - { - Source = source, - Path = "BadPath", - FallbackValue = "foofallback", - }; - - target.Bind(TextBlock.TextProperty, binding); - - Assert.Equal("foofallback", target.Text); - } - - [Fact] - public void Should_Return_FallbackValue_When_Invalid_Source_Type() - { - var target = new ProgressBar(); - var source = new Source { Foo = "foo" }; - var binding = new Binding - { - Source = source, - Path = "Foo", - FallbackValue = 42, - }; - - target.Bind(ProgressBar.ValueProperty, binding); - - Assert.Equal(42, target.Value); - } - - /// - /// Tests a problem discovered with ListBox with selection. - /// - /// - /// - Items is bound to DataContext first, followed by say SelectedIndex - /// - When the ListBox is removed from the logical tree, DataContext becomes null (as it's - /// inherited) - /// - This changes Items to null, which changes SelectedIndex to null as there are no - /// longer any items - /// - However, the news that DataContext is now null hasn't yet reached the SelectedIndex - /// binding and so the unselection is sent back to the ViewModel - /// - [Fact] - public void Should_Not_Write_To_Old_DataContext() - { - var vm = new OldDataContextViewModel(); - var target = new OldDataContextTest(); - - var fooBinding = new Binding - { - Path = "Foo", - Mode = BindingMode.TwoWay, - }; - - var barBinding = new Binding - { - Path = "Bar", - Mode = BindingMode.TwoWay, - }; - - // Bind Foo and Bar to the VM. - target.Bind(OldDataContextTest.FooProperty, fooBinding); - target.Bind(OldDataContextTest.BarProperty, barBinding); - target.DataContext = vm; - - // Make sure the control's Foo and Bar properties are read from the VM - Assert.Equal(1, target.GetValue(OldDataContextTest.FooProperty)); - Assert.Equal(2, target.GetValue(OldDataContextTest.BarProperty)); - - // Set DataContext to null. - target.DataContext = null; - - // Foo and Bar are no longer bound so they return 0, their default value. - Assert.Equal(0, target.GetValue(OldDataContextTest.FooProperty)); - Assert.Equal(0, target.GetValue(OldDataContextTest.BarProperty)); - - // The problem was here - DataContext is now null, setting Foo to 0. Bar is bound to - // Foo so Bar also gets set to 0. However the Bar binding still had a reference to - // the VM and so vm.Bar was set to 0 erroneously. - Assert.Equal(1, vm.Foo); - Assert.Equal(2, vm.Bar); - } - - [Fact] - public void AvaloniaObject_this_Operator_Accepts_Binding() - { - var target = new ContentControl - { - DataContext = new { Foo = "foo" } - }; - - target[!ContentControl.ContentProperty] = new Binding("Foo"); - - Assert.Equal("foo", target.Content); - } - - [Fact] - public void StyledProperty_SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() - { - var viewModel = new TestStackOverflowViewModel() - { - Value = 50 - }; - - var target = new StyledPropertyClass(); - - target.Bind(StyledPropertyClass.DoubleValueProperty, - new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel }); - - var child = new StyledPropertyClass(); - - child.Bind(StyledPropertyClass.DoubleValueProperty, - new Binding("DoubleValue") - { - Mode = BindingMode.TwoWay, - Source = target - }); - - Assert.Equal(1, viewModel.SetterInvokedCount); - - //here in real life stack overflow exception is thrown issue #855 and #824 - target.DoubleValue = 51.001; - - Assert.Equal(2, viewModel.SetterInvokedCount); - - double expected = 51; - - Assert.Equal(expected, viewModel.Value); - Assert.Equal(expected, target.DoubleValue); - Assert.Equal(expected, child.DoubleValue); - } - - [Fact] - public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() - { - var viewModel = new TestStackOverflowViewModel() - { - Value = 50 - }; - - var target = new DirectPropertyClass(); - - target.Bind(DirectPropertyClass.DoubleValueProperty, new Binding("Value") - { - Mode = BindingMode.TwoWay, - Source = viewModel - }); - - var child = new DirectPropertyClass(); - - child.Bind(DirectPropertyClass.DoubleValueProperty, - new Binding("DoubleValue") - { - Mode = BindingMode.TwoWay, - Source = target - }); - - Assert.Equal(1, viewModel.SetterInvokedCount); - - //here in real life stack overflow exception is thrown issue #855 and #824 - target.DoubleValue = 51.001; - - Assert.Equal(2, viewModel.SetterInvokedCount); - - double expected = 51; - - Assert.Equal(expected, viewModel.Value); - Assert.Equal(expected, target.DoubleValue); - Assert.Equal(expected, child.DoubleValue); - } - - private class StyledPropertyClass : AvaloniaObject - { - public static readonly StyledProperty DoubleValueProperty = - AvaloniaProperty.Register(nameof(DoubleValue)); - - public double DoubleValue - { - get { return GetValue(DoubleValueProperty); } - set { SetValue(DoubleValueProperty, value); } - } - } - - private class DirectPropertyClass : AvaloniaObject - { - public static readonly DirectProperty DoubleValueProperty = - AvaloniaProperty.RegisterDirect( - nameof(DoubleValue), - o => o.DoubleValue, - (o, v) => o.DoubleValue = v); - - private double _doubleValue; - public double DoubleValue - { - get { return _doubleValue; } - set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); } - } - } - - private class TestStackOverflowViewModel : INotifyPropertyChanged - { - public int SetterInvokedCount { get; private set; } - - public const int MaxInvokedCount = 1000; - - private double _value; - - public event PropertyChangedEventHandler PropertyChanged; - - public double Value - { - get { return _value; } - set - { - if (_value != value) - { - SetterInvokedCount++; - if (SetterInvokedCount < MaxInvokedCount) - { - _value = (int)value; - if (_value > 75) _value = 75; - if (_value < 25) _value = 25; - } - else - { - _value = value; - } - - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); - } - } - } - } - - [Fact] public void Binding_With_Null_Path_Works() { @@ -484,8 +27,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data { var xaml = @" + xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'> "; var loader = new AvaloniaXamlLoader(); @@ -498,73 +40,5 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data Assert.Equal("foo", textBlock.Text); } } - - private class TwoWayBindingTest : Control - { - public static readonly StyledProperty TwoWayProperty = - AvaloniaProperty.Register( - "TwoWay", - defaultBindingMode: BindingMode.TwoWay); - - public string TwoWay - { - get { return GetValue(TwoWayProperty); } - set { SetValue(TwoWayProperty, value); } - } - } - - public class Source : INotifyPropertyChanged - { - private string _foo; - - public string Foo - { - get { return _foo; } - set - { - _foo = value; - RaisePropertyChanged(); - } - } - - public event PropertyChangedEventHandler PropertyChanged; - - private void RaisePropertyChanged([CallerMemberName] string prop = "") - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); - } - } - - private class OldDataContextViewModel - { - public int Foo { get; set; } = 1; - public int Bar { get; set; } = 2; - } - - private class OldDataContextTest : Control - { - public static readonly StyledProperty FooProperty = - AvaloniaProperty.Register("Foo"); - - public static readonly StyledProperty BarProperty = - AvaloniaProperty.Register("Bar"); - - public OldDataContextTest() - { - Bind(BarProperty, this.GetObservable(FooProperty)); - } - } - - private class InheritanceTest : Decorator - { - public static readonly StyledProperty BazProperty = - AvaloniaProperty.Register(nameof(Baz), defaultValue: 6, inherits: true); - - public int Baz - { - get { return GetValue(BazProperty); } - set { SetValue(BazProperty, value); } - } - } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs index ccb13039f1..ab5b7e76a4 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs @@ -7,7 +7,6 @@ using System.Reactive.Subjects; using Moq; using Avalonia.Controls; using Avalonia.Data; -using Avalonia.Markup.Xaml.Data; using Avalonia.Styling; using Xunit; using System.Reactive.Disposables; @@ -19,46 +18,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data { public class BindingTests_TemplatedParent { - [Fact] - public void OneWay_Binding_Should_Be_Set_Up() - { - var target = CreateTarget(); - var binding = new Binding - { - Mode = BindingMode.OneWay, - RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), - Priority = BindingPriority.TemplatedParent, - Path = "Foo", - }; - - target.Object.Bind(TextBox.TextProperty, binding); - - target.Verify(x => x.Bind( - TextBox.TextProperty, - It.IsAny>(), - BindingPriority.TemplatedParent)); - } - - [Fact] - public void TwoWay_Binding_Should_Be_Set_Up() - { - var target = CreateTarget(); - var binding = new Binding - { - Mode = BindingMode.TwoWay, - RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), - Priority = BindingPriority.TemplatedParent, - Path = "Foo", - }; - - target.Object.Bind(TextBox.TextProperty, binding); - - target.Verify(x => x.Bind( - TextBox.TextProperty, - It.IsAny>(), - BindingPriority.TemplatedParent)); - } - [Fact] public void TemplateBinding_With_Null_Path_Works() { @@ -87,19 +46,5 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data Assert.Equal("Avalonia.Controls.Button", textBlock.Text); } } - - private Mock CreateTarget( - ITemplatedControl templatedParent = null, - string text = null) - { - var result = new Mock(); - - result.Setup(x => x.GetValue(Control.TemplatedParentProperty)).Returns(templatedParent); - result.Setup(x => x.GetValue((AvaloniaProperty)Control.TemplatedParentProperty)).Returns(templatedParent); - result.Setup(x => x.GetValue((AvaloniaProperty)TextBox.TextProperty)).Returns(text); - result.Setup(x => x.Bind(It.IsAny(), It.IsAny>(), It.IsAny())) - .Returns(Disposable.Empty); - return result; - } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index d55e34cbe6..8f2cba06a3 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -6,7 +6,7 @@ using System.Linq; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Media; using Avalonia.Styling; using Avalonia.UnitTests; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs similarity index 96% rename from tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs rename to tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs index 6dc56e425c..a35c7bdd9b 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs @@ -4,7 +4,7 @@ using Avalonia.Media; using Avalonia.UnitTests; using Xunit; -namespace Avalonia.Markup.Xaml.UnitTests.Data +namespace Avalonia.Markup.Xaml.UnitTests.MakrupExtensions { public class ResourceIncludeTests { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs index 57570d8f5c..cafad1056f 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using Avalonia.Data.Converters; namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs index e8e1646d44..91da8e18a1 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Data; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 0fcb9432ab..43df95cfce 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -4,7 +4,8 @@ using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Presenters; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Data.Converters; +using Avalonia.Markup.Data; using Avalonia.Markup.Xaml.Styling; using Avalonia.Markup.Xaml.Templates; using Avalonia.Media; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs index f1f4792d34..2c7e850fee 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Controls; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Markup.Xaml.Styling; using Avalonia.Media; using Avalonia.Styling; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs index a7ddfeeb00..cbcfa16771 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs @@ -3,7 +3,7 @@ using System.Linq; using Avalonia.Controls.Templates; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Markup.Xaml.Templates; using Avalonia.UnitTests; using Xunit; diff --git a/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs b/tests/Avalonia.Styling.UnitTests/ControlLocatorTests.cs similarity index 95% rename from tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs rename to tests/Avalonia.Styling.UnitTests/ControlLocatorTests.cs index b3e983036d..519a456ee2 100644 --- a/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs +++ b/tests/Avalonia.Styling.UnitTests/ControlLocatorTests.cs @@ -6,10 +6,11 @@ using System.Collections.Generic; using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls; +using Avalonia.LogicalTree; using Avalonia.UnitTests; using Xunit; -namespace Avalonia.Markup.UnitTests +namespace Avalonia.Styling.UnitTests { public class ControlLocatorTests { @@ -61,7 +62,7 @@ namespace Avalonia.Markup.UnitTests var locator = ControlLocator.Track(relativeTo, "target"); var target = new TextBlock { Name = "target" }; - var result = new List(); + var result = new List(); using (locator.Subscribe(x => result.Add(x))) { @@ -93,7 +94,7 @@ namespace Avalonia.Markup.UnitTests }; var locator = ControlLocator.Track(relativeTo, "target"); - var result = new List(); + var result = new List(); locator.Subscribe(x => result.Add(x)); var other = new TextBlock { Name = "target" }; @@ -138,7 +139,7 @@ namespace Avalonia.Markup.UnitTests var locator = ControlLocator.Track(relativeTo, "target"); var target = new TextBlock { Name = "target" }; - var result = new List(); + var result = new List(); using (locator.Subscribe(x => result.Add(x))) { diff --git a/tests/Avalonia.Styling.UnitTests/SetterTests.cs b/tests/Avalonia.Styling.UnitTests/SetterTests.cs index f376df4d0b..7c1623cf21 100644 --- a/tests/Avalonia.Styling.UnitTests/SetterTests.cs +++ b/tests/Avalonia.Styling.UnitTests/SetterTests.cs @@ -8,9 +8,10 @@ using Avalonia.Data; using Xunit; using System; using Avalonia.Controls.Templates; -using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Data; using Avalonia.Markup; using System.Globalization; +using Avalonia.Data.Converters; namespace Avalonia.Styling.UnitTests { From da0ab8b6814fb64181beb5af4dcda285d9229265 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 15 May 2018 01:25:08 -0500 Subject: [PATCH 04/16] Move the styling system up the stack to live right below Avalonia.Animation since it has no dependencies on anything further down the dependency chain --- src/Avalonia.Base/AvaloniaObject.cs | 2 +- src/Avalonia.Controls/Control.cs | 754 +---------------- src/Avalonia.Controls/HotkeyManager.cs | 6 +- src/Avalonia.Controls/IControl.cs | 30 +- src/Avalonia.Controls/Utils/AncestorFinder.cs | 24 +- .../Controls}/Classes.cs | 16 +- .../Controls}/IPseudoClasses.cs | 0 .../Controls}/ISetInheritanceParent.cs | 2 +- .../Controls}/ISetLogicalParent.cs | 2 +- src/Avalonia.Styling/Controls/NameScope.cs | 36 +- .../Controls/NameScopeExtensions.cs | 3 +- .../INamed.cs | 0 src/Avalonia.Styling/IStyledElement.cs | 42 + .../ControlLocator.cs | 53 +- src/Avalonia.Styling/StyledElement.cs | 783 ++++++++++++++++++ .../Styling/IRequiresTemplateInStyle.cs | 14 + src/Avalonia.Styling/Styling/IStyleable.cs | 1 + src/Avalonia.Styling/Styling/Setter.cs | 4 +- src/Avalonia.Styling/app.config | 15 - src/Avalonia.Visuals/Avalonia.Visuals.csproj | 3 +- src/Avalonia.Visuals/Visual.cs | 2 +- .../VisualTree/VisualLocator.cs | 46 + .../StaticResourceExtension.cs | 2 +- .../Avalonia.Markup/Avalonia.Markup.csproj | 2 - src/Markup/Avalonia.Markup/Data/Binding.cs | 41 +- .../Avalonia.Markup/Data/DelayedBinding.cs | 25 +- .../Avalonia.Markup/Data/RelativeSource.cs | 17 +- .../ItemsControlTests.cs | 4 +- .../Presenters/CarouselPresenterTests.cs | 2 +- .../Presenters/ItemsPresenterTests.cs | 2 +- .../Templates/TemplateExtensionsTests.cs | 20 +- .../Utils/AncestorFinderTests.cs | 2 +- .../Data/BindingTests.cs | 2 +- .../StyledElementTests.cs} | 17 +- .../StyledElementTests_NameScope.cs} | 4 +- .../StyledElementTests_Resources.cs} | 12 +- 36 files changed, 1028 insertions(+), 962 deletions(-) rename src/{Avalonia.Controls => Avalonia.Styling/Controls}/Classes.cs (95%) rename src/{Avalonia.Controls => Avalonia.Styling/Controls}/IPseudoClasses.cs (100%) rename src/{Avalonia.Controls => Avalonia.Styling/Controls}/ISetInheritanceParent.cs (88%) rename src/{Avalonia.Controls => Avalonia.Styling/Controls}/ISetLogicalParent.cs (85%) rename src/{Avalonia.Visuals => Avalonia.Styling}/INamed.cs (100%) create mode 100644 src/Avalonia.Styling/IStyledElement.cs rename src/Avalonia.Styling/{Controls => LogicalTree}/ControlLocator.cs (68%) create mode 100644 src/Avalonia.Styling/StyledElement.cs create mode 100644 src/Avalonia.Styling/Styling/IRequiresTemplateInStyle.cs delete mode 100644 src/Avalonia.Styling/app.config create mode 100644 src/Avalonia.Visuals/VisualTree/VisualLocator.cs rename tests/{Avalonia.Controls.UnitTests/ControlTests.cs => Avalonia.Styling.UnitTests/StyledElementTests.cs} (97%) rename tests/{Avalonia.Controls.UnitTests/ControlTests_NameScope.cs => Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs} (94%) rename tests/{Avalonia.Controls.UnitTests/ControlTests_Resources.cs => Avalonia.Styling.UnitTests/StyledElementTests_Resources.cs} (95%) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1a2db9fc3d..48e72db126 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -703,7 +703,7 @@ namespace Avalonia /// The default value. private object GetDefaultValue(AvaloniaProperty property) { - if (property.Inherits && _inheritanceParent is AvaloniaObject aobj) + if (property.Inherits && InheritanceParent is AvaloniaObject aobj) return aobj.GetValueInternal(property); return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType()); } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 89721e2e05..2bcd540264 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -29,53 +29,23 @@ namespace Avalonia.Controls /// /// The control class extends and adds the following features: /// - /// - An inherited . /// - A property to allow user-defined data to be attached to the control. - /// - A collection of class strings for custom styling. - /// - Implements to allow styling to work on the control. - /// - Implements to form part of a logical tree. + /// - public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize, IVisualBrushInitialize + public class Control : InputElement, IControl, INamed, ISupportInitialize, IVisualBrushInitialize, IRequiresTemplateInStyle { - /// - /// Defines the property. - /// - public static readonly StyledProperty DataContextProperty = - AvaloniaProperty.Register( - nameof(DataContext), - inherits: true, - notifying: DataContextNotifying); - /// /// Defines the property. /// public static readonly StyledProperty> FocusAdornerProperty = AvaloniaProperty.Register>(nameof(FocusAdorner)); - /// - /// Defines the property. - /// - public static readonly DirectProperty NameProperty = - AvaloniaProperty.RegisterDirect(nameof(Name), o => o.Name, (o, v) => o.Name = v); - - /// - /// Defines the property. - /// - public static readonly DirectProperty ParentProperty = - AvaloniaProperty.RegisterDirect(nameof(Parent), o => o.Parent); - /// /// Defines the property. /// public static readonly StyledProperty TagProperty = AvaloniaProperty.Register(nameof(Tag)); - - /// - /// Defines the property. - /// - public static readonly StyledProperty TemplatedParentProperty = - AvaloniaProperty.Register(nameof(TemplatedParent), inherits: true); - + /// /// Defines the property. /// @@ -88,20 +58,8 @@ namespace Avalonia.Controls public static readonly RoutedEvent RequestBringIntoViewEvent = RoutedEvent.Register("RequestBringIntoView", RoutingStrategies.Bubble); - private int _initCount; - private string _name; - private IControl _parent; - private readonly Classes _classes = new Classes(); private DataTemplates _dataTemplates; private IControl _focusAdorner; - private bool _isAttachedToLogicalTree; - private IAvaloniaList _logicalChildren; - private INameScope _nameScope; - private IResourceDictionary _resources; - private Styles _styles; - private bool _styled; - private Subject _styleDetach = new Subject(); - private bool _dataContextUpdating; /// /// Initializes static members of the class. @@ -112,127 +70,6 @@ namespace Avalonia.Controls PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled"); PseudoClass(IsFocusedProperty, ":focus"); PseudoClass(IsPointerOverProperty, ":pointerover"); - DataContextProperty.Changed.AddClassHandler(x => x.OnDataContextChangedCore); - } - - /// - /// Initializes a new instance of the class. - /// - public Control() - { - _nameScope = this as INameScope; - _isAttachedToLogicalTree = this is IStyleRoot; - } - - /// - /// Raised when the control is attached to a rooted logical tree. - /// - public event EventHandler AttachedToLogicalTree; - - /// - /// Raised when the control is detached from a rooted logical tree. - /// - public event EventHandler DetachedFromLogicalTree; - - /// - /// Occurs when the property changes. - /// - /// - /// This event will be raised when the property has changed and - /// all subscribers to that change have been notified. - /// - public event EventHandler DataContextChanged; - - /// - /// Occurs when the control has finished initialization. - /// - /// - /// The Initialized event indicates that all property values on the control have been set. - /// When loading the control from markup, it occurs when - /// is called *and* the control - /// is attached to a rooted logical tree. When the control is created by code and - /// is not used, it is called when the control is attached - /// to the visual tree. - /// - public event EventHandler Initialized; - - /// - /// Occurs when a resource in this control or a parent control has changed. - /// - public event EventHandler ResourcesChanged; - - /// - /// Gets or sets the name of the control. - /// - /// - /// An element's name is used to uniquely identify a control within the control's name - /// scope. Once the element is added to a logical tree, its name cannot be changed. - /// - public string Name - { - get - { - return _name; - } - - set - { - if (String.IsNullOrWhiteSpace(value)) - { - throw new InvalidOperationException("Cannot set Name to null or empty string."); - } - - if (_styled) - { - throw new InvalidOperationException("Cannot set Name : control already styled."); - } - - _name = value; - } - } - - /// - /// Gets or sets the control's classes. - /// - /// - /// - /// Classes can be used to apply user-defined styling to controls, or to allow controls - /// that share a common purpose to be easily selected. - /// - /// - /// Even though this property can be set, the setter is only intended for use in object - /// initializers. Assigning to this property does not change the underlying collection, - /// it simply clears the existing collection and addds the contents of the assigned - /// collection. - /// - /// - public Classes Classes - { - get - { - return _classes; - } - - set - { - if (_classes != value) - { - _classes.Replace(value); - } - } - } - - /// - /// Gets or sets the control's data context. - /// - /// - /// The data context is an inherited property that specifies the default object that will - /// be used for data binding. - /// - public object DataContext - { - get { return GetValue(DataContextProperty); } - set { SetValue(DataContextProperty, value); } } /// @@ -253,55 +90,6 @@ namespace Avalonia.Controls /// public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates()); - /// - /// Gets a value that indicates whether the element has finished initialization. - /// - /// - /// For more information about when IsInitialized is set, see the - /// event. - /// - public bool IsInitialized { get; private set; } - - /// - /// Gets the styles for the control. - /// - /// - /// Styles for the entire application are added to the Application.Styles collection, but - /// each control may in addition define its own styles which are applied to the control - /// itself and its children. - /// - public Styles Styles - { - get { return _styles ?? (Styles = new Styles()); } - set - { - Contract.Requires(value != null); - - if (_styles != value) - { - if (_styles != null) - { - (_styles as ISetStyleParent)?.SetParent(null); - _styles.ResourcesChanged -= ThisResourcesChanged; - } - - _styles = value; - - if (value is ISetStyleParent setParent && setParent.ResourceParent == null) - { - setParent.SetParent(this); - } - - _styles.ResourcesChanged += ThisResourcesChanged; - } - } - } - - /// - /// Gets the control's logical parent. - /// - public IControl Parent => _parent; - /// /// Gets or sets a context menu to the control. /// @@ -311,34 +99,6 @@ namespace Avalonia.Controls set { SetValue(ContextMenuProperty, value); } } - /// - /// Gets or sets the control's resource dictionary. - /// - public IResourceDictionary Resources - { - get => _resources ?? (Resources = new ResourceDictionary()); - set - { - Contract.Requires(value != null); - - var hadResources = false; - - if (_resources != null) - { - hadResources = _resources.Count > 0; - _resources.ResourcesChanged -= ThisResourcesChanged; - } - - _resources = value; - _resources.ResourcesChanged += ThisResourcesChanged; - - if (hadResources || _resources.Count > 0) - { - ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); - } - } - } - /// /// Gets or sets a user-defined object attached to the control. /// @@ -348,226 +108,11 @@ namespace Avalonia.Controls set { SetValue(TagProperty, value); } } - /// - /// Gets the control whose lookless template this control is part of. - /// - public ITemplatedControl TemplatedParent - { - get { return GetValue(TemplatedParentProperty); } - internal set { SetValue(TemplatedParentProperty, value); } - } - - /// - /// Gets the control's logical children. - /// - protected IAvaloniaList LogicalChildren - { - get - { - if (_logicalChildren == null) - { - var list = new AvaloniaList(); - list.ResetBehavior = ResetBehavior.Remove; - list.Validate = ValidateLogicalChild; - list.CollectionChanged += LogicalChildrenCollectionChanged; - _logicalChildren = list; - } - - return _logicalChildren; - } - } + public new IControl Parent => (IControl)base.Parent; /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; - /// - /// Gets the collection in a form that allows adding and removing - /// pseudoclasses. - /// - protected IPseudoClasses PseudoClasses => Classes; - - /// - /// Gets a value indicating whether the element is attached to a rooted logical tree. - /// - bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree; - - /// - /// Gets the control's logical parent. - /// - ILogical ILogical.LogicalParent => Parent; - - /// - /// Gets the control's logical children. - /// - IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren; - - /// - bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources; - - /// - IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode; - - /// - IAvaloniaReadOnlyList IStyleable.Classes => Classes; - - /// - /// Gets the type by which the control is styled. - /// - /// - /// Usually controls are styled by their own type, but there are instances where you want - /// a control to be styled by its base type, e.g. creating SpecialButton that - /// derives from Button and adds extra functionality but is still styled as a regular - /// Button. - /// - Type IStyleable.StyleKey => GetType(); - - /// - IObservable IStyleable.StyleDetach => _styleDetach; - - /// - bool IStyleHost.IsStylesInitialized => _styles != null; - - /// - IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent; - - /// - public virtual void BeginInit() - { - ++_initCount; - } - - /// - public virtual void EndInit() - { - if (_initCount == 0) - { - throw new InvalidOperationException("BeginInit was not called."); - } - - if (--_initCount == 0 && _isAttachedToLogicalTree) - { - InitializeStylesIfNeeded(); - - InitializeIfNeeded(); - } - } - - private void InitializeStylesIfNeeded(bool force = false) - { - if (_initCount == 0 && (!_styled || force)) - { - RegisterWithNameScope(); - ApplyStyling(); - _styled = true; - } - } - - private void InitializeIfNeeded() - { - if (_initCount == 0 && !IsInitialized) - { - IsInitialized = true; - Initialized?.Invoke(this, EventArgs.Empty); - } - } - - /// - void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) - { - this.OnAttachedToLogicalTreeCore(e); - } - - /// - void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) - { - this.OnDetachedFromLogicalTreeCore(e); - } - - /// - void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e) - { - ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); - } - - /// - bool IResourceProvider.TryGetResource(string key, out object value) - { - value = null; - return (_resources?.TryGetResource(key, out value) ?? false) || - (_styles?.TryGetResource(key, out value) ?? false); - } - - /// - /// Sets the control's logical parent. - /// - /// The parent. - void ISetLogicalParent.SetParent(ILogical parent) - { - var old = Parent; - - if (parent != old) - { - if (old != null && parent != null) - { - throw new InvalidOperationException("The Control already has a parent."); - } - - if (_isAttachedToLogicalTree) - { - var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot; - - if (oldRoot == null) - { - throw new AvaloniaInternalException("Was attached to logical tree but cannot find root."); - } - - var e = new LogicalTreeAttachmentEventArgs(oldRoot); - OnDetachedFromLogicalTreeCore(e); - } - - if (InheritanceParent == null || parent == null) - { - InheritanceParent = parent as AvaloniaObject; - } - - _parent = (IControl)parent; - - if (old != null) - { - old.ResourcesChanged -= ThisResourcesChanged; - } - if (_parent != null) - { - _parent.ResourcesChanged += ThisResourcesChanged; - } - ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); - - if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot) - { - var newRoot = FindStyleRoot(this); - - if (newRoot == null) - { - throw new AvaloniaInternalException("Parent is atttached to logical tree but cannot find root."); - } - - var e = new LogicalTreeAttachmentEventArgs(newRoot); - OnAttachedToLogicalTreeCore(e); - } - - RaisePropertyChanged(ParentProperty, old, _parent, BindingPriority.LocalValue); - } - } - - /// - /// Sets the control's inheritance parent. - /// - /// The parent. - void ISetInheritanceParent.SetParent(IAvaloniaObject parent) - { - InheritanceParent = parent; - } - /// void IVisualBrushInitialize.EnsureInitialized() { @@ -600,52 +145,6 @@ namespace Avalonia.Controls } } - /// - /// Adds a pseudo-class to be set when a property is true. - /// - /// The property. - /// The pseudo-class. - protected static void PseudoClass(AvaloniaProperty property, string className) - { - PseudoClass(property, x => x, className); - } - - /// - /// Adds a pseudo-class to be set when a property equals a certain value. - /// - /// The type of the property. - /// The property. - /// Returns a boolean value based on the property value. - /// The pseudo-class. - protected static void PseudoClass( - AvaloniaProperty property, - Func selector, - string className) - { - Contract.Requires(property != null); - Contract.Requires(selector != null); - Contract.Requires(className != null); - - if (string.IsNullOrWhiteSpace(className)) - { - throw new ArgumentException("Cannot supply an empty className."); - } - - property.Changed.Merge(property.Initialized) - .Where(e => e.Sender is Control) - .Subscribe(e => - { - if (selector((T)e.NewValue)) - { - ((Control)e.Sender).PseudoClasses.Add(className); - } - else - { - ((Control)e.Sender).PseudoClasses.Remove(className); - } - }); - } - /// /// Gets the element that recieves the focus adorner. /// @@ -655,22 +154,6 @@ namespace Avalonia.Controls return this; } - /// - /// Called when the control is added to a rooted logical tree. - /// - /// The event args. - protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) - { - } - - /// - /// Called when the control is removed from a rooted logical tree. - /// - /// The event args. - protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) - { - } - /// protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) { @@ -685,29 +168,6 @@ namespace Avalonia.Controls base.OnDetachedFromVisualTreeCore(e); } - /// - /// Called when the property changes. - /// - /// The event args. - protected virtual void OnDataContextChanged(EventArgs e) - { - DataContextChanged?.Invoke(this, EventArgs.Empty); - } - - /// - /// Called when the begins updating. - /// - protected virtual void OnDataContextBeginUpdate() - { - } - - /// - /// Called when the finishes updating. - /// - protected virtual void OnDataContextEndUpdate() - { - } - /// protected override void OnGotFocus(GotFocusEventArgs e) { @@ -757,211 +217,5 @@ namespace Avalonia.Controls _focusAdorner = null; } } - - private static void DataContextNotifying(IAvaloniaObject o, bool notifying) - { - if (o is Control control) - { - DataContextNotifying(control, notifying); - } - } - - private static void DataContextNotifying(Control control, bool notifying) - { - if (notifying) - { - if (!control._dataContextUpdating) - { - control._dataContextUpdating = true; - control.OnDataContextBeginUpdate(); - - foreach (var child in control.LogicalChildren) - { - if (child is Control c && - c.InheritanceParent == control && - !c.IsSet(DataContextProperty)) - { - DataContextNotifying(c, notifying); - } - } - } - } - else - { - if (control._dataContextUpdating) - { - control.OnDataContextEndUpdate(); - control._dataContextUpdating = false; - } - } - } - - private static IStyleRoot FindStyleRoot(IStyleHost e) - { - while (e != null) - { - if (e is IRenderRoot root) - { - return root as IStyleRoot; - } - - e = e.StylingParent; - } - - return null; - } - - private void ApplyStyling() - { - AvaloniaLocator.Current.GetService()?.ApplyStyles(this); - } - - private void RegisterWithNameScope() - { - if (_nameScope == null) - { - _nameScope = NameScope.GetNameScope(this) ?? ((Control)Parent)?._nameScope; - } - - if (Name != null) - { - _nameScope?.Register(Name, this); - - var visualParent = Parent as Visual; - - if (this is INameScope && visualParent != null) - { - // If we have e.g. a named UserControl in a window then we want that control - // to be findable by name from the Window, so register with both name scopes. - // This differs from WPF's behavior in that XAML manually registers controls - // with name scopes based on the XAML file in which the name attribute appears, - // but we're trying to avoid XAML magic in Avalonia in order to made code- - // created UIs easy. This will cause problems if a UserControl declares a name - // in its XAML and that control is included multiple times in a parent control - // (as the name will be duplicated), however at the moment I'm fine with saying - // "don't do that". - var parentNameScope = NameScope.FindNameScope(visualParent); - parentNameScope?.Register(Name, this); - } - } - } - - private static void ValidateLogicalChild(ILogical c) - { - if (c == null) - { - throw new ArgumentException("Cannot add null to LogicalChildren."); - } - } - - private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e) - { - // This method can be called when a control is already attached to the logical tree - // in the following scenario: - // - ListBox gets assigned Items containing ListBoxItem - // - ListBox makes ListBoxItem a logical child - // - ListBox template gets applied; making its Panel get attached to logical tree - // - That AttachedToLogicalTree signal travels down to the ListBoxItem - if (!_isAttachedToLogicalTree) - { - _isAttachedToLogicalTree = true; - - InitializeStylesIfNeeded(true); - - OnAttachedToLogicalTree(e); - AttachedToLogicalTree?.Invoke(this, e); - } - - foreach (var child in LogicalChildren.OfType()) - { - child.OnAttachedToLogicalTreeCore(e); - } - } - - private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e) - { - if (_isAttachedToLogicalTree) - { - if (Name != null) - { - _nameScope?.Unregister(Name); - } - - _isAttachedToLogicalTree = false; - _styleDetach.OnNext(this); - OnDetachedFromLogicalTree(e); - DetachedFromLogicalTree?.Invoke(this, e); - - foreach (var child in LogicalChildren.OfType()) - { - child.OnDetachedFromLogicalTreeCore(e); - } - -#if DEBUG - if (((INotifyCollectionChangedDebug)_classes).GetCollectionChangedSubscribers()?.Length > 0) - { - Logger.Warning( - LogArea.Control, - this, - "{Type} detached from logical tree but still has class listeners", - this.GetType()); - } -#endif - } - } - - private void OnDataContextChangedCore(AvaloniaPropertyChangedEventArgs e) - { - OnDataContextChanged(EventArgs.Empty); - } - - private void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - SetLogicalParent(e.NewItems.Cast()); - break; - - case NotifyCollectionChangedAction.Remove: - ClearLogicalParent(e.OldItems.Cast()); - break; - - case NotifyCollectionChangedAction.Replace: - ClearLogicalParent(e.OldItems.Cast()); - SetLogicalParent(e.NewItems.Cast()); - break; - - case NotifyCollectionChangedAction.Reset: - throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection"); - } - } - - private void SetLogicalParent(IEnumerable children) - { - foreach (var i in children) - { - if (i.LogicalParent == null) - { - ((ISetLogicalParent)i).SetParent(this); - } - } - } - - private void ClearLogicalParent(IEnumerable children) - { - foreach (var i in children) - { - if (i.LogicalParent == this) - { - ((ISetLogicalParent)i).SetParent(null); - } - } - } - - private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e) - { - ((ILogical)this).NotifyResourcesChanged(e); - } } } diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index 35c9d6a9c5..a59fb86fb7 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -55,13 +55,13 @@ namespace Avalonia.Controls public void Init() { _hotkeySub = _control.GetObservable(HotKeyProperty).Subscribe(OnHotkeyChanged); - _parentSub = AncestorFinder.Create(_control, typeof (TopLevel)).Subscribe(OnParentChanged); + _parentSub = AncestorFinder.Create(_control).Subscribe(OnParentChanged); } - private void OnParentChanged(IControl control) + private void OnParentChanged(TopLevel control) { Unregister(); - _root = (TopLevel) control; + _root = control; Register(); } diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs index 36e09b2ea1..e7f2903249 100644 --- a/src/Avalonia.Controls/IControl.cs +++ b/src/Avalonia.Controls/IControl.cs @@ -16,37 +16,11 @@ namespace Avalonia.Controls /// public interface IControl : IVisual, IDataTemplateHost, - ILogical, ILayoutable, IInputElement, INamed, - IResourceNode, - IStyleable, - IStyleHost + IStyledElement { - /// - /// Occurs when the control has finished initialization. - /// - event EventHandler Initialized; - - /// - /// Gets or sets the control's styling classes. - /// - new Classes Classes { get; set; } - - /// - /// Gets or sets the control's data context. - /// - object DataContext { get; set; } - - /// - /// Gets a value that indicates whether the element has finished initialization. - /// - bool IsInitialized { get; } - - /// - /// Gets the control's logical parent. - /// - IControl Parent { get; } + new IControl Parent { get; } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Utils/AncestorFinder.cs b/src/Avalonia.Controls/Utils/AncestorFinder.cs index 7e6523261a..6ee284e05d 100644 --- a/src/Avalonia.Controls/Utils/AncestorFinder.cs +++ b/src/Avalonia.Controls/Utils/AncestorFinder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reactive; using System.Reactive.Disposables; +using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reflection; using System.Text; @@ -14,15 +15,15 @@ namespace Avalonia.Controls.Utils { class FinderNode : IDisposable { - private readonly IControl _control; + private readonly IStyledElement _control; private readonly TypeInfo _ancestorType; - public IObservable Observable => _subject; - private readonly Subject _subject = new Subject(); + public IObservable Observable => _subject; + private readonly Subject _subject = new Subject(); private FinderNode _child; private IDisposable _disposable; - public FinderNode(IControl control, TypeInfo ancestorType) + public FinderNode(IStyledElement control, TypeInfo ancestorType) { _control = control; _ancestorType = ancestorType; @@ -33,7 +34,7 @@ namespace Avalonia.Controls.Utils _disposable = _control.GetObservable(Control.ParentProperty).Subscribe(OnValueChanged); } - private void OnValueChanged(IControl next) + private void OnValueChanged(IStyledElement next) { if (next == null || _ancestorType.IsAssignableFrom(next.GetType().GetTypeInfo())) _subject.OnNext(next); @@ -46,7 +47,7 @@ namespace Avalonia.Controls.Utils } } - private void OnChildValueChanged(IControl control) => _subject.OnNext(control); + private void OnChildValueChanged(IStyledElement control) => _subject.OnNext(control); public void Dispose() @@ -55,10 +56,15 @@ namespace Avalonia.Controls.Utils } } + public static IObservable Create(IStyledElement control) + where T : IStyledElement + { + return Create(control, typeof(T)).Cast(); + } - public static IObservable Create(IControl control, Type ancestorType) + public static IObservable Create(IStyledElement control, Type ancestorType) { - return new AnonymousObservable(observer => + return new AnonymousObservable(observer => { var finder = new FinderNode(control, ancestorType.GetTypeInfo()); var subscription = finder.Observable.Subscribe(observer); @@ -70,8 +76,6 @@ namespace Avalonia.Controls.Utils finder.Dispose(); }); }); - - } } } diff --git a/src/Avalonia.Controls/Classes.cs b/src/Avalonia.Styling/Controls/Classes.cs similarity index 95% rename from src/Avalonia.Controls/Classes.cs rename to src/Avalonia.Styling/Controls/Classes.cs index 48ce69f0c9..85935b030b 100644 --- a/src/Avalonia.Controls/Classes.cs +++ b/src/Avalonia.Styling/Controls/Classes.cs @@ -9,7 +9,7 @@ using Avalonia.Collections; namespace Avalonia.Controls { /// - /// Holds a collection of style classes for an . + /// Holds a collection of style classes for an . /// /// /// Similar to CSS, each control may have any number of styling classes applied. @@ -54,7 +54,7 @@ namespace Avalonia.Controls /// The class name. /// /// Only standard classes may be added via this method. To add pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override void Add(string name) @@ -73,7 +73,7 @@ namespace Avalonia.Controls /// The class names. /// /// Only standard classes may be added via this method. To add pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override void AddRange(IEnumerable names) @@ -114,7 +114,7 @@ namespace Avalonia.Controls /// The class name. /// /// Only standard classes may be added via this method. To add pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override void Insert(int index, string name) @@ -134,7 +134,7 @@ namespace Avalonia.Controls /// The class names. /// /// Only standard classes may be added via this method. To add pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override void InsertRange(int index, IEnumerable names) @@ -160,7 +160,7 @@ namespace Avalonia.Controls /// The class name. /// /// Only standard classes may be removed via this method. To remove pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override bool Remove(string name) @@ -175,7 +175,7 @@ namespace Avalonia.Controls /// The class name. /// /// Only standard classes may be removed via this method. To remove pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override void RemoveAll(IEnumerable names) @@ -201,7 +201,7 @@ namespace Avalonia.Controls /// The index of the class in the collection. /// /// Only standard classes may be removed via this method. To remove pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override void RemoveAt(int index) diff --git a/src/Avalonia.Controls/IPseudoClasses.cs b/src/Avalonia.Styling/Controls/IPseudoClasses.cs similarity index 100% rename from src/Avalonia.Controls/IPseudoClasses.cs rename to src/Avalonia.Styling/Controls/IPseudoClasses.cs diff --git a/src/Avalonia.Controls/ISetInheritanceParent.cs b/src/Avalonia.Styling/Controls/ISetInheritanceParent.cs similarity index 88% rename from src/Avalonia.Controls/ISetInheritanceParent.cs rename to src/Avalonia.Styling/Controls/ISetInheritanceParent.cs index 788ab77246..1a778fa0f9 100644 --- a/src/Avalonia.Controls/ISetInheritanceParent.cs +++ b/src/Avalonia.Styling/Controls/ISetInheritanceParent.cs @@ -4,7 +4,7 @@ namespace Avalonia.Controls { /// - /// Defines an interface through which a 's inheritance parent can be set. + /// Defines an interface through which a 's inheritance parent can be set. /// /// /// You should not usually need to use this interface - it is for advanced scenarios only. diff --git a/src/Avalonia.Controls/ISetLogicalParent.cs b/src/Avalonia.Styling/Controls/ISetLogicalParent.cs similarity index 85% rename from src/Avalonia.Controls/ISetLogicalParent.cs rename to src/Avalonia.Styling/Controls/ISetLogicalParent.cs index 4422033634..5fb450c896 100644 --- a/src/Avalonia.Controls/ISetLogicalParent.cs +++ b/src/Avalonia.Styling/Controls/ISetLogicalParent.cs @@ -6,7 +6,7 @@ using Avalonia.LogicalTree; namespace Avalonia.Controls { /// - /// Defines an interface through which a 's logical parent can be set. + /// Defines an interface through which a 's logical parent can be set. /// /// /// You should not usually need to use this interface - it is for advanced scenarios only. diff --git a/src/Avalonia.Styling/Controls/NameScope.cs b/src/Avalonia.Styling/Controls/NameScope.cs index 8b5bd81d3c..e3a29af541 100644 --- a/src/Avalonia.Styling/Controls/NameScope.cs +++ b/src/Avalonia.Styling/Controls/NameScope.cs @@ -16,7 +16,7 @@ namespace Avalonia.Controls /// Defines the NameScope attached property. /// public static readonly AttachedProperty NameScopeProperty = - AvaloniaProperty.RegisterAttached("NameScope"); + AvaloniaProperty.RegisterAttached("NameScope"); private readonly Dictionary _inner = new Dictionary(); @@ -31,53 +31,53 @@ namespace Avalonia.Controls public event EventHandler Unregistered; /// - /// Finds the containing name scope for a visual. + /// Finds the containing name scope for a styled element. /// - /// The visual. + /// The styled element. /// The containing name scope. - public static INameScope FindNameScope(Visual visual) + public static INameScope FindNameScope(StyledElement styled) { - Contract.Requires(visual != null); + Contract.Requires(styled != null); INameScope result; - while (visual != null) + while (styled != null) { - result = visual as INameScope ?? GetNameScope(visual); + result = styled as INameScope ?? GetNameScope(styled); if (result != null) { return result; } - visual = (visual as ILogical)?.LogicalParent as Visual; + styled = (styled as ILogical)?.LogicalParent as StyledElement; } return null; } /// - /// Gets the value of the attached on a visual. + /// Gets the value of the attached on a styled element. /// - /// The visual. + /// The styled element. /// The value of the NameScope attached property. - public static INameScope GetNameScope(Visual visual) + public static INameScope GetNameScope(StyledElement styled) { - Contract.Requires(visual != null); + Contract.Requires(styled != null); - return visual.GetValue(NameScopeProperty); + return styled.GetValue(NameScopeProperty); } /// - /// Sets the value of the attached on a visual. + /// Sets the value of the attached on a styled element. /// - /// The visual. + /// The styled element. /// The value to set. - public static void SetNameScope(Visual visual, INameScope value) + public static void SetNameScope(StyledElement styled, INameScope value) { - Contract.Requires(visual != null); + Contract.Requires(styled != null); - visual.SetValue(NameScopeProperty, value); + styled.SetValue(NameScopeProperty, value); } /// diff --git a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs index 49be842ffc..491e4d71a7 100644 --- a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs +++ b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using Avalonia.LogicalTree; -using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -73,7 +72,7 @@ namespace Avalonia.Controls Contract.Requires(control != null); return control.GetSelfAndLogicalAncestors() - .OfType() + .OfType() .Select(x => (x as INameScope) ?? NameScope.GetNameScope(x)) .FirstOrDefault(x => x != null); } diff --git a/src/Avalonia.Visuals/INamed.cs b/src/Avalonia.Styling/INamed.cs similarity index 100% rename from src/Avalonia.Visuals/INamed.cs rename to src/Avalonia.Styling/INamed.cs diff --git a/src/Avalonia.Styling/IStyledElement.cs b/src/Avalonia.Styling/IStyledElement.cs new file mode 100644 index 0000000000..8369717233 --- /dev/null +++ b/src/Avalonia.Styling/IStyledElement.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls; +using Avalonia.LogicalTree; +using Avalonia.Styling; + +namespace Avalonia +{ + public interface IStyledElement : + IStyleable, + IStyleHost, + ILogical, + IResourceProvider, + IResourceNode + { + /// + /// Occurs when the control has finished initialization. + /// + event EventHandler Initialized; + + /// + /// Gets a value that indicates whether the element has finished initialization. + /// + bool IsInitialized { get; } + + /// + /// Gets or sets the control's styling classes. + /// + new Classes Classes { get; set; } + + /// + /// Gets or sets the control's data context. + /// + object DataContext { get; set; } + + /// + /// Gets the control's logical parent. + /// + IStyledElement Parent { get; } + } +} diff --git a/src/Avalonia.Styling/Controls/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs similarity index 68% rename from src/Avalonia.Styling/Controls/ControlLocator.cs rename to src/Avalonia.Styling/LogicalTree/ControlLocator.cs index e03ec722ad..2858d11d9d 100644 --- a/src/Avalonia.Styling/Controls/ControlLocator.cs +++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs @@ -6,26 +6,9 @@ using System.Linq; using System.Reactive.Linq; using System.Reflection; using Avalonia.Controls; -using Avalonia.LogicalTree; -using Avalonia.VisualTree; -namespace Avalonia.Controls +namespace Avalonia.LogicalTree { - /// - /// The type of tree via which to track a control. - /// - public enum TreeType - { - /// - /// The visual tree. - /// - Visual, - /// - /// The logical tree. - /// - Logical, - } - /// /// Locates controls relative to other controls. /// @@ -94,40 +77,6 @@ namespace Avalonia.Controls }); } - public static IObservable Track(IVisual relativeTo, int ancestorLevel, Type ancestorType = null) - { - return TrackAttachmentToTree(relativeTo).Select(isAttachedToTree => - { - if (isAttachedToTree) - { - return relativeTo.GetVisualAncestors() - .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true) - .ElementAtOrDefault(ancestorLevel); - } - else - { - return null; - } - }); - } - - private static IObservable TrackAttachmentToTree(IVisual relativeTo) - { - var attached = Observable.FromEventPattern( - x => relativeTo.AttachedToVisualTree += x, - x => relativeTo.AttachedToVisualTree -= x) - .Select(x => true) - .StartWith(relativeTo.IsAttachedToVisualTree); - - var detached = Observable.FromEventPattern( - x => relativeTo.DetachedFromVisualTree += x, - x => relativeTo.DetachedFromVisualTree -= x) - .Select(x => false); - - var attachmentStatus = attached.Merge(detached); - return attachmentStatus; - } - private static IObservable TrackAttachmentToTree(ILogical relativeTo) { var attached = Observable.FromEventPattern( diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs new file mode 100644 index 0000000000..bd0d0b8c1f --- /dev/null +++ b/src/Avalonia.Styling/StyledElement.cs @@ -0,0 +1,783 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Text; +using Avalonia.Animation; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Diagnostics; +using Avalonia.Logging; +using Avalonia.LogicalTree; +using Avalonia.Styling; + +namespace Avalonia +{ + /// + /// Extends an with the following features: + /// + /// - An inherited . + /// - Implements to allow styling to work on the styled element. + /// - Implements to form part of a logical tree. + /// - A collection of class strings for custom styling. + /// + public class StyledElement : Animatable, IStyledElement, ISetLogicalParent, ISetInheritanceParent + { + /// + /// Defines the property. + /// + public static readonly StyledProperty DataContextProperty = + AvaloniaProperty.Register( + nameof(DataContext), + inherits: true, + notifying: DataContextNotifying); + + /// + /// Defines the property. + /// + public static readonly DirectProperty NameProperty = + AvaloniaProperty.RegisterDirect(nameof(Name), o => o.Name, (o, v) => o.Name = v); + + /// + /// Defines the property. + /// + public static readonly DirectProperty ParentProperty = + AvaloniaProperty.RegisterDirect(nameof(Parent), o => o.Parent); + + /// + /// Defines the property. + /// + public static readonly StyledProperty TemplatedParentProperty = + AvaloniaProperty.Register(nameof(TemplatedParent), inherits: true); + + private int _initCount; + private string _name; + private readonly Classes _classes = new Classes(); + private bool _isAttachedToLogicalTree; + private IAvaloniaList _logicalChildren; + private INameScope _nameScope; + private IResourceDictionary _resources; + private Styles _styles; + private bool _styled; + private Subject _styleDetach = new Subject(); + private bool _dataContextUpdating; + + /// + /// Initializes static members of the class. + /// + static StyledElement() + { + DataContextProperty.Changed.AddClassHandler(x => x.OnDataContextChangedCore); + } + + /// + /// Initializes a new instance of the class. + /// + public StyledElement() + { + _nameScope = this as INameScope; + _isAttachedToLogicalTree = this is IStyleRoot; + } + + /// + /// Raised when the styled element is attached to a rooted logical tree. + /// + public event EventHandler AttachedToLogicalTree; + + /// + /// Raised when the styled element is detached from a rooted logical tree. + /// + public event EventHandler DetachedFromLogicalTree; + + /// + /// Occurs when the property changes. + /// + /// + /// This event will be raised when the property has changed and + /// all subscribers to that change have been notified. + /// + public event EventHandler DataContextChanged; + + /// + /// Occurs when the styled element has finished initialization. + /// + /// + /// The Initialized event indicates that all property values on the styled element have been set. + /// When loading the styled element from markup, it occurs when + /// is called *and* the styled element + /// is attached to a rooted logical tree. When the styled element is created by code and + /// is not used, it is called when the styled element is attached + /// to the visual tree. + /// + public event EventHandler Initialized; + + /// + /// Occurs when a resource in this styled element or a parent styled element has changed. + /// + public event EventHandler ResourcesChanged; + + /// + /// Gets or sets the name of the styled element. + /// + /// + /// An element's name is used to uniquely identify an element within the element's name + /// scope. Once the element is added to a logical tree, its name cannot be changed. + /// + public string Name + { + get + { + return _name; + } + + set + { + if (String.IsNullOrWhiteSpace(value)) + { + throw new InvalidOperationException("Cannot set Name to null or empty string."); + } + + if (_styled) + { + throw new InvalidOperationException("Cannot set Name : styled element already styled."); + } + + _name = value; + } + } + + /// + /// Gets or sets the styled element's classes. + /// + /// + /// + /// Classes can be used to apply user-defined styling to styled elements, or to allow styled elements + /// that share a common purpose to be easily selected. + /// + /// + /// Even though this property can be set, the setter is only intended for use in object + /// initializers. Assigning to this property does not change the underlying collection, + /// it simply clears the existing collection and addds the contents of the assigned + /// collection. + /// + /// + public Classes Classes + { + get + { + return _classes; + } + + set + { + if (_classes != value) + { + _classes.Replace(value); + } + } + } + + /// + /// Gets or sets the control's data context. + /// + /// + /// The data context is an inherited property that specifies the default object that will + /// be used for data binding. + /// + public object DataContext + { + get { return GetValue(DataContextProperty); } + set { SetValue(DataContextProperty, value); } + } + + /// + /// Gets a value that indicates whether the element has finished initialization. + /// + /// + /// For more information about when IsInitialized is set, see the + /// event. + /// + public bool IsInitialized { get; private set; } + + /// + /// Gets the styles for the styled element. + /// + /// + /// Styles for the entire application are added to the Application.Styles collection, but + /// each styled element may in addition define its own styles which are applied to the styled element + /// itself and its children. + /// + public Styles Styles + { + get { return _styles ?? (Styles = new Styles()); } + set + { + Contract.Requires(value != null); + + if (_styles != value) + { + if (_styles != null) + { + (_styles as ISetStyleParent)?.SetParent(null); + _styles.ResourcesChanged -= ThisResourcesChanged; + } + + _styles = value; + + if (value is ISetStyleParent setParent && setParent.ResourceParent == null) + { + setParent.SetParent(this); + } + + _styles.ResourcesChanged += ThisResourcesChanged; + } + } + } + + /// + /// Gets or sets the styled element's resource dictionary. + /// + public IResourceDictionary Resources + { + get => _resources ?? (Resources = new ResourceDictionary()); + set + { + Contract.Requires(value != null); + + var hadResources = false; + + if (_resources != null) + { + hadResources = _resources.Count > 0; + _resources.ResourcesChanged -= ThisResourcesChanged; + } + + _resources = value; + _resources.ResourcesChanged += ThisResourcesChanged; + + if (hadResources || _resources.Count > 0) + { + ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); + } + } + } + + /// + /// Gets the styled element whose lookless template this styled element is part of. + /// + public ITemplatedControl TemplatedParent + { + get { return GetValue(TemplatedParentProperty); } + internal set { SetValue(TemplatedParentProperty, value); } + } + + /// + /// Gets the styled element's logical children. + /// + protected IAvaloniaList LogicalChildren + { + get + { + if (_logicalChildren == null) + { + var list = new AvaloniaList + { + ResetBehavior = ResetBehavior.Remove, + Validate = ValidateLogicalChild + }; + list.CollectionChanged += LogicalChildrenCollectionChanged; + _logicalChildren = list; + } + + return _logicalChildren; + } + } + + /// + /// Gets the collection in a form that allows adding and removing + /// pseudoclasses. + /// + protected IPseudoClasses PseudoClasses => Classes; + + /// + /// Gets a value indicating whether the element is attached to a rooted logical tree. + /// + bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree; + + /// + /// Gets the styled element's logical parent. + /// + public IStyledElement Parent { get; private set; } + + /// + /// Gets the styled element's logical parent. + /// + ILogical ILogical.LogicalParent => Parent; + + /// + /// Gets the styled element's logical children. + /// + IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren; + + /// + bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources; + + /// + IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode; + + /// + IAvaloniaReadOnlyList IStyleable.Classes => Classes; + + /// + /// Gets the type by which the styled element is styled. + /// + /// + /// Usually controls are styled by their own type, but there are instances where you want + /// a styled element to be styled by its base type, e.g. creating SpecialButton that + /// derives from Button and adds extra functionality but is still styled as a regular + /// Button. + /// + Type IStyleable.StyleKey => GetType(); + + /// + IObservable IStyleable.StyleDetach => _styleDetach; + + /// + bool IStyleHost.IsStylesInitialized => _styles != null; + + /// + IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent; + + /// + public virtual void BeginInit() + { + ++_initCount; + } + + /// + public virtual void EndInit() + { + if (_initCount == 0) + { + throw new InvalidOperationException("BeginInit was not called."); + } + + if (--_initCount == 0 && _isAttachedToLogicalTree) + { + InitializeStylesIfNeeded(); + + InitializeIfNeeded(); + } + } + + private void InitializeStylesIfNeeded(bool force = false) + { + if (_initCount == 0 && (!_styled || force)) + { + RegisterWithNameScope(); + ApplyStyling(); + _styled = true; + } + } + + protected void InitializeIfNeeded() + { + if (_initCount == 0 && !IsInitialized) + { + IsInitialized = true; + Initialized?.Invoke(this, EventArgs.Empty); + } + } + + /// + void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + this.OnAttachedToLogicalTreeCore(e); + } + + /// + void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + this.OnDetachedFromLogicalTreeCore(e); + } + + /// + void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + + /// + bool IResourceProvider.TryGetResource(string key, out object value) + { + value = null; + return (_resources?.TryGetResource(key, out value) ?? false) || + (_styles?.TryGetResource(key, out value) ?? false); + } + + /// + /// Sets the styled element's logical parent. + /// + /// The parent. + void ISetLogicalParent.SetParent(ILogical parent) + { + var old = Parent; + + if (parent != old) + { + if (old != null && parent != null) + { + throw new InvalidOperationException("The Control already has a parent."); + } + + if (_isAttachedToLogicalTree) + { + var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot; + + if (oldRoot == null) + { + throw new AvaloniaInternalException("Was attached to logical tree but cannot find root."); + } + + var e = new LogicalTreeAttachmentEventArgs(oldRoot); + OnDetachedFromLogicalTreeCore(e); + } + + if (InheritanceParent == null || parent == null) + { + InheritanceParent = parent as AvaloniaObject; + } + + Parent = (IStyledElement)parent; + + if (old != null) + { + old.ResourcesChanged -= ThisResourcesChanged; + } + if (Parent != null) + { + Parent.ResourcesChanged += ThisResourcesChanged; + } + ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); + + if (Parent is IStyleRoot || Parent?.IsAttachedToLogicalTree == true || this is IStyleRoot) + { + var newRoot = FindStyleRoot(this); + + if (newRoot == null) + { + throw new AvaloniaInternalException("Parent is atttached to logical tree but cannot find root."); + } + + var e = new LogicalTreeAttachmentEventArgs(newRoot); + OnAttachedToLogicalTreeCore(e); + } + + RaisePropertyChanged(ParentProperty, old, Parent, BindingPriority.LocalValue); + } + } + + /// + /// Sets the styled element's inheritance parent. + /// + /// The parent. + void ISetInheritanceParent.SetParent(IAvaloniaObject parent) + { + InheritanceParent = parent; + } + + /// + /// Adds a pseudo-class to be set when a property is true. + /// + /// The property. + /// The pseudo-class. + protected static void PseudoClass(AvaloniaProperty property, string className) + { + PseudoClass(property, x => x, className); + } + + /// + /// Adds a pseudo-class to be set when a property equals a certain value. + /// + /// The type of the property. + /// The property. + /// Returns a boolean value based on the property value. + /// The pseudo-class. + protected static void PseudoClass( + AvaloniaProperty property, + Func selector, + string className) + { + Contract.Requires(property != null); + Contract.Requires(selector != null); + Contract.Requires(className != null); + + if (string.IsNullOrWhiteSpace(className)) + { + throw new ArgumentException("Cannot supply an empty className."); + } + + property.Changed.Merge(property.Initialized) + .Where(e => e.Sender is StyledElement) + .Subscribe(e => + { + if (selector((T)e.NewValue)) + { + ((StyledElement)e.Sender).PseudoClasses.Add(className); + } + else + { + ((StyledElement)e.Sender).PseudoClasses.Remove(className); + } + }); + } + + /// + /// Called when the styled element is added to a rooted logical tree. + /// + /// The event args. + protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + } + + /// + /// Called when the styled element is removed from a rooted logical tree. + /// + /// The event args. + protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + } + + /// + /// Called when the property changes. + /// + /// The event args. + protected virtual void OnDataContextChanged(EventArgs e) + { + DataContextChanged?.Invoke(this, EventArgs.Empty); + } + + /// + /// Called when the begins updating. + /// + protected virtual void OnDataContextBeginUpdate() + { + } + + /// + /// Called when the finishes updating. + /// + protected virtual void OnDataContextEndUpdate() + { + } + + private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) + { + if (o is StyledElement element) + { + DataContextNotifying(element, updateStarted); + } + } + + private static void DataContextNotifying(StyledElement element, bool updateStarted) + { + if (updateStarted) + { + if (!element._dataContextUpdating) + { + element._dataContextUpdating = true; + element.OnDataContextBeginUpdate(); + + foreach (var child in element.LogicalChildren) + { + if (child is StyledElement s && + s.InheritanceParent == element && + !s.IsSet(DataContextProperty)) + { + DataContextNotifying(s, updateStarted); + } + } + } + } + else + { + if (element._dataContextUpdating) + { + element.OnDataContextEndUpdate(); + element._dataContextUpdating = false; + } + } + } + + private static IStyleRoot FindStyleRoot(IStyleHost e) + { + while (e != null) + { + if (e is IStyleRoot root) + { + return root; + } + + e = e.StylingParent; + } + + return null; + } + + private void ApplyStyling() + { + AvaloniaLocator.Current.GetService()?.ApplyStyles(this); + } + + private void RegisterWithNameScope() + { + if (_nameScope == null) + { + _nameScope = NameScope.GetNameScope(this) ?? ((StyledElement)Parent)?._nameScope; + } + + if (Name != null) + { + _nameScope?.Register(Name, this); + + var visualParent = Parent as StyledElement; + + if (this is INameScope && visualParent != null) + { + // If we have e.g. a named UserControl in a window then we want that control + // to be findable by name from the Window, so register with both name scopes. + // This differs from WPF's behavior in that XAML manually registers controls + // with name scopes based on the XAML file in which the name attribute appears, + // but we're trying to avoid XAML magic in Avalonia in order to made code- + // created UIs easy. This will cause problems if a UserControl declares a name + // in its XAML and that control is included multiple times in a parent control + // (as the name will be duplicated), however at the moment I'm fine with saying + // "don't do that". + var parentNameScope = NameScope.FindNameScope(visualParent); + parentNameScope?.Register(Name, this); + } + } + } + + private static void ValidateLogicalChild(ILogical c) + { + if (c == null) + { + throw new ArgumentException("Cannot add null to LogicalChildren."); + } + } + + private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e) + { + // This method can be called when a control is already attached to the logical tree + // in the following scenario: + // - ListBox gets assigned Items containing ListBoxItem + // - ListBox makes ListBoxItem a logical child + // - ListBox template gets applied; making its Panel get attached to logical tree + // - That AttachedToLogicalTree signal travels down to the ListBoxItem + if (!_isAttachedToLogicalTree) + { + _isAttachedToLogicalTree = true; + + InitializeStylesIfNeeded(true); + + OnAttachedToLogicalTree(e); + AttachedToLogicalTree?.Invoke(this, e); + } + + foreach (var child in LogicalChildren.OfType()) + { + child.OnAttachedToLogicalTreeCore(e); + } + } + + private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e) + { + if (_isAttachedToLogicalTree) + { + if (Name != null) + { + _nameScope?.Unregister(Name); + } + + _isAttachedToLogicalTree = false; + _styleDetach.OnNext(this); + OnDetachedFromLogicalTree(e); + DetachedFromLogicalTree?.Invoke(this, e); + + foreach (var child in LogicalChildren.OfType()) + { + child.OnDetachedFromLogicalTreeCore(e); + } + +#if DEBUG + if (((INotifyCollectionChangedDebug)_classes).GetCollectionChangedSubscribers()?.Length > 0) + { + Logger.Warning( + LogArea.Control, + this, + "{Type} detached from logical tree but still has class listeners", + this.GetType()); + } +#endif + } + } + + private void OnDataContextChangedCore(AvaloniaPropertyChangedEventArgs e) + { + OnDataContextChanged(EventArgs.Empty); + } + + private void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + SetLogicalParent(e.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Remove: + ClearLogicalParent(e.OldItems.Cast()); + break; + + case NotifyCollectionChangedAction.Replace: + ClearLogicalParent(e.OldItems.Cast()); + SetLogicalParent(e.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Reset: + throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection"); + } + } + + private void SetLogicalParent(IEnumerable children) + { + foreach (var i in children) + { + if (i.LogicalParent == null) + { + ((ISetLogicalParent)i).SetParent(this); + } + } + } + + private void ClearLogicalParent(IEnumerable children) + { + foreach (var i in children) + { + if (i.LogicalParent == this) + { + ((ISetLogicalParent)i).SetParent(null); + } + } + } + + private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e) + { + ((ILogical)this).NotifyResourcesChanged(e); + } + } +} diff --git a/src/Avalonia.Styling/Styling/IRequiresTemplateInStyle.cs b/src/Avalonia.Styling/Styling/IRequiresTemplateInStyle.cs new file mode 100644 index 0000000000..819d783061 --- /dev/null +++ b/src/Avalonia.Styling/Styling/IRequiresTemplateInStyle.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Styling +{ + /// + /// This is an interface for advanced scenarios to assist users in correct style development. + /// You as a user will not need to use this interface directly. + /// + public interface IRequiresTemplateInStyle + { + } +} diff --git a/src/Avalonia.Styling/Styling/IStyleable.cs b/src/Avalonia.Styling/Styling/IStyleable.cs index e03231e4ac..98c8501681 100644 --- a/src/Avalonia.Styling/Styling/IStyleable.cs +++ b/src/Avalonia.Styling/Styling/IStyleable.cs @@ -3,6 +3,7 @@ using System; using Avalonia.Collections; +using Avalonia.LogicalTree; namespace Avalonia.Styling { diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs index e4d0ce5fab..337e01c0b5 100644 --- a/src/Avalonia.Styling/Styling/Setter.cs +++ b/src/Avalonia.Styling/Styling/Setter.cs @@ -65,7 +65,7 @@ namespace Avalonia.Styling set { - if (value is IStyleable) + if (value is IRequiresTemplateInStyle) { throw new ArgumentException( "Cannot assign a control to Style.Value. Wrap the control in a