Browse Source

Merge branch 'master' into refactor/instanced-binding-creation

pull/1235/head
Steven Kirk 9 years ago
committed by GitHub
parent
commit
9df2df36cb
  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. 6
      src/Avalonia.Themes.Default/TabControl.xaml
  9. 8
      src/Avalonia.Themes.Default/TextBox.xaml
  10. 2
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  11. 25
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticExtension.cs
  12. 23
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TypeExtension.cs
  13. 4
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs
  14. 133
      tests/Avalonia.Controls.UnitTests/ControlTests.cs
  15. 7
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  16. 59
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs
  17. 9
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.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;

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

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

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>

Loading…
Cancel
Save