Set TargetObject in CallMethodAction as ViewModel from parent DataContext in XAML - c#

I'm creating a UWP app and so far I have been using the CallMethodAction to call methods from the ViewModel and it worked fine.
But now, I'm trying to use the same method for some buttons inside a ListView, and nothing happens when I click the button. The XAML code is:
<Page.DataContext>
<vm:RoomPageViewModel x:Name="ViewModel" />
</Page.DataContext>
<ListView x:Name="ActuatorListView"
ItemsSource="{x:Bind ViewModel.Room.Actuators}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:Actuator">
<Button x:Name="OnButton" Content="On">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior
EventName="Click"
SourceObject="{Binding ElementName=OnButton}">
<Core:CallMethodAction
MethodName="OnButton_Click"
TargetObject="{Binding ElementName=ViewModel, Mode=OneWay}" />
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have tried several ways to access the parent DataContext, but I found none working.

Because your view model is actually not an element, you can't use it with ElementName for binding.
The right solution in your case would be to give your page a name (x:Name="Page") and bind to its DataContext:
<Core:CallMethodAction
MethodName="OnButton_Click"
TargetObject="{Binding Path=DataContext, ElementName=Page, Mode=OneWay}" />

Related

How can I bind a button to a viewmodel command from within a data template that's in Page.Resources?

In my app using the Windows App SDK, I'm trying to get a button-tapped event to trigger a command in my viewmodel. I have gotten this to work when the data template is between the and on the page, but it won't work when the data template is in Page.Resources. I have to place some data templates in Page.Resources because I need to have multiple data templates in the TreeView and use a DataTemplateSelector to select the correct one based on the type of the item. I've gotten the template selection working, but I just can't get the button binding to work. It looks like the ElementName binding can't find the page name. Can anyone show me what I'm doing wrong?
<Page.Resources>
<DataTemplate x:Key="treeViewSFTemplate" x:DataType="models:SF">
<TreeViewItem ItemsSource="{Binding OwnedSFEs}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SFName}"/>
<Button Content="+">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding ElementName=sfSettingsPage, Path=ViewModel.AddNewSubfactorCommand}"/>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Button>
<Button Content="-"/>
</StackPanel>
</TreeViewItem>
</DataTemplate>
<DataTemplate x:Key="treeViewSFETemplate" x:DataType="models:SFE">
<TreeViewItem>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SFEName}"/>
<Button Content="+">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding ElementName=thisPage, Path=ViewModel.DeleteSFECommand}"/>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Button>
<Button Content="-"/>
</StackPanel>
</TreeViewItem>
</DataTemplate>
</Page.Resources>
And then in the in the page code below:
<TreeView x:Name="MyTreeView"
ItemsSource="{x:Bind ViewModel.SFList}"
ItemTemplateSelector="{StaticResource treeViewDataTemplateSelector}" />
The reason for this behavior is that you wrapped a TreeViewItem in
the DataTemplate. It will cause that TreeViewItem contains sub-TreeViewItem in the Visual Tree and you could not access correct DataContext with element name in your button. Please remove TreeViewItem from your code.
Like:
<DataTemplate x:Key="treeViewSFETemplate" x:DataType="models:SFE">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SFEName}"/>
<Button Content="+">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding ElementName=thisPage, Path=ViewModel.DeleteSFECommand}"/>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Button>
<Button Content="-"/>
</StackPanel>
</DataTemplate>
Assuming the view model MyViewModel for the page is bound to the DataContext property of the page and MyItemViewModel is the datatype of the item in a collection. The CommandParameter binding binds the view model of the item:
<Page
...
x:Name="MyPage"
...
<Page.Resources>
...
<DataTemplate x:Key="MyDataTemplate" x:DataType="vm:MyItemViewModel>
...
<Button
...
Command="{Binding ElementName=MyPage, Path=DataContext.MyCommand}"
CommandParameter="{Binding}"
...
Changes to the value of the property DataContext can be verified by adding an eventhandler after InitializeComponent() in the page constructor:
public MyPage()
{
InitializeComponent();
...
DataContextChanged += MyPage_DataContextChanged;
...
}
void MyPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if (DataContext is MyViewModel vm)
{
}
}

