5
votes

I have seen some answers regarding WP8 or others, however it seems that there is no triggers in WP8.1 (Or I am missing something?)

I have a datatemplate bound from the code (it is a hub datatemplate, and I have a mix of static and dynamic hubsections, therefore this datatemplate needs to be set from the code).

This datatemplate is defined in a separate xaml file, it includes a listbox (or listview) with another datatemplate defined for the items.

I need to bind a command on the item's tap or listbox selectionchanged (or something equivalent). However, the tap event defined in the template is not called, therefore I thought of binding a command on an UI element, but these seems not to support Commands neither interactivity triggers.

Any clue on how to handle that? :)

On the example below I don't get the event Item_Tapped nor ListBox_SelectionChanged, I would anyway prefer to bind one of these to a command in the viewmodel.

<DataTemplate x:Key="HubSectionTemplate">
    <Grid>
        <ListBox ItemsSource="{Binding MyNodes}"
            SelectionChanged="ListBox_SelectionChanged">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Height="64" Tapped="Item_Tapped" >
                        <TextBlock Text="{Binding MyText}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</DataTemplate>

This is how it is used from code:

HubSection hs = new HubSection()
{
    ContentTemplate = Application.Current.Resources[HUBSECTION_TEMPLATE] as DataTemplate,
    DataContext = model,
    Tag = model.UniqueId,
};
Hub.Sections.Insert(firstSectIdx + 1, hs);


public class Model
{
    public Guid UniqueId {get;set;}
    public List<ItemModel> MyNodes {get;set;}
}

public class ItemModel
{
    public string MyText {get;set;}
}

PS: The ItemModel is defined in another assembly and therefore should not be edited (the command should be in the Model class if possible)

--- EDIT ---

In order to simplify the problem, I use the following models:

public class Model
{
    public Guid UniqueId {get;set;}
    public List<ItemModel> MyNodes {get;set;}
    public ICommand MyCommand {get;set;}
}

public class ItemModel
{
    Model _Model;
    public ItemModel(Model m) {_Model = m; }
    public string MyText {get;set;}
    public ICommand MyCommand { get { return _Model.MyCommand; }}
}

And my (temporary) solution is to use a button in the itemtemplate:

    <ListView.ItemTemplate>
        <DataTemplate>
            <Button HorizontalAlignment="Stretch"  Command="{Binding TapCommand}" Height="64">
                <TextBlock Text="{Binding MyText}" />
            </Button>
        </DataTemplate>
    </ListView.ItemTemplate>
1
What about instead of using a StackPanel directly, wrap it in a styled button? That way you have the Button Command binding that you can use to intercept clicks. Another thing, you ask for item tap or selection changed. Beware that the two behave differently, if you select the same item twice, the click will fire but the seleciton changed won't.Fabio Marcolini
Regarding the behaviour it does not really matter as I am building something like a file explorer, therefore if the item is tapped, something should happened and cannot happened twice. If I don't find any other option I will end with using the button, but I don't really like using a button when it should not be one.Jean
I have edited your title. Please see, "Should questions include “tags” in their titles?", where the consensus is "no, they should not".John Saunders
I agree "in general", however this was a quite specific question as I had been able to handle this issue on other XAML plateforms but not on WP8.1. That is why I specified it in the title as it was important.Jean

1 Answers

11
votes

You can use Behaviors SDK.
In Visual Studio go to 'Tools -> Extension and updates' and install Behaviors SDK (XAML). Then reference it in your project using Add reference dialog.
After that add following namespaces to your page:

xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"

Now you can register events like tap on your stack panel using following syntax:

<DataTemplate>
    <StackPanel Orientation="Horizontal" Height="64">
        <TextBlock Text="{Binding MyText}" />
        <interactivity:Interaction.Behaviors>
            <core:EventTriggerBehavior EventName="Tapped">
                <core:InvokeCommandAction  Command="{Binding YourCommand}"/>
            </core:EventTriggerBehavior>
        </interactivity:Interaction.Behaviors>
    </StackPanel>
</DataTemplate>

However this code only works if your Command is defined in your ItemModel class. If you want to bind to the parent element Command, you can try something like this (not tested):

{Binding ElementName=LayoutRoot, Path=DataContext.ParentCommand}

But I would preferer having command on your ItemModel class


Edit: Solution without Behaviors SDK:
If you are using ListView (or something inherited from ListViewBase) you can use ItemClick event. To make it more reusable and Mvvm friendly you can implement your DependencyProperty like this:

public static class ItemClickCommand
{
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command", typeof(ICommand),
        typeof(ItemClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged));

    public static void SetCommand(DependencyObject d, ICommand value)
    {
        d.SetValue(CommandProperty, value);
    }

    public static ICommand GetCommand(DependencyObject d)
    {
        return (ICommand)d.GetValue(CommandProperty);
    }

    private static void OnCommandPropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var control = d as ListViewBase;
        if (control != null)
        {
            control.ItemClick += OnItemClick;                
        }

    }

    private static void OnItemClick(object sender, ItemClickEventArgs e)
    {
        var control = sender as ListViewBase;
        var command = GetCommand(control);

        if (command != null && command.CanExecute(e.ClickedItem))
        {
            command.Execute(e.ClickedItem);
        }
    }
}

Then your ListView will look like this:

<ListView 
    IsItemClickEnabled="True" 
    helpers:ItemClickCommand.Command="{Binding YourCommand}"
    ItemsSource="{Binding MyNodes}"
    ItemTemplate="{StaticResource YourDataTemplate}" />

In this case your child item is passed to your command as a parameter, so it should also solve your problem with your Command defined in parent model.