user6384353
user6384353

Reputation: 13

WPF: How to loop through ContextMenu to rearrange the items in it or add a menuitem as a subitem to Menu item

I have a ContextMenu with the following items My current Contextmenu My current Contextmenu

However I want it in this format My requirement
enter image description here

This is how far I have got

 foreach (var menuItem in oMenuItemList)
 {
      var cContextMenuItem = menuItem as MenuItem;
      if (cContextMenuItem != null)
      {
          _oContextMenu.Items.Add(menuItem);
          if (cContextMenuItem.Header.ToString() == "1.1")
          {
               MenuItem newMenuItem2 = new MenuItem();
               MenuItem newExistMenuItem = (MenuItem)this._oContextMenu.Items[0];

               newExistMenuItem.Items.Add(newMenuItem2);

               _oContextMenu.Items.Add(newExistMenuItem);
            }
        }
   }
 _oContextMenu.ItemsSource = oMenuItemList;

Updated code

IEnumerable oMenuItemList = null;
oMenuItemList = GetContextMenuItems(sMenuItems);

//the function
private List<object> GetContextMenuItems(string sMenuItems)
{
 //returns the list
}

_oContextMenu.ItemsSource = oMenuItemList;

After this Im trying to manipulate the _oContextMenu.

Upvotes: 1

Views: 1921

Answers (1)

Sam
Sam

Reputation: 1424

Here is example for your exact images:

Xaml:

<ContextMenu Opened="ContextMenu_OnOpened">
    <MenuItem Header="1.0" />
    <MenuItem Header="1.1" />
    <MenuItem Header="1.2" />
    <MenuItem Header="2.0" />
    <MenuItem Header="3.0" />
</ContextMenu>

Code-behind:

private void ContextMenu_OnOpened(object sender, RoutedEventArgs e)
{
    var contextMenu = (ContextMenu)sender;

    var firstItem = (MenuItem)contextMenu.Items[0];

    // Must create new array of existing menu items
    // to be able to edit existing context menu during loop enumeration.
    var allItems = contextMenu.Items.Cast<MenuItem>().ToArray();

    foreach (var item in allItems)
    {
        if (item.Header.ToString() == "1.1" || item.Header.ToString() == "1.2")
        {
            // Add menu item with the same header into another item as sub-item
            firstItem.Items.Add(new MenuItem { Header = item.Header });

            // Remove item from root level.
            contextMenu.Items.Remove(item);
        }
    }
}

Here I used conditions for specific headers text ("1.1" and "1.2") and put items inside first menu item, but you can implement any logic you need in your particular program.

Opened event triggers every time context menu opened, so you can make any dynamic changes that required in current moment of time.

Update1

Below is example when ItemsSource binding involved. Advantage is that you can interact just with MenuItemsCollection and don't worry about ContextMenu object itself, its visual items and other details.

Xaml:

<TextBox>
    <TextBox.ContextMenu>
        <ContextMenu Opened="ContextMenu_OnOpened" ItemsSource="{Binding MenuItemsCollection}" />
    </TextBox.ContextMenu>
</TextBox>

Code-behind:

public ObservableCollection<MenuItem> MenuItemsCollection { get; set; } = new ObservableCollection<MenuItem>
{
    new MenuItem { Header = "1.0" },
    new MenuItem { Header = "1.1" },
    new MenuItem { Header = "1.2" },
    new MenuItem { Header = "2.0" },
    new MenuItem { Header = "3.0" },
};

private void ContextMenu_OnOpened(object sender, RoutedEventArgs e)
{
    var firstItem = this.MenuItemsCollection[0];

    var allItems = this.MenuItemsCollection.ToArray();

    foreach (var item in allItems)
    {
        if (item.Header.ToString() == "1.1" || item.Header.ToString() == "1.2")
        {
            firstItem.Items.Add(item);
            this.MenuItemsCollection.Remove(item);
        }
    }
}

Update2

If you want assign new items list every time directly to ContextMenu element:

private void ContextMenu_OnOpened(object sender, RoutedEventArgs e)
{
    // var actualItems = GetListOfItemsAnyWayYouNeed();
    var actualItems = new List<MenuItem>
    {
        new MenuItem {
            Header = "1.0",
            Items =
            {
                new MenuItem { Header = "1.1" },
                new MenuItem { Header = "1.2" } }
            },
        new MenuItem { Header = "2.0" },
        new MenuItem { Header = "3.0" },
    };

    _oContextMenu.ItemsSource = actualItems;
}

Conclusion

After setting value to ItemsSource, you can not more manipulate the _oContextMenu, at least its Items property - you will get error from your first comment. That means you can't do things like in my original answer, because you assign directly to ItemsSource.

You try to set oMenuItemListdirectly to _oContextMenu.ItemsSource(similar to things shown in Update2), but after that you can't manipulate _oContextMenu.Items. You can only manipulate your oMenuItemList (if you save it in global variable), but it will not change actual menu content because your list is not ObservableCollection.

If you still want to do all things in code, try this:

ObservableCollection<object> oMenuItemList { get; set; }

...

oMenuItemList = GetContextMenuItems(sMenuItems);

//the function
private ObservableCollection<object> GetContextMenuItems(string sMenuItems)
{
    //returns the ObservableCollection
}

_oContextMenu.ItemsSource = oMenuItemList;

And any changes you will make in oMenuItemList (but not in _oContextMenu.Items) will applied in actual context menu.

But anyway the best approach is still to do like described in Update1.

Upvotes: 2

Related Questions