Browse Source

Make indexer produce/consume IBinding.

pull/691/head
Steven Kirk 10 years ago
parent
commit
3ee83263c8
  1. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  2. 47
      src/Avalonia.Base/AvaloniaObject.cs
  3. 23
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  4. 40
      src/Avalonia.Base/Data/IndexerBinding.cs
  5. 20
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  6. 13
      src/Avalonia.Diagnostics/Views/ControlDetailsView.cs
  7. 12
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  8. 6
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  9. 4
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  10. 2
      tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs

1
src/Avalonia.Base/Avalonia.Base.csproj

@ -44,6 +44,7 @@
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Data\BindingError.cs" />
<Compile Include="Data\IndexerBinding.cs" />
<Compile Include="Data\IValidationStatus.cs" />
<Compile Include="Data\ObjectValidationStatus.cs" />
<Compile Include="Diagnostics\INotifyCollectionChangedDebug.cs" />

47
src/Avalonia.Base/AvaloniaObject.cs

@ -178,59 +178,22 @@ namespace Avalonia
/// Gets or sets a binding for a <see cref="AvaloniaProperty"/>.
/// </summary>
/// <param name="binding">The binding information.</param>
public IObservable<object> this[IndexerDescriptor binding]
public IBinding this[IndexerDescriptor binding]
{
get
{
return CreateBindingDescriptor(binding);
return new IndexerBinding(this, binding.Property, binding.Mode);
}
set
{
var metadata = binding.Property.GetMetadata(GetType());
var sourceBinding = value as IBinding;
var mode = (binding.Mode == BindingMode.Default) ?
metadata.DefaultBindingMode :
binding.Mode;
var sourceBinding = value as IndexerDescriptor;
if (sourceBinding == null && mode > BindingMode.OneWay)
{
mode = BindingMode.OneWay;
}
switch (mode)
{
case BindingMode.Default:
case BindingMode.OneWay:
Bind(binding.Property, value, binding.Priority);
break;
case BindingMode.OneTime:
SetValue(binding.Property, sourceBinding.Source.GetValue(sourceBinding.Property), binding.Priority);
break;
case BindingMode.OneWayToSource:
sourceBinding.Source.Bind(sourceBinding.Property, this.GetObservable(binding.Property), binding.Priority);
break;
case BindingMode.TwoWay:
var subject = sourceBinding.Source.GetSubject(sourceBinding.Property, sourceBinding.Priority);
var instanced = new InstancedBinding(subject, BindingMode.TwoWay, sourceBinding.Priority);
BindingOperations.Apply(this, binding.Property, instanced, null);
break;
}
this.Bind(binding.Property, sourceBinding);
}
}
protected virtual IndexerDescriptor CreateBindingDescriptor(IndexerDescriptor source)
{
return new IndexerDescriptor
{
Mode = source.Mode,
Priority = source.Priority,
Property = source.Property,
Source = this,
};
}
public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess();
@ -389,6 +352,8 @@ namespace Avalonia
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(source != null);
VerifyAccess();
if (property.IsDirect)

23
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -16,6 +16,11 @@ namespace Avalonia
/// </summary>
public static class AvaloniaObjectExtensions
{
public static IBinding AsBinding<T>(this IObservable<T> source)
{
return new BindingAdaptor(source.Select(x => (object)x));
}
/// <summary>
/// Gets an observable for a <see cref="AvaloniaProperty"/>.
/// </summary>
@ -293,5 +298,23 @@ namespace Avalonia
handler(target)(e);
}
}
private class BindingAdaptor : IBinding
{
private IObservable<object> _source;
public BindingAdaptor(IObservable<object> source)
{
this._source = source;
}
public InstancedBinding Initiate(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor = null)
{
return new InstancedBinding(_source);
}
}
}
}

40
src/Avalonia.Base/Data/IndexerBinding.cs

@ -0,0 +1,40 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Data
{
public class IndexerBinding : IBinding
{
public IndexerBinding(
IAvaloniaObject source,
AvaloniaProperty property,
BindingMode mode)
{
Source = source;
Property = property;
Mode = mode;
}
private IAvaloniaObject Source { get; }
public AvaloniaProperty Property { get; }
private BindingMode Mode { get; }
public InstancedBinding Initiate(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor = null)
{
var mode = Mode == BindingMode.Default ?
targetProperty.GetMetadata(target.GetType()).DefaultBindingMode :
Mode;
if (mode == BindingMode.TwoWay)
{
return new InstancedBinding(Source.GetSubject(Property), mode);
}
else
{
return new InstancedBinding(Source.GetObservable(Property), mode);
}
}
}
}

