Browse Source

Implement ContextMenu interactions.

repro-ienumerable-menu-navigation
Steven Kirk 8 years ago
parent
commit
e5662a8a18
  1. 148
      src/Avalonia.Controls/ContextMenu.cs

148
src/Avalonia.Controls/ContextMenu.cs

@ -1,16 +1,18 @@
using System;
using System.Reactive.Linq;
using System.Linq;
using System.ComponentModel;
using Avalonia.Controls.Platform;
using System.Collections.Generic;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
using Input; public class ContextMenu : SelectingItemsControl, IMenu
using Interactivity;
using LogicalTree;
using Primitives;
using System;
using System.Reactive.Linq;
using System.Linq;
using System.ComponentModel;
public class ContextMenu : SelectingItemsControl
{ {
private readonly IMenuInteractionHandler _interaction;
private bool _isOpen; private bool _isOpen;
private Popup _popup; private Popup _popup;
@ -20,6 +22,25 @@ namespace Avalonia.Controls
public static readonly DirectProperty<ContextMenu, bool> IsOpenProperty = public static readonly DirectProperty<ContextMenu, bool> IsOpenProperty =
AvaloniaProperty.RegisterDirect<ContextMenu, bool>(nameof(IsOpen), o => o.IsOpen); AvaloniaProperty.RegisterDirect<ContextMenu, bool>(nameof(IsOpen), o => o.IsOpen);
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenu"/> class.
/// </summary>
public ContextMenu()
{
_interaction = AvaloniaLocator.Current.GetService<IMenuInteractionHandler>() ??
new DefaultMenuInteractionHandler();
}
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenu"/> class.
/// </summary>
/// <param name="interactionHandler">The menu iteraction handler.</param>
public ContextMenu(IMenuInteractionHandler interactionHandler)
{
Contract.Requires<ArgumentNullException>(interactionHandler != null);
_interaction = interactionHandler;
}
/// <summary> /// <summary>
/// Initializes static members of the <see cref="ContextMenu"/> class. /// Initializes static members of the <see cref="ContextMenu"/> class.
@ -27,8 +48,6 @@ namespace Avalonia.Controls
static ContextMenu() static ContextMenu()
{ {
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
} }
/// <summary> /// <summary>
@ -36,6 +55,36 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool IsOpen => _isOpen; public bool IsOpen => _isOpen;
/// <inheritdoc/>
IMenuInteractionHandler IMenu.InteractionHandler => _interaction;
/// <inheritdoc/>
IMenuItem IMenuElement.SelectedItem
{
get
{
var index = SelectedIndex;
return (index != -1) ?
(IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
null;
}
set
{
SelectedIndex = ItemContainerGenerator.IndexFromContainer(value);
}
}
/// <inheritdoc/>
IEnumerable<IMenuItem> IMenuElement.SubItems
{
get
{
return ItemContainerGenerator.Containers
.Select(x => x.ContainerControl)
.OfType<IMenuItem>();
}
}
/// <summary> /// <summary>
/// Occurs when the value of the /// Occurs when the value of the
/// <see cref="P:Avalonia.Controls.ContextMenu.IsOpen" /> /// <see cref="P:Avalonia.Controls.ContextMenu.IsOpen" />
@ -50,7 +99,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public event CancelEventHandler ContextMenuClosing; public event CancelEventHandler ContextMenuClosing;
/// <summary> /// <summary>
/// Called when the <see cref="Control.ContextMenu"/> property changes on a control. /// Called when the <see cref="Control.ContextMenu"/> property changes on a control.
/// </summary> /// </summary>
@ -71,62 +119,53 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Called when a submenu is clicked somewhere in the menu. /// Opens the menu.
/// </summary> /// </summary>
/// <param name="e">The event args.</param> public void Open() => Open(null);
private void OnContextMenuClick(RoutedEventArgs e)
{
Hide();
FocusManager.Instance.Focus(null);
e.Handled = true;
}
/// <summary> /// <summary>
/// Closes the menu. /// Opens a context menu on the specified control.
/// </summary> /// </summary>
public void Hide() /// <param name="control">The control.</param>
public void Open(Control control)
{ {
if (_popup != null && _popup.IsVisible) if (_popup == null)
{ {
_popup.IsOpen = false; _popup = new Popup()
{
PlacementMode = PlacementMode.Pointer,
PlacementTarget = control,
StaysOpen = false,
ObeyScreenEdges = true
};
_popup.Closed += PopupClosed;
_interaction.Attach(this);
} }
SelectedIndex = -1; ((ISetLogicalParent)_popup).SetParent(control);
_popup.Child = this;
_popup.IsOpen = true;
SetAndRaise(IsOpenProperty, ref _isOpen, false); SetAndRaise(IsOpenProperty, ref _isOpen, true);
} }
/// <summary> /// <summary>
/// Shows a context menu for the specified control. /// Closes the menu.
/// </summary> /// </summary>
/// <param name="control">The control.</param> public void Close()
private void Show(Control control)
{ {
if (control != null) if (_popup != null && _popup.IsVisible)
{ {
if (_popup == null) _popup.IsOpen = false;
{ }
_popup = new Popup()
{
PlacementMode = PlacementMode.Pointer,
PlacementTarget = control,
StaysOpen = false,
ObeyScreenEdges = true
};
_popup.Closed += PopupClosed;
}
((ISetLogicalParent)_popup).SetParent(control);
_popup.Child = this;
_popup.IsOpen = true; SelectedIndex = -1;
SetAndRaise(IsOpenProperty, ref _isOpen, true); SetAndRaise(IsOpenProperty, ref _isOpen, false);
}
} }
private static void PopupClosed(object sender, EventArgs e) private void PopupClosed(object sender, EventArgs e)
{ {
var contextMenu = (sender as Popup)?.Child as ContextMenu; var contextMenu = (sender as Popup)?.Child as ContextMenu;
@ -152,7 +191,7 @@ namespace Avalonia.Controls
if (contextMenu.CancelClosing()) if (contextMenu.CancelClosing())
return; return;
control.ContextMenu.Hide(); control.ContextMenu.Close();
e.Handled = true; e.Handled = true;
} }
@ -161,7 +200,7 @@ namespace Avalonia.Controls
if (contextMenu.CancelOpening()) if (contextMenu.CancelOpening())
return; return;
contextMenu.Show(control); contextMenu.Open(control);
e.Handled = true; e.Handled = true;
} }
} }
@ -179,5 +218,10 @@ namespace Avalonia.Controls
ContextMenuOpening?.Invoke(this, eventArgs); ContextMenuOpening?.Invoke(this, eventArgs);
return eventArgs.Cancel; return eventArgs.Cancel;
} }
bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap)
{
throw new NotImplementedException();
}
} }
} }

Loading…
Cancel
Save