Browse Source

Merge branch 'master' into sdoroff-shape-layout-fixed

repro-selecting-items-control-not-working-inside-popup
Nikita Tsukanov 9 years ago
parent
commit
9f16df624f
  1. 2
      .travis.yml
  2. 6
      samples/ControlCatalog/SideBar.xaml
  3. 6
      samples/RenderTest/SideBar.xaml
  4. 2
      samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs
  5. 61
      src/Avalonia.Controls/Control.cs
  6. 8
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  7. 10
      src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs
  8. 12
      src/Avalonia.Styling/Styling/Setter.cs
  9. 6
      src/Avalonia.Themes.Default/TabControl.xaml
  10. 8
      src/Avalonia.Themes.Default/TextBox.xaml
  11. 3
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  12. 12
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  13. 1
      src/Gtk/Avalonia.Gtk3/PopupImpl.cs
  14. 36
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  15. 2
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  16. 25
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticExtension.cs
  17. 23
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TypeExtension.cs
  18. 4
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs
  19. 133
      tests/Avalonia.Controls.UnitTests/ControlTests.cs
  20. 6
      tests/Avalonia.Markup.UnitTests/Data/Plugins/DataAnnotationsValidationPluginTests.cs
  21. 7
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  22. 59
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs
  23. 9
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
  24. 37
      tests/Avalonia.Styling.UnitTests/SetterTests.cs

2
.travis.yml

@ -8,7 +8,7 @@ env:
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
- DOTNET_CLI_TELEMETRY_OPTOUT=1
mono:
- latest
- 5.2.0
dotnet: 2.0.0
script:
- ./build.sh --target "Travis" --platform "Mono" --configuration "Release"

6
samples/ControlCatalog/SideBar.xaml

@ -1,11 +1,11 @@
<Styles xmlns="https://github.com/avaloniaui">
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TabControl.sidebar">
<Setter Property="Template">
<ControlTemplate>
<DockPanel>
<ScrollViewer MinWidth="190" Background="{DynamicResource ThemeAccentBrush}" DockPanel.Dock="Left">
<TabStrip Name="PART_TabStrip"
MemberSelector="{Static TabControl.HeaderSelector}"
MemberSelector="{x:Static TabControl.HeaderSelector}"
Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex, Mode=TwoWay}">
<TabStrip.ItemsPanel>
@ -17,7 +17,7 @@
</ScrollViewer>
<Carousel Name="PART_Content"
Margin="8 0 0 0"
MemberSelector="{Static TabControl.ContentSelector}"
MemberSelector="{x:Static TabControl.ContentSelector}"
Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex}"
Transition="{TemplateBinding Transition}"

6
samples/RenderTest/SideBar.xaml

@ -1,11 +1,11 @@
<Styles xmlns="https://github.com/avaloniaui">
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TabControl.sidebar">
<Setter Property="Template">
<ControlTemplate>
<DockPanel>
<ScrollViewer MinWidth="190" Background="{DynamicResource ThemeAccentBrush}" DockPanel.Dock="Left">
<TabStrip Name="PART_TabStrip"
MemberSelector="{Static TabControl.HeaderSelector}"
MemberSelector="{x:Static TabControl.HeaderSelector}"
Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex, Mode=TwoWay}">
<TabStrip.ItemsPanel>
@ -17,7 +17,7 @@
</ScrollViewer>
<Carousel Name="PART_Content"
Margin="8 0 0 0"
MemberSelector="{Static TabControl.ContentSelector}"
MemberSelector="{x:Static TabControl.ContentSelector}"
Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex}"
Transition="{TemplateBinding Transition}"

2
samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs

@ -17,7 +17,7 @@ namespace VirtualizationTest.ViewModels
private int _newItemIndex;
private IReactiveList<ItemViewModel> _items;
private string _prefix = "Item";
private Orientation _orientation;
private Orientation _orientation = Orientation.Vertical;
private ItemVirtualizationMode _virtualizationMode = ItemVirtualizationMode.Simple;
public MainWindowViewModel()

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

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;

12
src/Avalonia.Styling/Styling/Setter.cs

@ -141,20 +141,20 @@ namespace Avalonia.Styling
{
var description = style?.ToString();
if (sourceInstance.Subject != null)
if (sourceInstance.Mode == BindingMode.TwoWay || sourceInstance.Mode == BindingMode.OneWayToSource)
{
var activated = new ActivatedSubject(activator, sourceInstance.Subject, description);
cloned = new InstancedBinding(activated, sourceInstance.Mode, BindingPriority.StyleTrigger);
}
else if (sourceInstance.Observable != null)
else if (sourceInstance.Mode == BindingMode.OneTime)
{
var activated = new ActivatedObservable(activator, sourceInstance.Observable, description);
cloned = new InstancedBinding(activated, sourceInstance.Mode, BindingPriority.StyleTrigger);
var activated = new ActivatedValue(activator, sourceInstance.Value, description);
cloned = new InstancedBinding(activated, BindingMode.OneWay, BindingPriority.StyleTrigger);
}
else
{
var activated = new ActivatedValue(activator, sourceInstance.Value, description);
cloned = new InstancedBinding(activated, BindingMode.OneWay, BindingPriority.StyleTrigger);
var activated = new ActivatedObservable(activator, sourceInstance.Observable ?? sourceInstance.Subject, description);
cloned = new InstancedBinding(activated, sourceInstance.Mode, BindingPriority.StyleTrigger);
}
}
else

