subprime
subprime

Reputation: 1215

Merging ToolStripMenuItems in one MenuStrip from different MEF Plugins

Currently I try to merge different ToolStripMenuItems into one MenuStrip. Therefor i've created an Interface called IMenu. This include a ToolStripMenuItem from the MEF Plugin and should be load into the Main-Window-Menustrip. The Problem is that if i run the application i get in the MenuStrip two DropDown elements with the same name. But what i want is that i can bind the menu of the MEF plugin into the MenuStrip of the application without double entries e.g. How can i make a good menu structure for MEF Plugins?

Example: I have from the main application this entries

File
|--> New
|--> Save
|--> Import
|--> Export

And then i create a plugin for Import / Export a specific type. Therefor i must add into the menu dynamicly entries under Import / Export. But how? How is your solution?

File
|--> New
|--> Save
|--> Import
|-----> To Word
|--> Export
|-----> From Word

Here my code: First the interface for the Plugin

public interface IMenu
{
  ToolStripMenuItem ToolStripItem { get; }
}

This is a plugin example for menu item

Help
|--> Update

[Export(typeof(IMenu))]
class UpdateMenuItems : IMenu
{
  System.Windows.Forms.ToolStripMenuItem helpMenuItem;
  System.Windows.Forms.ToolStripMenuItem updateMenuItem;

  public System.Windows.Forms.ToolStripMenuItem ToolStripItem
  {
    get { return helpMenuItem; }
  }

  public UpdateMenuItems()
  {
    InitializeComponent();
  }

  private void InitializeComponent()
  {
    this.updateMenuItem = new System.Windows.Forms.ToolStripMenuItem();
    this.updateMenuItem.Name = "Update";
    this.updateMenuItem.Size = new System.Drawing.Size(94, 20);
    this.updateMenuItem.Text = "Update";
    this.updateMenuItem.MergeIndex = 1;
    this.updateMenuItem.MergeAction = System.Windows.Forms.MergeAction.Insert;
    this.updateMenuItem.Click += updateMenuItem_Click;

    this.helpMenuItem = new System.Windows.Forms.ToolStripMenuItem();
    this.helpMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
          this.updateMenuItem});
    this.helpMenuItem.Name = "aboutToolStripMenuItem";
    this.helpMenuItem.Size = new System.Drawing.Size(44, 20);
    this.helpMenuItem.MergeAction = System.Windows.Forms.MergeAction.Insert;
    this.helpMenuItem.Text = "Help";

  }

  void updateMenuItem_Click(object sender, EventArgs e)
  {
    UpdateController update = new UpdateController();
    update.Execute();
  }    

This is another MEF Plugin which also has the root element "Help"

[Export(typeof(IMenu))]
class MenuItem : IMenu
{
  public ToolStripMenuItem ToolStripItem
  {
    get { return testItem; }
  }

  private System.Windows.Forms.ToolStripMenuItem testItem;
  private System.Windows.Forms.ToolStripMenuItem unterpunk1;
  private System.Windows.Forms.ToolStripMenuItem unterpunk2;

  public MenuItem()
  {
    InitializeComponent();
  }

  public void InitializeComponent()
  {
    this.testItem = new System.Windows.Forms.ToolStripMenuItem();
    this.testItem.Name = "aboutToolStripMenuItem";
    this.testItem.Size = new System.Drawing.Size(94, 20);
    this.testItem.Text = "Help";
    this.testItem.Click += testItem_Click;

    unterpunk1 = new ToolStripMenuItem();
    unterpunk1.Text = "Speichern";
    unterpunk1.MergeAction = MergeAction.Insert;
    unterpunk1.MergeIndex = 1;
    testItem.DropDownItems.Add(unterpunk1);

    unterpunk2 = new ToolStripMenuItem();
    unterpunk2.Text = "Prüfen";
    unterpunk2.MergeAction = MergeAction.Insert;
    unterpunk2.MergeIndex = 2;
    testItem.DropDownItems.Add(unterpunk2);

  }

  void testItem_Click(object sender, EventArgs e)
  {
    //
  }

In my main form i add all to the MenuStrip.

  foreach (var item in MenuItems)
  {
    this.menuStrip1.Items.Add(item.ToolStripItem);
  }

The ToolStripMenuItems don't merge automatically?

Upvotes: 2

Views: 2874

Answers (2)

Mike Fuchs
Mike Fuchs

Reputation: 12319

I doubt very much that there is an automatic feature that would do that, so here is how I'd approach the manual solution. There is one pitfall, which is that a ToolStripItem can at any time only have one parent. So adding it to the existing menu will remove it from the other, you need to take that into account (done in my method by checking if the item already had an owner). If that is a problem, you would need to clone the subitem first.

EDIT: I realized you will probably need a recursive function, so I replaced the code. Class MenuItem2 contains the Help/Speichern/Save As/About items.

public Form1()
{
    InitializeComponent();

    List<IMenu> menuItems = new List<IMenu>() { new UpdateMenuItems(), new MenuItem(), new MenuItem2() };
    MergeMenus(this.menuStrip1.Items, menuItems.Select(m => m.ToolStripItem));
}

/// <summary>
/// Recursive function that merges two ToolStripItem trees into one
/// </summary>
/// <param name="existingItems">Collection of existing ToolStripItems</param>
/// <param name="newItems">Collection of new ToolStripItems. IEnumerable instead of ToolStripItemCollection to allow for items without an owner</param>
private void MergeMenus(ToolStripItemCollection existingItems, IEnumerable<ToolStripItem> newItems)
{
    int count = newItems.Count();
    int removedFromCollection = 0; // keep track of items that are removed from the newItems collection 
    for (int i = 0; i < count; i++)
    {
        ToolStripItem newItem = newItems.ElementAt(i - removedFromCollection);
        bool merged = false;
        string key = newItem.Name; // the items are identified and compared by its name
        if (existingItems.ContainsKey(key))
        {
            ToolStripItem existingItem = existingItems[key];
            if (existingItem != null && existingItem.GetType().Equals(newItem.GetType()))
            {
                // check if the matching items are ToolStripMenuItems. if so, merge their children recursively
                if (newItem is ToolStripMenuItem)
                    MergeMenus(((ToolStripMenuItem)existingItem).DropDownItems, ((ToolStripMenuItem)newItem).DropDownItems.Cast<ToolStripItem>());

                // do not add this particular item (existing item with same name and type found)
                merged = true;
            }
        }

        if (!merged) // newItem does not exist in existingItems (or not as the same type)
        {
            // if there was an owner, the item will be removed from the collection and the next element's index needs to be adjusted
            if (newItem.Owner != null) 
                removedFromCollection++;

            // add item to the existing tree
            existingItems.Add(newItem);
        }
    }
}

Tested and seems to work:

enter image description here

Upvotes: 3

Alan
Alan

Reputation: 7951

I implemented a Main Menu, using MVVM and Unity through an IMenuService. I posted a rather complete answer to my own question on this same topic (but for menus instead of toolstrips). I think you can do something extremely similar for a ToolStrip to what I did in my code.

Please see the following answer:

DataTemplate to generate Menu with MVVM

Upvotes: 2

Related Questions