Browse Source

Started implementing XAML lifecycle listener.

We need to delay bindings until everything is set up before everything
works though.
pull/464/head
Steven Kirk 11 years ago
parent
commit
8ca890d168
  1. 31
      src/Markup/Perspex.Markup.Xaml/Context/PerspexLifeCycleListener.cs
  2. 1
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  3. 16
      src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs
  4. 22
      src/Perspex.Base/ISupportInitialize.cs
  5. 1
      src/Perspex.Base/Perspex.Base.csproj
  6. 61
      src/Perspex.Controls/Control.cs
  7. 67
      tests/Perspex.Controls.UnitTests/ControlTests.cs
  8. 2
      tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj

31
src/Markup/Perspex.Markup.Xaml/Context/PerspexLifeCycleListener.cs

@ -0,0 +1,31 @@
// 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 OmniXaml;
namespace Perspex.Markup.Xaml.Context
{
public class PerspexLifeCycleListener : IInstanceLifeCycleListener
{
public void OnAfterProperties(object instance)
{
}
public void OnAssociatedToParent(object instance)
{
}
public void OnBegin(object instance)
{
var isi = instance as ISupportInitialize;
isi?.BeginInit();
}
public void OnEnd(object instance)
{
var isi = instance as ISupportInitialize;
isi?.EndInit();
}
}
}

1
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@ -42,6 +42,7 @@
</Compile>
<Compile Include="Context\NameScopeWrapper.cs" />
<Compile Include="Context\PerspexAttachableXamlMember.cs" />
<Compile Include="Context\PerspexLifeCycleListener.cs" />
<Compile Include="Context\PerspexMemberValuePlugin.cs" />
<Compile Include="Context\PerspexNamespaceRegistry.cs" />
<Compile Include="Context\PerspexObjectAssembler.cs" />

16
src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs

@ -20,6 +20,7 @@ namespace Perspex.Markup.Xaml
public class PerspexXamlLoader : XmlLoader
{
private static PerspexParserFactory s_parserFactory;
private static IInstanceLifeCycleListener s_lifeCycleListener = new PerspexLifeCycleListener();
/// <summary>
/// Initializes a new instance of the <see cref="PerspexXamlLoader"/> class.
@ -77,7 +78,7 @@ namespace Perspex.Markup.Xaml
{
using (var stream = assetLocator.Open(uri))
{
return Load(stream, new Settings { RootInstance = rootInstance });
return Load(stream, rootInstance);
}
}
}
@ -106,7 +107,7 @@ namespace Perspex.Markup.Xaml
using (var stream = assetLocator.Open(uri))
{
return Load(stream, new Settings { RootInstance = rootInstance });
return Load(stream, rootInstance);
}
}
@ -124,7 +125,7 @@ namespace Perspex.Markup.Xaml
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
{
return Load(stream, new Settings { RootInstance = rootInstance });
return Load(stream, rootInstance);
}
}
@ -151,5 +152,14 @@ namespace Perspex.Markup.Xaml
yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm);
}
private object Load(Stream stream, object rootInstance)
{
return base.Load(stream, new Settings
{
RootInstance = rootInstance,
InstanceLifeCycleListener = s_lifeCycleListener,
});
}
}
}

22
src/Perspex.Base/ISupportInitialize.cs

@ -0,0 +1,22 @@
// 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.
namespace Perspex
{
/// <summary>
/// Specifies that this object supports a simple, transacted notification for batch
/// initialization.
/// </summary>
public interface ISupportInitialize
{
/// <summary>
/// Signals the object that initialization is starting.
/// </summary>
void BeginInit();
/// <summary>
/// Signals the object that initialization is complete.
/// </summary>
void EndInit();
}
}

1
src/Perspex.Base/Perspex.Base.csproj

@ -55,6 +55,7 @@
<Compile Include="DirectProperty.cs" />
<Compile Include="IPerspexObject.cs" />
<Compile Include="IStyledPropertyMetadata.cs" />
<Compile Include="ISupportInitialize.cs" />
<Compile Include="Metadata\DependsOnAttribute.cs" />
<Compile Include="Metadata\ContentAttribute.cs" />
<Compile Include="PerspexDisposable.cs" />

61
src/Perspex.Controls/Control.cs

