Click here to Skip to main content
15,885,876 members
Articles / Desktop Programming / Windows Forms

Craft Your Own Archiver 3 / 3

Rate me:
Please Sign up or sign in to vote.
2.47/5 (5 votes)
13 Dec 2008LGPL33 min read 22.5K   285   17   4
This article explain how to use cake3's threading support.
Image 1

 This tutorial include 3 parts,  

  1. Archive Operations 
  2. File List, Directory Tree and Drag n Drop
  3. Threading support (This Article)

Craft Your Own Archiver 3

Windows application generate a thread called main thread when started, in the previous cyoa1 and cyoa2 demo, both UI and Archive operations is run in the main thread. As a thread can only run one execution sequence at a time, if the main thread is occupied by the archive operations (which usually takes a long time), it will be unable to handle the UI work, this will make the screen not updating (and a [Not responding] appear in caption bar)

To avoid this problem, cakdir3 has a number of events, so when an event is called, it will call each event handler one by one, and they update the UI elements, and then when all event handler is called, it continue to do other work. This improve the responsiveness of the application, but the application is still laggy when an archive operation is working.

The another solution is to spawn a new thread for the archive operations, since UI and archive operations run on different threads, the UI is not affected.

To Do Something in a Thread

C#
1) private void DoWork(object data)
2) { //Do Something ... }
.....
3) public void ThreadWork(object data)
4) { 
5)   Thread newThread = new Thread(new ParameterizedThreadStart(DoWork));
6)   newThread.Start(data);
7) }
  • Line 6, when called, will start DoWork() method in a new thread, this will not block the current thread, unless you call newThread.Join();

as the main thread and the newly created thread are two different threads, one should not call the UI elements from the new Thread directly, and should use event instead.

C#
0.9) public EventHandler OnFinished;
1)   private void DoWork(object data)
2.1) {
2.2)    //Do Something ... 
2.3)    if (OnFinished != null)
2.4)      OnFinished(this, new EventArgs());
2.5) }
...
3)   public void ThreadWork(object data)
4)   { 
4.1)   OnFinished += new EventHandler(OnThreadFinished);
5)     Thread newThread = new Thread(new ParameterizedThreadStart(DoWork));
6)     newThread.Start(data);
7)   }
...
8)   public void OnThreadFinished(object sender, EventArgs e)
9)   { 
0)     if (tbMessage.InvokeRequired) 
a)       tbMessage.Invoke(new EventHandler(OnThreadFinished), new object[] { sender, e });
b)     else tbMessage.Text += "Finished" + Environment.NewLine; 
c)   }
  • Line 0-b, the InvokeRequired return whether the current thread (the thread running the code) is not equal to the control's thread (the UI thread).
    If true, it will call the same event again using the control's thread, and InvokeRequired will be called again and it will return false.
    if false, it will do the UI work.

This is how to thread the work, but Cake3 have a lot of events, operations, it will be time consuming to write threading support, thus I wrote a component for that.

Cake3 Thread Support Class (namespace Cake3.Queue)

  • CakdirWorkItem: represent one Extract, Add or Delete operations, support threading itself.
  • CakdirThreadQueue - A first-in, first-out queue for CakdirWorkItem.
  • ThreadQueueMonitor - WinForms control for viewing the CakdirThreadQueue.

To use CakdirWorkItem

C#
1) private CakdirWorkitem workItem;
...
2) ExtractOptions extrOptions = new ExtractOptions(archiveName, extractTo, new string[] { "*" }, allowOverwrite, useFolder);
3) workItem = new CakdirWorkItem(extrOptions);
4) workItem.OnStopWorking += new EventHandler(WorkItem_StopWorking);
5) workItem.Start();
  • Line 2, ExtractOptions is actually the same as cakdir.ExtractOptions, you can pass cakdir.ExtractOptions instead.
  • Line 3, CakdirWorkItem can be creating using a ExtractOptions, AddOptions or DeleteOptions as parameter.
  • Line 4, workItem has all the cakdir events (e.g. startworking, stopworking, progress) remember you have to use the InvokeRequired mentioned above when handling events.
  • Line 5, workItem.Start() will run the operation in a new thread, use workItem.Run() if you want to run in the same thread.

To use CakdirThreadQueue: (CakdirThreadQueue is not used in the demo)

