Quellcode durchsuchen

Merge branch 'master' into AdornerLayer-hit-test

Steven Kirk vor 7 Jahren
Ursprung
Commit
c45f8cdd59
28 geänderte Dateien mit 508 neuen und 262 gelöschten Zeilen
  1. 2 2
      samples/ControlCatalog/SideBar.xaml
  2. 2 2
      samples/RenderDemo/SideBar.xaml
  3. 33 106
      src/Avalonia.Base/AvaloniaObject.cs
  4. 5 4
      src/Avalonia.Base/IPriorityValueOwner.cs
  5. 2 2
      src/Avalonia.Base/PriorityValue.cs
  6. 172 0
      src/Avalonia.Base/ValueStore.cs
  7. 7 7
      src/Avalonia.Controls/Window.cs
  8. 1 1
      src/Avalonia.Themes.Default/AutoCompleteBox.xaml
  9. 1 1
      src/Avalonia.Themes.Default/DropDown.xaml
  10. 2 2
      src/Avalonia.Themes.Default/MenuItem.xaml
  11. 2 2
      src/Avalonia.Themes.Default/ScrollBar.xaml
  12. 5 5
      src/Avalonia.Themes.Default/ScrollViewer.xaml
  13. 3 4
      src/Avalonia.Themes.Default/Slider.xaml
  14. 2 2
      src/Avalonia.Themes.Default/TabControl.xaml
  15. 1 1
      src/Avalonia.Themes.Default/TextBox.xaml
  16. 1 3
      src/Avalonia.Themes.Default/TreeViewItem.xaml
  17. 0 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  18. 1 1
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  19. 40 45
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  20. 0 51
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
  21. 5 3
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/TypeDescriptorExtensions.cs
  22. 4 2
      src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs
  23. 178 0
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  24. 2 2
      tests/Avalonia.Base.UnitTests/PriorityValueTests.cs
  25. 21 0
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  26. 11 2
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs
  27. 4 10
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
  28. 1 1
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

+ 2 - 2
samples/ControlCatalog/SideBar.xaml

@@ -8,7 +8,7 @@
             <TabStrip Name="PART_TabStrip"
                       MemberSelector="{x:Static TabControl.HeaderSelector}"
                       Items="{TemplateBinding Items}"
-                      SelectedIndex="{TemplateBinding Path=SelectedIndex, Mode=TwoWay}">
+                      SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}">
               <TabStrip.ItemsPanel>
                 <ItemsPanelTemplate>
                   <StackPanel Orientation="Vertical"/>
@@ -20,7 +20,7 @@
                     Margin="8 0 0 0"
                     MemberSelector="{x:Static TabControl.ContentSelector}"
                     Items="{TemplateBinding Items}"
-                    SelectedIndex="{TemplateBinding Path=SelectedIndex}"
+                    SelectedIndex="{TemplateBinding SelectedIndex}"
                     PageTransition="{TemplateBinding PageTransition}"
                     Grid.Row="1"/>
         </DockPanel>

+ 2 - 2
samples/RenderDemo/SideBar.xaml

@@ -9,7 +9,7 @@
             <TabStrip Name="PART_TabStrip"
                       MemberSelector="{x:Static TabControl.HeaderSelector}"
                       Items="{TemplateBinding Items}"
-                      SelectedIndex="{TemplateBinding Path=SelectedIndex, Mode=TwoWay}">
+                      SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}">
               <TabStrip.ItemsPanel>
                 <ItemsPanelTemplate>
                   <StackPanel Orientation="Vertical"/>
@@ -21,7 +21,7 @@
                     Margin="8 0 0 0"
                     MemberSelector="{x:Static TabControl.ContentSelector}"
                     Items="{TemplateBinding Items}"
-                    SelectedIndex="{TemplateBinding Path=SelectedIndex}"
+                    SelectedIndex="{TemplateBinding SelectedIndex}"
                     PageTransition="{TemplateBinding PageTransition}"
                     Grid.Row="1"/>
         </DockPanel>

+ 33 - 106
src/Avalonia.Base/AvaloniaObject.cs

@@ -29,12 +29,6 @@ namespace Avalonia
         /// </summary>
         private IAvaloniaObject _inheritanceParent;
 
-        /// <summary>
-        /// The set values/bindings on this object.
-        /// </summary>
-        private readonly Dictionary<AvaloniaProperty, PriorityValue> _values =
-            new Dictionary<AvaloniaProperty, PriorityValue>();
-
         /// <summary>
         /// Maintains a list of direct property binding subscriptions so that the binding source
         /// doesn't get collected.
@@ -52,6 +46,7 @@ namespace Avalonia
         private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
 
         private DeferredSetter<AvaloniaProperty, object> _directDeferredSetter;
+        private ValueStore _values;
 
         /// <summary>
         /// Delayed setter helper for direct properties. Used to fix #855.
@@ -228,9 +223,20 @@ namespace Avalonia
             {
                 return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this);
             }
+            else if (_values != null)
+            {
+                var result = _values.GetValue(property);
+
+                if (result == AvaloniaProperty.UnsetValue)
+                {
+                    result = GetDefaultValue(property);
+                }
+
+                return result;
+            }
             else
             {
-                return GetValueInternal(property);
+                return GetDefaultValue(property);
             }
         }
 
@@ -257,7 +263,7 @@ namespace Avalonia
             Contract.Requires<ArgumentNullException>(property != null);
             VerifyAccess();
 
