Another ContextMenu Question ..

May 3, 2011 at 5:03 PM

So I've tried about everything I can think of and read all the posts. This is a last ditch effort ;)..I have a ViewModel layed out like below. I'm trying to get the CurrentItem(CodeBox which is just a ExtendedTextBox with more features) control to my ApplyTool Action. I want to access properties like Selection Start and length so i can apply the appropriate tool to that range. However no matter what i try i cannot get a reference to the that control. How would you go about doing that? I've tried classic bindings, reflecting the view, etc. still no luck. I understand that the ContextMenu is in a different visual tree, but how can i access that other control without reflection and adding a reference to my View in the ViewModel project??

Any help would be greatly appreciated.

 

       ConventionManager.AddElementConvention<ViewModel.Controls.CodeBoxControl.CodeBox>(ViewModel.Controls.CodeBoxControl.CodeBox.SelectionInfoProperty, "SelectionInfo", "SelectionInfoUpdated");

<StackPanel x:Name="LayoutRoot" VerticalAlignment="Stretch" Grid.Column="0">
            <StackPanel.Resources>
                <ContextMenu x:Key="TheMenu">
                    <ContextMenu.Resources>
                        <DataTemplate DataType="{x:Type local:ToolListItemViewModel  }">
                             <TextBlock Text="{Binding DisplayName}" Foreground="{Binding Path=ColorCode}" />
                        </DataTemplate>
                    </ContextMenu.Resources>
                    <MenuItem Header="Actions" ItemsSource="{Binding ToolItems.Tools}">       
                        <MenuItem.ItemContainerStyle>
                            <Style TargetType="{x:Type MenuItem}">
                                <Setter Property="cal:Message.Attach" Value="[Event Click] = [Action ApplyTool(CurrentRequest, $executionContext)]" />
                              
                                <Setter Property="cal:Action.TargetWithoutContext" Value="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type StackPanel}}}" />
                               <!--
                                <Setter Property="cal:Action.TargetWithoutContext" Value="{Binding ElementName=CurrentItem}" />
                               -->
                            </Style>
                        </MenuItem.ItemContainerStyle>
                    </MenuItem>
                </ContextMenu>
            </StackPanel.Resources>

            <ctb:CodeBox x:Name="CurrentItem" 
                         Height="{Binding ActualHeight,
                                 RelativeSource={RelativeSource Mode=FindAncestor,
                                 AncestorType  ={x:Type StackPanel}}}"
                         Request="{Binding}"
                         AcceptsReturn="True"
                         ContextMenu="{StaticResource TheMenu}">       
            </ctb:CodeBox>
        </StackPanel>

May 4, 2011 at 1:29 AM

Whoops figured out how to do it via reflection. I was using GetProperties.. I needed to call GetFields..

 

May 4, 2011 at 8:07 AM
Edited May 5, 2011 at 8:43 AM

Can't you try to get a reference to the CodeBox using the ContextMenu.PlacementTarget, using the RelativeSource?

<StackPanel x:Name="LayoutRoot"
            VerticalAlignment="Stretch"
            Grid.Column="0">
     <StackPanel.Resources>
          <ContextMenu x:Key="TheMenu">
               <ContextMenu.Resources>
                    <DataTemplate DataType="{x:Type local:ToolListItemViewModel  }">
                         <TextBlock Text="{Binding DisplayName}"
                                    Foreground="{Binding Path=ColorCode}" />
                    </DataTemplate>
               </ContextMenu.Resources>
               <MenuItem Header="Actions"
                         ItemsSource="{Binding ToolItems.Tools}">        
                    <MenuItem.ItemContainerStyle>
                         <Style TargetType="{x:Type MenuItem}">
                              <Setter Property="cal:Message.Attach"
                                      Value="[Event Click] = [Action ApplyTool(CurrentRequest, $executionContext)]" />
                              <Setter Property="cal:Action.TargetWithoutContext"
                                      Value="{Binding Path=PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
                         </Style>
                    </MenuItem.ItemContainerStyle>
               </MenuItem>
          </ContextMenu>
     </StackPanel.Resources>
     <ctb:CodeBox x:Name="CurrentItem"  
                  Height="{Binding ActualHeight, 
                  RelativeSource={RelativeSource Mode=FindAncestor, 
                  AncestorType  ={x:Type StackPanel}}}" 
                  Request="{Binding}" 
                  AcceptsReturn="True"
                  ContextMenu="{StaticResource TheMenu}"/>
