WPF - 使用 ContextMenu、MVVM 和 Command 将 ListView 的项目单元格文本复制到剪贴板

问题描述 投票:0回答:1

我试图实现一个非常简单的任务:使用右键单击上下文菜单将 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 中右键单击的元素关联起来的最佳方法是什么,然后运行命令与关联的“人”作为命令参数,谢谢。 我的 BaseViewModelRelayCommand 类可以正常工作,因为我也在其他类中成功使用了它们。 我已经在 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()));
}
wpf listview mvvm contextmenu clipboard
1个回答
0
投票

问题是,

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;
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.