-            return _values.TryGetValue(property, out PriorityValue value) ? value.IsAnimating : false;
+            return _values?.IsAnimating(property) ?? false;
         }
 
         /// <summary>
@@ -274,14 +280,7 @@ namespace Avalonia
             Contract.Requires<ArgumentNullException>(property != null);
             VerifyAccess();
 
-            PriorityValue value;
-
-            if (_values.TryGetValue(property, out value))
-            {
-                return value.Value != AvaloniaProperty.UnsetValue;
-            }
-
-            return false;
+            return _values?.IsSet(property) ?? false;
         }
 
         /// <summary>
@@ -369,14 +368,6 @@ namespace Avalonia
             }
             else
             {
-                PriorityValue v;
-
-                if (!_values.TryGetValue(property, out v))
-                {
-                    v = CreatePriorityValue(property);
-                    _values.Add(property, v);
-                }
-
                 Logger.Verbose(
                     LogArea.Property,
                     this,
@@ -385,7 +376,12 @@ namespace Avalonia
                     description,
                     priority);
 
-                return v.Add(source, (int)priority);
+                if (_values == null)
+                {
+                    _values = new ValueStore(this);
+                }
+
+                return _values.AddBinding(property, source, priority);
             }
         }
 
@@ -416,20 +412,12 @@ namespace Avalonia
         public void Revalidate(AvaloniaProperty property)
         {
             VerifyAccess();
-            PriorityValue value;
-
-            if (_values.TryGetValue(property, out value))
-            {
-                value.Revalidate();
-            }
+            _values?.Revalidate(property);
         }
 
         /// <inheritdoc/>
-        void IPriorityValueOwner.Changed(PriorityValue sender, object oldValue, object newValue)
+        void IPriorityValueOwner.Changed(AvaloniaProperty property, int priority, object oldValue, object newValue)
         {
-            var property = sender.Property;
-            var priority = (BindingPriority)sender.ValuePriority;
-
             oldValue = (oldValue == AvaloniaProperty.UnsetValue) ?
                 GetDefaultValue(property) :
                 oldValue;
@@ -439,7 +427,7 @@ namespace Avalonia
 
             if (!Equals(oldValue, newValue))
             {
-                RaisePropertyChanged(property, oldValue, newValue, priority);
+                RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority);
 
                 Logger.Verbose(
                     LogArea.Property,
@@ -448,14 +436,14 @@ namespace Avalonia
                     property,
                     oldValue,
                     newValue,
-                    priority);
+                    (BindingPriority)priority);
             }
         }
 
         /// <inheritdoc/>
-        void IPriorityValueOwner.BindingNotificationReceived(PriorityValue sender, BindingNotification notification)
+        void IPriorityValueOwner.BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
         {
-            UpdateDataValidation(sender.Property, notification);
+            UpdateDataValidation(property, notification);
         }
 
         /// <inheritdoc/>
@@ -468,10 +456,7 @@ namespace Avalonia
         /// Gets all priority values set on the object.
         /// </summary>
         /// <returns>A collection of property/value tuples.</returns>
-        internal IDictionary<AvaloniaProperty, PriorityValue> GetSetValues()
-        {
-            return _values;
-        }
+        internal IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => _values?.GetSetValues();
 
         /// <summary>
         /// Forces revalidation of properties when a property value changes.
@@ -660,68 +645,18 @@ namespace Avalonia
             }
         }
 
-        /// <summary>
-        /// Creates a <see cref="PriorityValue"/> for a <see cref="AvaloniaProperty"/>.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <returns>The <see cref="PriorityValue"/>.</returns>
-        private PriorityValue CreatePriorityValue(AvaloniaProperty property)
-        {
-            var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(GetType());
-            Func<object, object> validate2 = null;
-
-            if (validate != null)
-            {
-                validate2 = v => validate(this, v);
-            }
-
-            PriorityValue result = new PriorityValue(
-                this,
-                property,
-                property.PropertyType, 
-                validate2);
-
-            return result;
-        }
-
         /// <summary>
         /// Gets the default value for a property.
         /// </summary>
         /// <param name="property">The property.</param>
         /// <returns>The default value.</returns>
-        private object GetDefaultValue(AvaloniaProperty property)
+        internal object GetDefaultValue(AvaloniaProperty property)
         {
             if (property.Inherits && InheritanceParent is AvaloniaObject aobj)
-                return aobj.GetValueInternal(property);
+                return aobj.GetValue(property);
             return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
         }
 
-        /// <summary>
-        /// Gets a <see cref="AvaloniaProperty"/> value
-        /// without check for registered as this can slow getting the value
-        /// this method is intended for internal usage in AvaloniaObject only
-        /// it's called only after check the property is registered
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <returns>The value.</returns>
-        private object GetValueInternal(AvaloniaProperty property)
-        {
-            object result = AvaloniaProperty.UnsetValue;
-            PriorityValue value;
-
-            if (_values.TryGetValue(property, out value))
-            {
-                result = value.Value;
-            }
-
-            if (result == AvaloniaProperty.UnsetValue)
-            {
-                result = GetDefaultValue(property);
-            }
-
-            return result;
-        }
-
         /// <summary>
         /// Sets the value of a direct property.
         /// </summary>
@@ -802,21 +737,13 @@ namespace Avalonia
                     originalValue?.GetType().FullName ?? "(null)"));
             }
 
