Browse Source

Merge branch 'master' into fix-filedialog-bugs

pull/1720/head
Wiesław Šoltés 8 years ago
committed by GitHub
parent
commit
f48b9c352b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      samples/ControlCatalog/SideBar.xaml
  2. 4
      samples/RenderDemo/SideBar.xaml
  3. 139
      src/Avalonia.Base/AvaloniaObject.cs
  4. 9
      src/Avalonia.Base/IPriorityValueOwner.cs
  5. 4
      src/Avalonia.Base/PriorityValue.cs
  6. 172
      src/Avalonia.Base/ValueStore.cs
  7. 1
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  8. 14
      src/Avalonia.Controls/Window.cs
  9. 2
      src/Avalonia.Themes.Default/AutoCompleteBox.xaml
  10. 2
      src/Avalonia.Themes.Default/DropDown.xaml
  11. 4
      src/Avalonia.Themes.Default/MenuItem.xaml
  12. 4
      src/Avalonia.Themes.Default/ScrollBar.xaml
  13. 10
      src/Avalonia.Themes.Default/ScrollViewer.xaml
  14. 7
      src/Avalonia.Themes.Default/Slider.xaml
  15. 4
      src/Avalonia.Themes.Default/TabControl.xaml
  16. 2
      src/Avalonia.Themes.Default/TextBox.xaml
  17. 4
      src/Avalonia.Themes.Default/TreeViewItem.xaml
  18. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  19. 2
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  20. 85
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  21. 51
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
  22. 8
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/TypeDescriptorExtensions.cs
  23. 6
      src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs
  24. 178
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  25. 4
      tests/Avalonia.Base.UnitTests/PriorityValueTests.cs
  26. 21
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  27. 13
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs
  28. 14
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
  29. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

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

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

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

9
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.

4
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
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;
}
}
}

1
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -21,7 +21,6 @@ namespace Avalonia.Controls.Primitives
static AdornerLayer()
{
AdornedElementProperty.Changed.Subscribe(AdornedElementChanged);
IsHitTestVisibleProperty.OverrideDefaultValue(typeof(AdornerLayer), false);
}
public AdornerLayer()

14
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;
}

2
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">

2
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">

4
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}"

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

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

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

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

2
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}"

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

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

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

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

8
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;

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

4
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
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()
{

13
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>();

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

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

Loading…
Cancel
Save