I am getting this error/warning:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=DrivesListView'
When I press 'Refresh', the command does not fire. I am guessing since it is a ContextMenu, I need to somehow access the parent control in the binding path and then I can use MouseDownCommand
.
MouseDownCommand
is located in my tab viewmodel, TabItemViewModel
. My MainWindowViewModel
contains a list of TabItemViewModel
s, and that is the source of the TabControl
's items.
What I've tried:
1)
I've tried setting the ContextMenu Opened event to set the DataContext manually like this to see if it would fix the DataContext somehow:
private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
ContextMenu menu = sender as ContextMenu;
ListView listView = menu.PlacementTarget as ListView;
Grid grid = listView.Parent as Grid;
TabControl tabControl = grid.Parent as TabControl;
menu.DataContext = (TabItemViewModel)tabControl.SelectedItem;
}
The problem with this is the fact that I cannot seem to get the tabControl
from the grid
. Doing .Parent
just returns null
for some unknown reason.
2)
I also tried setting the Tag
of the control, which did not work either:
<ListView Grid.Row="1" Name="DrivesListView" ItemsSource="{Binding Drives}"
Tag="{Binding DataContext,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}">>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Refresh">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<i:InvokeCommandAction
Command="{Binding Path=PlacementTarget.Tag.MouseDownCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
CommandParameter="{Binding ElementName=DrivesListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</ListView.ContextMenu>
<ListView.Style>
<Style TargetType="{x:Type ListView}" BasedOn="{StaticResource MahApps.Styles.ListView}">
<Style.Triggers>
<DataTrigger Binding="{Binding DrivesListViewEnabled}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding DrivesListViewEnabled}" Value="false">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding Path=MouseDoubleClickCommand}"
CommandParameter="{Binding ElementName=DrivesListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.View>
<GridView>
<GridViewColumn x:Name="NameHeader" Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn x:Name="TypeHeader" Header="Type" DisplayMemberBinding="{Binding Type}"/>
<GridViewColumn x:Name="TotalSizeHeader" Header="Total Size" DisplayMemberBinding="{Binding TotalSize}"/>
<GridViewColumn x:Name="FreeSpaceHeader" Header="Free Space" DisplayMemberBinding="{Binding FreeSpace}"/>
</GridView>
</ListView.View>
</ListView>
Here is my XAML
<UserControl x:Class="FileExplorerModuleServer.Views.FileBrowserTabView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--ItemsSource is bound to the 'Tabs' property on the view-
model, while DisplayMemeberPath tells TabControl
which property on each tab has the tab's name -->
<TabControl Name="SubTabControl" Grid.Row="1"
ItemsSource="{Binding Tabs}"
DisplayMemberPath="Header" SelectedIndex="{Binding SelectedIndex}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=TabChangedCommand}"
CommandParameter="{Binding ElementName=SubTabControl}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" HorizontalAlignment="Left" Width="300" Text="{Binding Path}">
</TextBox>
<mah:ProgressRing Grid.Row="1" Height="1">
<mah:ProgressRing.Style>
<Style TargetType="{x:Type mah:ProgressRing}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsLoading}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding IsLoading}" Value="false">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</mah:ProgressRing.Style>
</mah:ProgressRing>
<ListView Grid.Row="1" Name="DrivesListView" ItemsSource="{Binding Drives}">
<ListView.ContextMenu>
<ContextMenu Opened="ContextMenu_Opened">
<MenuItem Header="Refresh">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<i:InvokeCommandAction
Command="{Binding Path=MouseDownCommand}"
CommandParameter="{Binding ElementName=DrivesListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</ListView.ContextMenu>
<ListView.Style>
<Style TargetType="{x:Type ListView}" BasedOn="{StaticResource MahApps.Styles.ListView}">
<Style.Triggers>
<DataTrigger Binding="{Binding DrivesListViewEnabled}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding DrivesListViewEnabled}" Value="false">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding Path=MouseDoubleClickCommand}"
CommandParameter="{Binding ElementName=DrivesListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.View>
<GridView>
<GridViewColumn x:Name="NameHeader" Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn x:Name="TypeHeader" Header="Type" DisplayMemberBinding="{Binding Type}"/>
<GridViewColumn x:Name="TotalSizeHeader" Header="Total Size" DisplayMemberBinding="{Binding TotalSize}"/>
<GridViewColumn x:Name="FreeSpaceHeader" Header="Free Space" DisplayMemberBinding="{Binding FreeSpace}"/>
</GridView>
</ListView.View>
</ListView>
Here is the ViewModel.cs
public ICommand MouseDownCommand
=> new RelayCommand<object>(e => MouseDown(e));
private void MouseDown(object commandParameter)
{
}
CodePudding user response:
As stated in the documentation, [the] menu [...] is specific to the context of the control.
In other words, the ContextMenu
has the same data context as it's parent control (here the ListView
).
To follow the MVVM pattern, you then only need to add a ICommand
in the data context of the ListView
and bind it to the MenuItem.Command
property.
The View:
<ListView ItemsSource="{Binding Drives}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Refresh" Command="{Binding RefreshCommand}" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Type" DisplayMemberBinding="{Binding Type}"/>
<GridViewColumn Header="Total Size" DisplayMemberBinding="{Binding TotalSize}"/>
<GridViewColumn Header="Free Space" DisplayMemberBinding="{Binding FreeSpace, StringFormat='{}{0:0.00}'}"/>
</GridView>
</ListView.View>
</ListView>
The ViewModel:
public class ViewModel
{
public ViewModel()
{
RefreshCommand = new ActionCommand(Refresh);
}
public IReadOnlyList<DriveViewModel> Drives { get; }
public ICommand RefreshCommand { get; }
private void Refresh()
{
// ...
}
}
Working demo available here.
CodePudding user response:
First of all, MouseDown
event doesn't seem to work well in the case of MenuItem
. So, you need to use PreviewMouseDown
or probaby Click
event instread. Also, you cannot use ElementName for referring elements outside of ContextMenu
.
Then, assuming DataContext
of the ListView
is implicitly bound to TabItemViewModel
and you want to specify that ListView
as CommandParameter
, it could be modified as follows:
<MenuItem Header="Refresh">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction
Command="{Binding PlacementTarget.DataContext.MouseDownCommand, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>