Hi, I'm looking at implementing a dynamic list of IDocuments on the 'Window' menu. Much like Visual Studio's Window menu displaying a list of open documents with the current document checked.

Looking at the IToolBar example included this uses MEF to import the available toolbars, which is essentially static.

Is there a way to hook into a IMenuItem's popup event? How would others implement this?

I tried using an EventSetter in the workbench XAML on the SubmenuOpened event like so:

<EventSetter Event="MenuItem.SubmenuOpened" 
    Handler="{Binding Path=(menu:IMenuItem.OnSubMenuOpened)}" />

I was then going to add code to dynamically populate the IMenuItem.Items in an overridden "OnSubMenuOpened" method.

Cheers, Dave

asked 25 Aug '10, 06:00

dhm's gravatar image

dhm
46117
accept rate: 100%

edited 11 Sep '10, 20:30

Scott%20Whitlock's gravatar image

Scott Whitlock ♦♦
696262833


Ok here goes; I've managed to get it working although I've not implemented the click-handler functionality yet. Active document is checked when the menu is displayed though.

IMenuItem:
  void OnSubMenuOpened(Object sender, RoutedEventArgs e);
  void OnSubMenuClosed(Object sender, RoutedEventArgs e);

AbstractMenuItem:
    public virtual void OnSubMenuOpened(Object sender, RoutedEventArgs e)
    {
        // Override this in subclasses to have code executed when the
        // items submenu are to be displayed.
    }

    public virtual void OnSubMenuClosed(Object sender, RoutedEventArgs e)
    {
        // Override this in subclasses to have code executed when the
        // items submenu is closed.
    }

Then in the WindowMenu:

    public override void OnSubMenuOpened(Object sender, RoutedEventArgs e)
    {
        List<IMenuItem> docItems = new List<IMenuItem>();
        String previousId = WindowSep2MenuItem.GUID;
        foreach (IDocument document in LayoutManager.Documents)
        {
            DocumentMenuItem docMi = new DocumentMenuItem(document, previousId, LayoutManager);
            previousId = docMi.ID;
            docItems.Add(docMi);
        }
        docItems.AddRange(items);

        MenuItem mi = (sender as MenuItem);
        mi.ItemsSource = null;
        this.Items = ExtensionService.Sort(docItems);
        mi.ItemsSource = this.Items;
    }

The 'DocumentMenuItem' class is very simple; consisting of the constructor which sets up the Header based on the document title, its ID, relative ID and direction so that they appear in order. This is why the previous ID is passed in; IDs could be autogenerated or based on IDocument property as you see fit.

And finally to update the WorkbenchView, in WorkbenchView.xaml:

    <EventSetter Event="MenuItem.SubmenuOpened" Handler="OnSubMenuOpened" />
    <EventSetter Event="MenuItem.SubmenuClosed" Handler="OnSubMenuClosed" />

In WorkbenchView.xaml.cs:

    private void OnSubMenuOpened(Object sender, EventArgs e)
    {
        MenuItem mi = (sender as MenuItem);
        IMenuItem miContext = (mi.DataContext as IMenuItem);

        miContext.OnSubMenuOpened(sender, new RoutedEventArgs());
    }

    private void OnSubMenuClosed(Object sender, EventArgs e)
    {
        MenuItem mi = (sender as MenuItem);
        IMenuItem miContext = (mi.DataContext as IMenuItem);

        miContext.OnSubMenuClosed(sender, new RoutedEventArgs());
    }

I also added a method to the LayoutManager to determine if a specified IDocument was the active document:

    /// <summary>
    /// Determine whether the specified IDocument is active.
    /// </summary>
    /// <param name="document"></param>
    /// <returns></returns>
    public bool IsActive(IDocument document)
    {
        return (ActiveDocument() == document);
    }

Hopefully that is clear enough. Not sure whether overridding the binding is the best approach so I welcome any feedback.

Cheers,

Dave

link

answered 25 Aug '10, 10:53

dhm's gravatar image

dhm
46117
accept rate: 100%

edited 25 Aug '10, 10:56

Looks pretty slick. I'll probably put that into the next version. Maybe I'll even include it in my app that I'm building on top of SoapBox Core. Thanks! :)

