Extending XamGrid with a Right-Click Context Menu

[Infragistics] Devin Rader / Friday, April 15, 2011

In Windows, users expect that right clicking in an app will show a context menu that exposes the actions they can take for a particular UI element.  Extending XamGrid to add the ability to show a right-click context menu is fairly simple.  In this blog, I will walk you through extending XamGrid with the XamContextMenu control to easily add a right-click context menu that shows when users right-click on cells in your applications grid.

Deriving XamGridEx

To start, I need to first create a derived version of XamGrid, which I’ll call XamGridEx.  Deriving allows me to add a single new property called ContextMenuSettings which I’ll used to expose a ContextMenuSettings object to support the configuration of the context menu.  The code below shows my derivation of XamGrid:

public class XamGridEx : XamGrid 
{ 
    ContextMenuSettings _contextMenuSettings;

    public ContextMenuSettings ContextMenuSettings 
    { 
        get 
        { 
            if (this._contextMenuSettings == null) 
            { 
                this._contextMenuSettings = 
                    new ContextMenuSettings(); 
                this._contextMenuSettings.Grid = this; 
            }

            return this._contextMenuSettings; 
        } 
        set 
        { 
            if (value != this._contextMenuSettings) 
            { 
                this._contextMenuSettings = value; 
                this._contextMenuSettings.Grid = this;

                this.OnPropertyChanged("ContextMenuSettings"); 
            } 
        } 
    } 
} 

Creating ContextMenuSettings

Next I’ll create a ContextMenuSettings class which contains the properties that allow me to configure the right-click context menu on my grid and the logic that wires it to the XamGridEx control.  Using a Settings class follows an existing API pattern of the XamGrid and allows me to isolate the properties together on a single object.

To create the ContextMenuSettings class, I derive a new class from SettingsBase:

public class ContextMenuSettings : SettingsBase
{       
    private ContextMenuManager _contextMenuMgr;

    public XamContextMenu ContextMenu { get; set; }

    public bool AllowContextMenu
    {
        get { return (bool)GetValue(AllowContextMenuProperty); }
        set { SetValue(AllowContextMenuProperty, value); }
    }

    public static readonly DependencyProperty AllowContextMenuProperty =
        DependencyProperty.Register("AllowContextMenu", 
            typeof(bool), typeof(ContextMenuSettings), 
                new PropertyMetadata(false, 
                    new PropertyChangedCallback(AllowContextMenuChanged)));

    private static void AllowContextMenuChanged(DependencyObject obj, 
        DependencyPropertyChangedEventArgs e)
    {
        ContextMenuSettings settings = (ContextMenuSettings)obj;
        settings.OnAllowContextMenuChanged((bool)e.NewValue);
    }

    internal virtual void OnAllowContextMenuChanged(bool value)
    {
    }

    ///  /// Hack to get around the fact that the SettingsBase.Grid /// property setter is internal /// 
    public XamGrid Grid
    {
        get { return base.Grid; }
        set { base.Grid = value; }
    }
}

My ContextMenuSettings class includes two configuration properties.  The public ContextMenu property allows me to provide the XamContextMenu control I want to use as the context menu to the grid and the AllowContextMenu property allows me to enable or disable that context menu.

Now that I’ve created the public properties, I can create the internal logic to attach/detach the context menu to/from the grid and check to make sure the user clicked on a CellControl, which is the only UI element in the grid I want to be a valid context menu source. 

To connect the context menu to grid I’ve created a method called AttachContextMenu. 

private void AttachContextMenu()
{
    if (_contextMenuMgr == null)
    {
        _contextMenuMgr = new ContextMenuManager();
        _contextMenuMgr.ContextMenu = this.ContextMenu;
        _contextMenuMgr.ContextMenu.Opening += 
            new EventHandler(ContextMenuOpening); _contextMenuMgr.ModifierKeys = ModifierKeys.None; _contextMenuMgr.OpenMode = OpenMode.RightClick; ContextMenuService.SetManager(this.Grid, _contextMenuMgr); } }

