This tutorial include 3 parts,
- Archive Operations
- File List, Directory Tree and Drag n Drop (This Article)
- Threading support
Craft Your Own Archiver 2
Please noted that in the attached demo, code for filelist and directory tree are placed in FileList.cs and FolderTree.cs,
I ve added IconDispenserMini.cs (for retrieve icon) and CakDataObject.cs (for drag n drop) as well.
What is Virtual Listview, and Why.
Try open an archive with thousands of files, using last demo in cyoa1,
you will notice the interface freeze for half minute or even longer.
actually the system is loading all the files to the file list before returning control.
Virtual listview use another mechanism to load, you have to specify the
number of items in the listview, and when user scroll to an item thats
not created, the listview will ask for an item (using events). Few
years ago when I had to use some 3rd party components when I was
developed using Delphi, it's much easier now as dotNet framework
included this feature.
To Create a Virtual FileList
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) }
- Line 1 enable the virtual mode, remember after you set it to true,
you cannot use listView.SelectedItem, (use SelectedIndices instead).
- Line 2 attach a event so listView can request a ListViewItem when needed.
- Line 3-4, instead of adding ListViewItem one by one, VirtualListSize is set to maxium size.
Keep in mind that after line 3 contentList is reference to same address
of cakdir.Archive_Contents, if you want them to reference different
address you can use the Clone() method.
- Line 7-10, create a new ListViewItem based on e.ItemIndex.
To Create a folder tree
Firstly we have to create the root node for all folder :
1) folderViewNode = new TreeNode(folderView);
2) if (contentList.GetSubdirectoryList().Count > 0)
3) folderViewNode.Nodes.Add("Uncached");
4) treeView.Nodes.Add(folderViewNode);
- Line 1 and 4 create a root node and add it to treeview's node list.
- Line 2 and 3 add a dummy node if there's subdirectory exists, so a [+] button appear and this node is expandable.
And then, when a node (root node or other node) is expanded, generate the subdirectory nodes.
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) }
- Line 5, contentList.GetSubdirectoryList() will return subdirectory of the specified path,
GetDirectoryList() will return all directories, these commands, do not change the contents.
Keep in mind that at this stage, the file list is not updated when you click on a folder, we have to link them up
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) }
- Line 6-8, Directory, Mask and SearchParam do change the contents once set,
Mask support * only,
SearchParam samples :
- n=*.txt (all text file)
- n=*.txt;d<08-01-08 (all text file that before Jan 08, 08)
- s>102400 (size > 100kb)
- n=*.txt;l=readme (all text file which contain at least one "readme" in it's contents)
and the file list should be alerted when updated.
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) }
- Line 7-8, VirtualListSize is set twice to avoid the new and old contentList.Count is equal.
To Implement Drag n Drop
Drag : Drag files from CYOA to other applications.
1) string[] fileNames = extractFile();
2) DataObject dataObj =new DataObject();
3) dataObj.SetData(DataFormats.FileDrop, fileNames);
4) listView.DoDragDrop(dataObj, DragDropEffects.Move | DragDropEffects.Copy);
- Line 3, specify the data dragging is FileDrop, and the fileNames.
- Line 4, after execute, the UI is freezed till the drag drop takes place.
Actually this is the code to support this feature, but the fileName
must exist when you execute this command, this will freeze the
application everytime a user try to drag.
After some googling, I found a solution.
You have to fool the DataObjects that you have the files that is going to drag and drop, by
creating dummy files
and folders (for folders, all you need is to create the root folder),
the dummy files will be replaced when actual drop take place.
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) }
Then you will have to create a new DataObject, and override it's GetData() method.
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) }
- Line 4, InDragDrop is a switch which look for CFSTR_INDRAGLOOP data in the dataObj,
it will return false when user drop the file, thus need to extract the
items, this CFSTR_INDRAGLOOP does require you to set it. (see below)
Then you have to alert the new DataObject to extract when user drop the files.
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 occurs during a drag-and-drop operation and
enables the drag source to determine whether the drag-and-drop
operation should be canceled. (msdn)
thus it wont be called if your item drag on non-droppable surface (e.g. Panel).
- Line 5, As above, CFSTR_INDRAGLOOP is set to the dataObj if KeyState = 0 (nothing is pressed)
Taken from msdn
- 1 - The left mouse button,
- 2 - The right mouse button.
- 4 - The SHIFT key.
- 8 - The CTL key.
- 16 - The middle mouse button.
- 32 - The ALT key
- Thus when user release left button,
- Nothing is pressed,
- Set CFSTR_INDRAGLOOP to 0,
- Dummy files is removed (in dataObj.GetData),
- Files is extracted (in dataObj.GetData),
- Drop operation take place.
Drop : Drop files from other applications to CYOA.
It is relatively simple compared with Drag.
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) }
- Line 3, GetDataPresent method determine make sure the item dropping is a file (DataFormats.FileDrop),
recall the dataObj.SetData(DataFormats.FileDrop, ....) above.
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) }
When you completed this part, most basic functionality should be
completed, but you may want to make the archive operation (extract /
add) more responsive, next article will explain the threading support
for cakdir.
History
12-05-2008 - First submitted to CodeProject.