Browse Source

Merge branch 'master' into monomac-deferred

pull/1240/head
Nikita Tsukanov 9 years ago
parent
commit
636fa04d94
  1. 5
      build/Splat.props
  2. 4
      packages.cake
  3. 1
      samples/BindingTest/BindingTest.csproj
  4. 1
      samples/RenderTest/RenderTest.csproj
  5. 1
      samples/VirtualizationTest/VirtualizationTest.csproj
  6. 1
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  7. 1
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  8. 61
      src/Avalonia.Controls/Control.cs
  9. 8
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  10. 46
      src/Avalonia.Controls/Shapes/Shape.cs
  11. 1
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  12. 53
      src/Avalonia.Diagnostics/LogManager.cs
  13. 1
      src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
  14. 10
      src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs
  15. 3
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  16. 8
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  17. 133
      tests/Avalonia.Controls.UnitTests/ControlTests.cs
  18. 113
      tests/Avalonia.Layout.UnitTests/ShapeLayoutTests.cs
  19. 6
      tests/Avalonia.Markup.UnitTests/Data/Plugins/DataAnnotationsValidationPluginTests.cs
  20. 1
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  21. 59
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs
  22. 23
      tests/Avalonia.UnitTests/TestWithServicesBase.cs

5
build/Splat.props

@ -1,5 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="Splat" Version="2.0.0" />
</ItemGroup>
</Project>

4
packages.cake

@ -107,7 +107,6 @@ public class Packages
context.Information("Setting NuGet package dependencies versions:");
var SerilogVersion = packageVersions["Serilog"].FirstOrDefault().Item1;
var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
var ReactiveUIVersion = packageVersions["reactiveui"].FirstOrDefault().Item1;
@ -121,7 +120,6 @@ public class Packages
var SharpDXDXGIVersion = packageVersions["SharpDX.DXGI"].FirstOrDefault().Item1;
context.Information("Package: Serilog, version: {0}", SerilogVersion);
context.Information("Package: Splat, version: {0}", SplatVersion);
context.Information("Package: Sprache, version: {0}", SpracheVersion);
context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
context.Information("Package: reactiveui, version: {0}", ReactiveUIVersion);
@ -245,7 +243,6 @@ public class Packages
Dependencies = new DependencyBuilder(this)
{
new NuSpecDependency() { Id = "Serilog", Version = SerilogVersion },
new NuSpecDependency() { Id = "Splat", Version = SplatVersion },
new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version },
@ -253,7 +250,6 @@ public class Packages
new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp2.0", Version = "4.3.0" },
new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp2.0", Version = "1.1.0" },
new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netcoreapp2.0", Version = "1.6.0" },
new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp2.0", Version = SplatVersion },
new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp2.0", Version = SerilogVersion },
new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp2.0", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp2.0", Version = SystemReactiveVersion },

1
samples/BindingTest/BindingTest.csproj

@ -152,7 +152,6 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

1
samples/RenderTest/RenderTest.csproj

@ -181,7 +181,6 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

1
samples/VirtualizationTest/VirtualizationTest.csproj

@ -148,7 +148,6 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

1
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -29,6 +29,5 @@
</ItemGroup>
<Import Project="..\..\..\build\Serilog.props" />
<Import Project="..\..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\..\build\Splat.props" />
<Import Project="..\..\..\build\Rx.props" />
</Project>

1
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -154,7 +154,6 @@
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\..\build\Serilog.props" />
<Import Project="..\..\..\build\Splat.props" />
<Import Project="..\..\..\build\Sprache.props" />
<Import Project="..\..\..\build\Rx.props" />
</Project>

61
src/Avalonia.Controls/Control.cs

