我试图实现一个非常简单的任务:使用右键单击上下文菜单将 ListView 单元格的文本复制到剪贴板,但该任务在 WPF 中似乎极其复杂。我读了几个话题和答案,也做了很多尝试,也问过ChatGPT,但总是有问题。
尝试 1 - 将上下文菜单放入单元格模板中:它找不到原点“RelativeSource FindAncestor,AncestorType ='System.Windows.Controls.ListView',AncestorLevel = 1”
尝试2 - 通过ListView的样式设置器设置ContextMenu:命令已执行,但参数为空
尝试3 - 使用静态命令:命令已执行,但参数为空
尝试 4 - 设置 ListView 的 ContextMenu 附加属性:调试输出表示找不到元素“myListView”,并且表示在“TestWindow1ViewModel”类型的对象中找不到“CopyToClipboardCommand”属性(?)(即使所有内容都正确连接,因为我可以在 XAML 中正确地“F12”所有内容)
尝试5 - 设置ListView的ContextMenu附加属性并使用静态命令:命令已执行,但参数为空
请告诉我这些尝试出了什么问题,以及如何告诉 ContextMenu 考虑当前选定的 listView 项目,或者更好地将上下文菜单与 listView 中右键单击的元素关联起来的最佳方法是什么,然后运行命令与关联的“人”作为命令参数,谢谢。 我的 BaseViewModel 和 RelayCommand 类可以正常工作,因为我也在其他类中成功使用了它们。 我已经在 XAML 中评论了所有这些尝试,这是代码:
Person.cs
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
TestWindow1ViewModel.cs
public class TestWindow1ViewModel : BaseViewModel
{
private ObservableCollection<Person> persons;
public ObservableCollection<Person> Persons
{
get => persons;
set => SetField(ref persons, value);
}
public TestWindow1ViewModel()
{
Persons = new ObservableCollection<Person>(new List<Person>
{
new Person { Id = 1, Name = "John" },
new Person { Id = 2, Name = "Paul" }
});
CopyToClipboardCommand = new RelayCommand(CopyToClipboardCommandExecute);
}
public ICommand CopyToClipboardCommand;
private void CopyToClipboardCommandExecute(object parameter)
{
if (!(parameter is string stringParameter)) return;
Clipboard.SetText(stringParameter);
}
}
TestWindow1.xaml
<Window x:Class="MyNamespace.TestWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyNamespace"
mc:Ignorable="d"
Title="TestWindow1" Height="450" Width="800">
<Window.Resources>
<local:TestWindow1ViewModel x:Key="DefaultViewModel"/>
<Style TargetType="{x:Type ListView}">
<Setter Property="View">
<Setter.Value>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:Person}">
<Grid>
<TextBlock Text="{Binding Id}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:Person}">
<TextBlock Text="{Binding Name}">
<!--<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="ATTEMPT 1"
Command="{Binding CopyToClipboardCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"
/>
</ContextMenu>
</TextBlock.ContextMenu>-->
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</Setter.Value>
</Setter>
<!--<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="ATTEMPT 2"
Command="local:TestWindow1StaticCommands.StaticCommand"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"/>
</ContextMenu>
</Setter.Value>
</Setter>-->
</Style>
</Window.Resources>
<Window.DataContext>
<StaticResource ResourceKey="DefaultViewModel"/>
</Window.DataContext>
<Grid>
<ListView x:Name="myListView" ItemsSource="{Binding Path=Persons}">
<!--<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="ATTEMPT 3"
Command="local:TestWindow1StaticCommands.StaticCommand"
CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>-->
<!--<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="ATTEMPT 4"
Command="{Binding CopyToClipboardCommand}"
CommandParameter="{Binding SelectedItem, ElementName=myListView}" />
</ContextMenu>
</ListView.ContextMenu>-->
<!--<ListView.ContextMenu>
<ContextMenu DataContext="{Binding ElementName=myListView}">
<MenuItem Header="ATTEMPT 5"
Command="local:TestWindow1StaticCommands.StaticCommand"
CommandParameter="{Binding SelectedItem, ElementName=myListView}" />
</ContextMenu>
</ListView.ContextMenu>-->
</ListView>
</Grid>
</Window>
TestWindow1StaticCommands.cs
public static class TestWindow1StaticCommands
{
public static RelayCommand StaticCommand = new RelayCommand(x =>
Clipboard.SetText(x.ToString()));
}
问题是,
ContextMenu
是在自己的窗口中渲染的,因此没有可以依赖的相对的ListView
。
您可以使用
RoutedUICommand
和 CommandBinding
之类的 来实现此目的
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<Style TargetType="{x:Type ListView}">
<Setter Property="View">
<Setter.Value>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:Person}">
<Grid>
<TextBlock Text="{Binding Id}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:Person}">
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Copy"
CommandParameter="Name" />
</ContextMenu>
</TextBlock.ContextMenu></TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<ListView ItemsSource="{Binding Persons}">
<ListView.CommandBindings>
<CommandBinding CanExecute="PersonNameCopyCommand_CanExecute"
Command="ApplicationCommands.Copy"
Executed="PersonNameCopyCommand_Executed" />
</ListView.CommandBindings>
</ListView>
</Grid>
</Window>
并在代码后面
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void PersonNameCopyCommand_Executed( object sender, ExecutedRoutedEventArgs e )
{
if ( e.Parameter is string par && par == nameof( Person.Name ) && e.OriginalSource is ListViewItem lvi && lvi.DataContext is Person p )
{
Clipboard.SetText( p.Name );
}
}
private void PersonNameCopyCommand_CanExecute( object sender, CanExecuteRoutedEventArgs e )
{
if ( e.Parameter is string par && par == nameof( Person.Name ) && e.OriginalSource is ListViewItem lvi && lvi.DataContext is Person p && !string.IsNullOrEmpty( p.Name ) )
{
e.CanExecute = true;
}
}
}
}