我开源了一个文件下载库,原因是我的几个项目里面都有自己的文件下载库,我想要统一这些文件下载库。开源出去可以让更多小伙伴帮我踩坑,开源项目是
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;
// 忽略代码
可以看到对应的行的数据就是通过 parameter 参数传入到后台代码的方法,也就是通过命令的参数可以拿到当前右击的 ListViewItem 的数据
那么如何让命令拿到 DataContext 的参数?刚才咱也说到了右键菜单是放在 ListViewItem 的,而 DataContext 是会在视觉树继承的,所以右键菜单的 DataContext 和右击的行的是相同的