20
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -2,10 +2,7 @@
// 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 Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Logging;
using Avalonia.LogicalTree;
@ -274,23 +271,6 @@ namespace Avalonia.Controls.Primitives
}
}
/// <inheritdoc/>
protected sealed override IndexerDescriptor CreateBindingDescriptor(IndexerDescriptor source)
{
var result = base.CreateBindingDescriptor(source);
// If the binding is a template binding, then complete when the Template changes.
if (source.Priority == BindingPriority.TemplatedParent)
{
var templateChanged = this.GetObservable(TemplateProperty).Skip(1);
result.SourceObservable = result.Source.GetObservable(result.Property)
.TakeUntil(templateChanged);
}
return result;
}
/// <inheritdoc/>
protected override IControl GetTemplateFocusTarget()
{

13
src/Avalonia.Diagnostics/Views/ControlDetailsView.cs

@ -49,7 +49,7 @@ namespace Avalonia.Diagnostics.Views
},
},
[GridRepeater.TemplateProperty] = pt,
[!GridRepeater.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Properties),
[!GridRepeater.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Properties).AsBinding(),
}
};
}
@ -62,19 +62,24 @@ namespace Avalonia.Diagnostics.Views
{
Text = property.Name,
TextWrapping = TextWrapping.NoWrap,
[!ToolTip.TipProperty] = property.WhenAnyValue(x => x.Diagnostic),
[!ToolTip.TipProperty] = property
.WhenAnyValue(x => x.Diagnostic)
.AsBinding(),
};
yield return new TextBlock
{
TextWrapping = TextWrapping.NoWrap,
[!TextBlock.TextProperty] = property.WhenAnyValue(v => v.Value).Select(v => v?.ToString()),
[!TextBlock.TextProperty] = property
.WhenAnyValue(v => v.Value)
.Select(v => v?.ToString())
.AsBinding(),
};
yield return new TextBlock
{
TextWrapping = TextWrapping.NoWrap,
[!TextBlock.TextProperty] = property.WhenAnyValue(x => x.Priority),
[!TextBlock.TextProperty] = property.WhenAnyValue(x => x.Priority).AsBinding(),
};
}
}

12
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@ -227,14 +227,14 @@ namespace Avalonia.Base.UnitTests
public void this_Operator_Binds_One_Way()
{
Class1 target1 = new Class1();
Class1 target2 = new Class1();
IndexerDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneWay);
Class2 target2 = new Class2();
IndexerDescriptor binding = Class2.BarProperty.Bind().WithMode(BindingMode.OneWay);
target1.SetValue(Class1.FooProperty, "first");
target2[binding] = target1[!Class1.FooProperty];
target1.SetValue(Class1.FooProperty, "second");
Assert.Equal("second", target2.GetValue(Class1.FooProperty));
Assert.Equal("second", target2.GetValue(Class2.BarProperty));
}
[Fact]
@ -242,10 +242,9 @@ namespace Avalonia.Base.UnitTests
{
Class1 target1 = new Class1();
Class1 target2 = new Class1();
IndexerDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.TwoWay);
target1.SetValue(Class1.FooProperty, "first");
target2[binding] = target1[!Class1.FooProperty];
target2[!Class1.FooProperty] = target1[!!Class1.FooProperty];
Assert.Equal("first", target2.GetValue(Class1.FooProperty));
target1.SetValue(Class1.FooProperty, "second");
Assert.Equal("second", target2.GetValue(Class1.FooProperty));
@ -258,10 +257,9 @@ namespace Avalonia.Base.UnitTests
{
Class1 target1 = new Class1();
Class1 target2 = new Class1();
IndexerDescriptor binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneTime);
target1.SetValue(Class1.FooProperty, "first");
target2[binding] = target1[!Class1.FooProperty];
target2[!Class1.FooProperty] = target1[Class1.FooProperty.Bind().WithMode(BindingMode.OneTime)];
target1.SetValue(Class1.FooProperty, "second");
Assert.Equal("first", target2.GetValue(Class1.FooProperty));

6
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -162,8 +162,8 @@ namespace Avalonia.Controls.UnitTests
Content = new ItemsPresenter
{
Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty),
[~ItemsPresenter.ItemsPanelProperty] = parent.GetObservable(ItemsControl.ItemsPanelProperty),
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).AsBinding(),
[~ItemsPresenter.ItemsPanelProperty] = parent.GetObservable(ItemsControl.ItemsPanelProperty).AsBinding(),
}
});
}
@ -185,7 +185,7 @@ namespace Avalonia.Controls.UnitTests
new ScrollContentPresenter
{
Name = "PART_ContentPresenter",
[~ScrollContentPresenter.ContentProperty] = parent.GetObservable(ScrollViewer.ContentProperty),
[~ScrollContentPresenter.ContentProperty] = parent.GetObservable(ScrollViewer.ContentProperty).AsBinding(),
[~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty],
[~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty],
[~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty],

4
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@ -207,7 +207,7 @@ namespace Avalonia.Controls.UnitTests
Content = new ItemsPresenter
{
Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty),
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).AsBinding(),
}
};
}
@ -217,7 +217,7 @@ namespace Avalonia.Controls.UnitTests
return new ScrollContentPresenter
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty),
[~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty).AsBinding(),
};
}

2
tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs

@ -224,7 +224,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Child = new ContentPresenter
{
[~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty),
[~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty).AsBinding(),
}
};
}),

Loading…
Cancel
Save