FYI: ActionMessages can be added to attached events

Topics: Actions & Coroutines
May 18, 2011 at 8:33 PM

I'm trying to adapt a AvalonMVVMBehavior I came across in a discussion at http://avalondock.codeplex.com/discussions/222614 to use caliburn micro's actions. Because actions by default can only be defined on FrameworkElements, I had to find another way. It turned out to be pretty simple by using a RoutedEventTrigger, found at http://joyfulwpf.blogspot.com/2009/05/mvvm-invoking-command-on-attached-event.html...

 

<i:Interaction.Triggers>
    <trg:RoutedEventTrigger RoutedEvent="{x:Static behaviors:AvalonDockMVVM.ContentClosingEvent}">
        <cal:ActionMessage MethodName="ItemClosing"/>
    </trg:RoutedEventTrigger>
</i:Interaction.Triggers>

 

Before I found that, I was about to attempt to extend messaging to support commands...

  cal:Message.Attach="[Command ContentClosing] = [Action ItemClosing]"
Glad I didn't have to go down that rabbit hole. Although it would be nice, since there are so many libraries out there that use commands.

            <ad:DockingManager x:Name="DockManager"
                               cal:Message.Attach="[Event ContentClosing] = [Action ItemClosing]">
                <i:Interaction.Behaviors>
                    <behaviors:AvalonDockMVVM DocumentsSource="{Binding DocumentItems}"
                                              ToolWindowsSource="{Binding ToolItems}"
                                              DockingStyle="{Binding DockableStyleForCurrent}"
                                              Anchor="{Binding AnchorForCurrent}"
                                              DisplayMemberPath="DisplayName"
                                              IconMemberPath="ImagePath"
                                              ItemClosingAction="ItemClosing"
                                               />
                </i:Interaction.Behaviors>
               
                <ad:ResizingPanel Name="Test" Orientation="Vertical" >
                    <ad:ResizingPanel Orientation="Horizontal" >
                        <ad:DockablePane Name="LeftRegion" ad:ResizingPanel.ResizeWidth="200" />
                        <ad:DocumentPane Name="DocumentRegion">
                        </ad:DocumentPane>
                        <ad:DockablePane Name="RightRegion" ad:ResizingPanel.ResizeWidth="200" />
                    </ad:ResizingPanel>
                    <ad:DockablePane Name="BottomRegion" ad:ResizingPanel.ResizeHeight="200" />
                </ad:ResizingPanel>
            </ad:DockingManager>
May 19, 2011 at 9:58 AM

Actually, this hole is not that dark: here you can find a discussion about customizing the trigger parser to obtain the short syntax.


May 19, 2011 at 4:25 PM

Good stuff. Thanks.

I have already modified it to support some additional features...

cal:Message.Attach="[Event SomeEvent] = [Action MyHandler($convertargs)] : [Callback MyCallback]"

To use it, the view must implement the following interface...

 

/// <summary>
/// Denotes a view that is capable of converting certain events into an object that the
/// ViewModel understands.
/// </summary>
public interface ICanConvertEventArgs : IHaveActionMessageCallbacks
{
	/// <summary>
	/// Creates a new object, based on <see cref="context.EventArgs"/>, that the bound method understands.
	/// </summary>
	/// <param name="context">contains EventArgs and other information that may be helpful</param>
	/// <returns>An object that will be passed as a parameter to the view model's bound method</returns>
	object ConvertEventArgs(ActionExecutionContext context);
}

public delegate void ActionMessageCallback(ActionExecutionContext context, object[] arguments, object result);

/// <summary>
/// Denotes a view that has one or more callback methods that can be bound to by <see cref="ActionMessage"/>
/// </summary>
/// <remarks>
/// Callbacks are useful for inspecting the result and/or state of the parameters to determine if anything
/// special should be done with the <see cref="ActionExecutionContext.EventArgs"/>.
/// </remarks>
public interface IHaveActionMessageCallbacks
{
}

The marker interface IHaveActionMessageCallbacks was necessary because I could not figure out any other way to accurately identify the code-behind class when traversing the visual tree. The two interfaces should probably inherit a common marker interface rather than one inheriting the other, because neither feature is really dependent on the other. As it turns out I'm not even using the callback feature, since they are only necessary if I don't want to wrap/encapsulate an event.

 

Coordinator
May 19, 2011 at 5:01 PM

You can usually assume a codebehind is present if you encounter a UserControl of Window in the visual tree.

Coordinator
May 19, 2011 at 5:03 PM

If you are piggybacking this on top of Actions, then you can probably just use the View property from the ActionExecutionContext.

May 19, 2011 at 5:45 PM

Just finding a UserControl didn't seem to me like a good solution because (I believe) they can be nested in the visual tree. But I'll definitely look into the View property of ActionExecutionContext. Thanks.