소스 검색

Reimplemented TemplateBinding.

Rather than just use a standard `Binding`, make `TemplateBinding` a lightweight binding in the case where the binding is simply to a property on the templated parent.
Steven Kirk 7 년 전
부모
커밋
b8f127fce7

+ 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>

+ 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"/>

+ 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);
                 }
             }
         }

+ 44 - 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,62 @@ namespace Avalonia.Markup.Xaml.Converters
 
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
-            var s = (string)value;
-
-            string typeName;
-            string propertyName;
-            Type type = null;
+            var (owner, propertyName) = ParseProperty((string)value);
+            var ownerType = TryResolveOwnerByName(context, owner) ??
+                context.GetFirstAmbientValue<ControlTemplate>()?.TargetType ??
+                context.GetFirstAmbientValue<Style>()?.Selector?.TargetType;
 
-            ParseProperty(s, out typeName, out propertyName);
+            var ipvt = context.GetService<IProvideValueTarget>();
+            var idtp = context.GetService<IDestinationTypeProvider>();
+            var ao = context.GetFirstAmbientValue<AvaloniaObject>();
 
-            if (typeName == null)
+            if (ownerType == null)
             {
-                var style = context.GetFirstAmbientValue<Style>();
+                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.");
+            }
 
-                type = style?.Selector?.TargetType;
+            var property = AvaloniaPropertyRegistry.Instance.FindRegistered(ownerType, propertyName);
 
-                if (type == null)
-                {
-                    throw new Exception(
-                        "Could not determine the target type. Please fully qualify the property name.");
-                }
+            if (property == null)
+            {
+                throw new XamlLoadException($"Could not find AvaloniaProperty '{ownerType.Name}.{propertyName}'.");
             }
-            else
+
+            return property;
+        }
+
+        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);
             }
         }
     }

+ 23 - 19
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs

@@ -1,15 +1,13 @@
 // 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.Data;
+using Avalonia.Data.Converters;
+using Portable.Xaml.Markup;
 
 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
     {
@@ -17,35 +15,41 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
         {
         }
 
-        public TemplateBindingExtension(string path)
+        public TemplateBindingExtension(AvaloniaProperty property)
         {
-            Path = path;
+            Property = property;
         }
 
         public override object ProvideValue(IServiceProvider serviceProvider)
         {
-            return new Binding
+            return new TemplateBinding
             {
                 Converter = Converter,
-                ElementName = ElementName,
+                ConverterParameter = ConverterParameter,
                 Mode = Mode,
-                RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
-                Path = Path ?? string.Empty,
-                Priority = Priority,
+                Property = Property,
             };
         }
 
+        /// <summary>
+        /// Gets or sets the <see cref="IValueConverter"/> to use.
+        /// </summary>
         public IValueConverter Converter { get; set; }
 
-        public string ElementName { get; set; }
-
-        public object FallbackValue { 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; }
 
-        [ConstructorArgument("path")]
-        public string Path { get; set; }
-
-        public BindingPriority Priority { get; set; } = BindingPriority.TemplatedParent;
+        /// <summary>
+        /// Gets or sets the name of the source property on the templated parent.
+        /// </summary>
+        [ConstructorArgument("property")]
+        public AvaloniaProperty Property { get; set; }
     }
 }

+ 1 - 0
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs

@@ -166,6 +166,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
         {
             { typeof(Binding), typeof(BindingExtension) },
             { typeof(StyleInclude), typeof(StyleIncludeExtension) },
+            { typeof(TemplateBinding), typeof(TemplateBindingExtension) },
         };
 
         private XamlType GetAvaloniaXamlType(Type type)

+ 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);
     }
 }

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

@@ -0,0 +1,169 @@
+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;
+
+        /// <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));
+            }
+        }
+    }
+}

+ 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>