Browse Source

Merge pull request #405 from VitalElement/master

Implementation of Context Menus
pull/372/merge
Steven Kirk 11 years ago
parent
commit
3277d7aa1a
  1. 16
      samples/XamlTestApplicationPcl/Views/MainWindow.paml
  2. 149
      src/Perspex.Controls/ContextMenu.cs
  3. 16
      src/Perspex.Controls/Control.cs
  4. 1
      src/Perspex.Controls/Perspex.Controls.csproj
  5. 22
      src/Perspex.Controls/Primitives/Popup.cs
  6. 20
      src/Perspex.Themes.Default/ContextMenu.paml
  7. 1
      src/Perspex.Themes.Default/DefaultTheme.paml
  8. 3
      src/Perspex.Themes.Default/Perspex.Themes.Default.csproj

16
samples/XamlTestApplicationPcl/Views/MainWindow.paml

@ -38,7 +38,21 @@
</TabItem>
<TabItem Header="Text">
<StackPanel Margin="10" Gap="4">
<TextBlock Text="TextBlock" FontWeight="Medium" FontSize="20" Foreground="#212121" />
<TextBlock Text="TextBlock with ContextMenu" FontWeight="Medium" FontSize="20" Foreground="#212121">
<ContextMenu.ContextMenu>
<ContextMenu>
<MenuItem Header="Testing 1" />
<MenuItem Header="Testing 2" />
<MenuItem Header="Testing 3" />
<MenuItem Header="Testing 4">
<MenuItem Header="SubItem 1" />
<MenuItem Header="SubItem 2" />
<MenuItem Header="SubItem 3" />
<MenuItem Header="SubItem 4" />
</MenuItem>
</ContextMenu>
</ContextMenu.ContextMenu>
</TextBlock>
<TextBlock Text="A control for displaying text."
FontSize="13"
Foreground="#727272"

149
src/Perspex.Controls/ContextMenu.cs

@ -0,0 +1,149 @@
namespace Perspex.Controls
{
using Input;
using Interactivity;
using LogicalTree;
using Primitives;
using System;
using System.Reactive.Linq;
public class ContextMenu : SelectingItemsControl
{
private bool _isOpen;
/// <summary>
/// The popup window used to display the active context menu.
/// </summary>
private static Popup _popup;
/// <summary>
/// Initializes static members of the <see cref="ContextMenu"/> class.
/// </summary>
static ContextMenu()
{
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick);
}
/// <summary>
/// called when the <see cref="ContextMenuProperty"/> property changes on a control.
/// </summary>
/// <param name="e">The event args.</param>
private static void ContextMenuChanged(PerspexPropertyChangedEventArgs e)
{
var control = (Control)e.Sender;
if (e.OldValue != null)
{
control.PointerReleased -= ControlPointerReleased;
}
if (e.NewValue != null)
{
control.PointerReleased += ControlPointerReleased;
}
}
/// <summary>
/// Called when a submenu is clicked somewhere in the menu.
/// </summary>
/// <param name="e">The event args.</param>
private void OnContextMenuClick(RoutedEventArgs e)
{
Hide();
FocusManager.Instance.Focus(null);
e.Handled = true;
}
/// <summary>
/// Closes the menu.
/// </summary>
public void Hide()
{
if (_popup != null && _popup.IsVisible)
{
_popup.Close();
}
SelectedIndex = -1;
_isOpen = false;
}
/// <summary>
/// Shows a context menu for the specified control.
/// </summary>
/// <param name="control">The control.</param>
private static void Show(Control control)
{
if (control != null)
{
if(_popup == null)
{
_popup = new Popup()
{
PlacementTarget = control,
StaysOpen = false
};
_popup.Closed += PopupClosed;
}
((ISetLogicalParent)_popup).SetParent(control);
_popup.Child = control.ContextMenu;
_popup.Open();
control.ContextMenu._isOpen = true;
}
}
private static void PopupClosed(object sender, EventArgs e)
{
var contextMenu = (sender as Popup)?.Child as ContextMenu;
if (contextMenu != null)
{
foreach (MenuItem i in contextMenu.GetLogicalChildren())
{
i.IsSubMenuOpen = false;
}
contextMenu._isOpen = false;
}
}
private void PopupOpened(object sender, EventArgs e)
{
var selectedIndex = SelectedIndex;
if (selectedIndex != -1)
{
var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
container?.Focus();
}
}
private static void ControlPointerReleased(object sender, PointerReleasedEventArgs e)
{
var control = (Control)sender;
if (e.MouseButton == MouseButton.Right)
{
if (control.ContextMenu._isOpen)
{
control.ContextMenu.Hide();
}
Show(control);
}
else
{
control.ContextMenu.Hide();
}
}
}
}