-            PriorityValue v;
-
-            if (!_values.TryGetValue(property, out v))
+            if (_values == null)
             {
-                if (value == AvaloniaProperty.UnsetValue)
-                {
-                    return;
-                }
-
-                v = CreatePriorityValue(property);
-                _values.Add(property, v);
+                _values = new ValueStore(this);
             }
 
             LogPropertySet(property, value, priority);
-            v.SetValue(value, (int)priority);
+            _values.AddValue(property, value, (int)priority);
         }
 
         /// <summary>

+ 5 - 4
src/Avalonia.Base/IPriorityValueOwner.cs

@@ -13,18 +13,19 @@ namespace Avalonia
         /// <summary>
         /// Called when a <see cref="PriorityValue"/>'s value changes.
         /// </summary>
-        /// <param name="sender">The source of the change.</param>
+        /// <param name="property">The the property that has changed.</param>
+        /// <param name="priority">The priority of the value.</param>
         /// <param name="oldValue">The old value.</param>
         /// <param name="newValue">The new value.</param>
-        void Changed(PriorityValue sender, object oldValue, object newValue);
+        void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue);
 
         /// <summary>
         /// Called when a <see cref="BindingNotification"/> is received by a 
         /// <see cref="PriorityValue"/>.
         /// </summary>
-        /// <param name="sender">The source of the change.</param>
+        /// <param name="property">The the property that has changed.</param>
         /// <param name="notification">The notification.</param>
-        void BindingNotificationReceived(PriorityValue sender, BindingNotification notification);
+        void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification);
 
         /// <summary>
         /// Ensures that the current thread is the UI thread.

+ 2 - 2
src/Avalonia.Base/PriorityValue.cs

@@ -281,12 +281,12 @@ namespace Avalonia
 
                 if (notification == null || notification.HasValue)
                 {
-                    notify(() => Owner?.Changed(this, old, Value));
+                    notify(() => Owner?.Changed(Property, ValuePriority, old, Value));
                 }
 
                 if (notification != null)
                 {
-                    Owner?.BindingNotificationReceived(this, notification);
+                    Owner?.BindingNotificationReceived(Property, notification);
                 }
             }
             else

+ 172 - 0
src/Avalonia.Base/ValueStore.cs

@@ -0,0 +1,172 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Data;
+
+namespace Avalonia
+{
+    internal class ValueStore : IPriorityValueOwner
+    {
+        private readonly AvaloniaObject _owner;
+        private readonly Dictionary<AvaloniaProperty, object> _values =
+            new Dictionary<AvaloniaProperty, object>();
+
+        public ValueStore(AvaloniaObject owner)
+        {
+            _owner = owner;
+        }
+
+        public IDisposable AddBinding(
+            AvaloniaProperty property,
+            IObservable<object> source,
+            BindingPriority priority)
+        {
+            PriorityValue priorityValue;
+
+            if (_values.TryGetValue(property, out var v))
+            {
+                priorityValue = v as PriorityValue;
+
+                if (priorityValue == null)
+                {
+                    priorityValue = CreatePriorityValue(property);
+                    priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
+                    _values[property] = priorityValue;
+                }
+            }
+            else
+            {
+                priorityValue = CreatePriorityValue(property);
+                _values.Add(property, priorityValue);
+            }
+
+            return priorityValue.Add(source, (int)priority);
+        }
+
+        public void AddValue(AvaloniaProperty property, object value, int priority)
+        {
+            PriorityValue priorityValue;
+
+            if (_values.TryGetValue(property, out var v))
+            {
+                priorityValue = v as PriorityValue;
+
+                if (priorityValue == null)
+                {
+                    if (priority == (int)BindingPriority.LocalValue)
+                    {
+                        _values[property] = Validate(property, value);
+                        Changed(property, priority, v, value);
+                        return;
+                    }
+                    else
+                    {
+                        priorityValue = CreatePriorityValue(property);
+                        priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
+                        _values[property] = priorityValue;
+                    }
+                }
+            }
+            else
+            {
+                if (value == AvaloniaProperty.UnsetValue)
+                {
+                    return;
+                }
+
+                if (priority == (int)BindingPriority.LocalValue)
+                {
+                    _values.Add(property, Validate(property, value));
+                    Changed(property, priority, AvaloniaProperty.UnsetValue, value);
+                    return;
+                }
+                else
+                {
+                    priorityValue = CreatePriorityValue(property);
+                    _values.Add(property, priorityValue);
+                }
+            }
+
+            priorityValue.SetValue(value, priority);
+        }
+
+        public void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
+        {
+            ((IPriorityValueOwner)_owner).BindingNotificationReceived(property, notification);
+        }
+
+        public void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue)
+        {
+            ((IPriorityValueOwner)_owner).Changed(property, priority, oldValue, newValue);
+        }
+
+        public IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => throw new NotImplementedException();
+
+        public object GetValue(AvaloniaProperty property)
+        {
+            var result = AvaloniaProperty.UnsetValue;
+
+            if (_values.TryGetValue(property, out var value))
+            {
+                result = (value is PriorityValue priorityValue) ? priorityValue.Value : value;
+            }
+
+            return result;
+        }
+
+        public bool IsAnimating(AvaloniaProperty property)
+        {
+            return _values.TryGetValue(property, out var value) ? (value as PriorityValue)?.IsAnimating ?? false : false;
+        }
+
+        public bool IsSet(AvaloniaProperty property)
+        {
+            if (_values.TryGetValue(property, out var value))
+            {
+                return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue;
+            }
+
+            return false;
+        }
+
+        public void Revalidate(AvaloniaProperty property)
+        {
+            if (_values.TryGetValue(property, out var value))
+            {
+                (value as PriorityValue)?.Revalidate();
+            }
+        }
+
+        public void VerifyAccess() => _owner.VerifyAccess();
+
+        private PriorityValue CreatePriorityValue(AvaloniaProperty property)
+        {
+            var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType());
+            Func<object, object> validate2 = null;
+
+            if (validate != null)
+            {
+                validate2 = v => validate(_owner, v);
+            }
+
+            PriorityValue result = new PriorityValue(
+                this,
+                property,
+                property.PropertyType,
+                validate2);
+
+            return result;
+        }
+
+        private object Validate(AvaloniaProperty property, object value)
+        {
+            var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType());
+
+            if (validate != null && value != AvaloniaProperty.UnsetValue)
+            {
+                return validate(_owner, value);
+            }
+
+            return value;
+        }
+    }
+}

