Sample Pictures
Opened Item
---
CheckBox & RadioButton (Grouping) Support
Fast Menu Item Access (Underlined char)
Online Demo Instructions
Menu activation key gesture is set to "Ctrl" key. When menu activation key is pressed, items that have fast access will be shown with underlined char. If you activate menu with "Ctrl" key, you can open menu items just by pressing an underlined char with "Ctrl" key released.
Introduction
First of all, I want to apologize for language problems you may experience while reading this article. English is my second language, so sorry for any mis-spellings or language mistakes.
If you take a look to the article's pictures, I think there is no need to explain what it is. The main idea I was driven with is to develop basic application functionality that might be helpful. I think that everybody understands that Silverlight gives us an ability to implement Windows like applications and when it comes to application command navigation, my Menu might be very handy.
Background
The implementation of this control is a part of a business application that I am working on. The whole thing consists of many additional classes and controls that I will be posting later on. The final package will include:
WindowBase
class (modal mode support) - in progress...
- ToolStrips - in progress...
- Desktop (Windows like Desktop Emulation) - in progress...
- Taskbar - in progress
- DesktopApplication (This is a base class for Desktop's plugin development) - in progress...
Development Tools
The development tools that you need to compile & run this sample are listed below:
- Microsoft Visual Studio 2008
- Microsoft Expression Blend 3.0 (RCW)
- Silverlight SDK 3.0 (RCW)
- Microsoft Visual Studio Tools for Silverlight 3.0
- Microsoft Silverlight Toolkit July 2009
- Silverlight 3.0 RCW Developer Runtime
Key Features
- Mouse navigation
- Keyboard navigation
- Shortcut support
- Fast menu item access (similar to shortcut)
- Mixed navigation mode (mouse+keyboard)
- CheckBox support
- RadioButton support (MenuItem Grouping)
Note (shortcut support limitations):
Let me explain a little bit about using the shortcut feature. When the shortcut is being registered within your application, it might not work. This happens because the Browser handles particular shortcuts itself. Little sample. I have registered a shortcut in my sample application as an Action (Close - Ctrl+E). I have never received an event to SL input subsystem because the Browser handled this event that resulted in search bar being activated at the top of browser's window.
The second thing which you have to know about using shortcuts is that the input subsystem limits keyboard navigation in full screen mode.
Code Background
The code consists of the following classes:
Menu
- Root Menu Implementation
MenuItem
- Menu item implementation
MenuTracker
- This object tracks mouse/keyboard events and responsible for Item/SubItem popup functionality/keyboard navigation
MenuKeyGesture
- MenuItem shortcut container
MenuKeyGestureTypeConverter
- MenuItem XAML key gesture string converter
ItemAccessKey
- Fast menu item access key container
TypeExtensions
- Type extension methods
The most functional code is written in MenuItem
/MenuTracker
classes.
<Menu> Class
[TemplatePart(Name = "RootElement", Type = typeof(FrameworkElement))]
public class Menu : ItemsControl, IItemsControl
{
internal MenuTracker _menu_tracker;
internal static List<Menu> _instances;
public static readonly DependencyProperty ActivationKeyGestureProperty =
DependencyProperty.Register("ActivationKeyGesture",
typeof(MenuKeyGesture), typeof(Menu),
new PropertyMetadata(new MenuKeyGesture()
{ Gesture = Key.Ctrl, ModifierKey = ModifierKeys.None },
delegate(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}));
static Menu() {
_instances = new List<Menu>();
}
public Menu(){
base.DefaultStyleKey = typeof(Menu);
Loaded += delegate(object sender, RoutedEventArgs e)
{
_menu_tracker = new MenuTracker(this, this, this.GetRoot(), true);
};
_instances.Add(this);
}
~Menu()
{
_instances.Remove(this);
}
Guid _uniq_anime_guid = Guid.NewGuid();
public void AnimatePopup(Popup popup)
{
Storyboard sb = null;
popup.Child.Opacity = 0;
if (popup.Resources.Contains(_uniq_anime_guid.ToString()) == false)
{
Duration duration = new Duration(TimeSpan.FromMilliseconds(250));
DoubleAnimation opacity_animation =
new DoubleAnimation() { Duration = duration };
sb = new Storyboard() { Duration = duration };
sb.Children.Add(opacity_animation);
Storyboard.SetTarget(opacity_animation, popup.Child);
Storyboard.SetTargetProperty(opacity_animation,
new PropertyPath("(UIElement.Opacity)"));
opacity_animation.To = 1;
popup.Resources.Add(_uniq_anime_guid.ToString(), sb);
sb.Begin();
}
else
{
sb = popup.Resources[_uniq_anime_guid.ToString()] as Storyboard;
sb.Begin();
}
}
public static void CloseAllPopups()
{
_instances.ForEach(menu =>
{
try
{
menu.ClosePopups();
}
catch { }
});
}
protected override DependencyObject GetContainerForItemOverride()
{
return new MenuItem() { };
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MenuItem;
}
protected override void PrepareContainerForItemOverride
(DependencyObject element, object item)
{
if (element is MenuItem)
{
MenuItem container = (element as MenuItem);
if (IsItemItsOwnContainerOverride(item) == false)
container.Header = item;
container.Menu = this;
container.TopLevel = true;
}
base.PrepareContainerForItemOverride(element, item);
}
internal void ClosePopups()
{
this.Items.OfType<MenuItem>().ToList().ForEach(item =>
{
_menu_tracker.CloseAllChildPopups(item, true);
});
}
internal MenuTracker Tracker
{
get { return _menu_tracker; }
}
public MenuKeyGesture ActivationKeyGesture
{
get { return (MenuKeyGesture)GetValue(ActivationKeyGestureProperty); }
set { SetValue(ActivationKeyGestureProperty, value); }
}
IEnumerable<MenuItem> IItemsControl.InternalItems
{
get { return Items.OfType<MenuItem>(); }
}
}
This class contains a property that is not functional "Orientation
". :O) for the later implementation...
If you take a look at the code, you will see a static
field named "_instances
". This one is used in "MenuTracker
" object for closing all opened menu items for multi-instantiated Menu
classes when you left click anywhere on your page and it is not a MenuItem
object.
The control styling is done in the class constructor:
base.DefaultStyleKey = typeof(Menu);
All styles must be stored in a solution folder named "Themes" in single file "generic.xaml".
Let's take a look at Menu
style:
<!---->
<Style TargetType="local:Menu">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Menu">
<Grid x:Name="RootElement" Visibility="Visible" Background="#9999DDF0" >
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Margin="{TemplateBinding Margin}"
Padding="{TemplateBinding Padding}">
<ItemsPresenter/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<toolkit:WrapPanel ItemHeight="25" Margin="2,2,2,2"
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
The namespace prefix stands for a local project namespace:
xmlns:local="clr-namespace:System.Windows.Controls;assembly=DropDownMenu"
<MenuItem> Class
Let's take a look at MenuItem
class:
[
TemplatePart(Name = "RootElement", Type = typeof(FrameworkElement)),
TemplatePart(Name = "PART_Popup", Type = typeof(Popup)),
TemplatePart(Name = "PART_PopupTopRect",Type = typeof(Rectangle)),
TemplatePart(Name = "PART_Selected", Type = typeof(FrameworkElement)),
TemplatePart(Name = "PART_Opened", Type = typeof(FrameworkElement)),
TemplatePart(Name = "PART_Header", Type = typeof(ContentControl)),
TemplatePart(Name = "PART_LeftBar", Type = typeof(FrameworkElement)),
TemplatePart(Name = "PART_HitTest", Type = typeof(FrameworkElement)),
TemplatePart(Name = "PART_Separator", Type = typeof(FrameworkElement)),
TemplatePart(Name = "PART_SubMenuGrip", Type = typeof(FrameworkElement)),
ContentProperty("Items")
]
public class MenuItem : Control, IItemsControl
{
public class ItemAccessKey{
public string Key { get; set; }
public Border Border { get; set; }
}
private System.Windows.Controls.Primitives.Popup _popup;
private ContentControl _HeaderControl;
private FrameworkElement _PART_Seperator,
_PART_Selected, _PART_Opened, _RootElement;
private Rectangle _PART_LeftBar, _PART_PopupTopRect, _PART_HitTest;
private FrameworkElement _PART_SubMenuGrip;
internal MenuTracker _menu_tracker;
private ItemAccessKey _accessKey = null;
private bool _is_highlighted;
private bool _is_Opended;
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand), typeof(MenuItem),
new PropertyMetadata(null, delegate(DependencyObject d,
DependencyPropertyChangedEventArgs e){
}));
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked", typeof(bool), typeof(MenuItem),
new PropertyMetadata(false, delegate(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
MenuItem item = d as MenuItem;
if ( item != null && string.IsNullOrEmpty(item.GroupName) ==
false && ((bool)e.NewValue) )
{
IEnumerable<MenuItem> gr_items = null;
if (item.ParentItem != null)
{
gr_items = (from i in item.ParentItem.Items
where i.GroupName == item.GroupName
select i);
if ( gr_items.Count() > 0 )
{
gr_items = gr_items.Where(i => i != item && i.IsChecked);
gr_items.ToList().ForEach(i => { i.IsChecked = false; });
}
}
else if( item.Menu != null )
{
gr_items = (from i in item.Menu.Items.Cast<MenuItem>()
where i.GroupName == item.GroupName
select i);
if (gr_items.Count() > 0)
{
gr_items = gr_items.Where(i => i != item && i.IsChecked);
gr_items.ToList().ForEach(i => { i.IsChecked = false; });
}
}
}
}));
public static readonly DependencyProperty GroupNameProperty =
DependencyProperty.RegisterAttached("GroupName",
typeof(string), typeof(MenuItem),
new PropertyMetadata(null, delegate(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
}));
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.RegisterAttached("Items",
typeof(ObservableCollection<MenuItem>), typeof(MenuItem),
new PropertyMetadata(null, delegate(DependencyObject d,
DependencyPropertyChangedEventArgs e){
}));
public static readonly DependencyProperty TopLevelProperty =
DependencyProperty.RegisterAttached("TopLevel", typeof(bool), typeof(MenuItem),
new PropertyMetadata(delegate(DependencyObject d,
DependencyPropertyChangedEventArgs e){
}));
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.RegisterAttached("Header", typeof(object), typeof(MenuItem),
new PropertyMetadata(delegate(DependencyObject d,
DependencyPropertyChangedEventArgs e){
}));
public static readonly DependencyProperty IsMouseOverProperty =
DependencyProperty.Register("IsMouseOver",
typeof(bool), typeof(MenuItem), null);
public static readonly DependencyProperty KeyGestureProperty =
DependencyProperty.Register("KeyGesture",
typeof(MenuKeyGesture), typeof(MenuItem),
new PropertyMetadata(new MenuKeyGesture()
{ Gesture = Key.None, ModifierKey = ModifierKeys.None },
delegate(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MenuItem item = d as MenuItem;
if (item != null)
{
if (e.NewValue != null)
{
MenuKeyGesture gestute = e.NewValue as MenuKeyGesture;
if (gestute.Gesture != Key.None)
{
System.Windows.Automation.AutomationProperties.
SetAcceleratorKey(item, gestute.ToString());
}
}
}
}));
public event RoutedEventHandler Click;
public event RoutedEventHandler Opening;
public event RoutedEventHandler Opened;
public MenuItem()
{
TopLevel = false;
SetValue(ItemsProperty, new ObservableCollection<MenuItem>());
Items.CollectionChanged +=
new System.Collections.Specialized.NotifyCollectionChangedEventHandler
(OnItems_CollectionChanged);
base.DefaultStyleKey = typeof(MenuItem);
}
protected void OnItems_CollectionChanged(object sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
if (e.NewItems != null)
e.NewItems.Cast<MenuItem>().ToList().ForEach(item =>
{
if (TopLevel == false && Items.Count > 0 &&
_PART_SubMenuGrip != null)
_PART_SubMenuGrip.Visibility = Visibility.Visible;
item.TopLevel = false;
item.Menu = this.Menu;
item.ParentItem = this;
});
break;
}
}
protected void InitializeChildren(MenuItem item)
{
item.Items.ToList().ForEach(i =>
{
i.Menu = Menu;
i.ParentItem = item;
InitializeChildren(i);
});
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_RootElement = (FrameworkElement)GetTemplateChild("RootElement");
_popup = (Popup)GetTemplateChild("PART_Popup");
_PART_PopupTopRect = (Rectangle)GetTemplateChild("PART_PopupTopRect");
_popup.IsOpen = false;
_PART_Selected = (FrameworkElement)GetTemplateChild("PART_Selected");
_PART_Opened = (FrameworkElement)GetTemplateChild("PART_Opened");
_HeaderControl = (ContentControl)GetTemplateChild("PART_Header");
_PART_LeftBar = (Rectangle)GetTemplateChild("PART_LeftBar");
_PART_HitTest = (Rectangle)GetTemplateChild("PART_HitTest");
_PART_Seperator = (FrameworkElement)GetTemplateChild("PART_Separator");
_PART_SubMenuGrip = (FrameworkElement)GetTemplateChild("PART_SubMenuGrip");
_menu_tracker = new MenuTracker(Menu, this,
_popup.Child as FrameworkElement, false);
InitializeChildren(this);
if (TopLevel == false)
{
if ( Items.Count > 0 && _PART_SubMenuGrip != null )
_PART_SubMenuGrip.Visibility = Visibility.Visible;
if (_RootElement != null)
{
bool IsSeparator = Header != null && Header.ToString().Equals("-");
_RootElement.Height = Header != null &&
Header.ToString().Equals("-") ? 7 : 26;
if (IsSeparator)
_PART_Seperator.Visibility = Visibility.Visible;
else
_PART_Seperator.Visibility = Visibility.Collapsed;
}
if (_HeaderControl != null)
{
_HeaderControl.HorizontalAlignment = HorizontalAlignment.Left;
_HeaderControl.Margin = new Thickness(4, 0, 0, 0);
}
if (_PART_LeftBar != null)
_PART_LeftBar.Visibility = Visibility.Visible;
if (_PART_Opened != null && _PART_Opened is Border)
(_PART_Opened as Border).CornerRadius = new CornerRadius(2, 0, 2, 2);
}
else
{
if (_RootElement is Grid)
(_RootElement as Grid).ColumnDefinitions[2].Width = new GridLength(6d);
_PART_Seperator.Visibility = Visibility.Collapsed;
if (_PART_Opened != null && _PART_Opened is Border)
(_PART_Opened as Border).CornerRadius = new CornerRadius(2, 2, 0, 2);
if (_HeaderControl != null)
_HeaderControl.Margin = new Thickness(-17, 0, 0, 0);
}
if (IsHighlighted)
{
if (_PART_Selected != null)
_PART_Selected.Visibility = Visibility.Visible;
}
bool IsHeaderTextFound = false;
string HeaderText = string.Empty;
if (Header != null && typeof(string) ==
Header.GetType() && Header.ToString().Length >= 2 &&
Header.ToString().Contains('_'))
{
IsHeaderTextFound = true;
HeaderText = Header.ToString();
}
else if ( Header != null && Header is FrameworkElement )
{
HeaderText = FindAccessKeyHeaderControlText(Header as FrameworkElement);
if (string.IsNullOrEmpty(HeaderText) == false
&& HeaderText.Length >= 2 && HeaderText.Contains('_'))
IsHeaderTextFound = true;
else
HeaderText = null;
}
if( IsHeaderTextFound )
{
char[] h_text = HeaderText.ToCharArray();
Grid h_panel = new Grid() { };
if (Header is string)
Header = h_panel;
else
{
FrameworkElement h_control =
FindAccessKeyHeaderControl(Header as FrameworkElement);
if (h_control is ContentControl)
(h_control as ContentControl).Content = h_panel;
else
return;
}
double x = 0;
TextBlock t = new TextBlock()
{
Text = "",
FontFamily = FontFamily,
FontSize = FontSize,
FontWeight = FontWeight,
FontStyle = FontStyle,
FontStretch = FontStretch,
Foreground = Foreground,
Margin = new Thickness(0, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left
};
h_panel.Children.Add(t);
double last_width = 0;
for (int i = 0; i < h_text.Length; i++)
{
last_width = t != null ? t.ActualWidth : 0;
if (h_text[i].ToString().Equals("_") == false)
{
Run run = new Run()
{
Text = h_text[i].ToString(),
FontFamily = FontFamily,
FontSize = FontSize,
FontWeight = FontWeight,
FontStyle = FontStyle,
FontStretch = FontStretch,
Foreground = Foreground,
};
t.Inlines.Add(run);
}
if (_accessKey == null && h_text[i].ToString().Equals("_"))
{
Run run = new Run()
{
Text = h_text[i + 1].ToString(),
FontFamily = FontFamily,
FontSize = FontSize,
FontWeight = FontWeight,
FontStyle = FontStyle,
FontStretch = FontStretch,
Foreground = Foreground,
};
t.Inlines.Add(run);
if (t != null)
t.UpdateLayout();
this.UpdateLayout();
_accessKey = new ItemAccessKey()
{
Border = new Border()
{
BorderThickness = new Thickness(0, 0, 0, 1),
BorderBrush = Foreground,
Margin = new Thickness(x, t != null ?
t.ActualHeight - 4 : 0, 0, 0),
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left,
Width = t.ActualWidth - last_width,
Height = 2
},
Key = i + 1 < h_text.Length ? h_text[i + 1].ToString() : ""
};
h_panel.Children.Add(_accessKey.Border);
i++;
}
if (t != null)
t.UpdateLayout();
this.UpdateLayout();
x += t != null ? (t.ActualWidth + 0.1) - last_width : 0;
}
if (_accessKey != null)
_accessKey.Border.Opacity = 0.0d;
}
else if (Header != null && typeof(string) == Header.GetType()
&& Header.ToString().Equals("-") == false )
{
TextBlock t = null;
t = new TextBlock()
{
Text = Header.ToString(),
FontFamily = FontFamily,
FontSize = FontSize,
FontWeight = FontWeight,
FontStyle = FontStyle,
FontStretch = FontStretch,
Foreground = Foreground,
Margin = new Thickness(0, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left
};
Header = t;
}
}
public string FindHeaderControlText(FrameworkElement element)
{
int count = VisualTreeHelper.GetChildrenCount(element);
string ret_data = null;
if (count > 0)
{
for (int i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(element, i);
if (child is FrameworkElement)
{
if (child is ContentControl || child is TextBlock)
{
if (child is TextBlock)
{
ret_data = (child as TextBlock).Text;
return ret_data;
}
else if (child is ContentControl)
{
if ((child as ContentControl).Content != null &&
((child as ContentControl).Content is string))
ret_data = (child as ContentControl).Content.ToString();
else if ((child as ContentControl).Content
is FrameworkElement)
{
ret_data = FindHeaderControlText((child
as ContentControl).Content as FrameworkElement);
}
}
if( string.IsNullOrEmpty(ret_data) == false )
return ret_data;
}
}
if (ret_data != null)
return ret_data;
if (child != null && child.GetType() == typeof(FrameworkElement))
FindHeaderControlText(child as FrameworkElement);
}
}
else
{
if (element is TextBlock)
{
ret_data = (element as TextBlock).Text;
return ret_data;
}
else if (element is ContentControl)
{
if ((element as ContentControl).Content != null &&
((element as ContentControl).Content is string))
ret_data = (element as ContentControl).Content.ToString();
else if ((element as ContentControl).Content is FrameworkElement)
{
ret_data = FindHeaderControlText((element
as ContentControl).Content as FrameworkElement);
}
}
return ret_data;
}
return ret_data;
}
public string FindAccessKeyHeaderControlText(FrameworkElement element)
{
int count = VisualTreeHelper.GetChildrenCount(element);
string ret_data = null;
if (count > 0)
{
for (int i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(element, i);
if (child is FrameworkElement &&
(child as FrameworkElement).Tag != null &&
(child as FrameworkElement).Tag.GetType() == typeof(string) &&
string.IsNullOrEmpty((child as FrameworkElement).Tag.ToString())
== false &&
(child as FrameworkElement).Tag.ToString().ToLower().
Equals("accesskey"))
{
if (child is ContentControl || child is TextBlock )
{
if( child is TextBlock )
{
ret_data = (child as TextBlock).Text;
return ret_data;
}
else if (child is ContentControl)
{
if ((child as ContentControl).Content != null &&
((child as ContentControl).Content is string))
ret_data = (child as ContentControl).Content.ToString();
else if( (child as ContentControl).Content
is FrameworkElement )
{
ret_data = FindAccessKeyHeaderControlText
((child as ContentControl).Content as FrameworkElement);
}
}
return ret_data;
}
}
if ( ret_data != null )
return ret_data;
if( child != null && child.GetType() == typeof(FrameworkElement) )
FindAccessKeyHeaderControlText(child as FrameworkElement);
}
}
return ret_data;
}
public FrameworkElement FindAccessKeyHeaderControl(FrameworkElement element)
{
int count = VisualTreeHelper.GetChildrenCount(element);
FrameworkElement ret_data = null;
if (count > 0)
{
for (int i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(element, i);
if (child is FrameworkElement && (child as FrameworkElement).Tag
!= null &&
(child as FrameworkElement).Tag.GetType() == typeof(string) &&
string.IsNullOrEmpty((child
as FrameworkElement).Tag.ToString()) == false &&
(child as FrameworkElement).Tag.ToString().ToLower().
Equals("accesskey"))
{
if (child is ContentControl)
{
ret_data = child as FrameworkElement;
return ret_data;
}
}
if (ret_data != null)
return ret_data;
if (child != null && child.GetType() == typeof(FrameworkElement))
FindAccessKeyHeaderControlText(child as FrameworkElement);
}
}
return ret_data;
}
internal void ActivateAccessKey( bool IsActive )
{
if (_accessKey != null)
_accessKey.Border.Opacity = IsActive ? 1.0d : 0.0d;
}
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
if (IsEnabled)
IsMouseOver = true;
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
IsMouseOver = false;
if (IsOpened == false)
{
if (TopLevel)
{
MenuTracker tracker = Menu.Tracker;
if (tracker.IsMenuActivated)
{
IsHighlighted = true;
return;
}
}
IsHighlighted = false;
}
}
public void Add(MenuItem item)
{
Items.Add(item);
}
internal void FireClickEvent()
{
OnClick(new RoutedEventArgs());
}
protected virtual void OnClick(RoutedEventArgs e)
{
if( string.IsNullOrEmpty(GroupName) == true )
IsChecked = !IsChecked;
else if( IsChecked == false )
IsChecked = !IsChecked;
if (Click != null)
Click(this, e);
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public string GroupName
{
get { return (string)GetValue(GroupNameProperty); }
set { SetValue(GroupNameProperty, value); }
}
IEnumerable<MenuItem> IItemsControl.InternalItems
{
get { return Items; }
}
internal ItemAccessKey AccessKey
{
get { return _accessKey; }
}
internal MenuTracker Tracker
{
get { return _menu_tracker; }
}
public ObservableCollection<MenuItem> Items
{
get { return (ObservableCollection<MenuItem>)GetValue(ItemsProperty); }
}
public bool IsMouseOver
{
get
{
return (bool)this.GetValue(IsMouseOverProperty);
}
internal set
{
base.SetValue(IsMouseOverProperty, value);
}
}
public object Header
{
get { return GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public MenuKeyGesture KeyGesture
{
get { return (MenuKeyGesture)GetValue(KeyGestureProperty); }
set { SetValue(KeyGestureProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public bool TopLevel
{
get { return (bool)GetValue(TopLevelProperty); }
set { SetValue(TopLevelProperty, value); }
}
public bool IsOpened
{
get { return _is_Opended; }
set { _is_Opended = value; }
}
internal void SetHighlightedDirect(bool value)
{
_is_highlighted = value;
if (value)
{
if (_PART_Selected != null)
_PART_Selected.Visibility = Visibility.Visible;
}
else
{
if (_PART_Selected != null)
_PART_Selected.Visibility = Visibility.Collapsed;
}
}
public bool IsHighlighted
{
get
{
return _is_highlighted;
}
set
{
if ((Header != null && Header.ToString().Equals("-")) || IsEnabled == false)
return;
_is_highlighted = value;
if (value)
{
if (IsOpened && TopLevel)
return;
if (_PART_Selected != null)
_PART_Selected.Visibility = Visibility.Visible;
}
else
{
if (IsOpened)
return;
if (_PART_Selected != null)
_PART_Selected.Visibility = Visibility.Collapsed;
}
}
}
public void HidePopup()
{
if (IsOpened == false)
return;
IsOpened = false;
_popup.IsOpen = false;
IsHighlighted = false;
if (TopLevel)
{
if (_PART_Selected != null)
_PART_Selected.Visibility = Visibility.Collapsed;
if (_PART_Opened != null)
_PART_Opened.Visibility = Visibility.Collapsed;
}
}
public void ShowPopup(Point position)
{
if ( IsOpened )
return;
IsOpened = true;
IsHighlighted = true;
if (TopLevel == true)
{
if (_PART_Selected != null)
_PART_Selected.Visibility = Visibility.Collapsed;
if (_PART_Opened != null)
_PART_Opened.Visibility = Visibility.Visible;
}
Items.OfType<MenuItem>().Where(i => i._PART_Selected
!= null).ToList().ForEach( item =>
{
item._PART_Selected.Visibility = Visibility.Collapsed;
});
if (TopLevel)
{
_PART_PopupTopRect.Visibility = Visibility.Visible;
_PART_PopupTopRect.Width = ActualWidth - 2;
}
_popup.HorizontalOffset = position.X;
_popup.VerticalOffset = position.Y;
Application.Current.RootVisual.MouseLeftButtonDown +=
new MouseButtonEventHandler(OnRootVisual_MouseLeftButtonDown);
_popup.Closed += delegate(object popup_closed, EventArgs popup_args)
{
Application.Current.RootVisual.MouseLeftButtonDown -=
new MouseButtonEventHandler(OnRootVisual_MouseLeftButtonDown);
};
OnPopupOpening(new RoutedEventArgs());
_popup.IsOpen = true;
Menu.AnimatePopup(_popup);
OnPopupOpened(new RoutedEventArgs());
}
protected void OnPopupOpening(RoutedEventArgs e) {
if (Opening != null)
Opening(this, e);
}
protected void OnPopupOpened(RoutedEventArgs e) {
if (Opened != null)
Opened(this, e);
}
protected void OnRootVisual_MouseLeftButtonDown
(object sender, MouseButtonEventArgs e)
{
this._menu_tracker.CloseAllChildPopups(this, true);
}
public MenuItem ParentItem
{
get;
set;
}
public Menu Menu
{
get;
set;
}
public FrameworkElement RootElement
{
get { return _RootElement; }
}
public Rectangle HitTestArea
{
get { return _PART_HitTest; }
}
public System.Windows.Controls.Primitives.Popup Popup
{
get { return _popup; }
}
public bool HasKeyGesture
{
get { return KeyGesture != null && KeyGesture.Gesture != Key.None; }
}
public bool HasOpenedChild
{
get
{
return (from child in Items.Cast<MenuItem>()
where child.IsOpened select child).Count() > 0;
}
}
public MenuItem OpenedChild
{
get
{
return (from child in Items.Cast<MenuItem>()
where child.IsOpened select child).First();
}
}
public MenuItem GetLastOpenedChild()
{
MenuItem ret_val = null;
if (HasOpenedChild)
GetLastOpenedChild(OpenedChild, ref ret_val);
return ret_val;
}
public MenuItem GetHighlightedChild()
{
IEnumerable<MenuItem> found =
(from item in Items.Cast<MenuItem>() where item.IsHighlighted select item);
if (found.Count() > 0)
return found.First();
return null;
}
protected void GetLastOpenedChild(MenuItem item, ref MenuItem LastOpened)
{
if (item.IsOpened)
LastOpened = item;
if (item.HasOpenedChild)
item.GetLastOpenedChild(item.OpenedChild, ref LastOpened);
}
public bool IsSeparator
{
get { return Header != null && Header.ToString().Equals("-"); }
}
}
The styling method for this class is the same as for Menu
class and is done in the class constructor:
public MenuItem()
{
TopLevel = false;
SetValue(ItemsProperty, new ObservableCollection<MenuItem>());
Items.CollectionChanged +=
new System.Collections.Specialized.NotifyCollectionChangedEventHandler
(OnItems_CollectionChanged);
base.DefaultStyleKey = typeof(MenuItem);
}
The style for MenuItem
:
<local:MenuKeyGestureValueConverter x:Key="GestureConverter"/>
<local:BooleanToVisibilityConverter x:Key="BooleanVisibilityConveter"/>
-->
<Style TargetType="local:MenuItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MenuItem">
<Grid Opacity="1.0" x:Name="RootElement"
Visibility="Visible" Height="Auto" Width="Auto" >
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19" Width="26"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="26"/>
</Grid.ColumnDefinitions>
<Rectangle Margin="2,0,0,0" Grid.Column="0"
Visibility="Collapsed" x:Name="PART_LeftBar">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFE9E8E8" Offset="0"/>
<GradientStop Color="#FFE9E8E8" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Border Grid.ColumnSpan="4" Visibility="Collapsed"
Background="#FFF5F5F5" BorderBrush="#FF868686"
x:Name="PART_Opened" BorderThickness="1"
CornerRadius="2,2,2,0"/>
<Grid x:Name="PART_Selected" Margin="0,0,0,0"
Grid.ColumnSpan="4" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="0.5*"/>
<RowDefinition Height="0.5*"/>
</Grid.RowDefinitions>
<Rectangle Stroke="#FF9FBBE3" StrokeThickness="0.05"
RadiusX="2" RadiusY="2" Grid.RowSpan="2" x:Name="rectangle1">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFDAE4EF" Offset="0"/>
<GradientStop Color="#FFECEBC0" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.RowSpan="2" Margin="2,2,2,2"
RadiusX="2" RadiusY="2" x:Name="rectangle2">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFFFF9" Offset="0"/>
<GradientStop Color="#FFFFF7B9" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Margin="3,3,3,0" x:Name="rectangle3">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFF9D9" Offset="0"/>
<GradientStop Color="#FFFFEEB2" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Margin="3,0,3,3" Grid.Row="1" x:Name="rectangle4">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFD66B" Offset="0"/>
<GradientStop Color="#FFFFE086" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
<Border Grid.ColumnSpan="4" Visibility="Visible"
Background="#FF808080" BorderBrush="#FF868686"
x:Name="PART_Separator" BorderThickness="1"
CornerRadius="0,0,0,0" Width="Auto" Height="1"
Margin="30,0,3,0"/>
<Grid Grid.Column="1" Width="Auto" Height="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition Width="0.5*"/>
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0"
VerticalAlignment="Center" HorizontalAlignment="Center"
x:Name="PART_Header" Content="{TemplateBinding Header}"/>
<TextBlock Grid.Column="1" Margin="10,0,-12,0"
VerticalAlignment="Center" HorizontalAlignment="Right"
Visibility="{Binding
Path=HasKeyGesture,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource BooleanVisibilityConveter}}"
Text="{Binding
Path=KeyGesture,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource GestureConverter}}"
x:Name="PART_GestureString" TextWrapping="NoWrap">
</TextBlock>
</Grid>
<ContentControl HorizontalAlignment="Center"
Grid.Column="3" RenderTransformOrigin="0.5,0"
Margin="6,9,2,0" VerticalAlignment="Center"
Width="9" Height="9" x:Name="PART_SubMenuGrip"
Visibility="Collapsed">
<ContentControl.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="45"/>
<TranslateTransform/>
</TransformGroup>
</ContentControl.RenderTransform>
<ContentControl.Content>
<Canvas Width="9" Height="9"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Polygon Points="0,0 6,0 5,5 0,0"
Stroke="{x:Null}" StrokeThickness="2"
x:Name="polygon">
<Polygon.Fill>
<RadialGradientBrush>
<GradientStop Color="#FF000000"/>
<GradientStop Color="#FF000000" Offset="1"/>
</RadialGradientBrush>
</Polygon.Fill>
</Polygon>
</Canvas>
</ContentControl.Content>
</ContentControl>
<Rectangle Grid.ColumnSpan="3" Fill="White"
Opacity="0.01" x:Name="PART_HitTest"/>
<Popup x:Name="PART_Popup">
<Grid>
<Border BorderThickness="1,1,1,1"
Background="#FFF5F5F5" BorderBrush="#FF868686"
CornerRadius="0,2,2,2"/>
<ItemsControl ItemsSource="{TemplateBinding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel x:Name="PART_PopupItemsHost"
Orientation="Vertical" Margin="2,2,2,2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Rectangle Height="2" Width="80"
VerticalAlignment="Top" HorizontalAlignment="Left"
Margin="1,-1,0,0" Fill="#FFF5F5F5"
x:Name="PART_PopupTopRect" Visibility="Collapsed"/>
</Grid>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="Black"/>
</Style>
Both of the mentioned classes have an instance of MenuTracker
object to control MenuItems
popup behaviour and keyboard navigation.
<MenuTracker Class>
internal class MenuTracker
{
private FrameworkElement _root;
private bool _tracking;
private MenuItem _highlighted = null;
private Menu _menu;
private IItemsControl _owner;
private bool _IsRoot = false;
private bool _menu_was_activated = false;
private bool _menu_keyboard_navigation = false;
private bool _last_keyup_was_gesture;
private bool _do_not_process_next_keyup = false;
public MenuTracker(Menu menu, IItemsControl owner,
FrameworkElement root, bool IsRoot)
{
_IsRoot = IsRoot;
_root = root;
_owner = owner;
_menu = menu;
HookMenu();
}
private void HookMenu()
{
if (_root != null)
{
_root.MouseEnter += new MouseEventHandler(OnMenu_MouseEnter);
_root.MouseLeave += new MouseEventHandler(OnMenu_MouseLeave);
_root.MouseMove += new MouseEventHandler(OnMenu_MouseMove);
_root.MouseLeftButtonDown +=
new MouseButtonEventHandler(OnMenu_MouseLeftButtonDown);
if (_IsRoot)
{
_root.KeyUp += new KeyEventHandler(OnRoot_KeyUp);
_root.KeyDown += new KeyEventHandler(OnRoot_KeyDown);
}
}
}
protected void OnRoot_KeyDown(object sender, KeyEventArgs e)
{
if (_last_keyup_was_gesture)
{
_last_keyup_was_gesture = false;
return;
}
if (_IsRoot == true && _menu.ActivationKeyGesture.Gesture == e.Key)
{
this._menu_keyboard_navigation = true;
_menu.Items.Cast<MenuItem>().ToList().ForEach(item =>
{
ActivateAccessKey(item, true);
});
}
}
protected void OnRoot_KeyUp(object sender, KeyEventArgs e)
{
MenuItem g_item = null;
ModifierKeys mod = Keyboard.Modifiers;
_owner.InternalItems.ToList().ForEach(item =>
{
if (g_item != null)
return;
g_item = FindItemForGesture(item, e.Key, mod);
});
if (g_item != null)
{
_last_keyup_was_gesture = true;
g_item.FireClickEvent();
_menu.ClosePopups();
this._menu.Items.Cast<MenuItem>().ToList().ForEach(i =>
{
ActivateAccessKey(i, false);
});
_highlighted = null;
return;
}
else
{
if ( mod != ModifierKeys.None && _menu_keyboard_navigation == false )
{
_last_keyup_was_gesture = true;
return;
}
else if (_IsRoot == true && IsMenuActivated ==
false && _menu_keyboard_navigation && LookUpAccessKeyItem
(_menu.Items.Cast<MenuItem>(), false, e.PlatformKeyCode))
{
_do_not_process_next_keyup = true;
_tracking = true;
if (_highlighted != null)
{
if (_highlighted.IsOpened)
{
this.CloseAllChildPopups(_highlighted, true);
if (_highlighted.IsMouseOver == false)
{
_highlighted.IsHighlighted = false;
_highlighted = null;
}
else
_highlighted.SetHighlightedDirect(true);
_menu_keyboard_navigation = false;
_menu_was_activated = false;
_menu.Items.Cast<MenuItem>().ToList().ForEach(item =>
{
ActivateAccessKey(item, false);
});
}
else
{
if (_menu_was_activated)
{
_highlighted.SetHighlightedDirect(false);
_menu_keyboard_navigation = false;
_menu_was_activated = false;
_menu.Items.Cast<MenuItem>().ToList().ForEach(item =>
{
item.ActivateAccessKey(false);
});
}
else
{
_menu_was_activated = true;
this.CloseAllChildPopups(_highlighted, true);
_highlighted.IsHighlighted = false;
_highlighted = _menu.Items.Count() > 0
? _menu.Items.Cast<MenuItem>().First() : null;
if (_highlighted != null)
{
_highlighted.IsHighlighted = true;
}
}
}
}
else
{
_highlighted = _menu.Items.Count() > 0 ?
_menu.Items.Cast<MenuItem>().First() : null;
if (_highlighted != null)
{
_highlighted.IsHighlighted = true;
_menu_was_activated = true;
}
}
}
}
if (_highlighted != null && ( IsMenuActivated ||
_menu._menu_tracker.IsMenuActivated ) )
{
if ( _highlighted.IsOpened == false )
{
FindAccessKeyItem(_menu.Items.Cast<MenuItem>(),
false, e.PlatformKeyCode);
}
else
{
IEnumerable<MenuItem> items =
_highlighted.GetLastOpenedChild() != null ?
_highlighted.GetLastOpenedChild().Items : _highlighted.Items;
FindAccessKeyItem(items, true, e.PlatformKeyCode);
}
}
if (_last_keyup_was_gesture)
{
_last_keyup_was_gesture = false;
_menu.Items.Cast<MenuItem>().ToList().ForEach(item =>
{
item.ActivateAccessKey(false);
});
return;
}
if (_IsRoot == true && IsMenuKeyPressed(e.Key))
{
if (_do_not_process_next_keyup)
{
_do_not_process_next_keyup = false;
return;
}
_tracking = true;
if (_highlighted != null)
{
if (_highlighted.IsOpened)
{
this.CloseAllChildPopups(_highlighted, true);
if (_highlighted.IsMouseOver == false)
{
_highlighted.IsHighlighted = false;
_highlighted = null;
}
else
_highlighted.SetHighlightedDirect(true);
_menu_keyboard_navigation = false;
_menu_was_activated = false;
_menu.Items.Cast<MenuItem>().ToList().ForEach(item =>
{
ActivateAccessKey(item, false);
});
}
else
{
if (_menu_was_activated)
{
_highlighted.SetHighlightedDirect(false);
_menu_keyboard_navigation = false;
_menu_was_activated = false;
_menu.Items.Cast<MenuItem>().ToList().ForEach(item =>
{
item.ActivateAccessKey(false);
});
}
else
{
_menu_was_activated = true;
this.CloseAllChildPopups(_highlighted, true);
_highlighted.IsHighlighted = false;
_highlighted = _menu.Items.Count() > 0 ?
_menu.Items.Cast<MenuItem>().First() : null;
if (_highlighted != null)
{
_highlighted.IsHighlighted = true;
}
}
}
}
else
{
_highlighted = _menu.Items.Count() > 0 ?
_menu.Items.Cast<MenuItem>().First() : null;
if (_highlighted != null)
{
_highlighted.IsHighlighted = true;
_menu_was_activated = true;
}
}
}
if (_IsRoot && e.Key == Key.Escape && _menu_was_activated)
{
if (_highlighted != null)
{
if (_highlighted.IsOpened)
{
this.CloseAllChildPopups(_highlighted, true);
}
if (_highlighted.IsMouseOver == false)
{
_highlighted.IsHighlighted = false;
_highlighted = null;
}
_menu_keyboard_navigation = false;
_menu_was_activated = false;
_menu.Items.Cast<MenuItem>().ToList().ForEach(item =>
{
item.ActivateAccessKey(false);
});
}
}
if (_IsRoot && _menu_was_activated || _menu.Tracker.IsMenuActivated)
{
NavigateMenu(e.Key);
if (e.Key == Key.Enter)
{
if (_highlighted != null)
{
MenuItem last = _highlighted.GetLastOpenedChild();
MenuItem active = last != null ?
last.GetHighlightedChild() : _highlighted.GetHighlightedChild();
if (active != null)
{
if (active.Items.Count == 0)
{
active.FireClickEvent();
CloseAllChildPopups(active, true);
_menu_keyboard_navigation = false;
_menu_was_activated = false;
this._menu.Items.Cast<MenuItem>().ToList().ForEach(i =>
{
ActivateAccessKey(i, false);
});
_highlighted = null;
}
else
{
ShowPopup(active);
active.Items.Cast<MenuItem>().First()
.IsHighlighted = true;
}
}
}
}
}
}
protected bool LookUpAccessKeyItem(IEnumerable<MenuItem> items,
bool IsRootOpen, int KeyCode)
{
IEnumerable<MenuItem> found = (from i in items.Cast<MenuItem>()
where i.AccessKey != null &&
string.IsNullOrEmpty(i.AccessKey.Key)
== false &&
i.AccessKey.Key.Length > 0 &&
i.AccessKey.Key.ToLower() ==
((char)KeyCode).ToString().ToLower()
select i);
return found.Count() > 0;
}
protected void FindAccessKeyItem(IEnumerable<MenuItem> items,
bool IsRootOpen, int KeyCode)
{
IEnumerable<MenuItem> found = (from i in items.Cast<MenuItem>()
where i.AccessKey != null &&
string.IsNullOrEmpty
(i.AccessKey.Key) == false &&
i.AccessKey.Key.Length > 0 &&
i.AccessKey.Key.ToLower() ==
((char)KeyCode).ToString().ToLower()
select i);
if (found.Count() > 0)
{
if (found.First().Items.Count == 0)
{
_last_keyup_was_gesture = true;
found.First().FireClickEvent();
_menu.ClosePopups();
this._menu.Items.Cast<MenuItem>().ToList().ForEach(i =>
{
ActivateAccessKey(i, false);
});
this._menu._menu_tracker._menu_was_activated = false;
this._menu._menu_tracker._highlighted = null;
_highlighted = null;
return;
}
else
{
_menu_keyboard_navigation = true;
if (found.First().ParentItem != null &&
found.First().ParentItem.GetHighlightedChild() != null )
found.First().ParentItem.GetHighlightedChild().
IsHighlighted = false;
found.First().IsHighlighted = true;
if (IsRootOpen == false && _highlighted != found.First())
{
while(_highlighted != found.First())
NavigateMenu(Key.Right);
}
_menu_was_activated = true;
NavigateMenu(IsRootOpen ? Key.Right : Key.Down );
this._menu.Items.Cast<MenuItem>().ToList().ForEach(i =>
{
ActivateAccessKey(i, true);
});
return;
}
}
}
protected void NavigateMenu(Key key)
{
if (_highlighted != null)
{
List<MenuItem> root_items = null;
MenuItem fh;
int index = 0;
switch (key)
{
case Key.Left:
if (_highlighted.IsOpened == false)
{
root_items = _menu.Items.Cast<MenuItem>().ToList();
index = root_items.IndexOf(_highlighted);
if (index == 0)
index = root_items.Count - 1;
else
index = index - 1 >= 0 && index != 0 ? index - 1 : index;
_highlighted.IsHighlighted = false;
_highlighted = root_items[index];
_highlighted.IsHighlighted = true;
}
else
{
if (_highlighted.TopLevel)
{
if (_highlighted.HasOpenedChild)
{
MenuItem last = _highlighted.GetLastOpenedChild();
if (last != null)
{
last.HidePopup();
last.IsHighlighted = true;
_menu_keyboard_navigation = true;
}
}
else
{
root_items = _menu.Items.Cast<MenuItem>().ToList();
index = root_items.IndexOf(_highlighted);
if (index == 0)
index = root_items.Count - 1;
else
index = index - 1 >= 0 &&
index != 0 ? index - 1 : index;
_highlighted.IsHighlighted = false;
CloseAllChildPopups(_highlighted, true);
_highlighted = root_items[index];
_highlighted.IsHighlighted = true;
ShowPopup(_highlighted);
if (_highlighted.Items.Count > 0)
{
_highlighted.Items.Cast
<MenuItem>().First().IsHighlighted = true;
}
}
}
}
break;
case Key.Right:
if (_highlighted.IsOpened == false)
{
root_items = _menu.Items.Cast<MenuItem>().ToList();
index = root_items.IndexOf(_highlighted);
if (index == root_items.Count - 1)
index = 0;
else
index = index + 1
<= root_items.Count ? index + 1 : index;
_highlighted.IsHighlighted = false;
_highlighted = root_items[index];
_highlighted.IsHighlighted = true;
}
else
{
if (_highlighted.TopLevel)
{
if (_menu_keyboard_navigation == false)
{
root_items = _menu.Items.Cast<MenuItem>().ToList();
index = root_items.IndexOf(_highlighted);
if (index == root_items.Count - 1)
index = 0;
else
index = index + 1
<= root_items.Count ? index + 1 : index;
_highlighted.IsHighlighted = false;
CloseAllChildPopups(_highlighted, true);
_highlighted = root_items[index];
_highlighted.IsHighlighted = true;
ShowPopup(_highlighted);
if (_highlighted.Items.Count > 0)
_highlighted.Items.Cast
<MenuItem>().First().IsHighlighted = true;
}
else if (_menu_keyboard_navigation)
{
MenuItem last = _highlighted.GetLastOpenedChild();
MenuItem highlighted = null;
if (last != null)
highlighted = last.GetHighlightedChild();
else
highlighted = _highlighted.GetHighlightedChild();
if (highlighted != null)
{
if (highlighted.Items.Count == 0)
{
root_items = _menu.Items.Cast
<MenuItem>().ToList();
index = root_items.IndexOf(_highlighted);
if (index == root_items.Count - 1)
index = 0;
else
index = index + 1
<= root_items.Count ? index + 1 : index;
_highlighted.IsHighlighted = false;
CloseAllChildPopups(_highlighted, true);
_highlighted = root_items[index];
_highlighted.IsHighlighted = true;
ShowPopup(_highlighted);
if (_highlighted.Items.Count > 0)
_highlighted.Items.Cast
<MenuItem>().First().IsHighlighted = true;
}
else
{
ShowPopup(highlighted);
if (highlighted.Items.Count > 0)
{
highlighted =
highlighted.Items.Cast<MenuItem>().First();
highlighted.IsHighlighted = true;
}
}
}
}
}
}
break;
case Key.Up:
fh = null;
if (_highlighted.HasOpenedChild == false)
root_items = _highlighted.Items.Cast<MenuItem>().ToList();
else
{
fh = _highlighted.GetLastOpenedChild();
root_items = fh.Items.Cast<MenuItem>().ToList();
}
fh = fh == null ? _highlighted.GetHighlightedChild() :
fh.GetHighlightedChild();
again_up:
index = fh != null ? root_items.IndexOf(fh) : 0;
if (fh != null)
{
fh.IsHighlighted = false;
CloseAllChildPopups(fh, false);
}
if (index == 0)
index = root_items.Count - 1;
else if (fh != null)
index = index - 1 >= 0 && index != 0 ? index - 1 : index;
fh = root_items[index];
if (fh.IsSeparator)
goto again_up;
fh.IsHighlighted = true;
_menu_keyboard_navigation = true;
if (_highlighted.IsOpened == false)
ShowPopup(_highlighted);
break;
case Key.Down:
fh = null;
if (_highlighted.HasOpenedChild == false)
root_items = _highlighted.Items.Cast<MenuItem>().ToList();
else
{
fh = _highlighted.GetLastOpenedChild();
root_items = fh.Items.Cast<MenuItem>().ToList();
}
fh = fh == null ? _highlighted.GetHighlightedChild() :
fh.GetHighlightedChild();
again_down:
index = fh != null ? root_items.IndexOf(fh) : 0;
if (fh != null)
{
fh.IsHighlighted = false;
CloseAllChildPopups(fh, false);
}
if (index == root_items.Count - 1)
index = 0;
else if (fh != null)
index = index + 1 <= root_items.Count ? index + 1 : index;
fh = root_items[index];
if (fh.IsSeparator)
goto again_down;
if (_highlighted.IsOpened == false)
ShowPopup(_highlighted);
_menu_keyboard_navigation = true;
fh.IsHighlighted = true;
_highlighted.IsHighlighted = true;
break;
}
}
}
protected bool IsMenuKeyPressed(Key key)
{
ModifierKeys mod = Keyboard.Modifiers;
if (_menu.ActivationKeyGesture != null)
{
if ((_menu.ActivationKeyGesture.Gesture == key &&
mod == _menu.ActivationKeyGesture.ModifierKey))
{
return true;
}
}
return false;
}
protected MenuItem FindItemForGesture(MenuItem item, Key key, ModifierKeys mod)
{
if (item.KeyGesture != null)
{
if (item.KeyGesture.Gesture == key && item.KeyGesture.ModifierKey == mod)
return item;
}
MenuItem g_item = null;
item.Items.Cast<MenuItem>().ToList().ForEach(citem =>
{
if (g_item != null)
return;
g_item = FindItemForGesture(citem, key, mod);
});
return g_item;
}
protected void OnMenu_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (_IsRoot && _highlighted != null)
{
FrameworkElement element = _root;
MenuItem item = _highlighted;
Point screen_item_location =
item.TransformToVisual(element).Transform(new Point(0, 0));
Point screen_mouse_location = item.TransformToVisual(element).Transform(
e.GetPosition(item.HitTestArea));
Rect bounds = new Rect(screen_item_location.X,
screen_item_location.Y, (item as MenuItem).HitTestArea.ActualWidth,
(item as MenuItem).HitTestArea.ActualHeight);
bool is_item_mouse_over = bounds.Contains(screen_mouse_location);
if (is_item_mouse_over == false)
{
CloseAllChildPopups(_highlighted, true);
this._menu.Items.Cast<MenuItem>().ToList().ForEach(i =>
{
ActivateAccessKey(i, false);
});
this._menu._menu_tracker._menu_was_activated = false;
this._menu._menu_tracker._highlighted = null;
_highlighted = null;
return;
}
}
if (_highlighted != null)
{
if (_highlighted.Items.Count > 0)
{
ShowPopup(_highlighted);
}
else if (_highlighted.Items.Count == 0)
{
CloseAllChildPopups(_highlighted, true);
_highlighted.FireClickEvent();
_tracking = false;
_highlighted.IsHighlighted = false;
_menu_was_activated = false;
if (_highlighted.ParentItem != null)
{
MenuItem tmp = _highlighted.ParentItem;
while (tmp != null)
{
tmp.IsHighlighted = false;
tmp._menu_tracker._menu_was_activated = false;
tmp._menu_tracker._highlighted = null;
tmp = tmp.ParentItem;
}
}
this._menu.Items.Cast<MenuItem>().ToList().ForEach(i =>
{
ActivateAccessKey(i, false);
});
this._menu._menu_tracker._menu_was_activated = false;
this._menu._menu_tracker._highlighted = null;
_highlighted = null;
}
}
}
protected void ActivateAccessKey(MenuItem item, bool Activate)
{
item.ActivateAccessKey(Activate);
item.Items.Cast<MenuItem>().ToList().ForEach(i =>
{
ActivateAccessKey(i, Activate);
});
}
protected void HidePopup(MenuItem item)
{
if (item.IsOpened == false)
return;
item.HidePopup();
}
protected void ShowPopup(MenuItem item)
{
if (item.IsOpened || item.Items.Count == 0)
return;
if (item.TopLevel)
{
_menu_was_activated = true;
FrameworkElement element = _root.GetRoot();
Point screen_item_location =
item.TransformToVisual(element).Transform(new Point(0, 0));
Rect bounds = new Rect(screen_item_location.X,
screen_item_location.Y, item.HitTestArea.ActualWidth,
item.HitTestArea.ActualHeight);
item.ShowPopup(new Point(0, bounds.Height - 3));
item.Popup.UpdateLayout();
if ((bounds.Y + bounds.Height - 1 +
item.Popup.ActualHeight) > element.Height)
{
item.HidePopup();
bounds.Y -= (item.Popup.ActualHeight + bounds.Height);
item.ShowPopup(new Point(bounds.X, bounds.Y));
}
}
else
{
item.ParentItem.Items.Cast<MenuItem>().ToList().ForEach(ch =>
{ ch.HidePopup(); });
FrameworkElement element = _root.GetRoot();
Point screen_item_location =
item.TransformToVisual(element).Transform(new Point(0, 0));
Rect bounds = new Rect(screen_item_location.X,
screen_item_location.Y, item.HitTestArea.ActualWidth,
item.HitTestArea.ActualHeight);
item.ShowPopup(new Point(bounds.Width - 1,
1));
}
}
protected Rect GetPopupBounds(System.Windows.Controls.Primitives.Popup popup)
{
Rect bounds = new Rect(0, 0,
(popup.Child as FrameworkElement).ActualWidth,
(popup.Child as FrameworkElement).ActualHeight);
return bounds;
}
public void CloseAllChildPopups(MenuItem item, bool IncludeParent)
{
item.IsHighlighted = false;
if (IncludeParent && item.ParentItem != null)
{
MenuItem tmp = item.ParentItem;
while (tmp != null)
{
tmp.IsHighlighted = false;
tmp.HidePopup();
tmp = tmp.ParentItem;
}
}
item.Items.OfType<MenuItem>().ToList().ForEach(nitem =>
{
nitem.IsHighlighted = false;
nitem.HidePopup();
CloseAllChildPopups(nitem, IncludeParent);
});
}
protected virtual void OnMenu_MouseMove(object sender, MouseEventArgs e)
{
if (_tracking)
{
List<MenuItem> found = new List<MenuItem>
(_owner.InternalItems.Where(item => item.IsMouseOver == false));
found.ForEach(item =>
{
if (_menu_was_activated && item != _highlighted)
item.IsHighlighted = false;
});
(from item in _owner.InternalItems where item.IsMouseOver ==
true select item).ToList().ForEach(item =>
{
if (_highlighted != null && _highlighted != item)
{
bool _top_level_opened = false;
if (item.TopLevel == true)
{
_top_level_opened = _highlighted.IsOpened;
CloseAllChildPopups(_highlighted, false);
}
HidePopup(_highlighted);
_highlighted = item;
item.IsHighlighted = true;
if (_top_level_opened)
ShowPopup(_highlighted);
return;
}
if (_menu_keyboard_navigation && item.ParentItem
!= null && item.ParentItem.GetHighlightedChild() != null)
{
MenuItem hitem = item.ParentItem.GetHighlightedChild();
CloseAllChildPopups(hitem, false);
hitem.IsHighlighted = false;
hitem.HidePopup();
}
_highlighted = item;
item.IsHighlighted = true;
if (item.TopLevel == false)
{
ShowPopup(_highlighted);
}
});
if (_highlighted != null && _menu_was_activated)
{
_highlighted.IsHighlighted = true;
}
}
}
protected virtual void OnMenu_MouseEnter(object sender, MouseEventArgs e){
_tracking = true;
}
protected virtual void OnMenu_MouseLeave(object sender, MouseEventArgs e) {
}
public bool IsMenuActivated
{
get { return _menu_was_activated; }
}
}
The main trick of this object is that when a Menu
class constructor code is executed, it runs the following code to create a MenuTracker
class instance:
Loaded += delegate(object sender, RoutedEventArgs e)
{
_menu_tracker = new MenuTracker(this, this, this.GetRoot(), true);
};
The third parameter for a MenuTracker
class constructor is root element that will catch & handle mouse events such as MouseEnter
, MouseMove
and MouseDown
. That is how root menu items are populated & highlighted. The same way is used to control submenu items popup behaviour. Each MenuItem
class instance contains an instance of a MenuTracker
and the parameter mentioned before is Popup
object of a particular MenuItem
.
The mouse events handlers are hooked in method "HookMenu()
".
private void HookMenu()
{
if (_root != null)
{
_root.MouseEnter += new MouseEventHandler(OnMenu_MouseEnter);
_root.MouseLeave += new MouseEventHandler(OnMenu_MouseLeave);
_root.MouseMove += new MouseEventHandler(OnMenu_MouseMove);
_root.MouseLeftButtonDown += new MouseButtonEventHandler
(OnMenu_MouseLeftButtonDown);
if (_IsRoot)
{
_root.KeyUp += new KeyEventHandler(OnRoot_KeyUp);
_root.KeyDown += new KeyEventHandler(OnRoot_KeyDown);
}
}
}
The Code Usage Sample
<UserControl x:Class="DropDownMenu.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local2="clr-namespace:System.Windows.Controls;assembly=ToolStrips"
xmlns:local="clr-namespace:System.Windows.Controls;assembly=DropDownMenu"
xmlns:s="clr-namespace:System;assembly=mscorlib"
Width="Auto" Height="Auto">
<Grid Background="Gray">
<Border
Width="600"
Height="400"
VerticalAlignment="Center"
HorizontalAlignment="Center"
BorderBrush="#FF084C64"
BorderThickness="3,3,3,3"
CornerRadius="3,3,3,3" >
<Grid x:Name="LayoutRoot" Background="#FF75CEEE">
-->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
-->
<local:Menu>
<local:MenuItem Header="_File">
<local:MenuItem Header="_New">
<local:MenuItem Click="OnMenuClick">
<local:MenuItem.Header>
<StackPanel Margin="-24,0,0,0"
Orientation="Horizontal">
<Image VerticalAlignment="Center"
Width="16" Height="16"
Source="Resources/Images/NewDocumentHS.png"/>
<ContentControl Tag="AccessKey"
Margin="8,0,0,0" Content="_Project"
VerticalAlignment="Center"/>
</StackPanel>
</local:MenuItem.Header>
</local:MenuItem>
<local:MenuItem Click="OnMenuClick" Header="Web Site"/>
<local:MenuItem Click="OnMenuClick" Header="File"/>
<local:MenuItem Click="OnMenuClick"
Header="Project From Existing Code"/>
</local:MenuItem>
<local:MenuItem Header="_Open">
<local:MenuItem KeyGesture="Shift+O" Click="OnMenuClick">
<local:MenuItem.Header>
<StackPanel Margin="-24,0,0,0"
Orientation="Horizontal">
<Image VerticalAlignment="Center"
Width="16" Height="16"
Source="Resources/Images/openHS.png"/>
<TextBlock Margin="8,0,0,0"
Text="Project/Solution"
VerticalAlignment="Center"/>
</StackPanel>
</local:MenuItem.Header>
</local:MenuItem>
<local:MenuItem Click="OnMenuClick" Header="Web Site"/>
<local:MenuItem Click="OnMenuClick" Header="File"/>
<local:MenuItem Click="OnMenuClick" Header="Convert"/>
</local:MenuItem>
<local:MenuItem Header="-"/>
<local:MenuItem Header="Add">
<local:MenuItem Click="OnMenuClick" Header="New Project"/>
<local:MenuItem Click="OnMenuClick" Header="Web Site"/>
<local:MenuItem Header="-"/>
<local:MenuItem Click="OnMenuClick"
Header="Existing Project"/>
<local:MenuItem Click="OnMenuClick"
Header="Existing Web Site"/>
</local:MenuItem>
<local:MenuItem Header="-"/>
<local:MenuItem KeyGesture="Shift+E" Click="OnMenuClick"
Header="Close"/>
<local:MenuItem Header="-"/>
<local:MenuItem Click="OnFullScreenClick" Header="Full _Screen"/>
<local:MenuItem Header="-"/>
<local:MenuItem Click="OnMenuClick" Header="Exit"/>
</local:MenuItem>
<local:MenuItem Header="_Edit">
<local:MenuItem Click="OnMenuClick">
<local:MenuItem.Header>
<StackPanel Margin="-24,0,0,0" Orientation="Horizontal">
<Image VerticalAlignment="Center"
Width="16" Height="16"
Source="Resources/Images/Edit_UndoHS.png"/>
<ContentControl Tag="AccessKey"
Margin="8,0,0,0" Content="_Undo"
VerticalAlignment="Center"/>
</StackPanel>
</local:MenuItem.Header>
</local:MenuItem>
<local:MenuItem Click="OnMenuClick">
<local:MenuItem.Header>
<StackPanel Margin="-24,0,0,0" Orientation="Horizontal">
<Image VerticalAlignment="Center"
Width="16" Height="16"
Source="Resources/Images/Edit_RedoHS.png"/>
<ContentControl Tag="AccessKey"
Margin="8,0,0,0" Content="_Redo"
VerticalAlignment="Center"/>
</StackPanel>
</local:MenuItem.Header>
</local:MenuItem>
<local:MenuItem Header="-"/>
<local:MenuItem KeyGesture="Shift+X" Click="OnMenuClick">
<local:MenuItem.Header>
<StackPanel Margin="-24,0,0,0" Orientation="Horizontal">
<Image VerticalAlignment="Center"
Width="16" Height="16"
Source="Resources/Images/CutHS.png"/>
<TextBlock Margin="8,0,0,0" Text="Cut"
VerticalAlignment="Center"/>
</StackPanel>
</local:MenuItem.Header>
</local:MenuItem>
<local:MenuItem KeyGesture="Shift+C" Click="OnMenuClick">
<local:MenuItem.Header>
<StackPanel Margin="-24,0,0,0" Orientation="Horizontal">
<Image VerticalAlignment="Center"
Width="16" Height="16"
Source="Resources/Images/CopyHS.png"/>
<TextBlock Margin="8,0,0,0"
Text="Copy" VerticalAlignment="Center"/>
</StackPanel>
</local:MenuItem.Header>
</local:MenuItem>
<local:MenuItem KeyGesture="Shift+V" Click="OnMenuClick">
<local:MenuItem.Header>
<StackPanel Margin="-24,0,0,0" Orientation="Horizontal">
<Image VerticalAlignment="Center"
Width="16" Height="16"
Source="Resources/Images/PasteHS.png"/>
<TextBlock Margin="8,0,0,0"
Text="Paste" VerticalAlignment="Center"/>
</StackPanel>
</local:MenuItem.Header>
</local:MenuItem>
</local:MenuItem>
<local:MenuItem Header="_Help">
<local:MenuItem Click="OnMenuClick" Header="About"/>
</local:MenuItem>
<local:MenuItem Header="Deep Items _1">
<local:MenuItem Header="Item 1.1">
<local:MenuItem Header="Item 1.1.1">
<local:MenuItem Header="Item 1.1.1.1"
Click="OnMenuClick"/>
<local:MenuItem Header="Item 1.1.1.2"
Click="OnMenuClick"/>
<local:MenuItem Header="Item 1.1.1.3"
Click="OnMenuClick"/>
</local:MenuItem>
<local:MenuItem Header="Item 1.1.2">
<local:MenuItem Header="Item 1.1.2.1"
Click="OnMenuClick"/>
<local:MenuItem Header="Item 1.1.2.2"
Click="OnMenuClick"/>
<local:MenuItem Header="Item 1.1.2.3"
Click="OnMenuClick"/>
</local:MenuItem>
</local:MenuItem>
<local:MenuItem Header="Item 1.2" Click="OnMenuClick"/>
<local:MenuItem Header="Item 1.3" Click="OnMenuClick"/>
<local:MenuItem Header="Item 1.4" Click="OnMenuClick"/>
</local:MenuItem>
<local:MenuItem Header="Check Boxes">
<local:MenuItem x:Name="view_checked_1" IsChecked="True"
Click="OnMenuClick">
<local:MenuItem.Header>
<StackPanel Margin="-24,0,0,0" Orientation="Horizontal">
<CheckBox IsChecked=
"{Binding ElementName=view_checked_1,
Path=IsChecked, Mode=TwoWay}" Width="16"
Height="16" VerticalAlignment="Center"/>
<ContentControl Tag="AccessKey"
Margin="8,0,0,0" Content="Status _bar"
VerticalAlignment="Center"/>
</StackPanel>
</local:MenuItem.Header>
</local:MenuItem>
<local:MenuItem Header="-"/>
<local:MenuItem x:Name="view_checked_gr_1"
GroupName="RadioButtonGroup_1"
IsChecked="True" Click="OnMenuClick">
<local:MenuItem.Header>
<StackPanel Margin="-24,0,0,0" Orientation="Horizontal">
<RadioButton IsChecked=
"{Binding ElementName=view_checked_gr_1,
Path=IsChecked, Mode=TwoWay}" Width="16"
Height="16" VerticalAlignment="Center"/>
<ContentControl Tag="AccessKey"
Margin="8,0,0,0" Content="Full Mode"
VerticalAlignment="Center"/>
</StackPanel>
</local:MenuItem.Header>
</local:MenuItem>
<local:MenuItem x:Name="view_checked_gr_2"
GroupName="RadioButtonGroup_1"
IsChecked="False" Click="OnMenuClick">
<local:MenuItem.Header>
<StackPanel Margin="-24,0,0,0" Orientation="Horizontal">
<RadioButton IsChecked=
"{Binding ElementName=view_checked_gr_2,
Path=IsChecked, Mode=TwoWay}" Width="16"
Height="16" VerticalAlignment="Center"/>
<ContentControl Tag="AccessKey"
Margin="8,0,0,0" Content="Compact Mode"
VerticalAlignment="Center"/>
</StackPanel>
</local:MenuItem.Header>
</local:MenuItem>
<local:MenuItem x:Name="view_checked_gr_3"
GroupName="RadioButtonGroup_1"
IsChecked="False" Click="OnMenuClick">
<local:MenuItem.Header>
<StackPanel Margin="-24,0,0,0" Orientation="Horizontal">
<RadioButton IsChecked=
"{Binding ElementName=view_checked_gr_3,
Path=IsChecked, Mode=TwoWay}" Width="16"
Height="16" VerticalAlignment="Center"/>
<ContentControl Tag="AccessKey"
Margin="8,0,0,0" Content="Simple Mode"
VerticalAlignment="Center"/>
</StackPanel>
</local:MenuItem.Header>
</local:MenuItem>
</local:MenuItem>
<local:MenuItem Header="Item 3" Click="OnMenuClick">
<local:MenuItem Header="Item 3.1" Click="OnMenuClick"/>
<local:MenuItem Header="Item 3.2" Click="OnMenuClick"/>
<local:MenuItem Header="Item 3.3" Click="OnMenuClick"/>
</local:MenuItem>
<local:MenuItem Header="Item 4" Click="OnMenuClick">
<local:MenuItem Header="Item 4.1" Click="OnMenuClick"/>
<local:MenuItem Header="Item 4.2" Click="OnMenuClick"/>
<local:MenuItem Header="Item 4.3" Click="OnMenuClick"/>
</local:MenuItem>
<local:MenuItem Header="Item 5" Click="OnMenuClick">
<local:MenuItem Header="Item 5.1" Click="OnMenuClick"/>
<local:MenuItem Header="Item 5.2" Click="OnMenuClick"/>
<local:MenuItem Header="Item 5.3" Click="OnMenuClick"/>
</local:MenuItem>
<local:MenuItem Header="Item 6" Click="OnMenuClick">
<local:MenuItem Header="Item 6.1" Click="OnMenuClick"/>
<local:MenuItem Header="Item 6.2" Click="OnMenuClick"/>
<local:MenuItem Header="Item 6.3" Click="OnMenuClick"/>
</local:MenuItem>
<local:MenuItem Header="Item 7" Click="OnMenuClick">
<local:MenuItem Header="Item 7.1" Click="OnMenuClick"/>
<local:MenuItem Header="Item 7.2" Click="OnMenuClick"/>
<local:MenuItem Header="Item 7.3" Click="OnMenuClick"/>
</local:MenuItem>
<local:MenuItem Header="Item 8" Click="OnMenuClick">
<local:MenuItem Header="Item 8.1" Click="OnMenuClick"/>
<local:MenuItem Header="Item 8.2" Click="OnMenuClick"/>
<local:MenuItem Header="Item 8.3" Click="OnMenuClick"/>
</local:MenuItem>
</local:Menu>
</Grid>
</Border>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center"
Foreground="Red" FontSize="16" x:Name="_click_text" Grid.Row="1"
Text="Nothing was clicked!"/>
</Grid>
</UserControl>
All you have to do is put a couple of lines in XAML to see it in action:
<Grid x:Name="LayoutRoot" Background="#FF75CEEE">
-->
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
-->
<local:Menu Grid.Row="0" BorderBrush="#FF117396"
BorderThickness="2,2,2,2" Foreground="#FF1187B1" Margin="0,0,0,0">
<MenuItem Header="File">
<MenuItem Header="Open"/>
</MenuItem>
</local:Menu>
<Grid
Well, I think that's it. If anybody needs help with the code, feel free to e-mail me, or just post a response.