@ -31,7 +31,7 @@ namespace Perspex.Controls
/// - Implements <see cref="IStyleable"/> to allow styling to work on the control.
/// - Implements <see cref="ILogical"/> to form part of a logical tree.
/// </remarks>
public class Control : InputElement, IControl, INamed, ISetLogicalParent
public class Control : InputElement, IControl, INamed, ISetLogicalParent, ISupportInitialize
{
/// <summary>
/// Defines the <see cref="DataContext"/> property.
@ -84,6 +84,7 @@ namespace Perspex.Controls
public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent =
RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble);
private int _initCount;
private string _name;
private IControl _parent;
private readonly Classes _classes = new Classes();
@ -93,6 +94,7 @@ namespace Perspex.Controls
private IPerspexList<ILogical> _logicalChildren;
private INameScope _nameScope;
private Styles _styles;
private bool _styled;
private Subject<Unit> _styleDetach = new Subject<Unit>();
/// <summary>
@ -154,9 +156,9 @@ namespace Perspex.Controls
throw new InvalidOperationException("Cannot set Name to empty string.");
}
if (_isAttachedToLogicalTree)
if (_styled)
{
throw new InvalidOperationException("Cannot set Name : control already added to tree.");
throw new InvalidOperationException("Cannot set Name : control already styled.");
}
_name = value;
@ -310,6 +312,28 @@ namespace Perspex.Controls
/// <inheritdoc/>
IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
/// <inheritdoc/>
void ISupportInitialize.BeginInit()
{
++_initCount;
}
/// <inheritdoc/>
void ISupportInitialize.EndInit()
{
if (_initCount == 0)
{
throw new InvalidOperationException("BeginInit was not called.");
}
if (--_initCount == 0 && !_styled)
{
RegisterWithNameScope();
ApplyStyling();
_styled = true;
}
}
/// <summary>
/// Gets a value which indicates whether a change to the <see cref="DataContext"/> is in
/// the process of being notified.
@ -457,18 +481,15 @@ namespace Perspex.Controls
// - That AttachedToLogicalTree signal travels down to the ListBoxItem
if (!_isAttachedToLogicalTree)
{
if (_nameScope == null)
{
_nameScope = NameScope.GetNameScope(this) ?? ((Control)Parent)?._nameScope;
}
_isAttachedToLogicalTree = true;
if (Name != null)
if (_initCount == 0)
{
_nameScope?.Register(Name, this);
RegisterWithNameScope();
ApplyStyling();
_styled = true;
}
_isAttachedToLogicalTree = true;
PerspexLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
AttachedToLogicalTree?.Invoke(this, e);
}
@ -605,6 +626,24 @@ namespace Perspex.Controls
return null;
}
private void ApplyStyling()
{
PerspexLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
}
private void RegisterWithNameScope()
{
if (_nameScope == null)
{
_nameScope = NameScope.GetNameScope(this) ?? ((Control)Parent)?._nameScope;
}
if (Name != null)
{
_nameScope?.Register(Name, this);
}
}
private static void ValidateLogicalChild(ILogical c)
{
if (c == null)

67
tests/Perspex.Controls.UnitTests/ControlTests.cs

@ -145,6 +145,73 @@ namespace Perspex.Controls.UnitTests
}
}
[Fact]
public void Styles_Not_Applied_Until_Initialization_Finished()
{
using (PerspexLocator.EnterScope())
{
var root = new TestRoot();
var child = new Border();
var styler = new Mock<IStyler>();
PerspexLocator.CurrentMutable.Bind<IStyler>().ToConstant(styler.Object);
((ISupportInitialize)child).BeginInit();
root.Child = child;
styler.Verify(x => x.ApplyStyles(It.IsAny<IStyleable>()), Times.Never());
((ISupportInitialize)child).EndInit();
styler.Verify(x => x.ApplyStyles(child), Times.Once());
}
}
[Fact]
public void Adding_To_Logical_Tree_Should_Register_With_NameScope()
{
using (PerspexLocator.EnterScope())
{
var root = new TestRoot();
var child = new Border();
child.Name = "foo";
root.Child = child;
Assert.Same(root.FindControl<Border>("foo"), child);
}
}
[Fact]
public void Name_Cannot_Be_Set_After_Added_To_Logical_Tree()
{
using (PerspexLocator.EnterScope())
{
var root = new TestRoot();
var child = new Border();
root.Child = child;
Assert.Throws<InvalidOperationException>(() => child.Name = "foo");
}
}
[Fact]
public void Name_Can_Be_Set_While_Initializing()
{
using (PerspexLocator.EnterScope())
{
var root = new TestRoot();
var child = new Border();
((ISupportInitialize)child).BeginInit();
root.Child = child;
child.Name = "foo";
Assert.Null(root.FindControl<Border>("foo"));
((ISupportInitialize)child).EndInit();
Assert.Same(root.FindControl<Border>("foo"), child);
}
}
private class TestControl : Control
{
public new PerspexObject InheritanceParent => base.InheritanceParent;

2
tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj

@ -103,7 +103,9 @@
<Compile Include="SamplePerspexObject.cs" />
<Compile Include="StyleTests.cs" />
<Compile Include="Templates\DataTemplateTests.cs" />
<Compile Include="Xaml\BasicTests.cs" />
<Compile Include="Xaml\BindingTests.cs" />
<Compile Include="Xaml\InitializationOrderTracker.cs" />
<Compile Include="Xaml\NonControl.cs" />
<Compile Include="Xaml\TreeDataTemplateTests.cs" />
<Compile Include="TypeProviderMock.cs" />

Loading…
Cancel
Save