|
|
|
@ -12,90 +12,159 @@ namespace Perspex.Controls |
|
|
|
using Perspex.Rendering; |
|
|
|
using Perspex.VisualTree; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Displays a popup window.
|
|
|
|
/// </summary>
|
|
|
|
public class Popup : Control, ILogical, IVisualTreeHost |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="Child"/> property.
|
|
|
|
/// </summary>
|
|
|
|
public static readonly PerspexProperty<Control> ChildProperty = |
|
|
|
PerspexProperty.Register<Popup, Control>("Child"); |
|
|
|
PerspexProperty.Register<Popup, Control>(nameof(Child)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="IsOpen"/> property.
|
|
|
|
/// </summary>
|
|
|
|
public static readonly PerspexProperty<bool> IsOpenProperty = |
|
|
|
PerspexProperty.Register<Popup, bool>("IsOpen"); |
|
|
|
PerspexProperty.Register<Popup, bool>(nameof(IsOpen)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="PlacementMode"/> property.
|
|
|
|
/// </summary>
|
|
|
|
public static readonly PerspexProperty<PlacementMode> PlacementModeProperty = |
|
|
|
PerspexProperty.Register<Popup, PlacementMode>("PlacementMode", defaultValue: PlacementMode.Bottom); |
|
|
|
PerspexProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="PlacementTarget"/> property.
|
|
|
|
/// </summary>
|
|
|
|
public static readonly PerspexProperty<Control> PlacementTargetProperty = |
|
|
|
PerspexProperty.Register<Popup, Control>("PlacementTarget"); |
|
|
|
PerspexProperty.Register<Popup, Control>(nameof(PlacementTarget)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="StaysOpen"/> property.
|
|
|
|
/// </summary>
|
|
|
|
public static readonly PerspexProperty<bool> StaysOpenProperty = |
|
|
|
PerspexProperty.Register<Popup, bool>("StaysOpen", true); |
|
|
|
PerspexProperty.Register<Popup, bool>(nameof(StaysOpen), true); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The root of the popup.
|
|
|
|
/// </summary>
|
|
|
|
private PopupRoot popupRoot; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The top level control of the Popup's visual tree.
|
|
|
|
/// </summary>
|
|
|
|
private TopLevel topLevel; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The popup's logical child.
|
|
|
|
/// </summary>
|
|
|
|
private PerspexSingleItemList<ILogical> logicalChild = new PerspexSingleItemList<ILogical>(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes static members of the <see cref="Popup"/> class.
|
|
|
|
/// </summary>
|
|
|
|
static Popup() |
|
|
|
{ |
|
|
|
IsOpenProperty.Changed.Subscribe(IsOpenChanged); |
|
|
|
IsOpenProperty.Changed.AddClassHandler<Popup>(x => x.IsOpenChanged); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="Popup"/> class.
|
|
|
|
/// </summary>
|
|
|
|
public Popup() |
|
|
|
{ |
|
|
|
this.GetObservableWithHistory(ChildProperty).Subscribe(ChildChanged); |
|
|
|
this.GetObservableWithHistory(ChildProperty).Subscribe(this.ChildChanged); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Raised when the popup closes.
|
|
|
|
/// </summary>
|
|
|
|
public event EventHandler Closed; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Raised when the popup opens.
|
|
|
|
/// </summary>
|
|
|
|
public event EventHandler Opened; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Raised when the popup root has been created, but before it has been shown.
|
|
|
|
/// </summary>
|
|
|
|
public event EventHandler PopupRootCreated; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the control to display in the popup.
|
|
|
|
/// </summary>
|
|
|
|
public Control Child |
|
|
|
{ |
|
|
|
get { return this.GetValue(ChildProperty); } |
|
|
|
set { this.SetValue(ChildProperty, value); } |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets a value indicating whether the popup is currently open.
|
|
|
|
/// </summary>
|
|
|
|
public bool IsOpen |
|
|
|
{ |
|
|
|
get { return this.GetValue(IsOpenProperty); } |
|
|
|
set { this.SetValue(IsOpenProperty, value); } |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the placement mode of the popup in relation to the <see cref="PlacementTarget"/>.
|
|
|
|
/// </summary>
|
|
|
|
public PlacementMode PlacementMode |
|
|
|
{ |
|
|
|
get { return this.GetValue(PlacementModeProperty); } |
|
|
|
set { this.SetValue(PlacementModeProperty, value); } |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the control that is used to determine the popup's position.
|
|
|
|
/// </summary>
|
|
|
|
public Control PlacementTarget |
|
|
|
{ |
|
|
|
get { return this.GetValue(PlacementTargetProperty); } |
|
|
|
set { this.SetValue(PlacementTargetProperty, value); } |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the root of the popup window.
|
|
|
|
/// </summary>
|
|
|
|
public PopupRoot PopupRoot |
|
|
|
{ |
|
|
|
get { return this.popupRoot; } |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets a value indicating whether the popup should stay open when the popup loses focus.
|
|
|
|
/// </summary>
|
|
|
|
public bool StaysOpen |
|
|
|
{ |
|
|
|
get { return this.GetValue(StaysOpenProperty); } |
|
|
|
set { this.SetValue(StaysOpenProperty, value); } |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the logical children of the popup.
|
|
|
|
/// </summary>
|
|
|
|
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren |
|
|
|
{ |
|
|
|
get { return this.logicalChild; } |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the root of the popup window.
|
|
|
|
/// </summary>
|
|
|
|
IVisual IVisualTreeHost.Root |
|
|
|
{ |
|
|
|
get { return this.popupRoot; } |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Opens the popup.
|
|
|
|
/// </summary>
|
|
|
|
public void Open() |
|
|
|
{ |
|
|
|
if (this.popupRoot == null) |
|
|
|
@ -125,6 +194,9 @@ namespace Perspex.Controls |
|
|
|
this.Opened?.Invoke(this, EventArgs.Empty); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Closes the popup.
|
|
|
|
/// </summary>
|
|
|
|
public void Close() |
|
|
|
{ |
|
|
|
if (this.popupRoot != null) |
|
|
|
@ -139,40 +211,56 @@ namespace Perspex.Controls |
|
|
|
this.Closed?.Invoke(this, EventArgs.Empty); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Measures the control.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="availableSize">The available size for the control.</param>
|
|
|
|
/// <returns>A size of 0,0 as Popup itself takes up no space.</returns>
|
|
|
|
protected override Size MeasureCore(Size availableSize) |
|
|
|
{ |
|
|
|
return new Size(); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Called when the control is added to the visual tree.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="root">THe root of the visual tree.</param>
|
|
|
|
protected override void OnAttachedToVisualTree(IRenderRoot root) |
|
|
|
{ |
|
|
|
base.OnAttachedToVisualTree(root); |
|
|
|
this.topLevel = root as TopLevel; |
|
|
|
} |
|
|
|
|
|
|
|
protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot) |
|
|
|
/// <summary>
|
|
|
|
/// Called when the control is removed to the visual tree.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="root">THe root of the visual tree.</param>
|
|
|
|
protected override void OnDetachedFromVisualTree(IRenderRoot root) |
|
|
|
{ |
|
|
|
base.OnDetachedFromVisualTree(oldRoot); |
|
|
|
base.OnDetachedFromVisualTree(root); |
|
|
|
this.topLevel = null; |
|
|
|
} |
|
|
|
|
|
|
|
private static void IsOpenChanged(PerspexPropertyChangedEventArgs e) |
|
|
|
/// <summary>
|
|
|
|
/// Called when the <see cref="IsOpen"/> property changes.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="e">The event args.</param>
|
|
|
|
private void IsOpenChanged(PerspexPropertyChangedEventArgs e) |
|
|
|
{ |
|
|
|
Popup popup = e.Sender as Popup; |
|
|
|
|
|
|
|
if (popup != null) |
|
|
|
if ((bool)e.NewValue) |
|
|
|
{ |
|
|
|
if ((bool)e.NewValue) |
|
|
|
{ |
|
|
|
popup.Open(); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
popup.Close(); |
|
|
|
} |
|
|
|
this.Open(); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
this.Close(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Called when the <see cref="Child"/> property changes.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="values">The old and new values.</param>
|
|
|
|
private void ChildChanged(Tuple<Control, Control> values) |
|
|
|
{ |
|
|
|
if (values.Item1 != null) |
|
|
|
@ -188,6 +276,10 @@ namespace Perspex.Controls |
|
|
|
this.logicalChild.SingleItem = values.Item2; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the position for the popup based on the placement properties.
|
|
|
|
/// </summary>
|
|
|
|
/// <returns>The popup's position in screen coordinates.</returns>
|
|
|
|
private Point GetPosition() |
|
|
|
{ |
|
|
|
var target = this.PlacementTarget ?? this.GetVisualParent<Control>(); |
|
|
|
@ -215,6 +307,12 @@ namespace Perspex.Controls |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Conditionally closes the popup in response to an event, based on the value of the
|
|
|
|
/// <see cref="StaysOpen"/> property.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="sender">The event sender.</param>
|
|
|
|
/// <param name="e">The event args.</param>
|
|
|
|
private void MaybeClose(object sender, EventArgs e) |
|
|
|
{ |
|
|
|
if (!this.StaysOpen) |
|
|
|
|