6
src/Avalonia.Themes.Default/TabControl.xaml

@ -1,4 +1,4 @@
<Styles xmlns="https://github.com/avaloniaui">
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TabControl">
<Setter Property="Template">
<ControlTemplate>
@ -7,11 +7,11 @@
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<TabStrip Name="PART_TabStrip"
MemberSelector="{Static TabControl.HeaderSelector}"
MemberSelector="{x:Static TabControl.HeaderSelector}"
Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex, Mode=TwoWay}"/>
<Carousel Name="PART_Content"
MemberSelector="{Static TabControl.ContentSelector}"
MemberSelector="{x:Static TabControl.ContentSelector}"
Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex}"
Transition="{TemplateBinding Transition}"

8
src/Avalonia.Themes.Default/TextBox.xaml

@ -1,4 +1,4 @@
<Styles xmlns="https://github.com/avaloniaui">
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TextBox">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
@ -18,12 +18,12 @@
Text="{TemplateBinding Watermark}"
DockPanel.Dock="Top">
<TextBlock.IsVisible>
<MultiBinding Converter="{Static BoolConverters.And}">
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="UseFloatingWatermark"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="Text"
Converter="{Static StringConverters.NotNullOrEmpty}"/>
Converter="{x:Static StringConverters.NotNullOrEmpty}"/>
</MultiBinding>
</TextBlock.IsVisible>
</TextBlock>
@ -44,7 +44,7 @@
<TextBlock Name="watermark"
Opacity="0.5"
Text="{TemplateBinding Watermark}"
IsVisible="{TemplateBinding Path=Text, Converter={Static StringConverters.NullOrEmpty}}"/>
IsVisible="{TemplateBinding Path=Text, Converter={x:Static StringConverters.NullOrEmpty}}"/>
<TextPresenter Name="PART_TextPresenter"
Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}"

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 =

12
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);
@ -102,6 +105,9 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_window_resize(IntPtr gtkWindow, int width, int height);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_window_set_override_redirect(IntPtr gdkWindow, bool value);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_widget_realize(GtkWidget gtkWidget);
@ -390,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;
@ -402,6 +409,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_window_get_size GtkWindowGetSize;
public static D.gtk_window_resize GtkWindowResize;
public static D.gdk_window_resize GdkWindowResize;
public static D.gdk_window_set_override_redirect GdkWindowSetOverrideRedirect;
public static D.gtk_widget_set_size_request GtkWindowSetSizeRequest;
public static D.gtk_window_set_default_size GtkWindowSetDefaultSize;
public static D.gtk_window_get_position GtkWindowGetPosition;

1
src/Gtk/Avalonia.Gtk3/PopupImpl.cs

@ -19,6 +19,7 @@ namespace Avalonia.Gtk3
public PopupImpl() : base(CreateWindow())
{
OverrideRedirect = true;
}
}
}

36
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -32,6 +32,7 @@ namespace Avalonia.Gtk3
private IDeferredRenderOperation _nextRenderOperation;
private readonly AutoResetEvent _canSetNextOperation = new AutoResetEvent(true);
internal IntPtr? GdkWindowHandle;
private bool _overrideRedirect;
public WindowBaseImpl(GtkWindow gtkWidget)
{
@ -69,12 +70,15 @@ namespace Avalonia.Gtk3
private bool OnConfigured(IntPtr gtkwidget, IntPtr ev, IntPtr userdata)
{
int w, h;
Native.GtkWindowGetSize(GtkWidget, out w, out h);
var size = ClientSize = new Size(w, h);
if (_lastSize != size)
if (!OverrideRedirect)
{
Resized?.Invoke(size);
_lastSize = size;
Native.GtkWindowGetSize(GtkWidget, out w, out h);
var size = ClientSize = new Size(w, h);
if (_lastSize != size)
{
Resized?.Invoke(size);
_lastSize = size;
}
}
var pos = Position;
if (_lastPosition != pos)
@ -406,6 +410,28 @@ namespace Avalonia.Gtk3
if (GtkWidget.IsClosed)
return;
Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height);
if (OverrideRedirect)
{
var size = ClientSize = value;
if (_lastSize != size)
{
Resized?.Invoke(size);
_lastSize = size;
}
}
}
public bool OverrideRedirect
{
get => _overrideRedirect;
set
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Native.GdkWindowSetOverrideRedirect(Native.GtkWidgetGetWindow(GtkWidget), value);
_overrideRedirect = value;
}
}
}
public IScreenImpl Screen

