[英]Bind Command To ContextMenu Item
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
.我猜,因为它是一个 ContextMenu,我需要以某种方式访问绑定路径中的父控件,然后我可以使用MouseDownCommand
。
MouseDownCommand
is located in my tab viewmodel, TabItemViewModel
. MouseDownCommand
位于我的选项卡视图TabItemViewModel
中。 My MainWindowViewModel
contains a list of TabItemViewModel
s, and that is the source of the TabControl
's items.我的MainWindowViewModel
包含TabItemViewModel
的列表,这是TabControl
项目的来源。
I've tried setting the ContextMenu Opened event to set the DataContext manually like this to see if it would fix the DataContext somehow:我尝试设置 ContextMenu Opened 事件来手动设置 DataContext ,看看它是否会以某种方式修复 DataContext :
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
.问题在于我似乎无法从grid
获取tabControl
。 Doing .Parent
just returns null
for some unknown reason.执行.Parent
只是出于某种未知原因返回null
。
I also tried setting the Tag
of the control, which did not work either:我还尝试设置控件的Tag
,但也没有用:
<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这是我的 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这是 ViewModel.cs
public ICommand MouseDownCommand
=> new RelayCommand<object>(e => MouseDown(e));
private void MouseDown(object commandParameter)
{
}
First of all, MouseDown
event doesn't seem to work well in the case of MenuItem
.首先,在MenuItem
的情况下, MouseDown
事件似乎无法正常工作。 So, you need to use PreviewMouseDown
or probaby Click
event instread.因此,您需要使用PreviewMouseDown
或 probaby Click
事件插入。 Also, you cannot use ElementName for referring elements outside of ContextMenu
.此外,您不能使用 ElementName 来引用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:然后,假设ListView
的DataContext
隐式绑定到TabItemViewModel
并且您希望将该ListView
指定为CommandParameter
,可以将其修改如下:
<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>
As stated in the documentation , [the] menu [...] is specific to the context of the control .如文档中所述, [the] menu [...] 特定于控件的上下文。
In other words, the ContextMenu
has the same data context as it's parent control (here the ListView
).换句话说, ContextMenu
具有与其父控件(此处为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.要遵循 MVVM 模式,您只需在ListView
的数据上下文中添加一个ICommand
并将其绑定到MenuItem.Command
属性。
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()
{
// ...
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.