Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Craft Your Own Archiver 2 / 3

3.00/5 (4 votes)
5 Dec 2008LGPL34 min read 25.1K   307  
This article explain how to create virtual filelist, directory tree and how to implement drag and drop using CAKE3.
Image 1

 


This tutorial include 3 parts,

  1. Archive Operations 
  2. File List, Directory Tree and Drag n Drop   (This Article) 
  3. 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 

C#
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 :
C#
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.
C#
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
C#
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.
C#
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.

C#
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.
C#
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.
C#
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.
C#
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.

C#
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.
C#
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.  



License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)