Browse Source

Merge branch 'issue-284'

pull/484/head
Steven Kirk 11 years ago
parent
commit
3d5cab29e3
  1. 4
      src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs
  2. 24
      src/Perspex.Base/PerspexObject.cs
  3. 3
      src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  4. 5
      src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs
  5. 9
      src/Perspex.Controls/Templates/ITreeDataTemplate.cs
  6. 44
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs
  7. 4
      tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj
  8. 79
      tests/Perspex.Controls.UnitTests/TreeViewTests.cs
  9. 1
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs
  10. 1
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs
  11. 6
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs
  12. 1
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs
  13. 1
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs
  14. 1
      tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj
  15. 4
      tests/Perspex.UnitTests/NotifyingBase.cs
  16. 1
      tests/Perspex.UnitTests/Perspex.UnitTests.csproj

4
src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -36,12 +36,12 @@ namespace Perspex.Markup.Xaml.Templates
}
}
public IEnumerable ItemsSelector(object item)
public InstancedBinding ItemsSelector(object item)
{
if (ItemsSource != null)
{
var obs = new ExpressionObserver(item, ItemsSource.Path);
return obs.Take(1).Wait() as IEnumerable;
return new InstancedBinding(obs);
}
return null;

24
src/Perspex.Base/PerspexObject.cs

@ -25,6 +25,17 @@ namespace Perspex
/// </remarks>
public class PerspexObject : IPerspexObject, IPerspexObjectDebug, INotifyPropertyChanged
{
/// <summary>
/// Maintains a list of direct property binding subscriptions so that the binding source
/// doesn't get collected.
/// </summary>
/// <remarks>
/// If/when we provide a ClearBindings() method, then this collection will be need to be
/// moved to an instance field and indexed by property, but until that point a static
/// collection will suffice.
/// </remarks>
private static List<IDisposable> s_directBindings = new List<IDisposable>();
/// <summary>
/// The parent object that inherited values are inherited from.
/// </summary>
@ -401,9 +412,20 @@ namespace Perspex
property,
GetDescription(source));
return source
IDisposable subscription = null;
subscription = source
.Select(x => TypeUtilities.CastOrDefault(x, property.PropertyType))
.Do(_ => { }, () => s_directBindings.Remove(subscription))
.Subscribe(x => SetValue(property, x));
s_directBindings.Add(subscription);
return Disposable.Create(() =>
{
subscription.Dispose();
s_directBindings.Remove(subscription);
});
}
else
{

3
src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Perspex.Controls.Templates;
using Perspex.Data;
namespace Perspex.Controls.Generators
{
@ -76,7 +77,7 @@ namespace Perspex.Controls.Generators
var result = new T();
result.SetValue(ContentProperty, template.Build(item));
result.SetValue(ItemsProperty, template.ItemsSelector(item));
BindingOperations.Apply(result, ItemsProperty, template.ItemsSelector(item), null);
if (!(item is IControl))
{

5
src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs

@ -4,6 +4,7 @@
using System;
using System.Collections;
using System.Reflection;
using Perspex.Data;
namespace Perspex.Controls.Templates
{
@ -59,9 +60,9 @@ namespace Perspex.Controls.Templates
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The child items, or null if no child items.</returns>
public IEnumerable ItemsSelector(object item)
public InstancedBinding ItemsSelector(object item)
{
return this?._itemsSelector(item);
return new InstancedBinding(this?._itemsSelector(item));
}
/// <summary>

9
src/Perspex.Controls/Templates/ITreeDataTemplate.cs

@ -1,7 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections;
using Perspex.Data;
namespace Perspex.Controls.Templates
{
@ -14,7 +14,10 @@ namespace Perspex.Controls.Templates
/// Selects the child items of an item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The child items, or null if no child items.</returns>
IEnumerable ItemsSelector(object item);
/// <returns>
/// An <see cref="InstancedBinding"/> holding the items, or an observable that tracks the
/// items. May return null if no child items.
/// </returns>
InstancedBinding ItemsSelector(object item);
}
}

44
tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs

@ -333,6 +333,50 @@ namespace Perspex.Base.UnitTests
Assert.Equal("second", target.Foo);
}
[Fact]
public void Binding_To_Direct_Property_Does_Not_Get_Collected()
{
var target = new Class2();
Func<WeakReference> setupBinding = () =>
{
var source = new Subject<string>();
var sub = target.Bind((PerspexProperty)Class1.FooProperty, source);
return new WeakReference(source);
};
var weakSource = setupBinding();
GC.Collect();
Assert.True(weakSource.IsAlive);
}
[Fact]
public void Binding_To_Direct_Property_Gets_Collected_When_Completed()
{
var target = new Class2();
Func<WeakReference> setupBinding = () =>
{
var source = new Subject<string>();
var sub = target.Bind((PerspexProperty)Class1.FooProperty, source);
return new WeakReference(source);
};
var weakSource = setupBinding();
Action completeSource = () =>
{
((ISubject<string>)weakSource.Target).OnCompleted();
};
completeSource();
GC.Collect();
Assert.False(weakSource.IsAlive);
}
[Fact]
public void Property_Notifies_Initialized()
{

4
tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj

@ -148,6 +148,10 @@
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
<Name>Perspex.Markup.Xaml</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Markup\Perspex.Markup\Perspex.Markup.csproj">
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
<Name>Perspex.Markup</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Animation\Perspex.Animation.csproj">
<Project>{D211E587-D8BC-45B9-95A4-F297C8FA5200}</Project>
<Name>Perspex.Animation</Name>

79
tests/Perspex.Controls.UnitTests/TreeViewTests.cs

@ -1,13 +1,16 @@
// Copyright (c) The Perspex 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.Collections.Generic;
using System.Linq;
using Perspex.Collections;
using Perspex.Controls.Presenters;
using Perspex.Controls.Templates;
using Perspex.Data;
using Perspex.Input;
using Perspex.LogicalTree;
using Perspex.Markup.Data;
using Perspex.UnitTests;
using Xunit;
@ -252,6 +255,39 @@ namespace Perspex.Controls.UnitTests
Assert.NotNull(NameScope.GetNameScope((TreeViewItem)item));
}
[Fact]
public void Should_React_To_Children_Changing()
{
var data = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = data,
DataTemplates = CreateNodeDataTemplate(),
};
ApplyTemplates(target);
Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0));
Assert.Equal(new[] { "Child1", "Child2" }, ExtractItemHeader(target, 1));
Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2));
// Make sure that the binding to Node.Children does not get collected.
GC.Collect();
data[0].Children = new PerspexList<Node>
{
new Node
{
Value = "NewChild1",
}
};
Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0));
Assert.Equal(new[] { "NewChild1" }, ExtractItemHeader(target, 1));
}
private void ApplyTemplates(TreeView tree)
{
tree.ApplyTemplate();
@ -303,9 +339,7 @@ namespace Perspex.Controls.UnitTests
{
return new DataTemplates
{
new FuncTreeDataTemplate<Node>(
x => new TextBlock { Text = x.Value },
x => x.Children),
new TestTreeDataTemplate()
};
}
@ -360,10 +394,45 @@ namespace Perspex.Controls.UnitTests
}
}
private class Node
private class Node : NotifyingBase
{
private IPerspexList<Node> _children;
public string Value { get; set; }
public IPerspexList<Node> Children { get; set; }
public IPerspexList<Node> Children
{
get
{
return _children;
}
set
{
_children = value;
RaisePropertyChanged(nameof(Children));
}
}
}
private class TestTreeDataTemplate : ITreeDataTemplate
{
public IControl Build(object param)
{
var node = (Node)param;
return new TextBlock { Text = node.Value };
}
public InstancedBinding ItemsSelector(object item)
{
var obs = new ExpressionObserver(item, nameof(Node.Children));
return new InstancedBinding(obs);
}
public bool Match(object data)
{
return data is Node;
}
}
}
}

