Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UWP Databinding: How to set button command to parent DataContext inside DataTemplate

Short explanation of need: I need to fire the command of a button inside a DataTemplate, using a method from the DataContext of the ViewModel.

Short explanation of problem: The templated button command only seems to be bindable to the datacontext of the item itself. The syntax used by WPF and Windows 8.1 apps to walk up the visual tree doesn't seem to work, including ElementName and Ancestor binding. I would very much prefer not to have my button command located inside the MODEL.

Side Note: This is built with the MVVM design method.

The below code generates the list of items on the VIEW. That list is one button for each list item.

            <ItemsControl x:Name="listView" Tag="listOfStories" Grid.Row="0" Grid.Column="1"
            ItemsSource="{x:Bind ViewModel.ListOfStories}"
            ItemTemplate="{StaticResource storyTemplate}"
            Background="Transparent"
            IsRightTapEnabled="False"
            IsHoldingEnabled="False"
            IsDoubleTapEnabled="False"
                 />

Inside the page resources of the same VIEW, I have created a DataTemplate, containing the problematic button in question. I went ahead and stripped out most of the formatting inside the button, such as text, to make the code easier to read on this side. Everything concerning the button works, except for the problem listed, which is the binding of the command.

<Page.Resources>
        <DataTemplate x:Name="storyTemplate" x:DataType="m:Story">
            <Button
                Margin="0,6,0,0"
                Width="{Binding ColumnDefinitions[1].ActualWidth, ElementName=storyGrid, Mode=OneWay}"
                HorizontalContentAlignment="Stretch"
                CommandParameter="{Binding DataContext, ElementName=Page}"
                Command="{Binding Source={StaticResource Locator}}">

                <StackPanel HorizontalAlignment="Stretch" >
                    <TextBlock Text="{x:Bind StoryTitle, Mode=OneWay}"
                        FontSize="30"
                        TextTrimming="WordEllipsis"
                        TextAlignment="Left"/>
                </StackPanel>
            </Button>
        </DataTemplate>
    </Page.Resources>

Because this is a DataTemplate, the DataContext has been set to the individual items that comprise the list (MODEL). What I need to do is select the DataContext of the list itself (VIEWMODEL), so I can then access a navigation command.

If you are interested in the code-behind of the VIEW page, please see below.

    public sealed partial class ChooseStoryToPlay_View : Page
    {
    public ChooseStoryToPlay_View()
    {
        this.InitializeComponent();
        this.DataContextChanged += (s, e) => { ViewModel = DataContext as ChooseStoryToPlay_ViewModel; };
    }
    public ChooseStoryToPlay_ViewModel ViewModel { get; set; }
}

I've tried setting it by ElementName, among many other attempts, but all have failed. Intellisense detects "storyTemplate" as an option when ElementName is input, which is the name of the DataTemplate shown in the first code block of this question.

I don't believe my problem can be unique, however I'm having great difficulty finding a solution for UWP. Allow me to apologize in advance in this is a simple question, but I've spent nearly two days researching answers, with none seeming to work for UWP.

Thank you guys!

like image 384
RAB Avatar asked Jan 23 '16 20:01

RAB


2 Answers

What MVVM toolkit are you using (if any)? In MVVM Light, you can get a hold of ViewModel from DataTemplate same way you set DataContext for your view:

<DataTemplate x:Key="SomeTemplate">
    <Button Command="{Binding Main.MyCommand, Source={StaticResource ViewModelLocator}}"/>
</DataTemplate>
like image 140
Andrei Ashikhmin Avatar answered Nov 13 '22 11:11

Andrei Ashikhmin


It really is unfortunate that there is no ancestor binding in UWP. This makes scenarios like yours much more difficult to implement.

The only way I can think of is to create a DependencyProperty for ViewModel on your Page:

public ChooseStoryToPlay_ViewModel ViewModel
{
    get { return (ChooseStoryToPlay_ViewModel)GetValue(ViewModelProperty); }
    set { SetValue(ViewModelProperty, value); }
}

public static readonly DependencyProperty ViewModelProperty =
    DependencyProperty.Register("ViewModel", typeof(ChooseStoryToPlay_ViewModel), typeof(MainPage), new PropertyMetadata(0));

Now you can bind to it from your data template:

<DataTemplate x:Name="storyTemplate" x:DataType="local:Story">
    <Button
        Margin="0,6,0,0"
        Width="{Binding ColumnDefinitions[1].ActualWidth, ElementName=storyGrid, Mode=OneWay}"
        HorizontalContentAlignment="Stretch"
        CommandParameter="{x:Bind Page}"
        Command="{Binding ViewModel.NavigateCommand, ElementName=Page}">

        <StackPanel HorizontalAlignment="Stretch" >
            <TextBlock Text="{x:Bind StoryTitle, Mode=OneWay}"
                FontSize="30"
                TextTrimming="WordEllipsis"
                TextAlignment="Left"/>
        </StackPanel>
    </Button>
</DataTemplate>

A couple of things to notice:

  • In CommandParameter I assumed that in your Story class there is a Page property that you want to pass as a parameter to your command. You can bind to any other property of Story class here or the class itself.
  • You have to set the name of your page to Page (x:name="Page"), so that you can reference it using ElementName in the data template.
  • I assumed that the command you're calling on the ViewModel is named NavigateCommand and accepts a parameter of the same type as the property bound to CommandParameter:

    public ICommand NavigateCommand { get; } = 
        new RelayCommand<string>(name => Debug.WriteLine(name));
    

I hope this helps and is applicable to your scenario.

like image 34
Damir Arh Avatar answered Nov 13 '22 11:11

Damir Arh