Click here to Skip to main content
15,891,951 members
Articles / Web Development / HTML

WPF x FileExplorer x MVVM

Rate me:
Please Sign up or sign in to vote.
4.99/5 (52 votes)
24 Nov 2012LGPL323 min read 290.3K   9.4K   228  
This article describe how to construct FileExplorer controls included DirectoryTree and FileList, using Model-View-ViewModel (MVVM) pattern.
/*
 * CtrlSoft.Windows.Controls.WinProgressDialog
 * 
 * Copyright (c) 2004-2005
 * by Igor V. Velikorossov of CtrlSoft ( http://www.ctrlsoft.net )
 * All Rights Reserved
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions 
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 * 
 */ 
// http://www.dotnet247.com/247reference/msgs/55/276586.aspx
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vcwlkCOMInteropPart1CClientTutorial.asp
//
// http://support.microsoft.com/default.aspx?scid=kb;en-us;823071
//

using System;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Text;

namespace CtrlSoft.Win.UI {

	//	#if (_WIN32_IE >= 0x0500)
	//	/// IProgressDialog
	//	// {F8383852-FCD3-11d1-A6B9-006097DF5BD4}
	//	DEFINE_GUID(CLSID_ProgressDialog,       0xf8383852, 0xfcd3, 0x11d1, 0xa6, 0xb9, 0x0, 0x60, 0x97, 0xdf, 0x5b, 0xd4);
	//	// {EBBC7C04-315E-11d2-B62F-006097DF5BD4}
	//	DEFINE_GUID(IID_IProgressDialog,        0xebbc7c04, 0x315e, 0x11d2, 0xb6, 0x2f, 0x0, 0x60, 0x97, 0xdf, 0x5b, 0xd4);
	//	#endif // _WIN32_IE >= 0x0500

	#region internal class ProgressDialog
	[ComImport]
	[Guid("F8383852-FCD3-11d1-A6B9-006097DF5BD4")] 
	internal class ProgressDialog {
	}
	#endregion // internal class ProgressDialog