</StackPanel>

I tipicallly use this trick to 'join' the ContextMenu visual tree with the related Window one (of course, if the ContextMenu is not placed using the PlacementTarget, this would fail, but in standard scenarios this is not an issue).

May 4, 2011 at 4:40 PM

So I went ahead and tried that. Now my understanding of the TargetWithoutContext is that'll set the Target value in the ExecutionContext to the binding? In this instance it failed to do that. However I was able to piggy back on the MenuItem's commandParameter and get the expected results.. see below:

<MenuItem Header="Actions" ItemsSource="{Binding ToolItems.Tools}">       
                        <MenuItem.ItemContainerStyle>
                            <Style TargetType="{x:Type MenuItem}">
                                <Setter Property="cal:Message.Attach" Value="[Event Click] = [Action ApplyTool($executionContext)]" />
                                <Setter Property="cal:Action.TargetWithoutContext"
                                      Value="{Binding Path=PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
                                <Setter Property="CommandParameter" Value="{Binding Path=PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" />
                                <!--
                                <Setter Property="cal:Action.TargetWithoutContext" Value="{Binding ElementName=LayoutRoot}" />
 -->
                            </Style>
                        </MenuItem.ItemContainerStyle>
                    </MenuItem>


As you can see the TargetWithoutContext is set to the same binding as the CommandParameter. However, the target returned in the executionContext is the ToolListItemViewModel. Not what I was expecting..
It also appeared to cause the bubbling of the action message to fail to get to my overall viewmodel and only the ToolListItemViewModel viewModel got the message. I'm using the RTW version of the framework. Is there a known issue with this?
Piggybacking on the CommandParameter worked just fine.. digging down to the MenuItem i was able to retrieve the CodeBox that way..

May 4, 2011 at 5:54 PM

Well, as far as I know, the ExecutionContext should contain the actual Target, so the behaviour you're experiencing should not be the proper one.

I'll investigate into it this evening and get back with a proper answer! :)

May 4, 2011 at 6:04 PM

Yea, I thought it seemed fishy when I was setting the target to bindings and not getting the expected results. ie confusing the hell out of me ;). I wasn't sure if it was me or the framework. I can piggyback for now and move forward. Definitely interested in your findings though so i can remove that layer of mis-direction. Thanks!

May 5, 2011 at 4:36 PM

Ok, I have investigated the issue and the behaviour is by design, given the code I provided above.

The actual issue is that I overlooked the fact the you are trying to use the Target as a way to get a reference to a control (while my intention was to give you insights on how to 'jump' from the ContextMenu visual tree to the main one).

The real purpose of the ActionMessage.Target is defining an entry point to begin searching for a method (the Action implementation), while the ActionExecutionContext.Target is a reference to the object where the action method was found.

This is an explanation of what happens in the above code:

  1. the Action.Target is set to the CodeBox
  2. once the ActionExecutionContext is created, CM tries to retrieve the object implementing the required method using the Action.Target and walking-up the visual tree starting from the UI element where the ActionMessage was defined;
  3. the CodeBox (Action.Target) is tested for the action ApplyTool and is rejected, so the search goes on bubbling up and fails to retrieve a proper target;
  4. the fallback method kicks in and the DataContext of the object where the ActionMessage was defined (in the above code it is the ToolListItemViewModel) is tested to check if the required method is available;
  5. if the method is found, the ActionExecutionContext.Target is set to the DataContext, otherwise an exception is raised;

Note that the action bubbling fails because the visual tree navigation fails to reach an object where a Target is set.

To re-cap:

  • The Action.Target/TargetWithoutContext should be set to the object where the method is implemented
  • The ActionExecutionContext.Target will have the actual target where the method was found, considering the complete fallback mechanism

 