+ 7 - 7
src/Avalonia.Controls/Window.cs

@@ -302,18 +302,17 @@ namespace Avalonia.Controls
 
         internal void Close(bool ignoreCancel)
         {
-            var cancelClosing = false;
             try
             {
-                cancelClosing = HandleClosing();
+                if (!ignoreCancel && HandleClosing())
+                {
+                    return;
+                }
             }
             finally
             {
-                if (ignoreCancel || !cancelClosing)
-                {                  
-                    PlatformImpl?.Dispose();
-                    HandleClosed();
-                }
+                PlatformImpl?.Dispose();
+                HandleClosed();
             }
         }
 
@@ -324,6 +323,7 @@ namespace Avalonia.Controls
         {
             var args = new CancelEventArgs();
             Closing?.Invoke(this, args);
+
             return args.Cancel;
         }
 

+ 1 - 1
src/Avalonia.Themes.Default/AutoCompleteBox.xaml

@@ -16,7 +16,7 @@
                    DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" />
           
           <Popup Name="PART_Popup"
-                 MinWidth="{TemplateBinding Bounds.Width}"
+                 MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
                  MaxHeight="{TemplateBinding MaxDropDownHeight}"
                  PlacementTarget="{TemplateBinding}"
                  StaysOpen="False">

+ 1 - 1
src/Avalonia.Themes.Default/DropDown.xaml

@@ -32,7 +32,7 @@
             </ToggleButton>
             <Popup Name="PART_Popup"
                    IsOpen="{TemplateBinding IsDropDownOpen, Mode=TwoWay}"
-                   MinWidth="{TemplateBinding Bounds.Width}"
+                   MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
                    MaxHeight="{TemplateBinding MaxDropDownHeight}"
                    PlacementTarget="{TemplateBinding}"
                    StaysOpen="False">

+ 2 - 2
src/Avalonia.Themes.Default/MenuItem.xaml

@@ -45,7 +45,7 @@
             <Popup Name="PART_Popup"
                    PlacementMode="Right"
                    StaysOpen="True"
-                   IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}"
+                   IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}"
                    ObeyScreenEdges="True">
               <Border Background="{TemplateBinding Background}"
                       BorderBrush="{DynamicResource ThemeBorderMidBrush}"
@@ -92,7 +92,7 @@
               </ContentPresenter.DataTemplates>
             </ContentPresenter>
             <Popup Name="PART_Popup"
-                   IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}"
+                   IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}"
                    StaysOpen="True" 
                    ObeyScreenEdges="True">
               <Border Background="{TemplateBinding Background}"

+ 2 - 2
src/Avalonia.Themes.Default/ScrollBar.xaml

@@ -16,7 +16,7 @@
                                Grid.Column="1"
                                Minimum="{TemplateBinding Minimum}"
                                Maximum="{TemplateBinding Maximum}"
-                               Value="{TemplateBinding Path=Value, Mode=TwoWay}"
+                               Value="{TemplateBinding Value, Mode=TwoWay}"
                                ViewportSize="{TemplateBinding ViewportSize}"
                                Orientation="{TemplateBinding Orientation}">
                             <Track.DecreaseButton>
@@ -67,7 +67,7 @@
                                Grid.Column="1"
                                Minimum="{TemplateBinding Minimum}"
                                Maximum="{TemplateBinding Maximum}"
-                               Value="{TemplateBinding Path=Value, Mode=TwoWay}"
+                               Value="{TemplateBinding Value, Mode=TwoWay}"
                                ViewportSize="{TemplateBinding ViewportSize}"
                                Orientation="{TemplateBinding Orientation}">
                             <Track.DecreaseButton>

+ 5 - 5
src/Avalonia.Themes.Default/ScrollViewer.xaml

@@ -9,21 +9,21 @@
                                 CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
                                 CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}"
                                 Content="{TemplateBinding Content}"
-                                Extent="{TemplateBinding Path=Extent, Mode=TwoWay}"
+                                Extent="{TemplateBinding Extent, Mode=TwoWay}"
                                 Margin="{TemplateBinding Padding}"
-                                Offset="{TemplateBinding Path=Offset, Mode=TwoWay}"
-                                Viewport="{TemplateBinding Path=Viewport, Mode=TwoWay}"/>
+                                Offset="{TemplateBinding Offset, Mode=TwoWay}"
+                                Viewport="{TemplateBinding Viewport, Mode=TwoWay}"/>
         <ScrollBar Name="horizontalScrollBar"
                    Orientation="Horizontal"
                    Maximum="{TemplateBinding HorizontalScrollBarMaximum}"