16
src/Perspex.Controls/Control.cs

@ -65,6 +65,13 @@ namespace Perspex.Controls
public static readonly StyledProperty<ITemplatedControl> TemplatedParentProperty =
PerspexProperty.Register<Control, ITemplatedControl>(nameof(TemplatedParent), inherits: true);
/// <summary>
/// Defines the <see cref="ContextMenu"/> property.
/// </summary>
public static readonly StyledProperty<ContextMenu> ContextMenuProperty =
PerspexProperty.Register<Control, ContextMenu>(nameof(ContextMenu));
/// <summary>
/// Event raised when an element wishes to be scrolled into view.
/// </summary>
@ -204,6 +211,15 @@ namespace Perspex.Controls
/// </summary>
public IControl Parent => _parent;
/// <summary>
/// Gets or sets a context menu to the control.
/// </summary>
public ContextMenu ContextMenu
{
get { return GetValue(ContextMenuProperty); }
set { SetValue(ContextMenuProperty, value); }
}
/// <summary>
/// Gets or sets a user-defined object attached to the control.
/// </summary>

1
src/Perspex.Controls/Perspex.Controls.csproj

@ -43,6 +43,7 @@
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Classes.cs" />
<Compile Include="ContextMenu.cs" />
<Compile Include="DockPanel.cs" />
<Compile Include="Expander.cs" />
<Compile Include="Generators\ItemContainer.cs" />

22
src/Perspex.Controls/Primitives/Popup.cs

@ -7,6 +7,8 @@ using Perspex.Interactivity;
using Perspex.Metadata;
using Perspex.Rendering;
using Perspex.VisualTree;
using Perspex.LogicalTree;
using System.Linq;
namespace Perspex.Controls.Primitives
{
@ -170,6 +172,11 @@ namespace Perspex.Controls.Primitives
_popupRoot.Position = GetPosition();
if (_topLevel == null && PlacementTarget != null)
{
_topLevel = PlacementTarget.GetSelfAndLogicalAncestors().First(x => x is TopLevel) as TopLevel;
}
if (_topLevel != null)
{
_topLevel.Deactivated += TopLevelDeactivated;
@ -190,8 +197,12 @@ namespace Perspex.Controls.Primitives
{
if (_popupRoot != null)
{
_topLevel.RemoveHandler(PointerPressedEvent, PointerPressedOutside);
_topLevel.Deactivated -= TopLevelDeactivated;
if (_topLevel != null)
{
_topLevel.RemoveHandler(PointerPressedEvent, PointerPressedOutside);
_topLevel.Deactivated -= TopLevelDeactivated;
}
_popupRoot.Hide();
}
@ -210,16 +221,15 @@ namespace Perspex.Controls.Primitives
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
base.OnAttachedToLogicalTree(e);
_topLevel = e.Root as TopLevel;
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_topLevel = null;
}

20
src/Perspex.Themes.Default/ContextMenu.paml

@ -0,0 +1,20 @@
<Style xmlns="https://github.com/perspex" Selector="ContextMenu">
<Setter Property="BorderBrush" Value="Gray"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="4,2"/>
<Setter Property="FontSize" Value="12" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
KeyboardNavigation.TabNavigation="Continue"/>
</Border>
</ControlTemplate>
</Setter>
</Style>

1
src/Perspex.Themes.Default/DefaultTheme.paml

@ -11,6 +11,7 @@
<StyleInclude Source="resm:Perspex.Themes.Default.ListBox.paml?assembly=Perspex.Themes.Default"/>
<StyleInclude Source="resm:Perspex.Themes.Default.ListBoxItem.paml?assembly=Perspex.Themes.Default"/>
<StyleInclude Source="resm:Perspex.Themes.Default.Menu.paml?assembly=Perspex.Themes.Default"/>
<StyleInclude Source="resm:Perspex.Themes.Default.ContextMenu.paml?assembly=Perspex.Themes.Default"/>
<StyleInclude Source="resm:Perspex.Themes.Default.MenuItem.paml?assembly=Perspex.Themes.Default"/>
<StyleInclude Source="resm:Perspex.Themes.Default.PopupRoot.paml?assembly=Perspex.Themes.Default"/>
<StyleInclude Source="resm:Perspex.Themes.Default.ProgressBar.paml?assembly=Perspex.Themes.Default"/>

3
src/Perspex.Themes.Default/Perspex.Themes.Default.csproj

@ -147,6 +147,9 @@
<EmbeddedResource Include="DropDownItem.paml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="ContextMenu.paml">
<SubType>Designer</SubType>
</EmbeddedResource>
<None Include="packages.config" />
<EmbeddedResource Include="PopupRoot.paml">
<SubType>Designer</SubType>

Loading…
Cancel
Save