C#
1) public CakdirThreadQueue CTQ;
...
2) CTQ = new CakdirThreadQueue();
3) CTQ.OnQueueChanged = new EventHandler(CTQ_QueueChanged);
...
4) CTQ.Enqueue(workItem);
...
5) public void CTQ_QueueChanged(object sender, EventArgs e)
6) {
7)   if (this.InvokeRequired)
8)     this.Invoke(new EventHandler(CTQ_QueueChanged), new object[] {sender, e});
9)   else
0)   if (CTQ.Count == 0) 
a)     tbMessage.Text += "Finished";
b)   else tbMessage.Text += CTQ.Queue[0].LastMessage; 
c) }
  • Line 3, QueueChanged is called everytime a workItem added, started, stopped, removed.
  • Line 4, will add the workitem to the queue, remember that you dont have to call the Start() method, the queue will start it automatically when it is available.

To Use ThreadQueueMonitor

Drop it to your form (queueMonitor), then call the following to link the CakdirThreadQueue.

C#
1) queueMonitor.RegisterThreadQueue(CTQ); 
You may want to
write your own ThreadQueueMonitor based on Cakdir's one as well, source
code can be found under "Cake3 \ Queue \ ThreadQueueMonitor.cs".

How - tos

Get Path Information

C#
string Current = Directory.GetCurrentDirectory();
string Desktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
string MyDocument = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
string MyApplicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string System = Environment.GetFolderPath(Environment.SpecialFolder.System);
string Framework = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
string Temp = System.IO.Path.GetTempPath();
string CurrentUserPath {  get { RegistryKey rKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList", false);
				string dir = Utils.AppendSlash((string)rKey.GetValue("ProfilesDirectory"));
				string userdir = dir + System.Environment.UserName + "\\";
				if (Directory.Exists(userdir)) return userdir;
				if (Directory.Exists(dir)) return dir;
                                return (new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Personal)).Parent.FullName); } }
string SharedPath  {  get {  RegistryKey rKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList", false);
                             string dir = (string)rKey.GetValue("Public");
                             if (Utils.DirectoryExists(dir)) return dir;
                             return Path.Combine(new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Personal)).Parent.Parent.FullName, "Public");  } }
string ProgramPath { get { System.Reflection.Assembly assembly;
			   assembly = System.Reflection.Assembly.GetExecutingAssembly();
			   if (assembly != null)
				return Path.GetPathRoot(assembly.Location);
			   else
			        return Path.GetPathRoot(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName)); } }

Rename a file or folder in cakdir

C#
private static void RenameFile(Cakdir3 cakdir, string fullPath, string newPath)
{
     string tempPath = Utils.NewTempPath("qzTemp");
     string basePath = Utils.AppendSlash(tempPath);

     cakdir.Extract(fullPath, tempPath, true, true);
     fullPath = Utils.RemoveFrontSlash(fullPath);
     newPath = Utils.RemoveFrontSlash(newPath);
     File.Copy(tempPath + fullPath, tempPath + newPath);

     cakdir.AddOptions.baseFolder = basePath;
     cakdir.AddOptions.addFile = new string[] { basePath + newPath };
     cakdir.AddOptions.addFolder = AddOptions.folderMode.relative;
     cakdir.Add();

     cakdir.Delete(fullPath);

     cakdir.List("*");
}
    
private static void RenameFolder(Cakdir3 cakdir, string fullPath, string newPath)
{
     string path = Utils.AppendSlash(fullPath);
     string lastPath = Utils.ExtractFileName(Utils.RemoveSlash(path));
     if (lastPath == newPath)
       return;

     string tempPath = Utils.NewTempPath("qzTemp");            
     string basePath = Utils.AppendSlash(tempPath + Utils.RemoveFrontSlash(path));


     cakdir.Extract(path + "*", tempPath, true, true);
     cakdir.Delete(path + "*");
     cakdir.AddOptions.baseFolder = basePath;
     cakdir.AddOptions.addFile = new string[] { basePath + "*" };
     cakdir.AddOptions.addFolder = AddOptions.folderMode.relative;
     cakdir.AddToFolder(path.Replace("\\" + lastPath, "") + newPath);
     cakdir.List("*");
}

HotEdit (Allow user to modify a file/path in archive then update, actually just extract, display a dialog and then add.)

C#
internal static void HotEdit(string archive, string path, string hotEditPath)
{
    string tempPath;			
    if (hotEditPath != "")
    {
	Utils.RemoveFile(hotEditPath + path);								
	tempPath = hotEditPath;
    }
    else tempPath = Utils.NewTempPath("qzTemp");
			
    Cakdir3 c3 = new Cakdir3(archive);
    c3.Extract(path, tempPath, true, true);
    path = Utils.RemoveFrontSlash(path);
			
    Utils.OpenDirectory(Utils.ExtractFilePath(tempPath + path));
    string msg = String.Format(
    	"HotEdit started, any changes to the following file will be updated to the archive when you pressed [OK]. \r\n" +
	"\r\n"+				
        "Archive : {0}\r\n" +
	"File    : {1}\r\n\r\n" +
	"Press [OK] to stop monitoring.", archive, tempPath + path);
	MessageBox.Show(msg,  archive, MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1); 
			
     c3.Add(tempPath + path, AddOptions.folderMode.relative, tempPath);				
}