Unfortunally, it seems that in this scenario your only option is piggy-back riding, unless you use this addition to CM to support full XAML syntax in Message.Attach (refer to the post stating how to achieve this without parameter evaluation).

Note: I tried to use the above method but it didn't work in my test project. The action was properly registered but not invoked. There is probably an issue with my code... apparently I'm too sleepy today...

 

 

May 5, 2011 at 5:04 PM

Thanks bladewise for that really well written response! I understand CM a little better now ;).  I saw that post and  thought that would work also but it seemed like a lot of work ;). However, since I already wrote a method to reflect the view out and get the CB which i use for just that viewmodel instance and using the commandParameter in a couple of places works. So I won't bother trying to change it. Again thanks!

May 5, 2011 at 5:57 PM

If you are interested, I added a simple class to enable the XAML support in Message.Attach here.

Jul 1, 2012 at 8:43 PM

I had a email question about this thread, and after using ContextMenu's with caliburn micro for awhile, this is the solution i tend to use now. I think other people may use a similar technique also. 

The problem with context menus is the visual tree seperation. It's not attached to the main tree. 


For example:      If in your view you have something like this: 

<Element.ContextMenu>
      <ContextMenu>
          <MenuItem Header="SomeItem" cal:Message.Attach="[Click]=[ClickEventOnViewModel($executionContext)]" />

 the "DataContext" for the MenuItem is not resolved to the element's DataContext. Due to this, when the default action bubbling occurs, it fails to resolve your target Action.

Inorder to have the ClickEvent action resolve, you need to some how resolve the "Target" for the action or set the datacontext somehow. You can do it via a couple of tricks. I like to keep this in the view, it's cleaner. 


To "tie" to the original visual tree WPF provides the PlacementTarget property on ContextMenu. So you can go from the MenuItem -> ContextMenu via the Parent Property of the MenuItem.

Something like this:       

(Being very explicit)   
DataContext(or cal:TargetWithoutContext)="{Binding Path=Parent.PlacementTarget, RelativeSource={RelativeSoruce Mode=Self}}"

Here's a more complex expample pulled from something i'm working on. This trick works for lots of different controls (ListView,TreeView, etc) if you understand the core concept. And works in most cases that I've run into. 

The DataTemplate could be for a TreeViewItem/ListViewItem, whatever. In this case the Item is being wraped in a expander, for a header/ expandable items.  I use the Tag property to "jump" to the root ViewModel on the Expander. I do this by setting the Tag value to the DataContext of the rootElement of the view. Although i could jump to any element in the visual tree from here and use some other property/value.

 

           <DataTemplate DataType="{x:Type local:SomeType}">
              <Expander Tag="{Binding DataContext, ElementName=_rootElement}">
                  <Expander.ContextMenu>
                      <ContextMenu Tag="{Binding Path=Parent.PlacementTarget.DataContext, RelativeSource={RelativeSource Mode=Self}}" >
                         <MenuItem Header="Remove" 
                                   cal:Message.Attach="[Event Click] = [Action Remove($executionContext)]"
			           cal:Action.TargetWithoutContext="{Binding Path=Parent.Tag, RelativeSource={RelativeSource Mode=Self}}"/>

Then in the ContextMenu I use the Tag again to jump to the placement target's tag. The PlacementTarget would be the Expander. This will keep the original DataContexxt intact incase it's generated dynamically, or if i need to set it explicitly to some value.

Next i use the TargetWithoutContext to resolve to the ContextMenu's Tag, which resolves to the Expander's Tag which would be the ViewModel of this View to do the action. Sometimes I'll even set the MenuItem's tag to the "Item" to ensure i know which item is going to the action which i've seen some oddities. 

Hope this helps someone!

Jul 2, 2012 at 6:34 AM

Hi.

I have raised this thread again and I was happy to get the answer. Unfortunately, it is just a part of my problem. So, I'll try here to give more details.

I would like to have context menu that I can set from ViewModel. So, I have created observable collection of menu items:


protected ObservableCollection<string> _menuItems = new ObservableCollection<string>()        {            "Item1",            "Item2"        };