-                   Value="{TemplateBinding Path=HorizontalScrollBarValue, Mode=TwoWay}"
+                   Value="{TemplateBinding HorizontalScrollBarValue, Mode=TwoWay}"
                    ViewportSize="{TemplateBinding HorizontalScrollBarViewportSize}"
                    Visibility="{TemplateBinding HorizontalScrollBarVisibility}"
                    Grid.Row="1"/>
         <ScrollBar Name="verticalScrollBar"
                    Orientation="Vertical"
                    Maximum="{TemplateBinding VerticalScrollBarMaximum}"
-                   Value="{TemplateBinding Path=VerticalScrollBarValue, Mode=TwoWay}"
+                   Value="{TemplateBinding VerticalScrollBarValue, Mode=TwoWay}"
                    ViewportSize="{TemplateBinding VerticalScrollBarViewportSize}"
                    Visibility="{TemplateBinding VerticalScrollBarVisibility}"
                    Grid.Column="1"/>

+ 3 - 4
src/Avalonia.Themes.Default/Slider.xaml

@@ -11,7 +11,7 @@
             <RowDefinition Height="Auto"/>
           </Grid.RowDefinitions>
           <Border Name="TrackBackground" Grid.Row="1" Height="4" Margin="6,0" VerticalAlignment="Center"/>
-          <Track Name="PART_Track" Grid.Row="1">
+          <Track Name="PART_Track" Grid.Row="1" Orientation="Horizontal">
             <Track.DecreaseButton>
                <RepeatButton Name="PART_DecreaseButton"
                              Classes="repeattrack" />
@@ -46,7 +46,7 @@
             <ColumnDefinition Width="Auto"/>
           </Grid.ColumnDefinitions>
           <Border Name="TrackBackground" Grid.Column="1" Width="4" Margin="0,6" HorizontalAlignment="Center"/>
-          <Track Name="PART_Track" Grid.Column="1">
+          <Track Name="PART_Track" Grid.Column="1" Orientation="Vertical">
             <Track.DecreaseButton>
                <RepeatButton Name="PART_DecreaseButton"
                              Classes="repeattrack" />
@@ -72,8 +72,7 @@
   <Style Selector="Slider /template/ Track#PART_Track">
     <Setter Property="Minimum" Value="{TemplateBinding Minimum}"/>
     <Setter Property="Maximum" Value="{TemplateBinding Maximum}"/>
-    <Setter Property="Value" Value="{TemplateBinding Path=Value, Mode=TwoWay}"/>
-    <Setter Property="Orientation" Value="{TemplateBinding Orientation}"/>
+    <Setter Property="Value" Value="{TemplateBinding Value, Mode=TwoWay}"/>
   </Style>
   <Style Selector="Slider /template/ Border#TrackBackground">
     <Setter Property="BorderThickness" Value="2"/>

+ 2 - 2
src/Avalonia.Themes.Default/TabControl.xaml

@@ -9,11 +9,11 @@
             <TabStrip Name="PART_TabStrip"
                       MemberSelector="{x:Static TabControl.HeaderSelector}"
                       Items="{TemplateBinding Items}"
-                      SelectedIndex="{TemplateBinding Path=SelectedIndex, Mode=TwoWay}"/>
+                      SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}"/>
             <Carousel Name="PART_Content"
                       MemberSelector="{x:Static TabControl.ContentSelector}"
                       Items="{TemplateBinding Items}"
-                      SelectedIndex="{TemplateBinding Path=SelectedIndex}"
+                      SelectedIndex="{TemplateBinding SelectedIndex}"
                       PageTransition="{TemplateBinding PageTransition}"
                       Grid.Row="1"/>
           </DockPanel>

+ 1 - 1
src/Avalonia.Themes.Default/TextBox.xaml

@@ -36,7 +36,7 @@
                   <TextBlock Name="watermark"
                              Opacity="0.5"
                              Text="{TemplateBinding Watermark}"
-                             IsVisible="{TemplateBinding Path=Text, Converter={x:Static StringConverters.NullOrEmpty}}"/>
+                             IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.NullOrEmpty}}"/>
                   <TextPresenter Name="PART_TextPresenter"
                                  Text="{TemplateBinding Text, Mode=TwoWay}"
                                  CaretIndex="{TemplateBinding CaretIndex}"

+ 1 - 3
src/Avalonia.Themes.Default/TreeViewItem.xaml

@@ -7,14 +7,12 @@
           <Grid ColumnDefinitions="16, Auto">
             <ToggleButton Name="expander"
                           Focusable="False"
-                          IsChecked="{TemplateBinding Path=IsExpanded, Mode=TwoWay}"/>
+                          IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}"/>
             <ContentPresenter Name="PART_HeaderPresenter"
                               Background="{TemplateBinding Background}"
                               BorderBrush="{TemplateBinding BorderBrush}"
                               BorderThickness="{TemplateBinding BorderThickness}"
                               Content="{TemplateBinding Header}"
-                              HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
-                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                               Padding="{TemplateBinding Padding}"
                               TemplatedControl.IsTemplateFocusTarget="True"
                               Grid.Column="1"/>

+ 0 - 1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@@ -48,7 +48,6 @@
         <Compile Include="Converters\SelectorTypeConverter.cs" />
         <Compile Include="MarkupExtensions\BindingExtension.cs" />
         <Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