Read and write INI file : (Download qzIniFiles__Cs_.zip - 2.45 KB )

C#
IniFile = new QzIniFiles(iniFile);
IniFile["Section"]["Key"].Value = "aString";
IniFile["Section"]["Key1"].ValueAsInt = 1;
IniFile["Section"]["Key2"].ValueAsBool = true;
Debug.WriteLine(IniFile["Section"]["Key"].Value);
Debug.WriteLine(IniFile["Section"]["Key1"].ValueAsInt);
Debug.WriteLine(IniFile["Section"]["Key2"].ValueAsBool);

ArrayList strings = new ArrayList();
strings.Add("1");
strings.Add("2");
strings.Add("3");
IniFile.WriteStringList("Section", "List", strings);	
ArrayList strings2 = IniFile.ReadStringList("Section","List"));
IniFile.UpdateFile();

Create Shortcut

check here

Get Context Menu Commands for Any File

C#
internal static Hashtable PollShCmdList(string filename)
{
	Hashtable retVal = new Hashtable();			
	if (filename != "")
	{
		string ext = Utils.ExtractFileExt(filename);
		if (ext == "") return retVal;
		
        	RegistryKey rk0 = Registry.ClassesRoot.OpenSubKey(ext, false);
		if (rk0 != null)
		{
			string ftype = (string)rk0.GetValue("");
			rk0.Close();

			if (ftype != null)
			{
				RegistryKey rk = Registry.ClassesRoot.OpenSubKey(ftype + "\\shell\\", false);
				if (rk != null)
				{
				string[] keys = rk.GetSubKeyNames();;
				rk.Close();
					
				foreach (string key in keys)
					if ((key.ToLower() != "printto") &&
					    (key.ToLower() != "runas") &&
					    (key.ToLower() != "file"))
						{
				RegistryKey rk1 = Registry.ClassesRoot.OpenSubKey(ftype + "\\shell\\" + key + "\\command", false);
				string cmdline = (string)rk1.GetValue("");
				rk1.Close();
				if (cmdline != "")
					retVal.Add(key, cmdline);
				}
			}
						
		}
		}
		if (retVal.Count == 0)
			retVal.Add("Open with...", "");
	}
	return retVal;
}

Get Icon for Non-Existing File

C#
[StructLayout(LayoutKind.Sequential)]
 internal struct SHFILEINFO
 { 
            public IntPtr hIcon;
            public IntPtr iIcon;
            public uint dwAttributes;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string szDisplayName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
            public string szTypeName;
};
internal const uint SHGFI_ICON = 0x100;
internal const uint SHGFI_TYPENAME =0x400;
internal const uint SHGFI_LARGEICON = 0x0; // 'Large icon
internal const uint SHGFI_SMALLICON = 0x1; // 'Small icon
internal const uint SHGFI_SYSICONINDEX = 16384;
internal const uint SHGFI_USEFILEATTRIBUTES = 16;
[DllImport("shell32.dll")]
internal static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes,
   ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);

public static Icon GetSmallFileIcon(string fileName)
{
	SHFILEINFO shinfo = new SHFILEINFO();
	
	SHGetFileInfo(fileName, 0, ref shinfo,
              (uint)Marshal.SizeOf(shinfo),SHGFI_ICON |SHGFI_SMALLICON |
              SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES);
	return System.Drawing.Icon.FromHandle(shinfo.hIcon);
}
		
public static Icon GetLargeFileIcon(string fileName)
{
	SHFILEINFO shinfo = new SHFILEINFO();
	
	SHGetFileInfo(fileName, 0, ref shinfo,
	              (uint)Marshal.SizeOf(shinfo),SHGFI_ICON |
	              SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES);
	return System.Drawing.Icon.FromHandle(shinfo.hIcon);
}

Further Reading

History

  • 12-05-2008 - First submitted to CodeProject.
  • 12-14-2008 - Add QzIniFiles, How-tos

License

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


Written By
Founder
Hong Kong Hong Kong

Comments and Discussions

 
GeneralMy vote of 1 Pin
g0got213-Dec-08 19:34
g0got213-Dec-08 19:34 
GeneralMy vote of 2 Pin
#realJSOP5-Dec-08 4:27
mve#realJSOP5-Dec-08 4:27 
AnswerRe: My vote of 2 Pin
Leung Yat Chun5-Dec-08 8:13
Leung Yat Chun5-Dec-08 8:13 
GeneralRe: My vote of 2 Pin
#realJSOP22-Sep-11 1:44
mve#realJSOP22-Sep-11 1:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.