0

I need to know when a tool strip menu item is about to be shown (in order to create the content on a JIT basis).

I have a potentially very large hierarchy of stuff. It can be navigated in a treeview, but also via "breadcrumbs", e.g. "Music > Pop > Michael Jackson > Bad". The breadcrumbs are implemented by a toolstrip, and each level is presented with a LinkLabel. When clicking a link, e.g. "Music", a context menu is dynamically built with all the siblings of the current item.

I am trying to create the illusion that the entire hierarchy is always loaded. When I fetch data, I make sure to get enough information to present a node AND to know whether that node has children. If it does have children, I create a dummy node with the special meaning "this item has unloaded children" and put it as the only child. This causes the treeview to render the node as expandable. I then use the BeforeExpand event of the treeview to fetch the next level and update the nodes collection just in time, when it's actually going to be displayed.

This works well for the tree, but I need to do the same thing for the breadcrumbs-style context menus. I already create a sub-menu for items that have children, putting my special "unloaded children" item in there. The problem is I can't find an event that lets me know when that sub-menu is about to be displayed. This can happen by hovering, clicking, keyboard navigation and perhaps more; I really don't want to hack it if there is a more appropriate hook I just haven't found..

In other words, what I need is something equivalent to "BeforeExpand" but for ToolStripMenuItem (the event btw ought to be called "NodeExpanding" according to MS' own naming guidelines, which say the gerund form ("ing") should be used rather than the "Before" prefix, and the past tense ("Expanded") rather than the "After" prefix).

I hope it's clear what I'm trying to do, and that someone knows how to solve this!

The Dag
  • 1,811
  • 16
  • 22

1 Answers1

0

It turns out I can use the Paint event. I really didn't think it would work, but it does.

If we use the terms "item" for ToolStripMenuItem and "node" for a logical node in the hierarchy, we can pseudocode (actually C# code from my PoC-project) the Paint handler as:

void menuItem_Paint(object sender, PaintEventArgs e)
{
    var item = (ToolStripMenuItem)sender;

    var node = item.Tag as Node;
    if (node == null || node.Parent == null || node.Parent.RealChildren)
        return;

    node = node.Parent;
    item = FindMenuItem(menu.Items, node);

    if (node != null)
        ReplaceDummyWithData(node, item);
}

void ReplaceDummyWithData(Node node, ToolStripMenuItem item)
{
    Dbug("Removing dummy: {0}", node.Label);

    // Modify logical tree..
    node.Remove(node.Children[0]);
    MakeNode(node, "Data #1");
    MakeNode(node, "Data #2");

    // Rebuild menu items..
    item.DropDownItems.Clear();
    AddMenuItems(item.DropDownItems, node.Children);
    node.RealChildren = true;
}

I really didn't think this would work, but it seems to work fine. I have just hard-coded an infinitely-deep hierarchy where every item has two children labelled "Data #1" and "Data #2", but it's not difficult to modify this so items are created based on real data. The point was to figure out a way to modify the tree of menu items just-in-time, and this appears to be an acceptable means to that end.

FindMenuItem is a recursive search for the item tagged with the given node,

ToolStripMenuItem FindMenuItem(ToolStripItemCollection collection, Node node)
{
    foreach (ToolStripMenuItem item in collection)
    {
        if (item.Tag == node)
            return item;

        var found = FindMenuItem(item.DropDownItems, node);
        if (found != null) 
            return found;
    }
    return null;
}

and MakeNode in the PoC always creates a dummy child, though in the data-driven thing I am going to do so based on whether it actually has children:

Node MakeNode(Node parent, string label)
{
    var n = new Node(parent) { Label = label, RealChildren = false };
    new Node(n) { Label = "dummy" };
    return n;
}

The Node constructor takes the parent node; not the most readable code perhaps. It's from my PoC and the purpose of the code was to figure out a solution to my problem, not to write code that is great in other respects!

The Dag
  • 1,811
  • 16
  • 22