Bind keypress event to ListViewItem in WPF

I have a ListView which populates the view with ListViewItems containing an image and text(file browser). How can I fire a Command when the user presses the 'Enter' key on a selected item while respecting the MVVM design pattern? I've searched and found a few solutions, but none of them seem to work for me.
<ListView ScrollViewer.HorizontalScrollBarVisibility="Hidden"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.ScrollUnit="Item"
Background="#fdfaf4"
Name="filesView"
ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<!-- The image and item name -->
<Grid Width="{Binding ActualWidth, ElementName=filesView, Converter={x:Static converter:GridWidthToListViewWidthConverter.Instance}}"
Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.07*" MinWidth="25" MaxWidth="40" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Drive, file or folder -->
<Image Grid.Column="0"
Margin="0,0,5,0"
Name="itemType"
Source="{Binding Type,
Converter={x:Static converter:HeaderToImageConverter.Instance}}" />
<!-- The text is binded to the image size, so they'll expand/shrink together -->
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
FontSize="{Binding ActualHeight,
ElementName=itemType, Converter={x:Static converter:ImageSizeToFontSizeConverter.Instance}}"
Text="{Binding Name}" />
<!-- The command to enter a drive/folder is called from here -->
<Grid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding EnterCommand, Mode=TwoWay}" />
<KeyBinding Key="Enter" Command="{Binding EnterCommand, Mode=TwoWay}" />
</Grid.InputBindings>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The MouseBinding works just fine. I've tried putting the KeyBinding in the ListView instead of the grid and getting the focused item with the SelectedItem property but still nothing.
Implement the PreviewKeyDown event for the root Grid in the ItemTemplate or the ListViewItem container in the code-behind of the view and simply execute the command from there, e.g.:
private void ListViewItem_PreviewKeyDown(object sender, KeyEventArgs e)
{
var viewModel = DataContext as YourViewModel;
viewModel.YourCommand.Execute(null);
}
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewKeyDown" Handler="ListViewItem_PreviewKeyDown" />
</Style>
</ListView.ItemContainerStyle>
Or implement a behaviour that hooks up the event handler and does the same: https://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF.
Neither approach breaks the MVVM pattern since you are invoking the exact same view model command from the exact same view that the XAML markup is a part of.
MVVM is not about eliminating code from the views, it is about separation of concerns. If you invoke the command using a KeyBinding or an event handler doesn't matter.
Try Gesture="Enter";
<Grid.InputBindings>
<KeyBinding Gesture="Enter" Command="{Binding EnterCommand}" />
</Grid.InputBindings>

Context menu event or trigger of a control inside an itemtemplate of a listview is not fired

I've got following problem. Following situation in my xaml code:
<ListView ItemsSource="{Binding ListViewItems}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Label Content="Test">
<Label.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuItems}">
</ContextMenu>
</Label.ContextMenu>
</Label>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseUp">
<i:InvokeCommandAction Command="{Binding LabelMouseUpCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
After clicking a label no context menu is shown and the trigger does not work as well, LabelMouseUpCommand method is not entered. I fear the listview handles the click itself and does not pass it to the embedded controls.
Is there any way to pass it to the controls. In future i want to add several controls to the itemtemplate and everyone has it own different context menu.
I found the answer for my problem with this stackoverflow article
There the author explains that the contextmenu does not lie in the same visual tree as the listview. Therefore my initial binding can't work because the source can't be found within the visual tree.
Furthermore Sinatr was totally right, the trigger was initially defined for listview, not for the label.
Here is my working code:
<ListView ItemsSource="{Binding ListViewItems}" x:Name="listViewMain">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Label Content="Test">
<Label.ContextMenu>
<ContextMenu ItemsSource="{Binding DataContext.MenuItems, Source={x:Reference listViewMain}}">
</ContextMenu>
</Label.ContextMenu>
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseUp">
<i:InvokeCommandAction Command="{Binding DataContext.LabelMouseUpCommand, Source={x:Reference listViewMain}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Label>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

