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!
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>
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:
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.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With