public ObservableCollection<string> MenuItems { get { return _menuItems; } }

I have put following lines to xaml file to create menu:

 

        <Grid.ContextMenu>
            <ContextMenu ItemsSource="{Binding Path=MenuItems}" cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}">
                <ContextMenu.ItemContainerStyle>
                    <Style TargetType="MenuItem">
                        <Setter Property="Header" Value="{Binding Path=Header}"/>
                        <Setter Property="cal:Message.Attach" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Header}"/>
                        <!--<Setter Property="cal:Action.TargetWithoutContext"
                    Value="{Binding Path=PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"/>-->
                    </Style>
                </ContextMenu.ItemContainerStyle>
            </ContextMenu>
        </Grid.ContextMenu>

 

So, I was able to get ContextMenu on the screen, but click on the menu item always generated exception.

I have tried a lot of different options for TargetWithoutContext (you can see 2 tries in the code) but I was not able to solve the problem.

So, I step through the code and I have noticed that before compilation of Action related to my MenuItem, datacontext of MenuItem is always set to the string.

After that I have tried something else. Instead of the strings I have prepared a little class in the view model

    public class TestClassAction
    {
        public string Header { get; set; }

        public Action Callback { get; set; }

        public void DoCallback()
        {
            Callback();
        }
    }

I have initialized my property in constructor:
            _contextItemsAction = new ObservableCollection<TestClassAction>()
            {
                new TestClassAction() { Header = "Item1", Callback = new Action(this.DoItem1) },
                new TestClassAction() { Header = "Item2", Callback = new Action(this.DoItem2) }
            };

And, I changed a xaml:

        <Grid.ContextMenu>
            <ContextMenu ItemsSource="{Binding Path=ContextItemsAction}">
                <ContextMenu.ItemContainerStyle>
                    <Style TargetType="MenuItem">
                        <Setter Property="Header" Value="{Binding Path=Header}"/>
                        <Setter Property="cal:Message.Attach" Value="DoCallback"/>
                    </Style>
                </ContextMenu.ItemContainerStyle>
            </ContextMenu>
        </Grid.ContextMenu>

Now, I am able to call my actions. But, I have 3 questions.
First. Is it possible to get same result using just collection of strings (first example)?
Second. Is it possible to remove DoCallback function from my class and call Action delegate directly from xaml code?

Third. What is prefered ("Caliburn") way to solve this?

Best wishes, Franis.

 

Jul 2, 2012 at 4:51 PM
Edited Jul 2, 2012 at 5:08 PM

Okie your scenario is totally doable. I see a couple of issues. My first response to this was wrong. Here is a better example (I edited out my first response to this). This answer works exactly as expected. 

This is just a problem of scoping the right values at the right time. Because Caliburn as a Mapping between MenuItem and click, this works. I even learned something new doing this ;). Anyways i hope the code is straightforward if you have questions please let me know!

 

 

<UserControl x:Class="TestApp.Views.TestView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel  x:Name="_rootElement">
             <!-- Tag="{Binding DataContext, ElementName=_rootElement"-->                                           
        <Grid Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type StackPanel}}}">
            <Grid.ContextMenu>
                <ContextMenu ItemsSource="{Binding Items}" 
                             cal:Action.TargetWithoutContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" 
                             >
                    <ContextMenu.ItemContainerStyle>
                        <Style TargetType="MenuItem">
                            <Setter Property="Header" Value="{Binding }"/>
                            <Setter Property="cal:Message.Attach" Value="{Binding }" />
                        </Style>
                    </ContextMenu.ItemContainerStyle>
                </ContextMenu>
            </Grid.ContextMenu>
            <Label Content="Hello">
            </Label>
        </Grid>
    </StackPanel>
</UserControl>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Caliburn.Micro;
using System.Windows.Controls;


namespace TestApp.ViewModels
{
    public class TestViewModel : PropertyChangedBase
    {

        public ObservableCollection<String> Items
        {
            get
            {
                return new ObservableCollection<string>() { "MenuItem1", "MenuItem2", "MenuItem3" };
            }
        }

