I want to be able to display a context menù or something else when my mouse is over a series of specified framework element on a given Silverlight Application; keep in mind we do not have support for mouse right click event due to a series of reasons that goes from difficulty of implementation on a cross browser platform to the fact that Mac users generally do not have it.

So we need to think of a little different approach and think of a UI element that can be used to display the menù or whatever we want. The idea I had was to Highlight the control (or framework element) that is under the mouse and display a clickable anchor on the top-right corner of the bounding box surrounding the control.

The anchor can then be clicked to display the menù. The effect I want to obtain is diplayed in the following picture, a blue bounding box surrounding the element and a button to use as anchor.


The idea is quite simple, we need an external Canvas that will contain our main page and controls, we will dynamically create a Border and a Button that will be used for the Highlight and for the Anchor, these controls will be added to the canvas and will be resized and repositioned at runtime to fit over the control we have under the mouse. Also we will set the Z-Index of the controls to be greater of the control we want to 'extend', this way they will be displayed on top.

To diplay and remove the highlight we subscribe to the MouseEnter event of the control we want to extend and to the MouseLeave event of the bounding box (Border) used to Highlight the control.

All these function will be wrapped in an Helper class that will be instantiated on the main page, it will also have an AddContextMenu function that will be called for any FrameworkElement we want to extend with a context menu. The class is quite straightforward to implement and doesn't need too many comments, so here it is:

public class ContextMenuExtender { private Canvas _canvas = null; private Dictionary<FrameworkElement, ContextMenuAnchor> _anchors = new Dictionary<FrameworkElement, ContextMenuAnchor>(); private FrameworkElement _current = null; /// <summary> /// the external canvas that will be used to hold the controls /// </summary> /// <param name="canvas"></param> public ContextMenuExtender(Canvas canvas) { _canvas = canvas; } /// <summary> /// extends the framework element /// </summary> /// <param name="ctrl"></param> /// <param name="click"></param> public void AddContextMenu(FrameworkElement ctrl, RoutedEventHandler click) { if (!_anchors.ContainsKey(ctrl)) { ctrl.MouseEnter += new MouseEventHandler(_ctrl_MouseEnter); ContextMenuAnchor anchor = new ContextMenuAnchor(); _anchors.Add(ctrl, anchor); anchor.button.Click += click; anchor.button.Click += new RoutedEventHandler(button_Click); anchor.border.MouseLeave += new MouseEventHandler(b_MouseLeave); } } void button_Click(object sender, RoutedEventArgs e) { RemoveHighlight(sender as Button); } void _ctrl_MouseEnter(object sender, MouseEventArgs e) { HighlightControl(sender as FrameworkElement); } /// <summary> /// reposition the border and the anchor over the control /// </summary> /// <param name="ctrl"></param> void HighlightControl(FrameworkElement ctrl) { //this is a patch to have only 1 element highlited at a time, // sometimes if you move the mouse //too fast, silverlight misses to raise the MouseLeave event. if ((_current != null) && (_current != ctrl)) RemoveHighlight(_current); _current = ctrl; ContextMenuAnchor anchor = _anchors[ctrl]; if (anchor != null) { Button btn = anchor.button; Border b = anchor.border; if (!_canvas.Children.Contains(b)) { GeneralTransform gt = ctrl.TransformToVisual(_canvas); Point p = gt.Transform(new Point(0, 0)); b.Width = ctrl.ActualWidth; b.Height = ctrl.ActualHeight; b.SetValue(Canvas.TopProperty, p.Y); b.SetValue(Canvas.LeftProperty, p.X); _canvas.Children.Add(b); Canvas.SetZIndex(b, Canvas.GetZIndex(ctrl) + 1); btn.SetValue(Canvas.TopProperty, p.Y); btn.SetValue(Canvas.LeftProperty, p.X + ctrl.ActualWidth - btn.Width); _canvas.Children.Add(btn); Canvas.SetZIndex(btn, Canvas.GetZIndex(ctrl) + 1); } } } void b_MouseLeave(object sender, MouseEventArgs e) { RemoveHighlight(sender as Border); } #region RemoveHighlight functions void RemoveHighlight(Border ctrl) { ContextMenuAnchor anchor = (from r in _anchors where r.Value.border == ctrl select r.Value).Single(); RemoveHighlight(anchor); } void RemoveHighlight(Button ctrl) { ContextMenuAnchor anchor = (from r in _anchors where r.Value.button == ctrl select r.Value).Single(); RemoveHighlight(anchor); } void RemoveHighlight(FrameworkElement ctrl) { RemoveHighlight(_anchors[ctrl]); } void RemoveHighlight(ContextMenuAnchor anchor) { _canvas.Children.Remove(anchor.border); _canvas.Children.Remove(anchor.button); } #endregion /// <summary> /// this class will contain out anchor and highlight controls /// </summary> private class ContextMenuAnchor { public Border border = new Border(); public Button button = new Button(); public ContextMenuAnchor() { button.Width = 10; button.Height = 10; border.BorderBrush = new SolidColorBrush(Colors.Blue); border.BorderThickness = new Thickness(1); } } }


The class can be used like follows:

<UserControl x:Class="ContextMenuExtender.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <Canvas x:Name="surface"> <StackPanel x:Name="LayoutRoot" Background="White" Margin="10"> <StackPanel Orientation="Horizontal"> <TextBlock Margin="0,0,10,0">TEST</TextBlock> ...


public partial class Page : UserControl { public Page() { InitializeComponent(); ContextMenuExtender cm = new ContextMenuExtender(surface); cm.AddContextMenu(txt, btn_Click); cm.AddContextMenu(ell, btn_Click); cm.AddContextMenu(txt2, btn_Click); cm.AddContextMenu(ell2, btn_Click); } ...


The example is far to be a complete reusable control (it misses skinning and customization options for the highlight and the anchor button), but can be used as a stating point for your ContextMenuExtender.

Attached to this article there's a complete working example:

Technorati Tag: ,

Related Content