打造你自己的压缩器 2 / 3





3.00/5 (4投票s)
本文介绍如何使用 CAKE3 创建虚拟文件列表、目录树以及实现拖放功能。

本教程包含 3 部分:
打造你自己的压缩器 2
请注意,在附带的演示中,文件列表和目录树的代码分别位于 FileList.cs 和 FolderTree.cs 中。
我还添加了 IconDispenserMini.cs(用于检索图标)和 CakDataObject.cs(用于拖放)。
什么是虚拟列表视图,为什么需要它。
尝试打开一个包含数千个文件的存档,使用 cyoa1 中的最后一个演示,你会注意到界面会冻结半分钟甚至更长时间。
实际上,系统在返回控件之前正在将所有文件加载到文件列表中。
虚拟列表视图使用另一种机制进行加载,你需要指定列表视图中的项目数量,当用户滚动到尚未创建的项目时,列表视图会(通过事件)请求一个项目。 几年前,当我使用 Delphi 进行开发时,我不得不使用一些第三方组件,现在随着 .NET 框架包含了这个功能,就容易多了。
创建虚拟文件列表
1) listView.VirtualMode = true;
2) listView.RetrieveVirtualItem +=
new RetrieveVirtualItemEventHandler(FileList_RetrieveVirtualItem);
...
3) ContentList contentList = cakdir.Archive_Contents;
4) listView.VirtualListSize = contentList.Count;
....
5) void FileList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
6) {
7) if (e.ItemIndex < cakdir.Archive_Contents.Count)
8) {
9) ContentType ct = cakdir.Archive_Contents[e.ItemIndex];
0) e.item = new ListViewItem(new string[]
{ct.fileName, Utils.SizeInK(ct.fileSize) });
a) }
b) }
- 第 1 行启用虚拟模式,请记住,将其设置为 true 后,你将无法使用 listView.SelectedItem,(而是使用 SelectedIndices)。
- 第 2 行附加一个事件,以便列表视图在需要时请求 ListViewItem。
- 第 3-4 行,不逐个添加 ListViewItem,而是将 VirtualListSize 设置为最大尺寸。
请注意,第 3 行之后,contentList 是 cakdir.Archive_Contents 的同一地址引用,如果你希望它们引用不同的地址,可以使用 Clone() 方法。
- 第 7-10 行,根据 e.ItemIndex 创建一个新的 ListViewItem。
创建文件夹树
首先,我们需要为所有文件夹创建根节点。
1) folderViewNode = new TreeNode(folderView);
2) if (contentList.GetSubdirectoryList().Count > 0)
3) folderViewNode.Nodes.Add("Uncached");
4) treeView.Nodes.Add(folderViewNode);
- 第 1 行和第 4 行创建一个根节点并将其添加到 treeview 的节点列表中。
- 第 2 行和第 3 行添加一个虚拟节点(dummy node),如果存在子目录,则会出现一个“+”按钮,并且该节点是可展开的。
1) void FolderTree_BeforeExpand(object sender,
System.Windows.Forms.TreeViewCancelEventArgs e)
2) {
3) e.Node.Nodes.Clear();
4) string path = Utils.AppendSlash(e.Node.FullPath.Replace(folderView, ""));
5) foreach (string s in contentList.GetSubdirectoryList(path))
6) {
7) TreeNode tn = new TreeNode(s);
8) if (contentList.GetSubdirectoryList(path + s + "\\").Count > 0)
9) tn.Nodes.Add("Uncached");
0) e.Node.Nodes.Add(tn);
a) }
b) }
- 第 5 行,contentList.GetSubdirectoryList() 将返回指定路径的子目录。
GetDirectoryList() 将返回所有目录,这些命令不会更改内容。
1) void FolderTree_AfterSelect(object sender, TreeViewEventArgs e)
2) {
3) if (e.Node == null) return;
4) contentList.Subdir = false;
5) string path = Utils.AppendSlash(e.Node.FullPath.Replace(folderView, ""));
6) contentList.Directory = path;
7) contentList.Mask = "*";
8) contentList.SearchParam = null;
9) }
- 第 6-8 行,Directory、Mask 和 SearchParam 在设置后会更改内容。
Mask 只支持 *。
SearchParam 示例
- n=*.txt(所有文本文件)
- n=*.txt;d<08-01-08(2008 年 1 月 8 日之前的godic文本文档)
- s>102400(大小 > 100kb)
- n=*.txt;l=readme(包含至少一个“readme”的godic文本文档)
3) ContentList contentList = cakdir.Archive_Contents;
3.1) contentList.OnItemChanged += new EventHandler(ContentList_OnItemChanged);
4) listView.VirtualListSize = contentList.Count;
...
5) public void ContentList_OnItemChanged(Object sender, EventArgs e)
6) {
7) listView.VirtualListSize = 0;
8) listView.VirtualListSize = contentList.Count;
9) }
- 第 7-8 行,VirtualListSize 被设置了两次,以避免新旧 contentList.Count 相等。
实现拖放
拖动:从 CYOA 将文件拖到其他应用程序。
1) string[] fileNames = extractFile();
2) DataObject dataObj =new DataObject();
3) dataObj.SetData(DataFormats.FileDrop, fileNames);
4) listView.DoDragDrop(dataObj, DragDropEffects.Move | DragDropEffects.Copy);
- 第 3 行,指定拖动的数据是 FileDrop,以及文件名。
- 第 4 行,执行后,UI 会冻结,直到拖放完成。
经过一些搜索,我找到了一个解决方案。
你需要通过**创建虚拟文件**和文件夹(对于文件夹,你只需要创建根文件夹)来欺骗 DataObjects,让你拥有将要拖放的文件,虚拟文件将在实际放置时被替换。
1) void ListView_ItemDrag(object sender, System.Windows.Forms.ItemDragEventArgs e)
2) {
3) string[] tempPaths = getTempFilePaths();
4) foreach (string path in tempPaths)
5) CreateTemporaryFileName(path);
6) dataObj.SetData(DataFormats.FileDrop, tempPaths);
7) listView.DoDragDrop(dataObj, DragDropEffects.Move | DragDropEffects.Copy);
8) }
然后,你需要创建一个新的 DataObject,并重写其 GetData() 方法。1) public override object GetData(String format)
2) {
3) Object obj = base.GetData(format);
4) bool InDragDrop = (0 != (int)GetData(ShellClipboardFormats.CFSTR_INDRAGLOOP));
5) if (System.Windows.Forms.DataFormats.FileDrop == format &&
6) !InDragLoop && !extracted)
7) {
8) foreach (string item in TempFileList())
9) System.IO.File.Delete(item);
0) cakdir.Extract(....)
a) extracted = true;
b) }
c) return obj;
d) }
- 第 4 行,InDragDrop 是一个开关,它在 dataObj 中查找 CFSTR_INDRAGLOOP 数据。
当用户放置文件时,它会返回 false,因此需要提取项目,这个 CFSTR_INDRAGLOOP 需要你设置它(见下文)。
1) void ListView_QueryContinueDrag(object sender,
System.Windows.Forms.QueryContinueDragEventArgs e)
2) {
3) if (e.KeyState == 0)
4) {
5) dataObj.SetData(ShellClipboardFormats.CFSTR_INDRAGLOOP, 0);
6) e.Action = DragAction.Drop;
7) this.AllowDrop = true;
8) return;
9) }
0) else e.Action = DragAction.Continue;
a) }
- QueryContinueDrag 在拖放操作期间发生,并允许拖动源确定是否应取消拖放操作(msdn)。
因此,如果你的项目拖到不可放置的表面(例如 Panel),它将不会被调用。
- 第 5 行,如上所述,如果 KeyState = 0(没有按下任何键),则将 CFSTR_INDRAGLOOP 设置为 dataObj。
来自 msdn
- 1 - 鼠标左键,
- 2 - 鼠标右键。
- 4 - SHIFT 键。
- 8 - CTL 键。
- 16 - 鼠标中键。
- 32 - ALT 键
- 因此,当用户释放左键时,
- 没有按下任何键,
- 将 CFSTR_INDRAGLOOP 设置为 0,
- 虚拟文件被移除(在 dataObj.GetData 中),
- 文件被提取(在 dataObj.GetData 中),
- 放置操作完成。
放置:从其他应用程序将文件放置到 CYOA。
与拖动相比,这相对简单。
1) void FileList_DragOver(object sender, DragEventArgs e)
2) {
3) if (!e.Data.GetDataPresent(DataFormats.FileDrop) ||
(cakdir == null) || (cakdir.ArchiveName == "")
4) || (!cakdir.CanAdd))
5) {
6) e.Effect = DragDropEffects.None;
7) return;
8) }
9) e.Effect = DragDropEffects.Copy;
0) }
- 第 3 行,GetDataPresent 方法确定要放置的项目是否为文件(DataFormats.FileDrop)。
回顾上面的 dataObj.SetData(DataFormats.FileDrop, ....)。
1) void ListView_DragDrop(object sender, DragEventArgs e)
2) {
3) if (e.Data.GetDataPresent(DataFormats.FileDrop))
4) {
5) cakdir.Add(....)
6) cakdir.List("*");
7) }
8) }
完成这部分后,大部分基本功能应该已经完成,但你可能希望使存档操作(提取/添加)更具响应性,下一篇文章将介绍 cakdir 的线程支持。
历史
2008-05-12 - 首次提交到 CodeProject。