将命令绑定到上下文菜单项
Bind Command To ContextMenu Item
我收到这个 error/warning:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=DrivesListView'
当我按下 'Refresh' 时,命令没有触发。我猜因为它是一个 ContextMenu,我需要以某种方式访问绑定路径中的父控件,然后我可以使用 MouseDownCommand
.
MouseDownCommand
位于我的选项卡视图模型中,TabItemViewModel
。我的 MainWindowViewModel
包含 TabItemViewModel
的列表,这是 TabControl
项目的来源。
我试过的:
1)
我试过设置 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;
}
问题在于我似乎无法从 grid
中获取 tabControl
。 .Parent
只是 returns null
出于某种未知原因。
2)
我也试过设置控件的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>
这是我的 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>
这是ViewModel.cs
public ICommand MouseDownCommand
=> new RelayCommand<object>(e => MouseDown(e));
private void MouseDown(object commandParameter)
{
}
首先,MouseDown
事件在MenuItem
的情况下似乎效果不佳。因此,您需要使用 PreviewMouseDown
或可能的 Click
事件替代。此外,您不能使用 ElementName 来引用 ContextMenu
.
之外的元素
然后,假设 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>
如 the documentation 所述,[the] 菜单 [...] 特定于控件的上下文。
换句话说,ContextMenu
与其父控件(此处为 ListView
)具有相同的数据上下文。
要遵循 MVVM 模式,您只需要在 ListView
的数据上下文中添加一个 ICommand
并将其绑定到 MenuItem.Command
属性.
观点:
<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>
视图模型:
public class ViewModel
{
public ViewModel()
{
RefreshCommand = new ActionCommand(Refresh);
}
public IReadOnlyList<DriveViewModel> Drives { get; }
public ICommand RefreshCommand { get; }
private void Refresh()
{
// ...
}
}
可用的工作演示 here。
我收到这个 error/warning:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=DrivesListView'
当我按下 'Refresh' 时,命令没有触发。我猜因为它是一个 ContextMenu,我需要以某种方式访问绑定路径中的父控件,然后我可以使用 MouseDownCommand
.
MouseDownCommand
位于我的选项卡视图模型中,TabItemViewModel
。我的 MainWindowViewModel
包含 TabItemViewModel
的列表,这是 TabControl
项目的来源。
我试过的:
1)
我试过设置 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;
}
问题在于我似乎无法从 grid
中获取 tabControl
。 .Parent
只是 returns null
出于某种未知原因。
2)
我也试过设置控件的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>
这是我的 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>
这是ViewModel.cs
public ICommand MouseDownCommand
=> new RelayCommand<object>(e => MouseDown(e));
private void MouseDown(object commandParameter)
{
}
首先,MouseDown
事件在MenuItem
的情况下似乎效果不佳。因此,您需要使用 PreviewMouseDown
或可能的 Click
事件替代。此外,您不能使用 ElementName 来引用 ContextMenu
.
然后,假设 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>
如 the documentation 所述,[the] 菜单 [...] 特定于控件的上下文。
换句话说,ContextMenu
与其父控件(此处为 ListView
)具有相同的数据上下文。
要遵循 MVVM 模式,您只需要在 ListView
的数据上下文中添加一个 ICommand
并将其绑定到 MenuItem.Command
属性.
观点:
<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>
视图模型:
public class ViewModel
{
public ViewModel()
{
RefreshCommand = new ActionCommand(Refresh);
}
public IReadOnlyList<DriveViewModel> Drives { get; }
public ICommand RefreshCommand { get; }
private void Refresh()
{
// ...
}
}
可用的工作演示 here。