在Windows应用程序开发中,经常需要实现文件选择功能。传统的方法是通过对话框让用户选择文件,但这种方式对于选择多个文件来说并不方便。为了提高用户体验,可以使用拖放的方式来选择文件。本文将介绍一个基于MFC的CListCtrl类——CFileDropListCtrl,它允许用户从资源管理器中拖放文件或文件夹到列表控件中。
CFileDropListCtrl 是一个从 CListCtrl 派生的类,它接受从资源管理器拖放的文件和/或文件夹。它可以根据文件扩展名过滤文件类型,解析快捷方式,检查重复项,并且允许通过可选的用户回调函数自定义处理拖放的项。这个类是为了解决 CFileDialog 在多文件选择方面的可用性问题而开发的,并且更适合有经验的用户。
CFileDialog 用于选择文件添加到列表控件中,这在某些情况下是可行的,但是从不同的文件夹中选择多个文件通常很繁琐,尤其是在 NT 和 95 上 CFileDialog 的小尺寸(在 98 和 2000 上可以调整大小)。使用 Windows 资源管理器定位文件并将它们拖放到列表控件上要简单得多,对于有经验的用户来说也更直观。
可以通过替换原始的 CListCtrl 并指定想要接受的拖放项类型,快速地将此功能添加到新项目或现有项目中。默认情况下,列表会自己插入项——对于每一个项调用 CListCtrl::InsertItem(0, csFilename)。这将适用于拥有的任何风格的列表(小图标、大图标、列表、报告)。
请注意,如果关联了一个图像列表,将使用默认图像(索引 0),在报告视图中,文件名将被插入到第一列。如果想要更高级的功能,比如在报告视图中有几列并显示每个文件的大小和属性,可以!给控件一个回调函数,它将在每次合适的文件被拖放时通知——然后由来插入它,详见下文。
CFileDropListCtrl 有两个公共成员用于更新和检索设置:
BOOL SetDropMode(const CFileDropListCtrl::DROPLISTMODE& dropMode);
DROPLISTMODE GetDropMode() const;
结构 DROPLISTMODE:
struct DROPLISTMODE {
UINT iMask;
CString csFileExt;
LPFN_DROP_FILES_CALLBACK pfnCallback;
};
iMask:指定接受哪种类型的项——这些标志的组合:
FileDropListCtrl::DL_ACCEPT_FILES 允许拖放文件
CFileDropListCtrl::DL_ACCEPT_FOLDERS 允许拖放文件夹
CFileDropListCtrl::DL_FILTER_EXTENSION 只接受指定扩展名的文件。在 csFileExt 中指定
CFileDropListCtrl::DL_USE_CALLBACK 为每个拖放的项接收回调,指定在 pfnCallback 中
CFileDropListCtrl::DL_ALLOW_DUPLICATES 即使路径名已经在列表中,也接受路径名(如果通过回调函数处理插入,则忽略)
csFileExt:要过滤的文件扩展名。使用格式 ".extension"。除非指定了 DL_FILTER_EXTENSION,否则忽略。
pfnCallback:回调函数的地址。除非指定了 DL_USE_CALLBACK,否则忽略。
SetDropMode() 返回值:
- TRUE 如果模式成功更改
- FALSE 如果模式无效(指定了 DL_USE_CALLBACK,但没有填充 pfnCallback)。将使用默认设置(接受文件和文件夹,不允许重复项)
在资源编辑器中,向对话框添加一个列表控件,并在“扩展样式”页面上检查“接受文件”属性。
将列表样式设置为“列表”,除非使用回调函数自己插入项。请记住,如果选择“报告”样式,必须在插入任何项之前插入一列。
使用类向导为列表分配一个 CListCtrl 成员变量,例如 m_List。
现在在对话框类的头文件中:
#include "FileDropListCtrl.h"
并将列表成员变量类型从 CListCtrl 更改为 CFileDropListCtrl:
CFileDropListCtrl m_List;
现在可以指定列表控件要接受哪种类型的项。在这种情况下,最好将其放在对话框类的 OnInitDialog() 中:
CFileDropListCtrl::DROPLISTMODE dropMode;
dropMode.iMask = CFileDropListCtrl::DL_ACCEPT_FILES |
CFileDropListCtrl::DL_FILTER_EXTENSION;
dropMode.csFileExt = _T(".txt");
m_List.SetDropMode(dropMode);
这将设置列表只接受扩展名为 ".txt" 的文件。
注意:默认模式是接受所有类型的文件和文件夹,但不允许重复条目。
可选地指定一个回调函数。如果没有这个,控件将负责将项插入到列表中,尽管很简单。如果想要更高级的功能,应该安装一个回调——控件将使用这个在每次合适的文件被拖放时通知——然后由来插入它。
在对话框类中声明回调作为静态成员函数,或者作为全局函数:
static HRESULT CALLBACK OnListFileDropped(CListCtrl* pList,
const CString& csPathname,
const UINT& iPathType );
在 OnInitDialog() 中注册回调以及其他信息——类似于步骤 4:
dropMode.iMask |= CFileDropListCtrl::DL_USE_CALLBACK;
dropMode.pfnCallback = CMyDialog::OnListFileDropped;
以下是一个自定义插入的示例,通过显示拖放文件的大小:
HRESULT CMyDialog::OnListFileDropped(CListCtrl* pList,
const CString& csPathname,
const UINT& iPathType );
{
// 仅对文件执行此操作。
if (CFileDropListCtrl::DL_FILE_TYPE == iPathType)
{
// 获取其大小
CString csFileSize = GetFileSize(csPathname);
// 在第一列插入文件名
int nItem = pList->InsertItem(0, csPathname, IMAGE_INDEX);
// 在第二列插入大小
pList->SetItemText(nitem, 1, csFileSize);
}
return S_OK;
}
所有代码都完成了。现在将输入库 "shlwapi.lib" 添加到项目的链接设置中。这是为了 PathFindExtension(),这是许多有用的 Shell 实用程序函数之一。
处理拖放文件的方法是通用的,可以应用于任何从 CWnd 派生的控件(例如 CEdit)。只需要处理和覆盖 2 条消息——WM_CREATE 和 WM_DROPFILES:
CWnd::OnCreate()——调用 DragAcceptFiles(TRUE) 以注册动态创建的窗口作为拖放目标。
CWnd::OnDropFiles()——处理文件:
以下是一个在子类化的 CEdit 控件中处理 WM_DROPFILES 的示例:
void CMyEdit::OnDropFiles(HDROP dropInfo)
{
// 获取拖放的路径名(文件或文件夹)数量
UINT nNumFilesDropped = DragQueryFile(dropInfo, 0xFFFFFFFF, NULL, 0);
// 迭代它们并做一些有趣的事情
TCHAR szFilename[MAX_PATH + 1];
for (UINT nFile = 0; nFile < nNumFilesDropped; nFile++)
{
// 获取路径名
DragQueryFile(dropInfo, nFile, szFilename, MAX_PATH + 1);
// 用它做一些事情...这只是一个示例
CString csText;
GetWindowText(csText);
SetWindowText(csText + _T("; ") + szFilename);
}
// Windows 分配了文件信息的内存,所以必须清理它
DragFinish(dropInfo);
}
此外,可能希望在处理文件名之前展开任何拖放的快捷方式,所以请查看 CFileDropListCtrl::ExpandShortcut()。它使用 COM 接口 IShellLink 来解析它们。