EventTriggerBehavior Tapped not firing

I'm using MVVM-light approach for Pushpins on my Map.
I have bound the Tapped Event to a Command in my ViewModel.
However the event is not triggered.
All the other commands and properties bind perfectly.
I have also tried as an example to use a regular event, but it's also not firing.
My Command in my VM
private RelayCommand<Check> _showDetailCheckCommand;
public RelayCommand<Check> ShowDetailCheckCommand
{
get
{
Debug.WriteLine("Binded");
return _showDetailCheckCommand ?? (_showDetailCheckCommand = new RelayCommand<Check>((c) =>
{
Debug.WriteLine("Action!");
}));
}
}
In my XAML
<Page
x:Name="pageRoot"
...
<Maps:MapControl x:Name="Map" IsEnabled="False" Margin="0,8,0,8" MapServiceToken="******" LandmarksVisible="False" PedestrianFeaturesVisible="False" TrafficFlowVisible="True" ZoomLevel="16">
<!-- Incidents -->
<Maps:MapItemsControl ItemsSource="{Binding checks}">
<Maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<Image Tag="{Binding}" Source="{Binding image_path}" Maps:MapControl.Location="{Binding geodata, Converter={StaticResource RoadsmartCoordinatesToGeoPointConverter}}" Maps:MapControl.NormalizedAnchorPoint=".5,.5" >
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding DataContext.ShowDetailCheckCommand, ElementName=pageRoot}" CommandParameter="{Binding}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Image>
</DataTemplate>
</Maps:MapItemsControl.ItemTemplate>
</Maps:MapItemsControl>
</Maps:MapControl>
My output log does say:
'Binded'
But when I click on the image on the map the 'Action' is not executed.
Stupid of me.
I had the Map property isEnabled set to false.
Which is confusing because all the Map gestures work, but none of the binded XAML controls on top of it not.
Stupid mistake but nonetheless it might help people.

MVVM Light - Listbox of UserControls, how to know on which ItemIndex a button is clicked

I got a Listbox where each item is a Usercontrol MatchPanel.
That UserControl has a button.
I d like to remove an item when I click on the button of that item.
I used the SelectedItem which is bind to my ViewModel and works well. But sometimes I m able to click on a button of one item without moving the SelectedItem value (the Listbox item dont get focused even if I click on the button of that item...).
Hence I m looking for a way to receive in the command CloseSelectedMatchCommand a parameter which would tell me, for the button I have clicked, at which index of the Listbox it is.
Thanks
Here is my View
<UserControl
DataContext="{Binding ListTradingMatches, Source={StaticResource Locator}}" Height="503.175" Width="409">
<ListBox ItemsSource="{Binding Path=ListMatches}" SelectedItem="{Binding Path=SelectedMatch}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<local:MatchPanel />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is my MatchPanel UserControl
<UserControl x:Class="MatchPanel"
<Label Content="Pts"/>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}},
Path=DataContext.CloseSelectedMatchCommand}" CommandParameter="{Binding}">
</Button>
</Grid>
You can set the DataContext of your UserControl to the item in the DataTemplate:
<DataTemplate>
<StackPanel>
<local:MatchPanel DataContext="{Binding}" />
</StackPanel>
</DataTemplate>
Now the DataContext is set to the item from the collection, you can set the CommandParameter to the relevant property from that object...:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}, Path=DataContext.CloseSelectedMatchCommand}"
CommandParameter="{Binding Id}" />
..., or just the whole object:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}, Path=DataContext.CloseSelectedMatchCommand}"
CommandParameter="{Binding}" />

Categories

Resources