	#region internal interface IProgressDialog
	[ComImport]
	[Guid("EBBC7C04-315E-11d2-B62F-006097DF5BD4")]
	[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
	internal interface IProgressDialog {

		[PreserveSig]
		void StartProgressDialog( 
			/*HWND */		IntPtr	hwndParent, 
			[MarshalAs(UnmanagedType.IUnknown)]
			/*IUnknown* */	object	punkEnableModless, 
			/*DWORD */		uint	dwFlags, 
			/*LPCVOID */	IntPtr	pvResevered
			);

		[PreserveSig]
		void StopProgressDialog();

		[PreserveSig]
		void SetTitle( 
			[MarshalAs(UnmanagedType.LPWStr)]
			/*LPCWSTR */	string pwzTitle 
			);

		[PreserveSig]
		void SetAnimation( 
			/*HINSTANCE */	IntPtr hInstAnimation, 
			/*UINT */		ushort idAnimation
			);

		[PreserveSig]
		[return: MarshalAs(UnmanagedType.Bool)]
		bool HasUserCancelled();

		[PreserveSig]
		void SetProgress( 
			/*DWORD */		uint dwCompleted, 
			/*DWORD */		uint dwTotal
			);
		[PreserveSig]
		void SetProgress64( 
			/*ULONGLONG */	ulong ullCompleted, 
			/*ULONGLONG */	ulong ullTotal
			);

		[PreserveSig]
		void SetLine(
			/*DWORD */		uint	dwLineNum, 
			[MarshalAs(UnmanagedType.LPWStr)]
			/*LPCWSTR */	string pwzString, 
			[MarshalAs(UnmanagedType.VariantBool)]
			/*BOOL */		bool	fCompactPath, 
			/*LPCVOID */	IntPtr	pvResevered
			);

		[PreserveSig]
		void SetCancelMsg( 
			[MarshalAs(UnmanagedType.LPWStr)]
			/*LPCWSTR */	string pwzCancelMsg, 
			/*LPCVOID */	object pvResevered
			);

		[PreserveSig]
		void Timer( 
			/*DWORD */		uint	dwTimerAction, 
			/*LPCVOID */	object	pvResevered
			);

	}
	#endregion // internal interface IProgressDialog

	#region public class WinProgressDialog : IDisposable
	/// <summary>
	/// .NET Wrapper for IProgressDialog interface and COM object
	/// </summary>
	/// <example>This is an example of how to invoke WinProgressDialog
	/// <code>
	/// WinProgressDialog pDialog;
	/// private uint _max, _step = 0;
	///
	/// private void button1_Click(object sender, System.EventArgs e) {
	/// 	this.pDialog = new WinProgressDialog( this.Handle );
	///
	/// 	if( this.pDialog != null ) {
	/// 		this.pDialog.Title			= "Hello world!";
	/// 		this.pDialog.CancelMessage	= "hold on a sec...";
	/// 		this.pDialog.Flags = 
	/// 			WinProgressDialog.IPD_Flags.Normal | 
	/// 			WinProgressDialog.IPD_Flags.Modal |
	/// 			WinProgressDialog.IPD_Flags.NoMinimize |
	/// 			WinProgressDialog.IPD_Flags.AutoTime
	/// 			;
	/// 		this.pDialog.Animation = WinProgressDialog.IPD_Animations.FileMove;
	///
	/// 		this.pDialog.Complete		= ( this._step = 0 );
	/// 		this.pDialog.Total			= ( this._max = DoCalc() );
	///
	/// 		this.pDialog.OnUserCancelled += 
	/// 			new WinProgressDialog.UserCancelledHandler( 
	/// 						pDialog_OnUserCancelled 
	/// 						);
	/// 		this.pDialog.OnBeforeProgressUpdate += 
	/// 			new WinProgressDialog.BeforeProgressUpdateHandler( 
	/// 						pDialog_OnBeforeProgressUpdate 
	/// 						);
	///
	/// 		WinProgressDialog.ProgressOperationCallback progressUpdate =
	/// 			new WinProgressDialog.ProgressOperationCallback( this.DoStep );
	///
	/// 		this.pDialog.Start( progressUpdate );
	/// 	}
	///
	/// 	this.pDialog.Dispose();
	/// }
	///
	/// private uint DoCalc() {
	/// 	// pretend to do some calc 
	/// 	System.Threading.Thread.Sleep( 2000 );
	/// 	// get some biggish number
	/// 	Random rand = new Random();
	/// 	return (uint)rand.Next( 150, 500 );
	/// }
	///
	/// private uint DoStep() {
	/// 	// pretend to do some calc
	/// 	System.Threading.Thread.Sleep( 250 );
	/// 	this._step += 13;
	/// 	return this._step;
	/// }
	///
	/// private void pDialog_OnUserCancelled(object sender, EventArgs e) {
	/// 	// pretend to do some calc 
	/// 	System.Threading.Thread.Sleep( 2000 );
	/// }
	///
	/// private void pDialog_OnBeforeProgressUpdate(object sender, EventArgs e) {
	/// 	this._step++;
	/// 	this.pDialog.SetLine( 
	/// 		WinProgressDialog.IPD_Lines.LineOne,
	/// 		"Line #1: step " + this._step.ToString(), 
	/// 		false
	/// 		);
	/// 	this.pDialog.SetLine( 
	/// 		WinProgressDialog.IPD_Lines.LineTwo,
	/// 		"Line #2: step " + this._step.ToString() + 
	/// 			". ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890", 
	/// 		true
	/// 		);
	///
	/// }
	///	</code>
	/// </example>
	public class WinProgressDialog : IDisposable {

		#region Enums / Consts
		/// <summary>
		/// Flags that control the operation of the progress dialog box
		/// </summary>
		/// <remarks>Flags for IProgressDialog::StartProgressDialog() (dwFlags)</remarks>
		[Flags]
			public enum IPD_Flags : uint {
			/// <summary>
			/// Default normal progress dlg behavior
			/// </summary>
			Normal          = 0x00000000,      // 
			/// <summary>
			/// The dialog is modal to its hwndParent (default is modeless)
			/// </summary>
			Modal           = 0x00000001,      // 
			/// <summary>
			/// automatically updates the "Line3" text with the "time remaining"
			/// </summary>
			/// <remarks>You cant call SetLine3 if you pass this!</remarks>
			AutoTime        = 0x00000002,      // 
			/// <summary>
			/// Do not show the "time remaining" if this is set. We need this if dwTotal &lt; dwCompleted for sparse files
			/// </summary>
			NoTime          = 0x00000004,      // 
			/// <summary>
			/// Do not have a minimize button in the caption bar
			/// </summary>
			NoMinimize      = 0x00000008,      // 
			/// <summary>
			/// Do not display the progress bar
			/// </summary>
			NoProgressBar   = 0x00000010       // 
		}

		/// <summary>
		/// File operation animations resource IDs in shell32.dll
		/// </summary>
		public enum IPD_Animations : ushort {
			FileMove				= 160,
			FileCopy				= 161,
			FlyingPapers			= 165,
			SearchGlobe				= 166,
			PermanentDelete			= 164,
			FromRecycleBinDelete	= 163,
			ToRecycleBinDelete		= 162,
			SearchComputer			= 152,
			SearchDocument			= 151,
			SearchFlashlight		= 150,
			Custom					= 0,
			NoAnimation				= ushort.MaxValue
		}

		/// <summary>
		/// Line numbers on which to display a message
		/// </summary>
		public enum IPD_Lines : byte {
			/// <summary>
			/// Top line of the dialog
			/// </summary>
			LineOne = 1,
			/// <summary>
			/// Middle line of the dialog
			/// </summary>
			LineTwo,
			/// <summary>
			/// Bottom line of the dialog (usually used for "time remaining")
			/// </summary>
			LineThree
		}

		/// <summary>
		/// Action to take when loading the module
		/// </summary>
		[Flags]
			private enum LoadLibraryExFlags : uint {
			DontResolveDllReferences	= 0x00000001,
			LoadLibraryAsDatafile		= 0x00000002,
			LoadWithAlteredSearchPath	= 0x00000008,
			LoadIgnoreCodeAuthzLevel	= 0x00000010
		}

		/// <summary>
		/// Time Actions (dwTimerAction)
		/// </summary>
		private	const uint	PDTIMER_RESET	= 0x00000001;
		/// <summary>
		/// Maximum message length
		/// </summary>
		private const int	MAX_CAPACITY	= 45;

		#endregion // Enums / Consts


		#region WinAPI imports
		// WinAPI imports
		[DllImport("shlwapi.dll", CharSet=CharSet.Auto)]
		static extern int PathCompactPathEx(
			[Out] 
			StringBuilder pszOut, 
			string	szPath, 
			int		cchMax, 
			int		dwFlags
			);

		[DllImport("kernel32",CharSet=CharSet.Auto,SetLastError=true)]
		static extern IntPtr LoadLibraryEx(
			[MarshalAs(UnmanagedType.LPTStr)] 
			string	lpFileName,
			IntPtr	hFile,
			LoadLibraryExFlags dwFlags
			);

		[DllImport("user32.dll")]
		static extern bool SetForegroundWindow(IntPtr hWnd);

		#endregion // WinAPI imports


		#region events and delegates
		/// <summary>
		/// Handler for <see cref="OnUserCancelled"/> event
		/// </summary>
		public delegate void UserCancelledHandler( object sender, EventArgs e );
		/// <summary>
		/// Event to be fired when a user cancels the dialog
		/// </summary> 
		public event UserCancelledHandler OnUserCancelled;

		/// <summary>
		/// Handler for <see cref="OnBeforeProgressUpdate"/> event
		/// </summary>
		public delegate void BeforeProgressUpdateHandler( object sender, EventArgs e );
		/// <summary>
		/// Event to be fired before <see cref="ProgressOperationCallback"/> function is called
		/// </summary> 
		public event BeforeProgressUpdateHandler OnBeforeProgressUpdate;

		/// <summary>
		/// Callback function which performs some activities and updates the progress information
		/// </summary>
		public delegate uint ProgressOperationCallback();

		#endregion // events and delegates


		private IProgressDialog		m_ipDialog		= null;
		private IPD_Flags			m_flags			= IPD_Flags.Normal;
		private IPD_Animations		m_animation		= IPD_Animations.NoAnimation;
		private IntPtr				m_hwnd			= IntPtr.Zero;
		private IntPtr				m_hinstance 	= IntPtr.Zero;
		private string				m_title			= string.Empty;
		private string				m_cancelMsg		= string.Empty;
		private ulong				m_dwComplete	= 0;
		private ulong				m_dwTotal		= 0;
		private bool				m_useULong		= false;
		private bool				m_dlgClosed		= true;
		private bool				m_disposed		= false;


		#region public WinProgressDialog( IntPtr hWnd )
		/// <summary>
		/// .ctor
		/// </summary>
		/// <param name="hWnd">Owner's handle</param>
		public WinProgressDialog( IntPtr hWnd ) {
			this.m_hwnd = hWnd;
			//	IProgressDialog * ppd;
			//	CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_IProgressDialog, (void **)&ppd);
			ProgressDialog pDialog	= new ProgressDialog();
			this.m_ipDialog			= (IProgressDialog) pDialog;
		}
		/// <summary>
		/// Destructor
		/// </summary>
		~WinProgressDialog() {
			if( !this.m_disposed )
				this.Dispose();
		}
		#endregion // public WinProgressDialog( IntPtr hWnd )


		#region public IPD_Animations Animation
		/// <summary>
		/// Gets or sets an AVI clip that will run in the dialog box
		/// </summary>
		public IPD_Animations Animation {
			get{ return this.m_animation; }
			set{ 
				if( this.m_animation == value )
					return;
				this.m_animation = value;

				if( this.m_animation != IPD_Animations.NoAnimation ) {
					if( this.m_hinstance == IntPtr.Zero )
						this.m_hinstance = LoadLibraryEx( "shell32.dll\0", 
							IntPtr.Zero,
							LoadLibraryExFlags.LoadLibraryAsDatafile | 
							LoadLibraryExFlags.DontResolveDllReferences
							);

					if( this.m_hinstance == IntPtr.Zero )
						throw new Exception( "Failed to map shell32.dll resource" );

					// Set the title of the dialog.
					//	ppd->SetTitle(L"My Slow Operation");  
					this.m_ipDialog.SetAnimation( this.m_hinstance, (ushort)this.m_animation ); 
				}
				else 
					this.m_ipDialog.SetAnimation( IntPtr.Zero, 0 ); 
			}
		}
		#endregion // public IPD_Animations Animation

		#region public string CancelMessage
		/// <summary>
		/// Gets or sets a message to be displayed if the user cancels the operation
		/// </summary>
		public string CancelMessage {
			get{ return this.m_cancelMsg; }
			set{ 
				if( this.m_cancelMsg == value )
					return;
				this.m_cancelMsg = value;
				// null-terminate if required
				if( !this.m_cancelMsg.EndsWith("\0") )
					this.m_cancelMsg += '\0';
				// Will only be displayed if Cancel button is pressed.
				//	ppd->SetCancelMsg(L"Please wait while the current operation is cleaned up", NULL);
				this.m_ipDialog.SetCancelMsg( this.m_cancelMsg, null );
			}
		}
		#endregion // public string CancelMessage

		#region public uint Complete
		/// <summary>
		/// Gets or sets application-defined value that indicates 
		/// what proportion of the operation has been completed at 
		/// the time the method was called
		/// </summary>
		public uint Complete {
			get{ return (uint)this.m_dwComplete; }
			set{ 
				this.m_dwComplete	= value; 
				this.m_useULong		= false;
			}
		}
		/// <summary>
		/// Gets or sets application-defined value that indicates 
		/// what proportion of the operation has been completed at 
		/// the time the method was called
		/// </summary>
		/// <remarks>Allows the use of values larger than one DWORD (4 GB)</remarks>
		public ulong Complete64 {
			get{ return this.m_dwComplete; }
			set{ 
				this.m_dwComplete	= value; 
				this.m_useULong		= true;
			}
		}
		#endregion // public uint Complete

		#region public IPD_Flags Flags
		/// <summary>
		/// Gets or sets flags that control the operation of the progress dialog box
		/// </summary>
		public IPD_Flags Flags {
			get{ return this.m_flags; }
			set{ this.m_flags = value; }
		}
		#endregion // public IProgressDialogFlags Flags

		#region public string Title
		/// <summary>
		/// Gets or sets the title of the progress dialog box
		/// </summary>
		public string Title {
			get{ return this.m_title; }
			set{ 
				if( this.m_title == value )
					return;
				this.m_title = value;
				// null-terminate if required
				if( !this.m_title.EndsWith("\0") )
					this.m_title += "\0";
				// Set the title of the dialog.
				//	ppd->SetTitle(L"My Slow Operation");  
				this.m_ipDialog.SetTitle( this.m_title ); 
			}
		}
		#endregion // public string Title

		#region public uint Total
		/// <summary>
		/// Gets or sets application-defined value that specifies 
		/// what value <i>Complete</i> will have when the operation is complete
		/// </summary>
		public uint Total {
			get{ return (uint)this.m_dwTotal; }
			set{ 
				this.m_dwTotal	= value; 
				this.m_useULong	= false;
			}
		}
		/// <summary>
		/// Gets or sets application-defined value that specifies 
		/// what value <i>Complete</i> will have when the operation is complete
		/// </summary>
		/// <remarks>Allows the use of values larger than one DWORD (4 GB)</remarks>
		public ulong Total64 {
			get{ return this.m_dwTotal; }
			set{ 
				this.m_dwTotal	= value; 
				this.m_useULong	= true;
			}
		}
		#endregion // public uint Total


		#region public void SetLine( IPD_Lines line, string text, bool compactPath )
		/// <summary>
		/// Displays a message on a specified line
		/// </summary>
		/// <param name="line">Line on which a message to be displayed</param>
		/// <param name="text">Message's text</param>
		/// <param name="compactPath">Indicates whether a long text needs to be shortened</param>
		public void SetLine( IPD_Lines line, string text, bool compactPath ) {
			// compact path if required
			if( compactPath ) {
				StringBuilder sb = new StringBuilder( MAX_CAPACITY );
				PathCompactPathEx( sb, text, MAX_CAPACITY, 0 );
				text = sb.ToString();
				sb = null;
			}
			// null-terminate if required
			if( !text.EndsWith("\0") )
				text += "\0";
			//	ppd->SetLine(2, L"I'm processing item n", FALSE, NULL);
			this.m_ipDialog.SetLine( (uint)line, text, false, IntPtr.Zero ); 
		}
		#endregion // public void SetLine( IProgressDialogLines line, string text, bool compactPath )

		#region public void Start( ProgressOperationCallback callback )
		/// <summary>
		/// Starts the progress dialog box
		/// </summary>
		/// <param name="callback">Callback function which to be performed on each step</param>
		/// <remarks>This method automatically calls <see cref="Stop"/> upon <see cref="Complete"/> >= <see cref="Total"/></remarks>
		public void Start( ProgressOperationCallback callback ) {
			//	ppd->StartProgressDialog(hwndParent, punk, PROGDLG_AUTOTIME, NULL); // Display and enable automatic estimated time remaining.
			this.m_ipDialog.StartProgressDialog( 
				this.m_hwnd, 
				null, 
				(uint)( this.m_flags ), 
				IntPtr.Zero );

			this.m_dlgClosed = false;

			//	// Reset because CalcTotalUnitsToDo() took a long time and the estimated time
			//	// is based on the time between ::StartProgressDialog() and the first
			//	// ::SetProgress() call.
			//	ppd->Timer(PDTIMER_RESET, NULL);
			this.m_ipDialog.Timer( PDTIMER_RESET, null );

			//	for (nIndex = 0; nIndex < nTotal; nIndex++) {
			//for( ulong i=0; i<this.m_dwTotal; i++ ) {
			while( this.m_dwTotal > this.m_dwComplete ) {
				//
				//	ppd->SetLine(2, L"I'm processing item n", FALSE, NULL);
				if( OnBeforeProgressUpdate != null ) 
					OnBeforeProgressUpdate( this, EventArgs.Empty );

				//	dwComplete += DoSlowOperation();
				if( callback != null )
					this.m_dwComplete = callback();
				else
					this.m_dwComplete++;
				//
				//	ppd->SetProgress(dwCompleted, dwTotal);
				if( this.m_useULong ) 
					this.m_ipDialog.SetProgress64( this.m_dwComplete, this.m_dwTotal );
				else
					this.m_ipDialog.SetProgress( (uint)this.m_dwComplete, (uint)this.m_dwTotal );
				//	if (TRUE == ppd->HasUserCancelled())
				//		break;
				if( this.m_ipDialog.HasUserCancelled() ) {
					// raise an event if required
					if( OnUserCancelled != null ) {
						OnUserCancelled( this, EventArgs.Empty );
					}
					break;
				}
			}
			//}

			this.Stop();
		}
		#endregion // public void Start( ProgressOperationCallback callback )

		#region public void Stop()
		/// <summary>
		/// Stops the progress dialog box and removes it from the screen
		/// </summary>
		public void Stop() {
			if( this.m_dlgClosed )
				return;
			//	ppd->StopProgressDialog();
			this.m_ipDialog.StopProgressDialog();
			// looks like after the dialog is closed the owner window gets lost behind other windows
			//SetForegroundWindow( this.m_hwnd );
			// flag dialog as closed
			this.m_dlgClosed = true;
		}
		#endregion // public void Stop()


		#region IDisposable Members

		/// <summary>
		/// Disposes the object and releases resources used
		/// </summary>
		public void Dispose() {
			if( this.m_disposed )
				return;
			// make sure the dialog is closed
			if( !this.m_dlgClosed )
				this.Stop();
			this.m_ipDialog = null;
			this.m_disposed = true;
		}

		#endregion
	}
	#endregion // public class WinProgressDialog : IDisposable

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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