@ -101,6 +101,7 @@ namespace Avalonia.Controls
private Styles _styles;
private bool _styled;
private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
private bool _dataContextUpdating;
/// <summary>
/// Initializes static members of the <see cref="Control"/> class.
@ -111,6 +112,7 @@ namespace Avalonia.Controls
PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled");
PseudoClass(IsFocusedProperty, ":focus");
PseudoClass(IsPointerOverProperty, ":pointerover");
DataContextProperty.Changed.AddClassHandler<Control>(x => x.OnDataContextChangedCore);
}
/// <summary>
@ -681,18 +683,26 @@ namespace Avalonia.Controls
}
/// <summary>
/// Called before the <see cref="DataContext"/> property changes.
/// Called when the <see cref="DataContext"/> property changes.
/// </summary>
protected virtual void OnDataContextChanging()
/// <param name="e">The event args.</param>
protected virtual void OnDataContextChanged(EventArgs e)
{
DataContextChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Called after the <see cref="DataContext"/> property changes.
/// Called when the <see cref="DataContext"/> begins updating.
/// </summary>
protected virtual void OnDataContextChanged()
protected virtual void OnDataContextBeginUpdate()
{
}
/// <summary>
/// Called when the <see cref="DataContext"/> finishes updating.
/// </summary>
protected virtual void OnDataContextEndUpdate()
{
DataContextChanged?.Invoke(this, EventArgs.Empty);
}
/// <inheritdoc/>
@ -745,24 +755,38 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="DataContext"/> property begins and ends being notified.
/// </summary>
/// <param name="o">The object on which the DataContext is changing.</param>
/// <param name="notifying">Whether the notifcation is beginning or ending.</param>
private static void DataContextNotifying(IAvaloniaObject o, bool notifying)
{
var control = o as Control;
if (o is Control control)
{
DataContextNotifying(control, notifying);
}
}
if (control != null)
private static void DataContextNotifying(Control control, bool notifying)
{
if (notifying)
{
if (notifying)
if (!control._dataContextUpdating)
{
control.OnDataContextChanging();
control._dataContextUpdating = true;
control.OnDataContextBeginUpdate();
foreach (var child in control.LogicalChildren)
{
if (child is Control c && !c.IsSet(DataContextProperty))
{
DataContextNotifying(c, notifying);
}
}
}
else
}
else
{
if (control._dataContextUpdating)
{
control.OnDataContextChanged();
control.OnDataContextEndUpdate();
control._dataContextUpdating = false;
}
}
}
@ -881,6 +905,11 @@ namespace Avalonia.Controls
}
}
private void OnDataContextChangedCore(AvaloniaPropertyChangedEventArgs e)
{
OnDataContextChanged(EventArgs.Empty);
}
private void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)