2
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -77,9 +77,7 @@
<Compile Include="MarkupExtensions\StyleResourceExtension.cs" />
<Compile Include="MarkupExtensions\BindingExtension.cs" />
<Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
<Compile Include="MarkupExtensions\StaticExtension.cs" />
<Compile Include="MarkupExtensions\TemplateBindingExtension.cs" />
<Compile Include="MarkupExtensions\TypeExtension.cs" />
<Compile Include="Parsers\SelectorGrammar.cs" />
<Compile Include="Parsers\SelectorParser.cs" />
<Compile Include="PortableXaml\AvaloniaTypeAttributeProvider.cs" />

25
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticExtension.cs

@ -1,25 +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;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
public class StaticExtension : Portable.Xaml.Markup.StaticExtension
{
public StaticExtension()
{
}
public StaticExtension(string member)
: base(member)
{
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return base.ProvideValue(serviceProvider);
}
}
}

23
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TypeExtension.cs

@ -1,23 +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;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
public class TypeExtension : Portable.Xaml.Markup.TypeExtension
{
public TypeExtension()
{
}
public TypeExtension(string typeName) : base(typeName)
{
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return base.ProvideValue(serviceProvider);
}
}
}

4
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs

@ -135,6 +135,8 @@ namespace Avalonia.Markup.Xaml.PortableXaml
};
}
protected override bool LookupIsUnknown() => false;
protected override XamlType LookupType()
{
var propType = GetPropertyType();
@ -294,8 +296,6 @@ namespace Avalonia.Markup.Xaml.PortableXaml
return Property.IsReadOnly;
}
protected override bool LookupIsUnknown() => false;
protected override Type GetPropertyType()
{
return Property.PropertyType;

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

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

7
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -609,8 +609,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
public void Multi_Xaml_Binding_Is_Parsed()
{
var xaml =
@"<MultiBinding xmlns='https://github.com/avaloniaui'
Converter='{Static BoolConverters.And}'>
@"<MultiBinding xmlns='https://github.com/avaloniaui' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
Converter ='{x:Static BoolConverters.And}'>
<Binding Path='Foo' />
<Binding Path='Bar' />
</MultiBinding>";
@ -818,10 +818,11 @@ do we need it?")]
{
var xaml =
@"<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBlock Tag='{Static local:NonControl.StringProperty}'/>
<TextBlock Tag='{x:Static local:NonControl.StringProperty}'/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>";

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

9
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs

@ -17,9 +17,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=mscorlib'>
xmlns:sys='clr-namespace:System;assembly=mscorlib'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.DataTemplates>
<DataTemplate DataType='{Type sys:String}'>
<DataTemplate DataType='{x:Type sys:String}'>
<Canvas Name='foo'/>
</DataTemplate>
</Window.DataTemplates>
@ -43,10 +44,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
<Window xmlns='https://github.com/avaloniaui' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests;assembly=Avalonia.Markup.Xaml.UnitTests'>
<Window.DataTemplates>
<DataTemplate DataType='{Type local:TestViewModel}'>
<DataTemplate DataType='{x:Type local:TestViewModel}'>
<Canvas Name='foo' DataContext='{Binding Child}'/>
</DataTemplate>
</Window.DataTemplates>

37
tests/Avalonia.Styling.UnitTests/SetterTests.cs

@ -8,6 +8,9 @@ using Avalonia.Data;
using Xunit;
using System;
using Avalonia.Controls.Templates;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Markup;
using System.Globalization;
namespace Avalonia.Styling.UnitTests
{
@ -61,5 +64,39 @@ namespace Avalonia.Styling.UnitTests
Assert.NotNull(NameScope.GetNameScope((Control)control.Child));
}
[Fact]
public void Does_Not_Call_Converter_ConvertBack_On_OneWay_Binding()
{
var control = new Decorator { Name = "foo" };
var style = Mock.Of<IStyle>();
var binding = new Binding("Name", BindingMode.OneWay)
{
Converter = new TestConverter(),
RelativeSource = new RelativeSource(RelativeSourceMode.Self),
};
var setter = new Setter(Decorator.TagProperty, binding);
var activator = new BehaviorSubject<bool>(true);
setter.Apply(style, control, activator);
Assert.Equal("foobar", control.Tag);
// Issue #1218 caused TestConverter.ConvertBack to throw here.
activator.OnNext(false);
Assert.Null(control.Tag);
}
private class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString() + "bar";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
}

Loading…
Cancel
Save