-        <Compile Include="MarkupExtensions\TemplateBindingExtension.cs" />
         <Compile Include="PortableXaml\AvaloniaTypeAttributeProvider.cs" />
         <Compile Include="PortableXaml\AvaloniaXamlType.cs" />
         <Compile Include="PortableXaml\TypeDescriptorExtensions.cs" />

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@@ -139,7 +139,7 @@ namespace Avalonia.Markup.Xaml
                     {
                         uriString = new Uri(baseUri, uri).AbsoluteUri;
                     }
-                    throw new XamlLoadException("Error loading xaml at " + uriString, e);
+                    throw new XamlLoadException("Error loading xaml at " + uriString + ": " + e.Message, e);
                 }
             }
         }

+ 40 - 45
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs

@@ -2,19 +2,21 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.ComponentModel;
 using System.Globalization;
-using System.Linq;
+using System.Text.RegularExpressions;
+using Avalonia.Markup.Xaml.Templates;
+using Avalonia.Styling;
+using Portable.Xaml;
+using Portable.Xaml.ComponentModel;
+using Portable.Xaml.Markup;
 
 namespace Avalonia.Markup.Xaml.Converters
 {
-    using Avalonia.Styling;
-    using Portable.Xaml;
-    using Portable.Xaml.ComponentModel;
-    using System.ComponentModel;
-    using Portable.Xaml.Markup;
-
     public class AvaloniaPropertyTypeConverter : TypeConverter
     {
+        private static readonly Regex regex = new Regex(@"^\(?(\w*)\.(\w*)\)?|(.*)$");
+
         public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
         {
             return sourceType == typeof(string);
@@ -22,65 +24,58 @@ namespace Avalonia.Markup.Xaml.Converters
 
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
-            var s = (string)value;
+            var (owner, propertyName) = ParseProperty((string)value);
+            var ownerType = TryResolveOwnerByName(context, owner) ??
+                context.GetFirstAmbientValue<ControlTemplate>()?.TargetType ??
+                context.GetFirstAmbientValue<Style>()?.Selector?.TargetType;
 
-            string typeName;
-            string propertyName;
-            Type type = null;
+            if (ownerType == null)
+            {
+                throw new XamlLoadException(
+                    $"Could not determine the owner type for property '{propertyName}'. " +
+                    "Please fully qualify the property name or specify a target type on " +
+                    "the containing template.");
+            }
 
-            ParseProperty(s, out typeName, out propertyName);
+            var property = AvaloniaPropertyRegistry.Instance.FindRegistered(ownerType, propertyName);
 
-            if (typeName == null)
+            if (property == null)
             {
-                var style = context.GetFirstAmbientValue<Style>();
+                throw new XamlLoadException($"Could not find AvaloniaProperty '{ownerType.Name}.{propertyName}'.");
+            }
 
-                type = style?.Selector?.TargetType;
+            return property;
+        }
 
-                if (type == null)
-                {
-                    throw new Exception(
-                        "Could not determine the target type. Please fully qualify the property name.");
-                }
-            }
-            else
+        private Type TryResolveOwnerByName(ITypeDescriptorContext context, string owner)
+        {
+            if (owner != null)
             {
-                var typeResolver = context.GetService<IXamlTypeResolver>();
-                type = typeResolver.Resolve(typeName);
+                var resolver = context.GetService<IXamlTypeResolver>();
+                var result = resolver.Resolve(owner);
 
-                if (type == null)
+                if (result == null)
                 {
-                    throw new Exception($"Could not find type '{typeName}'.");
+                    throw new XamlLoadException($"Could not find type '{owner}'.");
                 }
-            }
-
-            AvaloniaProperty property = AvaloniaPropertyRegistry.Instance.FindRegistered(type, propertyName);
 
-            if (property == null)
-            {
-                throw new Exception(
-                    $"Could not find AvaloniaProperty '{type.Name}.{propertyName}'.");
+                return result;
             }
 
-            return property;
+            return null;
         }
 
-        private void ParseProperty(string s, out string typeName, out string propertyName)
+        private (string owner, string property) ParseProperty(string s)
         {
-            var split = s.Split('.');
+            var result = regex.Match(s);
 
-            if (split.Length == 1)
-            {
-                typeName = null;
-                propertyName = split[0];
-            }
-            else if (split.Length == 2)
+            if (result.Groups[1].Success)
             {
-                typeName = split[0];
-                propertyName = split[1];
+                return (result.Groups[1].Value, result.Groups[2].Value);
             }
             else
             {
-                throw new Exception($"Invalid property name: '{s}'.");
+                return (null, result.Groups[3].Value);
             }
         }
     }

+ 0 - 51
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs

@@ -1,51 +0,0 @@
-// 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.Data;
-
-namespace Avalonia.Markup.Xaml.MarkupExtensions
-{
-    using System;
-    using Avalonia.Data.Converters;
-    using Avalonia.Markup.Data;
-    using Portable.Xaml.Markup;
-
-    [MarkupExtensionReturnType(typeof(IBinding))]
-    public class TemplateBindingExtension : MarkupExtension
-    {
-        public TemplateBindingExtension()
-        {
-        }
-
-        public TemplateBindingExtension(string path)
-        {
-            Path = path;
-        }
-
-        public override object ProvideValue(IServiceProvider serviceProvider)
-        {
-            return new Binding
-            {
-                Converter = Converter,
-                ElementName = ElementName,
-                Mode = Mode,
-                RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
-                Path = Path ?? string.Empty,
-                Priority = Priority,
-            };
-        }
-
-        public IValueConverter Converter { get; set; }
-
-        public string ElementName { get; set; }
-
-        public object FallbackValue { get; set; }
-
-        public BindingMode Mode { get; set; }
-
-        [ConstructorArgument("path")]
-        public string Path { get; set; }
-
-        public BindingPriority Priority { get; set; } = BindingPriority.TemplatedParent;
-    }
-}