8
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -414,16 +414,16 @@ namespace Avalonia.Controls.Primitives
}
/// <inheritdoc/>
protected override void OnDataContextChanging()
protected override void OnDataContextBeginUpdate()
{
base.OnDataContextChanging();
base.OnDataContextBeginUpdate();
++_updateCount;
}
/// <inheritdoc/>
protected override void OnDataContextChanged()
protected override void OnDataContextEndUpdate()
{
base.OnDataContextChanged();
base.OnDataContextEndUpdate();
if (--_updateCount == 0)
{

46
src/Avalonia.Controls/Shapes/Shape.cs

@ -29,6 +29,8 @@ namespace Avalonia.Controls.Shapes
private Matrix _transform = Matrix.Identity;
private Geometry _definingGeometry;
private Geometry _renderedGeometry;
bool _calculateTransformOnArrange = false;
static Shape()
{
@ -150,13 +152,53 @@ namespace Avalonia.Controls.Shapes
this._definingGeometry = null;
InvalidateMeasure();
}
protected override Size MeasureOverride(Size availableSize)
{
bool deferCalculateTransform;
switch (Stretch)
{
case Stretch.Fill:
case Stretch.UniformToFill:
deferCalculateTransform = double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height);
break;
case Stretch.Uniform:
deferCalculateTransform = double.IsInfinity(availableSize.Width) && double.IsInfinity(availableSize.Height);
break;
case Stretch.None:
default:
deferCalculateTransform = false;
break;
}
if (deferCalculateTransform)
{
_calculateTransformOnArrange = true;
return DefiningGeometry.Bounds.Size;
}
else
{
_calculateTransformOnArrange = false;
return CalculateShapeSizeAndSetTransform(availableSize);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
if(_calculateTransformOnArrange)
{
_calculateTransformOnArrange = false;
CalculateShapeSizeAndSetTransform(finalSize);
}
return finalSize;
}
private Size CalculateShapeSizeAndSetTransform(Size availableSize)
{
// This should probably use GetRenderBounds(strokeThickness) but then the calculations
// will multiply the stroke thickness as well, which isn't correct.
var (size, transform) = CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch);
if (_transform != transform)
{
_transform = transform;

1
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -50,5 +50,4 @@
</EmbeddedResource>
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Splat.props" />
</Project>

53
src/Avalonia.Diagnostics/LogManager.cs

@ -1,53 +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 System;
using Avalonia.Layout;
using Splat;
namespace Avalonia.Diagnostics
{
public class LogManager : ILogManager
{
private static LogManager s_instance;
public static LogManager Instance => s_instance ?? (s_instance = new LogManager());
public ILogger Logger
{
get;
set;
}
public bool LogPropertyMessages
{
get;
set;
}
public bool LogLayoutMessages
{
get;
set;
}
public static void Enable(ILogger logger)
{
Instance.Logger = logger;
Locator.CurrentMutable.Register(() => Instance, typeof(ILogManager));
}
public IFullLogger GetLogger(Type type)
{
if ((type == typeof(AvaloniaObject) && LogPropertyMessages) ||
(type == typeof(Layoutable) && LogLayoutMessages))
{
return new WrappingFullLogger(Logger, type);
}
else
{
return new WrappingFullLogger(new NullLogger(), type);
}
}
}
}

1
src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj

@ -16,5 +16,4 @@
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
<Import Project="..\..\build\Splat.props" />
</Project>

10
src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs

@ -50,6 +50,16 @@ namespace Avalonia.LogicalTree
}
}
public static IEnumerable<ILogical> GetSelfAndLogicalDescendants(this ILogical logical)
{
yield return logical;
foreach (var descendent in logical.GetLogicalDescendants())
{
yield return descendent;
}
}
public static ILogical GetLogicalParent(this ILogical logical)
{
return logical.LogicalParent;

3
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@ -35,6 +35,9 @@ namespace Avalonia.Gtk3
X11.XInitThreads();
}catch{}
Resolver.Resolve();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
using (var backends = new Utf8Buffer("x11"))
Native.GdkSetAllowedBackends?.Invoke(backends);
Native.GtkInit(0, IntPtr.Zero);
var disp = Native.GdkGetDefaultDisplay();
DisplayClassName =

8
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@ -48,10 +48,13 @@ namespace Avalonia.Gtk3.Interop
public delegate void gtk_main_iteration();
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate GtkWindow gtk_window_new(GtkWindowType windowType);
public delegate GtkWindow gtk_window_new(GtkWindowType windowType);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_init(int argc, IntPtr argv);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk, optional: true)]
public delegate IntPtr gdk_set_allowed_backends (Utf8Buffer backends);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_present(GtkWindow gtkWindow);
@ -393,6 +396,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_window_new GtkWindowNew;
public static D.gtk_window_set_icon GtkWindowSetIcon;
public static D.gtk_window_set_modal GtkWindowSetModal;
public static D.gdk_set_allowed_backends GdkSetAllowedBackends;
public static D.gtk_init GtkInit;
public static D.gtk_window_present GtkWindowPresent;
public static D.gtk_widget_hide GtkWidgetHide;

133
tests/Avalonia.Controls.UnitTests/ControlTests.cs

