Browse Source

Added ContentPresenter.ChildChanging.

And listen for this event in `ContentControlMixin` in order to remove the logical child before setting the `DataContext`.

Fixes #1099
pull/2264/head
Steven Kirk 8 years ago
parent
commit
f54e48d9a2
  1. 29
      src/Avalonia.Controls/Mixins/ContentControlMixin.cs
  2. 42
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  3. 15
      src/Avalonia.Controls/Presenters/IContentPresenter.cs

29
src/Avalonia.Controls/Mixins/ContentControlMixin.cs

@ -19,8 +19,8 @@ namespace Avalonia.Controls.Mixins
/// <para>
/// The <see cref="ContentControlMixin"/> adds behavior to a control which acts as a content
/// control such as <see cref="ContentControl"/> and <see cref="HeaderedItemsControl"/>. It
/// updates keeps the control's logical children in sync with the content being displayed by
/// the control.
/// keeps the control's logical children in sync with the content being displayed by the
/// control.
/// </para>
public class ContentControlMixin
{
@ -49,25 +49,42 @@ namespace Avalonia.Controls.Mixins
Contract.Requires<ArgumentNullException>(content != null);
Contract.Requires<ArgumentNullException>(logicalChildrenSelector != null);
void ChildChanging(object s, AvaloniaPropertyChangedEventArgs e)
{
if (s is IControl sender && sender?.TemplatedParent is TControl parent)
{
UpdateLogicalChild(
sender,
logicalChildrenSelector(parent),
e.OldValue,
null);
}
}
void TemplateApplied(object s, RoutedEventArgs ev)
{
if (s is TControl sender)
{
var e = (TemplateAppliedEventArgs)ev;
var presenter = (IControl)e.NameScope.Find(presenterName);
var presenter = e.NameScope.Find(presenterName) as IContentPresenter;
if (presenter != null)
{
presenter.ApplyTemplate();
var logicalChildren = logicalChildrenSelector(sender);
var subscription = presenter
var subscription = new CompositeDisposable();
presenter.ChildChanging += ChildChanging;
subscription.Add(Disposable.Create(() => presenter.ChildChanging -= ChildChanging));
subscription.Add(presenter
.GetPropertyChangedObservable(ContentPresenter.ChildProperty)
.Subscribe(c => UpdateLogicalChild(
sender,
logicalChildren,
c.OldValue,
c.NewValue));
null,
c.NewValue)));
UpdateLogicalChild(
sender,

42
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -5,6 +5,7 @@ using System;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
@ -82,6 +83,7 @@ namespace Avalonia.Controls.Presenters
private IControl _child;
private bool _createdChild;
EventHandler<AvaloniaPropertyChangedEventArgs> _childChanging;
private IDataTemplate _dataTemplate;
private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
@ -188,6 +190,13 @@ namespace Avalonia.Controls.Presenters
set { SetValue(PaddingProperty, value); }
}
/// <inheritdoc/>
event EventHandler<AvaloniaPropertyChangedEventArgs> IContentPresenter.ChildChanging
{
add => _childChanging += value;
remove => _childChanging -= value;
}
/// <inheritdoc/>
public sealed override void ApplyTemplate()
{
@ -215,9 +224,30 @@ namespace Avalonia.Controls.Presenters
var newChild = CreateChild();
// Remove the old child if we're not recycling it.
if (oldChild != null && newChild != oldChild)
if (newChild != oldChild)
{
VisualChildren.Remove(oldChild);
if (oldChild != null)
{
VisualChildren.Remove(oldChild);
}
if (oldChild?.Parent == this)
{
// If we're the child's parent then the presenter isn't in a ContentControl's
// template.
LogicalChildren.Remove(oldChild);
}
else
{
// If we're in a ContentControl's template then invoke ChildChanging to let
// ContentControlMixin handle removing the logical child.
_childChanging?.Invoke(this, new AvaloniaPropertyChangedEventArgs(
this,
ChildProperty,
oldChild,
newChild,
BindingPriority.LocalValue));
}
}
// Set the DataContext if the data isn't a control.
@ -241,11 +271,9 @@ namespace Avalonia.Controls.Presenters
Child = newChild;
if (oldChild?.Parent == this)
{
LogicalChildren.Remove(oldChild);
}
// If we're in a ContentControl's template then the child's parent will have been
// set by ContentControlMixin in response to Child changing. If not, then we're
// standalone and should make the control our own logical child.
if (newChild.Parent == null && TemplatedParent == null)
{
LogicalChildren.Add(newChild);

15
src/Avalonia.Controls/Presenters/IContentPresenter.cs

@ -1,6 +1,8 @@
// 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.Controls.Mixins;
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls.Presenters
@ -20,5 +22,16 @@ namespace Avalonia.Controls.Presenters
/// Gets or sets the content to be displayed by the presenter.
/// </summary>
object Content { get; set; }
/// <summary>
/// Raised when <see cref="Child"/> property is about to change.
/// </summary>
/// <remarks>
/// This event should be raised after the child has been removed from the visual tree,
/// but before the <see cref="Child"/> property has changed. It is intended for consumption
/// by <see cref="ContentControlMixin"/> in order to update the host control's logical
/// children.
/// </remarks>
event EventHandler<AvaloniaPropertyChangedEventArgs> ChildChanging;
}
}
}

Loading…
Cancel
Save