Actions in ItemTemplate: ListBox vs ComboBox

Oct 1, 2010 at 4:07 PM
Edited Oct 1, 2010 at 4:07 PM

Hello

I've hit a case where actions are not behaving similarly in ListBox and ComboBox templates. For an action triggered in ItemTemplate of ComboBox, I'm receiving a "No target found for method ...", while for ListBox it works like a charm. Is this a known limitation/issue or looking like a bug?

Silverlight 4, latest Caliburn from sources. A demo app available on request.

Xaml:

 

    <StackPanel>
		<ListBox x:Name="Countries">
			<ListBox.ItemTemplate>
				<DataTemplate>
					<Button Content="{Binding}" cal:Message.Attach="SayHello($datacontext)" />
				</DataTemplate>
			</ListBox.ItemTemplate>
		</ListBox>
		<ComboBox x:Name="Cities">
			<ComboBox.ItemTemplate>
				<DataTemplate>
					<Button Content="{Binding}" cal:Message.Attach="SayHello($datacontext)" />
				</DataTemplate>
			</ComboBox.ItemTemplate>
		</ComboBox>
	</StackPanel>

View Model:

 

    public class ShellViewModel : PropertyChangedBase
    {
    	public ShellViewModel()
    	{
    		Countries = new[] {"Switzerland", "France"};
    		Cities = new[] {"Bern", "Paris"};
    	}

    	public IEnumerable<string> Countries { get; set; }
    	public IEnumerable<string> Cities { get; set; }

		public void SayHello(string from)
        {
            MessageBox.Show(string.Format("Hello from {0}!", from);
        }
    }

 

Full exception:

 

System.Exception was unhandled by user code
  Message=No target found for method SayHello.
  StackTrace:
       at Caliburn.Micro.ActionMessage.Invoke(Object eventArgs)
       at System.Windows.Interactivity.TriggerAction.CallInvoke(Object parameter)
       at System.Windows.Interactivity.TriggerBase.InvokeActions(Object parameter)
       at System.Windows.Interactivity.EventTriggerBase.OnEvent(EventArgs eventArgs)
       at System.Windows.Interactivity.EventTriggerBase.OnEventImpl(Object sender, EventArgs eventArgs)
       at System.Windows.Controls.Primitives.ButtonBase.OnClick()
       at System.Windows.Controls.Button.OnClick()
       at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
       at System.Windows.Controls.Control.OnMouseLeftButtonUp(Control ctrl, EventArgs e)
       at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName)
  InnerException: 

 

 

Coordinator
Oct 1, 2010 at 5:14 PM

If you can send me a demo app, that would be great. Please send it to robertheisenberg at hotmail dot com

Oct 9, 2011 at 12:59 PM
Edited Oct 9, 2011 at 1:51 PM

I'm experiencing this as well. A brief debugging session shows that the problem seems to be that when looking for the target of the action in the ActionMessage.SetMethodBinding method, it iterates the hierarchy of the source element (the button in this case) up until it finds a parent which responds successfully to Action.HasTargetSet.

In the case of the ListBox it finds the root view which has the viewmodel as the target, in the second case the root parent of the button happens to be an element of type PopupRoot, at which point the iteration stops. I think this is due to the nature of the ComboBox, which has a different structure compared to all other ItemControls.

The straightforward workaround is to set the Target manually, for example:

<UserControl x:Name="Root" ... />
[..]
<Button ca:Message.Attach="Remove($dataContext)" ca:Action.TargetWithoutContext="{Binding ElementName=Root, Path=DataContext}" />

Oct 9, 2011 at 9:31 PM
Edited Oct 9, 2011 at 10:19 PM

@simone_b is right: this is a known issue with action bubbling triggered by controls living in a visual tree belonging to a Popup (like the one used in the ComboBox template).
The reason is that SL doesn't allow to climb the visual tree "jumping" out of the Popup, which actually defines a subtree separated from the main one.
The same obviously happens with Popup and ContextMenu.
The usual (and unique, as far I know) workaround to this is to artifically "bridge" the datacontext outside the Popup to the controls living within the Popup, as Simone_b suggested.
In particular, setting cal:Action.TargetWithoutContext allows to specify the handler of the action, thus avoiding bubbling at all.

Oct 9, 2011 at 9:38 PM

Ciao Marco, this is exactly what I did, and also notice that I'm doing it with WPF, which apparently has the same behavior.

Oct 9, 2011 at 10:35 PM

Ciao!

Yes, it's a very similar behavior, even though WPF has the additional complexity of LogicalTree, which is also used to resolve DataContext within the scope of an element.
You may want to have a look at this Josh Smith's article http://www.codeproject.com/KB/WPF/ArtificialInheritanceCxt.aspx explaining different tecniques used to obtain the DataContext in different scenarios.

I saw you filed a workitem about this very issue; unfortunately the root cause is something we cannot fix, so the "official" solution is exaclty the one you proposed.
Those scenarios are actually the main reason of having TargetWithoutContext property (in addition to Target): it allows to overcome bubbling and specify action target directly, without affecting DataContext.