备案 控制台
学习
实践
活动
专区
工具
TVP
写文章
专栏首页 林德熙的博客 WPF 列表右键菜单比较符合 MVVM 的命令绑定方法
2 0

海报分享

WPF 列表右键菜单比较符合 MVVM 的命令绑定方法

群里小伙伴问我如何在 ListView 的右击的时候知道右击的是哪一项,他想要获取对应的行信息。他使用的是 GridView 做的,于是我告诉他需要自己写 ItemContainerStyle 在 GridViewRowPresenter 里添加右键的逻辑。但是这样写不清真,我问到了他的本质问题其实只是想要做右键菜单。刚好我在写一个测试应用,用于测试我的文件下载库,此时需要用到在右击某一个下载项的时候,拿到当前下载项的信息,给出右键菜单。我不使用小伙伴的逻辑,就按照我自己会采用的写法,我认为这样写比较符合 WPF 框架的设计,下面让我告诉大家我的用法,十分简单

我开源了一个文件下载库,原因是我的几个项目里面都有自己的文件下载库,我想要统一这些文件下载库。开源出去可以让更多小伙伴帮我踩坑,开源项目是 https://github.com/dotnet-campus/dotnetCampus.FileDownloader 欢迎小伙伴使用

我需要写一个简单的界面程序用来测试我这个库,我计划替换掉我现在自己使用的FDM工具,这样我如果自己下载炸了,我就会去修我的库

在使用的时候我发现我需要这样的一个功能,我需要在下载完成之后,自己去找下载到哪个文件夹,因此我期望能右击对应的下载项的时候,可以给出右键菜单,点击一下就能打开下载的文件所在的文件夹或者打开下载的文件

刚好我的下载界面用了 GridView 用来显示所有的下载项,代码如下

<ListView ItemsSource="{Binding Path=DownloadFileInfoList}">
    <ListView.View>
        <GridView>
            <GridViewColumn Width="200" Header="文件名" DisplayMemberBinding="{Binding FileName}" />
            <GridViewColumn Width="100" Header="大小" DisplayMemberBinding="{Binding FileSize}"/>
            <GridViewColumn Width="200" Header="进度" DisplayMemberBinding="{Binding DownloadProcess}"/>
            <GridViewColumn Width="100" Header="下载速度" DisplayMemberBinding="{Binding DownloadSpeed}"/>
            <GridViewColumn Width="200" Header="添加日期" DisplayMemberBinding="{Binding AddedTime}"/>
        </GridView>
    </ListView.View>

而此时如果我想要先获取所点击的 GridView 是哪一行,然后弹出右键菜单,设置对应的属性,此时的代码逻辑相对来说很复杂

在 WPF 如此优秀的框架里面怎么也需要提供更清真的方法

先忽略绑定的数据是什么,因为没什么意义。按照需求,咱需要一个右键菜单,好那么先创建一个右键菜单

        <ContextMenu x:Key="DownloadFileContextMenu">
            <MenuItem Header="Open File"></MenuItem>
            <MenuItem Header="Open Folder"></MenuItem>
        </ContextMenu>

右键菜单内容十分简单,通过 Header 给定显示的文本,创建右键菜单之后,那么如何让右键菜单绑定到 ListView 上?只需要通过 ItemContainerStyle 设置给 ListView 的每一项就可以了,如下面代码

<ListView Style="{x:Null}" ItemsSource="{Binding Path=DownloadFileInfoList}">
    <ListView.ItemContainerStyle>
        <Style TargetType="{x:Type ListViewItem}">
            <Setter Property="ContextMenu" Value="{StaticResource DownloadFileContextMenu}"/>
        </Style>
    </ListView.ItemContainerStyle>
    <ListView.View>
        <GridView>
            <GridViewColumn Width="200" Header="文件名" DisplayMemberBinding="{Binding FileName}" />
            <GridViewColumn Width="100" Header="大小" DisplayMemberBinding="{Binding FileSize}"/>
            <GridViewColumn Width="200" Header="进度" DisplayMemberBinding="{Binding DownloadProcess}"/>
            <GridViewColumn Width="100" Header="下载速度" DisplayMemberBinding="{Binding DownloadSpeed}"/>
            <GridViewColumn Width="200" Header="添加日期" DisplayMemberBinding="{Binding AddedTime}"/>
        </GridView>
    </ListView.View>
</ListView>

可以看到,主要的代码如下

    <ListView.ItemContainerStyle>
        <Style TargetType="{x:Type ListViewItem}">
            <Setter Property="ContextMenu" Value="{StaticResource DownloadFileContextMenu}"/>
        </Style>
    </ListView.ItemContainerStyle>

通过 ItemContainerStyle 设置一个样式,在样式里面更改 ContextMenu 的内容就可以了,代码量十分少

还有一个问题是如何让右键菜单知道当前点的哪一项?让右键菜单知道当前选中的是哪个 GridView 的 Row 是很逗比的,因为咱可以使用 WPF 的 DataContext 绑定的方法,让数据一层层分发。在每一个 GridView 的 Row 项里面都会使用 ListView 的 ItemSource 的数据的某一项,而咱按照 MVVM 的思想,应该变更的是数据而不是界面本身

而 DataContext 是在视觉树继承的,也就是在对应的元素的右键菜单也会拿到相同的 DataContext 的值。而我的业务是要右击打开下载项的文件夹或文件,此时的数据可以通过对应行的数据拿到

在 ContextMenu 的菜单里面需要绑定命令,而默认的命令不够好用,咱先磨一下刀,新建一个类,请看代码

    public class DelegateCommand : ICommand
        public Func<object, bool>? CanExecuteDelegate { set; get; }
        public Action<object>? ExecuteDelegate { set; get; }
        public bool CanExecute(object parameter)
            return CanExecuteDelegate?.Invoke(parameter) ?? true;
        public void Execute(object parameter)
            ExecuteDelegate?.Invoke(parameter);
        public event EventHandler? CanExecuteChanged;
    }

通过这个类就可以在 XAML 写绑定命令的资源和代码,请看代码

  <local:DelegateCommand x:Key="OpenFileCommand" ExecuteDelegate="OpenFileCommand_OnExecute" ></local:DelegateCommand>
  <local:DelegateCommand x:Key="OpenFolderCommand" ExecuteDelegate="OpenFolderCommand_OnExecute" ></local:DelegateCommand>

记得在 cs 代码里面添加对应的 OpenFileCommand_OnExecute OpenFolderCommand_OnExecute 方法

        private void OpenFileCommand_OnExecute(object parameter)
            if (!(parameter is DownloadFileInfo downloadFileInfo))
                return;
            // 这里的 parameter 就是对应的右击 ListViewItem 的绑定数据
        private void OpenFolderCommand_OnExecute(object parameter)
            if (!(parameter is DownloadFileInfo downloadFileInfo))
                return;