Caliburn.Micro attach event handler on event of Behavior

3.5k views Asked by At

I wrote my own Behavior to handle a swipe gesture and put it to the ItemTemplate of a ListView. If a swipe is completed, I raise an event for LeftSwipe or RightSwipe. This event should be handled by my ViewModel.

I use the syntax of Caliburn.Micro to attach handler to an event: cm:Message.Attach="[Event LeftSwipe] = [LeftSwipe($source, $eventArgs)".

This is my Behavior:

public class SwipeInteractionBehavior : DependencyObject, IBehavior
{
    public DependencyObject AssociatedObject { get; private set; }

    public void Attach(DependencyObject associatedObject)
    {
        // ...
    }

    public void Detach()
    {
        // ...
    }

    public event EventHandler LeftSwipe;

    public event EventHandler RightSwipe;

    // ...
    // ...

    private void OnLeftSwipe(FrameworkElement element)
    {
        // ...

        if (LeftSwipe != null)
        {
            LeftSwipe(this, EventArgs.Empty);
        }
    }

    private void OnRightSwipe(FrameworkElement element)
    {
        // ...
        if (RightSwipe != null)
        {
            RightSwipe(this, EventArgs.Empty);
        }

    }
}

This is how I use this Behavior inside of ListViews ItemTemplate:

        <ListView x:Name="Links" IsItemClickEnabled="True" SelectionMode="None" cm:Message.Attach="[Event ItemClick] = [Click($source, $eventArgs)]">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid>
                            <Border x:Name="todoItem" Loaded="Border_Loaded" Background="White">
                                <i:Interaction.Behaviors>
                                    <local:SwipeInteractionBehavior cm:Message.Attach="[Event LeftSwipe] = [LeftSwipe($source, $eventArgs)]" />
                                </i:Interaction.Behaviors>

                                <Grid>
                                    <StackPanel>
                                        <TextBlock Text="{Binding Title}" Style="{ThemeResource ListViewItemContentTextBlockStyle}" />
                                        <TextBlock Text="{Binding Url}" Style="{ThemeResource ListViewItemSubheaderTextBlockStyle}" />
                                        <TextBlock Text="{Binding Tags, Converter={StaticResource ListToString}}" />
                                    </StackPanel>
                                </Grid>
                            </Border>
                        </Grid>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

I ran in an exception if I raise the LeftSwipe event, this is my StackTrace:

System.Exception was not handled.
  HResult=-2146233088
  Message=No target found for method LeftSwipe.
  Source=Caliburn.Micro.Platform
  StackTrace:
       at Caliburn.Micro.ActionMessage.Invoke(Object eventArgs)
       at Caliburn.Micro.TriggerAction`1.Execute(Object sender, Object parameter)
       at Microsoft.Xaml.Interactivity.Interaction.ExecuteActions(Object sender, ActionCollection actions, Object parameter)
       at Microsoft.Xaml.Interactions.Core.EventTriggerBehavior.OnEvent(Object sender, Object eventArgs)
       at ReaderApp.SwipeInteractionBehavior.<>c__DisplayClass5.<OnLeftSwipe>b__4()
       at ReaderApp.Extensions.FrameworkElementExtension.<>c__DisplayClass2.<Animate>b__0(Object s, Object e)
  InnerException: 

ViewModel:

public class MainViewModel : ViewModelBase
{
    private readonly IEventAggregator eventAggregator;
    private readonly Database database;

    public BindableCollection<Link> Links
    {
        get;
        private set;
    }

    public MainViewModel(INavigationService navigationService, IEventAggregator eventAggregator, Database database)
        : base(navigationService)
    {
        this.eventAggregator = eventAggregator;
        this.database = database;

        Links = new BindableCollection<Link>();
    }

    public async void LeftSwipe(object sender, EventArgs e)
    {
        // ...
    }

    public void RightSwipe(object sender, EventArgs e)
    {
        // ...
    }
}
1

There are 1 answers

0
Nigel Sampson On BEST ANSWER

So the problem is that ActionMessage inherits TriggerAction<FrameworkElement> which means it can't attach correctly to SwipeInteractionBehavior.

It's also complicated by the fact there's some major API differences between the WPF / Silverlight / Windows Phone 8 Interactivity SDK and the WinRT Interactivity SDK. You can see a bit of what I mean in the Parser comments.

What I'd recommend is implementing SwipeInteractionBehavior as a Trigger Behaviour much like EventTriggerBehavior, this used to be a separate base class but with WinRT it's still IBehavior, the difference is that it has a property called Actions of type ActionCollection. Stupidly there is no interface is base class enforcing this so Caliburn.Micro is stuck making some assumptions.

You'd then use Interaction.ExecuteActions to trigger those actions, in the end your xaml should look something like.

<Border x:Name="SwipeTarget">
  <i:Interaction.Behaviors>
    <local:SwipeEventBehavior Direction="Left">
      <cm:ActionMessage AssociatedObject="{Binding ElementName=SwipeTarget" Method="LeftSwipe" />
    </local:SwipeEventBehavior>
  </i:Interaction.Behaviors>
</Border>

It's a bit more long winded, but we need to work around the limitations imposed by the changes in the SDK.