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.
<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.
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.