This method creates a new ContextMenuManager, assigns my ContextMenu control and wires up the menus Opening event to a method called ContextMenuOpening.

A corresponding method called DetachContextMenu is used to unwire the event and attached property.

private void DetachContextMenu()
{
    if (_contextMenuMgr != null)
    {
        if (_contextMenuMgr.ContextMenu!=null)
            _contextMenuMgr.ContextMenu.Opening 
                -= new EventHandler(ContextMenuOpening); ContextMenuService.SetManager(this.Grid, null); } _contextMenuMgr = null; }

The Attach and Detach methods are called from a helper method called ResetContextMenuSettings, which itself is called on an as-needed basis as the state of the settings object changes.

Finally, the XamContextMenus ContextMenuOpening event handler is used to check to make sure the user clicked on a CellControl.

private void ContextMenuOpening(object sender, OpeningEventArgs e)
{
    List elements = e.GetClickedElements(); if (elements.Count == 0) { e.Cancel = true; } }

Simple Context Menu Sample

To use the new context menu, I simply create a XamGridEx control and use the ContextMenuSettings object to enable and define the context menu I want to appear.

                  

Running this sample, I can now right click on cells in my grid and see a context menu appear.

 

Changing the Menu at Runtime

Now that I have a basic context menu showing, I want to also be able to manipulate the menu items at runtime based on the value of the cell that is clicked.  To do this I’ll add a new ContextMenuOpening event to my derived XamGridEx.

public delegate void OpeningEventHandler(object sender, 
                        ContextMenuOpeningEventArgs e);

public event OpeningEventHandler ContextMenuOpening;

The event uses the OpeningEventHander from the XamMenu control, but changes the event args from the MenuOpeningEventArgs to the new ContextMenuOpeningEventArgs object.  This new event args object allows me to add an additional property containing the grid cell to the arguments.

public class ContextMenuOpeningEventArgs 
{
    public bool Cancel { get; set; }
    public CellBase Cell { get; set; }
    public XamContextMenu Menu { get; internal set; }
    public Point MouseClickLocation { get; set; }
}

In my derived grid I can add a OnContextMenuOpening method which fires the ContextMenuOpening event:

internal void OnContextMenuOpening(object sender, ContextMenuOpeningEventArgs e)
{
    if (ContextMenuOpening != null)
    {
        ContextMenuOpening(sender, e);
    }
}

I also need to alter the ContextMenuOpening method in ContextMenuSettings to call the OnContextMenuOpening method:

private void ContextMenuOpening(object sender, OpeningEventArgs e)
{
    List elements = e.GetClickedElements(); if (elements.Count == 0) { e.Cancel = true; } //Hack: I have to manually transfer the menu args to my eventargs ContextMenuOpeningEventArgs args = new ContextMenuOpeningEventArgs(); args.Cancel = e.Cancel; args.MouseClickLocation = e.MouseClickLocation; args.Cell = elements.FirstOrDefault().Cell; args.Menu = _contextMenuMgr.ContextMenu; this.Grid.OnContextMenuOpening(this, args); if (args.Cancel) { e.Cancel = true; } }

Now in my application I can use the grid ContextMenuOpening event to manipulate the menu based on the clicked Cell:

private void XamGridEx_ContextMenuOpening(object sender, 
                             ContextMenuOpeningEventArgs e)
{
    if (e.Cell.Value.ToString().ToUpper().StartsWith("B"))
    {
        return;
    }

    if (e.Cell.Value.ToString().ToUpper().StartsWith("A"))
    {
        ((XamMenuItem)e.Menu.Items[1]).IsEnabled = false;
        return;
    }

    ((XamMenuItem)e.Menu.Items[1]).IsEnabled = true;
    e.Cancel = true;
    
}

Summary

Context Menus are a great way to add contextual commands to your application, and in the Windows world are a common expectation of users.  Adding a context menu that allows you to provide contextual commands to cells in the XamGrid is fairly easy by deriving a new grid control and leveraging the XamContextMenu.  The source code for this post can be found in the attachments.

XamGridContextMenuDemo.zip