1
tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs

@ -8,6 +8,7 @@ using System.Reactive.Linq;
using Perspex.Collections;
using Perspex.Diagnostics;
using Perspex.Markup.Data;
using Perspex.UnitTests;
using Xunit;
namespace Perspex.Markup.UnitTests.Data

1
tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Perspex.Markup.Data;
using Perspex.UnitTests;
using Xunit;
namespace Perspex.Markup.UnitTests.Data

6
tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs

@ -8,6 +8,7 @@ using System.Reactive.Linq;
using System.Reactive.Subjects;
using Microsoft.Reactive.Testing;
using Perspex.Markup.Data;
using Perspex.UnitTests;
using Xunit;
namespace Perspex.Markup.UnitTests.Data
@ -385,11 +386,6 @@ namespace Perspex.Markup.UnitTests.Data
RaisePropertyChanged(nameof(Next));
}
}
public void RaisePropertyChanged(string propertyName)
{
base.RaisePropertyChanged(propertyName);
}
}
private class Class2 : NotifyingBase, INext

1
tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using Perspex.Markup.Data;
using Perspex.UnitTests;
using Xunit;
namespace Perspex.Markup.UnitTests.Data

1
tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs

@ -7,6 +7,7 @@ using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using Perspex.Markup.Data;
using Perspex.UnitTests;
using Xunit;
namespace Perspex.Markup.UnitTests.Data

1
tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj

@ -96,7 +96,6 @@
<Compile Include="Data\ExpressionObserverTests_SetValue.cs" />
<Compile Include="Data\ExpressionObserverTests_Task.cs" />
<Compile Include="Data\ExpressionSubjectTests.cs" />
<Compile Include="Data\NotifyingBase.cs" />
<Compile Include="DefaultValueConverterTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UnitTestSynchronizationContext.cs" />

4
tests/Perspex.Markup.UnitTests/Data/NotifyingBase.cs → tests/Perspex.UnitTests/NotifyingBase.cs

@ -4,7 +4,7 @@
using System.ComponentModel;
using System.Linq;
namespace Perspex.Markup.UnitTests.Data
namespace Perspex.UnitTests
{
public class NotifyingBase : INotifyPropertyChanged
{
@ -34,7 +34,7 @@ namespace Perspex.Markup.UnitTests.Data
private set;
}
protected void RaisePropertyChanged(string propertyName)
public void RaisePropertyChanged(string propertyName)
{
_propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

1
tests/Perspex.UnitTests/Perspex.UnitTests.csproj

@ -62,6 +62,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="NotifyingBase.cs" />
<Compile Include="TestTemplatedRoot.cs" />
<Compile Include="TestRoot.cs" />
<Compile Include="TestServices.cs" />

Loading…
Cancel
Save