+ 5 - 3
src/Markup/Avalonia.Markup.Xaml/PortableXaml/TypeDescriptorExtensions.cs

@@ -44,15 +44,17 @@ namespace Portable.Xaml.ComponentModel
             var amb = ctx.GetService<IAmbientProvider>();
             var sc = ctx.GetService<IXamlSchemaContextProvider>().SchemaContext;
 
-            return amb.GetFirstAmbientValue(sc.GetXamlType(typeof(T))) as T;
+            // Because GetFirstAmbientValue uses XamlType.CanAssignTo it returns values that
+            // aren't actually of the correct type. Use GetAllAmbientValues instead.
+            return amb.GetAllAmbientValues(sc.GetXamlType(typeof(T))).OfType<T>().FirstOrDefault();
         }
 
         public static T GetLastOrDefaultAmbientValue<T>(this ITypeDescriptorContext ctx) where T : class
         {
-            return ctx.GetAllambientValues<T>().LastOrDefault() as T;
+            return ctx.GetAllAmbientValues<T>().LastOrDefault() as T;
         }
 
-        public static IEnumerable<T> GetAllambientValues<T>(this ITypeDescriptorContext ctx) where T : class
+        public static IEnumerable<T> GetAllAmbientValues<T>(this ITypeDescriptorContext ctx) where T : class
         {
             var amb = ctx.GetService<IAmbientProvider>();
             var sc = ctx.GetService<IXamlSchemaContextProvider>().SchemaContext;

+ 4 - 2
src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs

@@ -1,6 +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 System;
 using Avalonia.Controls;
 using Avalonia.Controls.Templates;
 using Avalonia.Metadata;
@@ -14,7 +15,8 @@ namespace Avalonia.Markup.Xaml.Templates
         [TemplateContent]
         public object Content { get; set; }
 
-        public IControl Build(ITemplatedControl control)
-                            => TemplateContent.Load(Content);
+        public Type TargetType { get; set; }
+
+        public IControl Build(ITemplatedControl control) => TemplateContent.Load(Content);
     }
 }

+ 178 - 0
src/Markup/Avalonia.Markup/Data/TemplateBinding.cs

@@ -0,0 +1,178 @@
+using System;
+using System.Globalization;
+using System.Reactive.Subjects;
+using Avalonia.Data.Converters;
+using Avalonia.Reactive;
+
+namespace Avalonia.Data
+{
+    /// <summary>
+    /// A XAML binding to a property on a control's templated parent.
+    /// </summary>
+    public class TemplateBinding : SingleSubscriberObservableBase<object>,
+        IBinding,
+        IDescription,
+        ISubject<object>
+    {
+        private IStyledElement _target;
+        private Type _targetType;
+
+        public TemplateBinding()
+        {
+        }
+
+        public TemplateBinding(AvaloniaProperty property)
+        {
+            Property = property;
+        }
+
+        /// <inheritdoc/>
+        public InstancedBinding Initiate(
+            IAvaloniaObject target,
+            AvaloniaProperty targetProperty,
+            object anchor = null,
+            bool enableDataValidation = false)
+        {
+            // Usually each `TemplateBinding` will only be instantiated once; in this case we can
+            // use the `TemplateBinding` object itself as the instanced binding in order to save
+            // allocating a new object. If the binding *is* instantiated more than once (which can
+            // happen if it appears in a `Setter` for example, then just make a clone and instantiate
+            // that.
+            if (_target == null)
+            {
+                _target = (IStyledElement)target;
+                _targetType = targetProperty?.PropertyType;
+
+                return new InstancedBinding(
+                    this,
+                    Mode == BindingMode.Default ? BindingMode.OneWay : Mode,
+                    BindingPriority.TemplatedParent);
+            }
+            else
+            {
+                var clone = new TemplateBinding
+                {
+                    Converter = Converter,
+                    ConverterParameter = ConverterParameter,
+                    Property = Property,
+                };
+
+                return clone.Initiate(target, targetProperty, anchor, enableDataValidation);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the <see cref="IValueConverter"/> to use.
+        /// </summary>
+        public IValueConverter Converter { get; set; }
+
+        /// <summary>
+        /// Gets or sets a parameter to pass to <see cref="Converter"/>.
+        /// </summary>
+        public object ConverterParameter { get; set; }
+
+        /// <summary>
+        /// Gets or sets the binding mode.
+        /// </summary>
+        public BindingMode Mode { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name of the source property on the templated parent.
+        /// </summary>
+        public AvaloniaProperty Property { get; set; }
+
+        /// <inheritdoc/>
+        public string Description => "TemplateBinding: " + Property;
+
+        void IObserver<object>.OnCompleted() => throw new NotImplementedException();
+        void IObserver<object>.OnError(Exception error) => throw new NotImplementedException();
+
+        void IObserver<object>.OnNext(object value)
+        {
+            if (_target.TemplatedParent != null && Property != null)
+            {
+                if (Converter != null)
+                {
+                    value = Converter.ConvertBack(
+                        value,
+                        Property.PropertyType,
+                        ConverterParameter,
+                        CultureInfo.CurrentCulture);
+                }
+
+                _target.TemplatedParent.SetValue(Property, value, BindingPriority.TemplatedParent);
+            }
+        }
+
+        protected override void Subscribed()
+        {
+            TemplatedParentChanged();
+            _target.PropertyChanged += TargetPropertyChanged;
+        }
+
+        protected override void Unsubscribed()
+        {
+            if (_target.TemplatedParent != null)
+            {
+                _target.TemplatedParent.PropertyChanged -= TemplatedParentPropertyChanged;
+            }
+
+            _target.PropertyChanged -= TargetPropertyChanged;
+        }
+
+        private void PublishValue()
+        {
+            if (_target.TemplatedParent != null)
+            {
+                var value = Property != null ?
+                    _target.TemplatedParent.GetValue(Property) :
+                    _target.TemplatedParent;
+
+                if (Converter != null)
+                {
+                    value = Converter.Convert(value, _targetType, ConverterParameter, CultureInfo.CurrentCulture);
+                }
+
+                PublishNext(value);
+            }
+            else
+            {
+                PublishNext(AvaloniaProperty.UnsetValue);
+            }
+        }
+
+        private void TemplatedParentChanged()
+        {
+            if (_target.TemplatedParent != null)
+            {
+                _target.TemplatedParent.PropertyChanged += TemplatedParentPropertyChanged;
+            }
+
+            PublishValue();
+        }
+
+        private void TargetPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Property == StyledElement.TemplatedParentProperty)
+            {
+                var oldValue = (IAvaloniaObject)e.OldValue;
+                var newValue = (IAvaloniaObject)e.OldValue;
+
+                if (oldValue != null)
+                {
+                    oldValue.PropertyChanged -= TemplatedParentPropertyChanged;
+                }
+
+                TemplatedParentChanged();
+            }
+        }
+
+        private void TemplatedParentPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Property == Property)
+            {
+                PublishNext(_target.TemplatedParent.GetValue(Property));
+            }
+        }
+    }
+}

