0

I am using Caliburn Micro in a WPF project where I want plugin components to be able to populate a toolbar. Each plugin gets a top level menu item and can populate it with submenus if they choose.

Example

<ToolBar>
    <Menu x:Name="ToolBarMenuItems">
        <Menu.ItemContainerStyle>
            <Style TargetType="{x:Type MenuItem}">
                <Setter Property="Header" Value="{Binding Path=Text}" />
                <Setter Property="ItemsSource" Value="{Binding Path=Children}" />
            </Style>
        </Menu.ItemContainerStyle>
    </Menu>
</ToolBar>

ToolBarMenuItems is a BindableCollection of an interface:

public BindableCollection<IMenuItemViewModel> ToolBarMenuItems { get; set; }

public interface IMenuItemViewModel
{
    string Text { get; set; }

    ObservableCollection<IMenuItemViewModel> Children { get; set; }

    bool CanRunCommand();

    void RunCommand();
}

This works fine as far as creating the menu goes, the problem is connecting the menu items to the RunCommand/CanRunCommand. Dynamic menus with Caliburn micro explained how to do it on a MenuItem:

<Menu>
    <MenuItem x:Name="ToolBarMenuItems" DisplayMemberPath="Text" Header="MyMenu" cal:Message.Attach="RunToolbarCommand($originalsourcecontext)"/>
</Menu>

Where RunToolBarCommand is a method in the view model public void RunToolbarCommand(IMenuItemViewModel menuItem) and $originalsourcecontext refers to setting the following in the bootstrapper

MessageBinder.SpecialValues.Add("$originalsourcecontext", context =>
{
    var args = context.EventArgs as RoutedEventArgs;
    var fe = args?.OriginalSource as FrameworkElement;
    return fe?.DataContext;
});

But as I said, I need to do this for the entire menu and not just on a MenuItem, but I don't know how to use Caliburn Micro to bind the methods.

  • i dont understant your problem, could you elaborate? you have a tips functional for menuitem, but where is your problem about binding method? show a sample of what you want..sorry for my english – Frenchy May 27 '20 at 18:37
  • Basically the working example I have is for a MenuItem where each object in the list would be a submenuitem. If I had standard menus like File, Edit, Plugins etc. this would be perfect, I would put it on the Plugins MenuItem and each plugin would get a submenu to play around with. I don't want it on the MenuItem though, I want it on the Menu and each object in the collection should be a top level MenuItem on that Menu. This is because I want a toolbar where each plugin gets a top level MenuItem where it can add submenus if it needs. – Carl-Johan Andersson May 28 '20 at 06:05
  • I added an image to clarify. – Carl-Johan Andersson May 28 '20 at 06:31

1 Answers1

1

Add a setter to your Style that sets the cal:Message.Attach attached property and passes the $executionContext:

<Menu x:Name="ToolBarMenuItems">
    <Menu.ItemContainerStyle>
        <Style TargetType="{x:Type MenuItem}">
            <Setter Property="Header" Value="{Binding Path=Text}" />
            <Setter Property="ItemsSource" Value="{Binding Path=Children}" />
            <Setter Property="cal:Message.Attach" Value="RunCommand($executionContext)" />
        </Style>
    </Menu.ItemContainerStyle>
</Menu>

You need the context in your interface and implementation to be able to stop the event from bubbeling:

public void RunCommand(ActionExecutionContext context)
{
    if (context?.EventArgs is RoutedEventArgs routedEventArgs)
        routedEventArgs.Handled = true;

    MessageBox.Show("Run!");
}

Please refer to this question for more information about this.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • It works perfectly, thank you so much! I failed to realize that cal:Message.Attatch could be used as the property of the style, even though now it seems obvious when I see it (as so often is the case). – Carl-Johan Andersson May 28 '20 at 12:07