@ -8,6 +8,7 @@ using Moq;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;
using Avalonia.LogicalTree;
namespace Avalonia.Controls.UnitTests
{
@ -328,9 +329,139 @@ namespace Avalonia.Controls.UnitTests
Assert.True(target.IsInitialized);
}
private class TestControl : Control
[Fact]
public void DataContextChanged_Should_Be_Called()
{
var root = new TestStackPanel
{
Name = "root",
Children =
{
new TestControl
{
Name = "a1",
Child = new TestControl
{
Name = "b1",
}
},
new TestControl
{
Name = "a2",
DataContext = "foo",
},
}
};
var called = new List<string>();
void Record(object sender, EventArgs e) => called.Add(((Control)sender).Name);
root.DataContextChanged += Record;
foreach (TestControl c in root.GetLogicalDescendants())
{
c.DataContextChanged += Record;
}
root.DataContext = "foo";
Assert.Equal(new[] { "root", "a1", "b1", }, called);
}
[Fact]
public void DataContext_Notifications_Should_Be_Called_In_Correct_Order()
{
var root = new TestStackPanel
{
Name = "root",
Children =
{
new TestControl
{
Name = "a1",
Child = new TestControl
{
Name = "b1",
}
},
new TestControl
{
Name = "a2",
DataContext = "foo",
},
}
};
var called = new List<string>();
foreach (IDataContextEvents c in root.GetSelfAndLogicalDescendants())
{
c.DataContextBeginUpdate += (s, e) => called.Add("begin " + ((Control)s).Name);
c.DataContextChanged += (s, e) => called.Add("changed " + ((Control)s).Name);
c.DataContextEndUpdate += (s, e) => called.Add("end " + ((Control)s).Name);
}
root.DataContext = "foo";
Assert.Equal(
new[]
{
"begin root",
"begin a1",
"begin b1",
"changed root",
"changed a1",
"changed b1",
"end b1",
"end a1",
"end root",
},
called);
}
private interface IDataContextEvents
{
event EventHandler DataContextBeginUpdate;
event EventHandler DataContextChanged;
event EventHandler DataContextEndUpdate;
}
private class TestControl : Decorator, IDataContextEvents
{
public event EventHandler DataContextBeginUpdate;
public event EventHandler DataContextEndUpdate;
public new IAvaloniaObject InheritanceParent => base.InheritanceParent;
protected override void OnDataContextBeginUpdate()
{
DataContextBeginUpdate?.Invoke(this, EventArgs.Empty);
base.OnDataContextBeginUpdate();
}
protected override void OnDataContextEndUpdate()
{
DataContextEndUpdate?.Invoke(this, EventArgs.Empty);
base.OnDataContextEndUpdate();
}
}
private class TestStackPanel : StackPanel, IDataContextEvents
{
public event EventHandler DataContextBeginUpdate;
public event EventHandler DataContextEndUpdate;
protected override void OnDataContextBeginUpdate()
{
DataContextBeginUpdate?.Invoke(this, EventArgs.Empty);
base.OnDataContextBeginUpdate();
}
protected override void OnDataContextEndUpdate()
{
DataContextEndUpdate?.Invoke(this, EventArgs.Empty);
base.OnDataContextEndUpdate();
}
}
}
}

113
tests/Avalonia.Layout.UnitTests/ShapeLayoutTests.cs

@ -0,0 +1,113 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Layout.UnitTests
{
public class ShapeLayoutTests : TestWithServicesBase
{
public ShapeLayoutTests()
{
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToSingleton<MockPlatformRenderInterface>();
}
[Fact]
public void Shape_Transformation_Calculation_Should_Be_Deferred_To_Arrange_When_Strech_Is_Fill_And_Aviable_Size_Is_Infinite()
{
var shape = new Polygon()
{
Points = new List<Point>
{
new Point(0, 0),
new Point(10, 5),
new Point(0, 10)
},
Stretch = Stretch.Fill
};
var availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
shape.Measure(availableSize);
Geometry postMeasureGeometry = shape.RenderedGeometry;
Transform postMeasureTransform = postMeasureGeometry.Transform;
var finalSize = new Size(100, 50);
var finalRect = new Rect(finalSize);
shape.Arrange(finalRect);
Geometry postArrangeGeometry = shape.RenderedGeometry;
Transform postArrangeTransform = postArrangeGeometry.Transform;
Assert.NotEqual(postMeasureGeometry, postArrangeGeometry);
Assert.NotEqual(postMeasureTransform, postArrangeTransform);
Assert.Equal(finalSize, shape.Bounds.Size);
}
[Fact]
public void Shape_Transformation_Calculation_Should_Not_Be_Deferred_To_Arrange_When_Strech_Is_Fill_And_Aviable_Size_Is_Finite()
{
var shape = new Polygon()
{
Points = new List<Point>
{
new Point(0, 0),
new Point(10, 5),
new Point(0, 10)
},
Stretch = Stretch.Fill
};
var availableSize = new Size(100, 50);
shape.Measure(availableSize);
Geometry postMeasureGeometry = shape.RenderedGeometry;
Transform postMeasureTransform = postMeasureGeometry.Transform;
var finalRect = new Rect(availableSize);
shape.Arrange(finalRect);
Geometry postArrangeGeometry = shape.RenderedGeometry;
Transform postArrangeTransform = postArrangeGeometry.Transform;
Assert.Equal(postMeasureGeometry, postArrangeGeometry);
Assert.Equal(postMeasureTransform, postArrangeTransform);
Assert.Equal(availableSize, shape.Bounds.Size);
}
[Fact]
public void Shape_Transformation_Calculation_Should_Not_Be_Deferred_To_Arrange_When_Strech_Is_None()
{
var shape = new Polygon()
{
Points = new List<Point>
{
new Point(0, 0),
new Point(10, 5),
new Point(0, 10)
},
Stretch = Stretch.None
};
var availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
shape.Measure(availableSize);
Geometry postMeasureGeometry = shape.RenderedGeometry;
Transform postMeasureTransform = postMeasureGeometry.Transform;
var finalSize = new Size(100, 50);
var finalRect = new Rect(finalSize);
shape.Arrange(finalRect);
Geometry postArrangeGeometry = shape.RenderedGeometry;
Transform postArrangeTransform = postArrangeGeometry.Transform;
Assert.Equal(postMeasureGeometry, postArrangeGeometry);
Assert.Equal(postMeasureTransform, postArrangeTransform);
Assert.Equal(finalSize, shape.Bounds.Size);
}
}
}