+ 2 - 2
tests/Avalonia.Base.UnitTests/PriorityValueTests.cs

@@ -167,7 +167,7 @@ namespace Avalonia.Base.UnitTests
 
             target.Add(Single("foo"), 0);
 
-            owner.Verify(x => x.Changed(target, AvaloniaProperty.UnsetValue, "foo"));
+            owner.Verify(x => x.Changed(target.Property, target.ValuePriority, AvaloniaProperty.UnsetValue, "foo"));
         }
 
         [Fact]
@@ -180,7 +180,7 @@ namespace Avalonia.Base.UnitTests
             target.Add(subject, 0);
             subject.OnNext("bar");
 
-            owner.Verify(x => x.Changed(target, "foo", "bar"));
+            owner.Verify(x => x.Changed(target.Property, target.ValuePriority, "foo", "bar"));
         }
 
         [Fact]

+ 21 - 0
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@@ -188,6 +188,27 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
+        [Fact]
+        public void Closing_Should_Only_Be_Invoked_Once()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var window = new Window();
+                var count = 0;
+
+                window.Closing +=
+                    (sender, e) =>
+                    {
+                        count++;
+                    };
+
+                window.Show();
+                window.Close();
+
+                Assert.Equal(1, count);
+            }
+        }
+
         [Fact]
         public void Showing_Should_Start_Renderer()
         {

+ 11 - 2
tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs

@@ -8,7 +8,6 @@ using Avalonia.Markup.Xaml.Converters;
 using Avalonia.Styling;
 using Xunit;
 using System.ComponentModel;
-using Portable.Xaml.ComponentModel;
 using Portable.Xaml;
 using Portable.Xaml.Markup;
 
@@ -53,7 +52,17 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
 
             Assert.Equal(AttachedOwner.AttachedProperty, result);
         }
-        
+
+        [Fact]
+        public void ConvertFrom_Finds_Attached_Property_With_Parentheses()
+        {
+            var target = new AvaloniaPropertyTypeConverter();
+            var context = CreateContext();
+            var result = target.ConvertFrom(context, null, "(AttachedOwner.Attached)");
+
+            Assert.Equal(AttachedOwner.AttachedProperty, result);
+        }
+
         private ITypeDescriptorContext CreateContext(Style style = null)
         {
             var tdMock = new Mock<ITypeDescriptorContext>();

+ 4 - 10
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs

@@ -1,18 +1,12 @@
 // 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.Linq;
 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 Xunit;
 
 namespace Avalonia.Markup.Xaml.UnitTests.Data
 {
@@ -30,7 +24,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
     <Button Name='button'>
        <Button.Template>
          <ControlTemplate>
-           <TextBlock Text='{TemplateBinding}'/>
+           <TextBlock Tag='{TemplateBinding}'/>
          </ControlTemplate>
        </Button.Template>
     </Button>
@@ -43,7 +37,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
                 button.ApplyTemplate();
 
                 var textBlock = (TextBlock)button.GetVisualChildren().Single();
-                Assert.Equal("Avalonia.Controls.Button", textBlock.Text);
+                Assert.Same(button, textBlock.Tag);
             }
         }
     }

+ 1 - 1
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@@ -686,7 +686,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
 <Window xmlns='https://github.com/avaloniaui'
                 xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
     <Window.Template>
-        <ControlTemplate>
+        <ControlTemplate TargetType='Window'>
             <ContentPresenter Name='PART_ContentPresenter'
                         Content='{TemplateBinding Content}'/>
         </ControlTemplate>