        public void MenuItem1()
        {

        }
        public void MenuItem2()
        {

        }

        public void MenuItem3()
        {

        }

        public TestViewModel()
        {
            NotifyOfPropertyChange(() => Items);
        }
    }
}

 

 


Dec 16, 2012 at 10:55 PM

Thanks for this nice solution with ObservableCollection with context menu items/methods to call. However you would normally

have the names as item names as string resources and method names to call would normally differ from the strings. E.g.

  	public class ContentMenuItem
        {
            public string MenuItemName;
            public string MethodToCall;
        }
 
        /// <summary>
        /// Hold the content menu items/functions to be presented in the CustomerView 
        /// </summary>
        public ObservableCollection<ContentMenuItem> ContentMenuItems
        {
            get
            {
                return new ObservableCollection<ContentMenuItem>()
                           {
                               new ContentMenuItem() { MenuItemName="Delete", MethodToCall="DeleteCustomer" }, 
                               new ContentMenuItem() { MenuItemName="Edit", MethodToCall="EditCustomer" }, 
                               new ContentMenuItem() { MenuItemName="Create", MethodToCall="CreateCustomer" }
                           };
            }
        }

What would be the syntax binding to these ? The following syntax is not working:

        <DataGrid.ContextMenu>
                <ContextMenu ItemsSource="{Binding ContentMenuItems}" Style="{DynamicResource ContextMenuStyle}" 
                             cal:Action.TargetWithoutContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" >
                    <ContextMenu.ItemContainerStyle>
                        <Style TargetType="MenuItem">
                            <Setter Property="Header" Value="{Binding MenuItemName}"/>
                            <Setter Property="cal:Message.Attach" Value="{Binding MethodToCall}" />
                        </Style>
                    </ContextMenu.ItemContainerStyle>
                </ContextMenu>
            </DataGrid.ContextMenu>
Dec 17, 2012 at 12:01 AM
Edited Dec 17, 2012 at 12:04 AM

So, there is a couple of problems with your binding. You're bound to ContentMenuItems as your items source.. that is the context or the "Source" to the ItemContainer for a binding expression. Any method you are using will be under that context. As such ContentMenuItem would need to implement the method you specify. Here is an example that works. 

In addition you could use the ContentMenuItem class itself as a "dispatcher" and have the various method implementations contained there. 

 

<Label Content="Hello">
            <Label.ContextMenu>
                <ContextMenu ItemsSource="{Binding ContentMenuItems}">
                    <ContextMenu.ItemContainerStyle>
                        <Style TargetType="MenuItem">
                            <Setter Property="Header" Value="{Binding MenuItemName}"/>
                            <Setter Property="cal:Message.Attach" Value="{Binding MethodToCall}" />
                        </Style>
                    </ContextMenu.ItemContainerStyle>
                </ContextMenu>
            </Label.ContextMenu>
        </Label>

 

 

 

 [Export(typeof(IRootView))]
        public class RootViewModel : Screen
        {
            /// <summary>
            /// Hold the content menu items/functions to be presented in the CustomerView 
            /// </summary>
            public ObservableCollection<ContentMenuItem> ContentMenuItems
            {
                get
                {
                    return new ObservableCollection<ContentMenuItem>()
                               {
                                  new DeleteMenuItem() { MenuItemName = "Delete", MethodToCall="Delete"}
                               };
                }
            }   
    
        }
        

        public class DeleteMenuItem : ContentMenuItem{
            public void Delete(){

            }
        }
        public class ContentMenuItem : PropertyChangedBase
        {
            private string _menuItemName;
            public string MenuItemName
            {
                get { return _menuItemName; }
                set
                {
                    _menuItemName = value;
                    NotifyOfPropertyChange(() => MenuItemName); 
                }
            }
            private string _methodToCall;
            public string MethodToCall
            {
                get { return _methodToCall; }
                set
                {
                    _methodToCall = value;
                    NotifyOfPropertyChange(() => MethodToCall);
                }
            }
        }
        
      
    public interface IRootView { } 

 

 

Another way which would be instead just set the delegate for a ICommand and use the style to set the ICommand set on the ContentMenuItem.. 