6
tests/Avalonia.Markup.UnitTests/Data/Plugins/DataAnnotationsValidationPluginTests.cs

@ -49,6 +49,8 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.Between5And10));
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.Between5And10), accessor);
var result = new List<object>();
var errmsg = new RangeAttribute(5, 10).FormatErrorMessage(nameof(Data.Between5And10));
validator.Subscribe(x => result.Add(x));
validator.SetValue(3, BindingPriority.LocalValue);
@ -59,12 +61,12 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
{
new BindingNotification(5),
new BindingNotification(
new ValidationException("The field Between5And10 must be between 5 and 10."),
new ValidationException(errmsg),
BindingErrorType.DataValidationError,
3),
new BindingNotification(7),
new BindingNotification(
new ValidationException("The field Between5And10 must be between 5 and 10."),
new ValidationException(errmsg),
BindingErrorType.DataValidationError,
11),
}, result);

1
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -8,7 +8,6 @@
<Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Sprache.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />

59
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs

@ -1,7 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.UnitTests;
using Xunit;
@ -67,5 +70,61 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.True(called);
}
}
[Fact]
public void Can_Bind_Between_TabStrip_And_Carousel()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'>
<DockPanel>
<TabStrip Name='strip' DockPanel.Dock='Top' Items='{Binding Items}' SelectedIndex='0'>
<TabStrip.DataTemplates>
<DataTemplate>
<TextBlock Text='{Binding Header}'/>
</DataTemplate>
</TabStrip.DataTemplates>
</TabStrip>
<Carousel Name='carousel' Items='{Binding Items}' SelectedIndex='{Binding #strip.SelectedIndex}'>
<Carousel.DataTemplates>
<DataTemplate>
<TextBlock Text='{Binding Detail}'/>
</DataTemplate>
</Carousel.DataTemplates>
</Carousel>
</DockPanel>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var strip = window.FindControl<TabStrip>("strip");
var carousel = window.FindControl<Carousel>("carousel");
window.DataContext = new ItemsViewModel
{
Items = new[]
{
new ItemViewModel { Header = "Item1", Detail = "Detail1" },
new ItemViewModel { Header = "Item2", Detail = "Detail2" },
}
};
window.Show();
Assert.Equal(0, strip.SelectedIndex);
Assert.Equal(0, carousel.SelectedIndex);
}
}
private class ItemsViewModel
{
public IList<ItemViewModel> Items { get; set; }
}
private class ItemViewModel
{
public string Header { get; set; }
public string Detail { get; set; }
}
}
}

23
tests/Avalonia.UnitTests/TestWithServicesBase.cs

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.UnitTests
{
public class TestWithServicesBase : IDisposable
{
private IDisposable _scope;
public TestWithServicesBase()
{
_scope = AvaloniaLocator.EnterScope();
}
public void Dispose()
{
_scope.Dispose();
}
}
}
Loading…
Cancel
Save