(25 Aug '10, 12:10) Scott Whitlock ♦♦

Cool, no worries. Hopefully I'll add more features as I require them. I thought overridding the ItemsSource a little nasty, not sure if there is a better way to force a binding update?

(25 Aug '10, 12:47) dhm

I'm implementing File->Recently Opened menuitem. I need to make Recently Opened a dynamic list of items. Dave's patch is working great! Thanks Dave. Is this a planned update? Should I make a formal feature request?

(09 Sep '10, 11:03) BSalita

@BSalita: I tagged this question with feature-request, so I'll get it in there. :)

(10 Sep '10, 09:06) Scott Whitlock ♦♦

FWIW, here's how the new WindowMenu class works to implement this feature. It uses the new OnIsSubmenuOpenChanged virtual method to build the sub menu dynamically, similar to dhm's method, but it avoids having the ViewModel know about the View:

[Export(ExtensionPoints.Workbench.MainMenu.Self, typeof(IMenuItem))]
class WindowMenu : AbstractMenuItem, IPartImportsSatisfiedNotification
{
    public WindowMenu()
    {
        ID = Extensions.Workbench.MainMenu.Window;
        Header = Resources.Strings.Workbench_MainMenu_Window;
        InsertRelativeToID = Extensions.Workbench.MainMenu.Tools;
        BeforeOrAfter = RelativeDirection.After;
    }

    [Import(Services.Host.ExtensionService)]
    private IExtensionService extensionService { get; set; }

    [Import(Services.Layout.LayoutManager)]
    private ILayoutManager layoutManager { get; set; }

    [ImportMany(ExtensionPoints.Workbench.MainMenu.WindowMenu, 
        typeof(IMenuItem), AllowRecomposition = true)] 
    private IEnumerable<IMenuItem> menu { get; set; }

    public void OnImportsSatisfied()
    {
        Items = extensionService.Sort(menu);
    }

    protected override void OnIsSubmenuOpenChanged()
    {
        if (IsSubmenuOpen)
        {
            var newItems = new List<IMenuItem>();
            string previousId = Guid.NewGuid().ToString();
            foreach (IDocument doc in layoutManager.Documents)
            {
                var docMenuItem = new DocumentMenuItem(
                    doc, previousId, layoutManager);
                previousId = docMenuItem.ID;
                newItems.Add(docMenuItem);
            }
            Items = extensionService.SortAndJoin(
                menu, new ConcreteMenuItemSeparator(), newItems);
        }
    }
}
link

answered 12 Sep '10, 09:33

Scott%20Whitlock's gravatar image

Scott Whitlock ♦♦
696262833
accept rate: 50%

I deleted my original answer because it makes no sense - sorry!

Ok, so after pondering a bit, I agree this is a difficult situation to solve in the existing framework. If we could change IMenuItem, IDocument, or the LayoutManager to make this easier, what would be the ideal solution?

I'm not sure how you intended to find out which document the user has selected either. Did you have any ideas about that.

link

answered 25 Aug '10, 06:31

Scott%20Whitlock's gravatar image

Scott Whitlock ♦♦
696262833
accept rate: 50%

edited 25 Aug '10, 06:58

Heheh, no worries; good to get the discussion going.

Ok, I guess the way I would have done it in a non-extensible Forms app would be to hook into the opening MenuItem event and append items after a separator from a list of IDocuments that the LayoutManager knows about.

This goes back to my attempt at the EventSetter before; can we bind a IMenuItem (AbstractMenuItem) handler in XAML? Was I just doing it incorrectly? (I'm new to WPF and XAML) Then its a case of overridding that in the desired menuitem class.

(25 Aug '10, 07:02) dhm
Your answer
toggle preview

Follow this question

By Email:

Once you sign in you will be able to subscribe for any updates here

By RSS:

Answers

Answers and Comments

Markdown Basics

  • *italic* or _italic_
  • **bold** or __bold__
  • link:[text](http://url.com/ "Title")
  • image?![alt text](/path/img.jpg "Title")
  • numbered list: 1. Foo 2. Bar
  • to add a line break simply add two spaces to where you would like the new line to be.
  • basic HTML tags are also supported

Tags:

×17
×11
×6
×6
×3
×2

Asked: 25 Aug '10, 06:00

Seen: 2,528 times

Last updated: 12 Sep '10, 09:33

powered by OSQA