Something like: 

 

new DeleteMenuItem() { MenuItemName = "Delete", MethodToCall=new Command(){ MethodDelegate = ptrMethod} }

Additionally, if you wanted you could also create a dispatcher type deal to map methods to strings with a common dispatcher. You could use, 
the ContentMenuItem and add a "dispacher" to it. Then you could use the style to bind to the cal:TargetWithoutContext" and set that to the dispatcher with the String of the Method name.
That should work nicely. 






Dec 17, 2012 at 10:04 PM

Hi

Thank you very much for the example. It works ok - as intended. I have added a parameter to the ViewModel so that DeleteMenuItem can call the

ViewModel's original DeleteCustomer() method from the DeleteMenuItem class:

   internal sealed class CustomerViewModel : ScreenIManagementScreen
   {
       ....
        public ObservableCollection<ContentMenuItem> ContentMenuItems
        {
            get
            {
                return new ObservableCollection<ContentMenuItem>()
                           {
                               new CreateMenuItem(this) { MenuItemName=Strings.CustomerViewCreateCustomerButtonText, MethodToCall="Create" },
                               new EditMenuItem(this) { MenuItemName=Strings.CustomerViewEditCustomerButtonText, MethodToCall="Edit" }, 
                               new DeleteMenuItem(this) { MenuItemName=Strings.CustomerViewDeleteCustomerButtonText, MethodToCall="Delete"}
                           };
            }
        }
...
        public class DeleteMenuItem : ContentMenuItem
        {
            public DeleteMenuItem(CustomerViewModel viewModel) : base(viewModel) { }
 
            public void Delete()
            {
                viewModel.DeleteCustomer();
            }
        }
...
        public bool CanDeleteCustomer
        {
            get { return selectedCustomer != null; }
        }
        
        /// <summary>
        /// Delete existing Customer 
        /// </summary>
        public void DeleteCustomer()
        {
            if (selectedCustomer != null)
            {
                if (MessageBox.Show(Strings.CustomerViewDeleteConfirmation, Strings.CustomerViewDeleteConfirmationDialogTittle, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
                {
                    customerServiceProxy.DeleteCustomer(SelectedCustomer);
                }
            }
        }
 ...
 
One problem with this solution is CanDeleteCustomer() property (DeleteCustomer button) will only automatically enable (when an item is selected i the DataGrid) 
and disable (when no item is selected) a menu button outside context menu in the view and not the delete context menu item... 
so it is maybe not the right solution when having more buttons with e.g. same delete action... :
 
  <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition  MinHeight="100" />
        </Grid.RowDefinitions>
 
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,3,0,3 ">
            <Button Name="CreateCustomer"  Width="100" HorizontalAlignment="Left" Content="{x:Static Resources:Strings.CustomerViewCreateCustomerButtonText}" />
            <Button Name="EditCustomer"   Width="100" HorizontalAlignment="Left" Content="{x:Static Resources:Strings.CustomerViewEditCustomerButtonText}"/>
            <Button Name="DeleteCustomer" Width="100" HorizontalAlignment="Left" Content="{x:Static  Resources:Strings.CustomerViewDeleteCustomerButtonText}"/>
            <Button Name="ImportCustomer"  Width="100" HorizontalAlignment="Left" Margin = "10,0,0,0" Content="{x:Static  Resources:Strings.CustomerViewImportCustomerButtonText}" />
            </StackPanel>
 
        <DataGrid Height="500" Grid.Row="1" AutoGenerateColumns="False" x:Name="Customers" VirtualizingStackPanel.IsVirtualizing="True" 
              VirtualizingStackPanel.VirtualizationMode="Recycling"
              Tag="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type Grid }}}"
              RowHeight="25"
              IsReadOnly="True"
              helpers:RowDoubleClickHandler.MethodName="DoubleClick" >
   
            <!-- tip for creating Caliburn context menu: see http://caliburnmicro.codeplex.com/discussions/256163 -->
            <DataGrid.ContextMenu>
                <ContextMenu ItemsSource="{Binding ContentMenuItems}" Style="{DynamicResource ContextMenuStyle}">
                    <ContextMenu.ItemContainerStyle>
                        <Style TargetType="MenuItem">
                            <Setter Property="Header" Value="{Binding MenuItemName}"/>
                            <Setter Property="cal:Message.Attach" Value="{Binding MethodToCall}" />
                        </Style>
                    </ContextMenu.ItemContainerStyle>
                </ContextMenu>
            </DataGrid.ContextMenu>
  
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="CustomerName" Binding="{Binding CustomerName}" Header="Name" Width="150"/>
                  ...
            </DataGrid.Columns>
 
        </DataGrid>
 
    </Grid>

So i think it would maybe be a better solution if the XAML would bind to the original Method - e.g. DeleteCustomer()... any suggestion ? 
Dec 17, 2012 at 10:33 PM
Edited Dec 17, 2012 at 10:34 PM

Yea, that's understandable. I was hinting at that solution in this comment

"Additionally, if you wanted you could also create a dispatcher type deal to map methods to strings with a common dispatcher. You could use, the ContentMenuItem and add a "dispacher" to it. Then you could use the style to bind to the cal:TargetWithoutContext" and set that to the dispatcher with the String of the Method name."

 

This below is a solution using the above description. In addition you could remove the extra property from the CotentMenuItem if you could get the proper Binding path in the style via Relative pathing and Tag tricks.. The goal is to make sure the "Target" is set to the correct ViewModel that contains the method. 

 

Oh and you would set the ParentViewModel to the CustomerViewModel. 

 

 public class ContentMenuItem : PropertyChangedBase
        {
            private string _menuItemName;
            public string MenuItemName
            {
                get { return _menuItemName; }
                set
                {
                    _menuItemName = value;
                    NotifyOfPropertyChange(() => MenuItemName); 
                }
            }
            private string _methodToCall;
            public string MethodToCall
            {
                get { return _methodToCall; }
                set
                {
                    _methodToCall = value;
                    NotifyOfPropertyChange(() => MethodToCall);
                }
            }

            public PropertyChangedBase _parentViewModel;
            public PropertyChangedBase ParentViewModel
            {
                get { return _parentViewModel; }
                set
                {
                    _parentViewModel = value;
                    NotifyOfPropertyChange(() => ParentViewModel); 
                }
            } 


        }

 

 

            <Label.ContextMenu>
                <ContextMenu ItemsSource="{Binding ContentMenuItems}">
                    <ContextMenu.ItemContainerStyle>
                        <Style TargetType="MenuItem">
                            <Setter Property="Header" Value="{Binding MenuItemName}"/>
                            <Setter Property="cal:Message.Attach" Value="{Binding MethodToCall}" />
                            <Setter Property="cal:Action.TargetWithoutContext" Value="{Binding ParentViewModel}" />
                        </Style>
                    </ContextMenu.ItemContainerStyle>
                </ContextMenu>
            </Label.ContextMenu>
Dec 18, 2012 at 11:25 PM

Thank you for taking the time giving tips on making context menu Caliburn. I have played around with XAML, Bindings, TargetWithoutContent and found the right and simplest solution :-). This do not involve a (ekstra) ContentMenuItem class and a "ObservableCollection<ContentMenuItem> ContentMenuItems" att all. Instead i bind directly to the CustomerViewModel methods that i want to call having only one "DeleteCustomer" method in the ViewModel:

  <Grid x:Name="Root">
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition  MinHeight="100" />
        </Grid.RowDefinitions>
 
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,3,0,3 ">
            <Button Name="CreateCustomer"  Width="100" HorizontalAlignment="Left" Content="{x:Static Resources:Strings.CustomerViewCreateCustomerButtonText}" />
            <Button Name="EditCustomer"   Width="100" HorizontalAlignment="Left" Content="{x:Static Resources:Strings.CustomerViewEditCustomerButtonText}"/>
            <Button Name="DeleteCustomer" Width="100" HorizontalAlignment="Left" Content="{x:Static  Resources:Strings.CustomerViewDeleteCustomerButtonText}"/>
            <Button Name="ImportCustomer"  Width="100" HorizontalAlignment="Left" Margin = "10,0,0,0" Content="{x:Static  Resources:Strings.CustomerViewImportCustomerButtonText}" />
            </StackPanel>
 
        <DataGrid Height="600" Grid.Row="1" AutoGenerateColumns="False" x:Name="Customers" VirtualizingStackPanel.IsVirtualizing="True" 
              VirtualizingStackPanel.VirtualizationMode="Recycling"
              Tag="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type Grid }}}"
              RowHeight="25"
              IsReadOnly="True"
              helpers:RowDoubleClickHandler.MethodName="DoubleClick" >
     
            <DataGrid.Resources>
                <ContextMenu x:Key="RowContextMenu" Style="{DynamicResource ContextMenuStyle}">
                    <MenuItem Header="{x:Static Resources:Strings.CustomerViewCreateCustomerButtonText}"
                              cal:Message.Attach="CreateCustomer"
                              cal:Action.TargetWithoutContext="{Binding DataContext, Source={x:Reference Root}}" />
                    <MenuItem Header="{x:Static Resources:Strings.CustomerViewEditCustomerButtonText}"
                              cal:Message.Attach="EditCustomer"
                              cal:Action.TargetWithoutContext="{Binding DataContext, Source={x:Reference Root}}" />
                    <MenuItem Header="{x:Static Resources:Strings.CustomerViewDeleteCustomerButtonText}"
                              cal:Message.Attach="DeleteCustomer"
                              cal:Action.TargetWithoutContext="{Binding DataContext, Source={x:Reference Root}}" />
                  </ContextMenu>
            </DataGrid.Resources>
 
            <DataGrid.RowStyle>
                <Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowDefault}">
                    <Setter Property="ContextMenu" Value="{StaticResource RowContextMenu}"/>
                </Style>
            </DataGrid.RowStyle>
 
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="CustomerName" Binding="{Binding CustomerName}" Header="Name" Width="150"/>
                 ...
            </DataGrid.Columns>
 
        </DataGrid>
 
    </Grid>

The trick here was to find the right root for the TargetWithoutContext binding...

And of cause having a DeleteCustomer method in the ViewModel to be called:

class CustomerViewModel : ScreenIManagementScreen
    {
      ....
private Customer selectedCustomer;
        /// <summary>
        /// Holds current selected customer (if any) in CustomerView
        /// </summary>
        public Customer SelectedCustomer
        {
            get { return selectedCustomer; }
            set
            {
                selectedCustomer = value;
                NotifyOfPropertyChange(() => SelectedCustomer);
                NotifyOfPropertyChange(() => CanDeleteCustomer);
                NotifyOfPropertyChange(() => CanEditCustomer);
            }
        }
 
        public bool CanDeleteCustomer
        {
            get { return selectedCustomer != null; }
        }
        
        /// <summary>
        /// Delete existing Customer (note: if you change this Name also change it in: Strings.CustomerViewEdit
        /// </summary>
        public void DeleteCustomer()
        {
            if (selectedCustomer != null)
            {
                var messageBox = new ShowMessageBox();
 
                if (messageBox.ShowDialogMessage(
                    Strings.CustomerViewDeleteConfirmationDialogTittle, //title
                    Strings.CustomerViewDeleteConfirmation, //message
                    MessageBoxButton.YesNo) == MessageBoxResult.Yes)
                {
                    customerServiceProxy.DeleteCustomer(SelectedCustomer);
                }
            }
        }
...
I think the above solution is quit simple and that it will be usefull making context menu's in WPF/XAML/Caliburn Micro...
Dec 19, 2012 at 7:26 PM

Excellent! I'm glad I was able to help you discover a solution that works for your project! Once you can master ContextMenu's with caliburn micro you'll find yourself with a WAYYY better understanding of XAML, Bindings, and Caliburn. =)

The reason I originally had the solution based on observable collections is because I'm doing something a little trickier that requires binding a ItemSource to a context menu. (Think Apply->(ListOfItemsToApply) style of context menus. Where the actions I'm applying are dynamically loaded class from a plugin model.