Introduction
The reason for this article is to show a "pure" .NET wrapper for IProgressDialog [^] unleashed by sytelus in his Using Windows Explorer Progress Dialog In Your Application article.
Whilst the original article is quite good and I liked the idea greatly, I have found it a bit too "slow and heavy" with .tlb imports. I was also unable to find other articles/examples of implementation for .NET, so I decided to see if I can do it myself.
So here is what I have come up with.
Declaration of the Interface and COM for .NET
To start with, I've searched MSDN, Google and its groups for some clues about the interface. I've worked out that I need C# version of:
IProgressDialog * ppd;
CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER,
IID_IProgressDialog, (void **)&ppd);
After some Googling, I have come across an article at MSDN - COM Interop Part 1: C# Client Tutorial [^], which got me going:
[ComImport]
[Guid("F8383852-FCD3-11d1-A6B9-006097DF5BD4")]
internal class ProgressDialog {
}
[ComImport]
[Guid("EBBC7C04-315E-11d2-B62F-006097DF5BD4")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IProgressDialog {
...
}
where respective GUIDs are CLSID_ProgressDialog
and IID_IProgressDialog
.
The next step was translating interface definition from C++ to C#. For those who are interested, it is defined in ShlObj.h.
To cut a long story short, it was done, and a test app was put together without any serious problems except one - problem with marshaling bool
to BOOL
and vice versa. As it turned out there are two sides to the problem - first that there are two different BOOL
in C++ - 2 and 4 bytes in size, whilst bool
is only 1 byte (I'm not entirely sure in numbers, and can't find the page where I've seen it). Secondly, there is a bug in VS.NET 2003.
I tried various marshaling without any luck until Wraith, suggested using [PreserveSig]
attribute. Not surprisingly, it did work for HasUserCancelled()
method, which was the most problematic one.
Wraith, also saved me some time by sharing with us the results of his research of LoadLibraryEx
which allows loading up a DLL as resource without processing of DllMain()
.
.NET Wrapper
Methods
After the hurdles were overcome, it was a matter of wrapping them in some user-friendly manner (especially having MS providing an example along with the interface definition in ShlObj.h).
Instead of exposing all interface methods to the end-user (well, developer that is), I only expose three:
public void Start( ProgressOperationCallback callback )
The callback function is the one which will be doing the work (i.e., file copying, data mining etc.) and reporting the progress done back to the progress dialog.
This function shows a progress dialog, sets title, cancel message, and animation. Upon Complete
reaching Total
, it closes the dialog by calling Stop()
function.
public void Stop()
Stops processing and hides the dialog.
public void SetLine( IPD_Lines line, string text, bool compactPath )
Sets text for a specified line.
Properties
All other interface's methods are substituted by the properties:
Animation
Gets or sets an AVI clip that will run in the dialog box.
CancelMessage
Gets or sets a message to be displayed if the user cancels the operation.
Complete
Gets or sets the application-defined value that indicates what proportion of the operation has been completed at the time the method was called.
Complete64
Gets or sets the application-defined value that indicates what proportion of the operation has been completed at the time the method was called.
Flags
Gets or sets flags that control the operation of the progress dialog box.
Title
Gets or sets the title of the progress dialog box.
Total
Gets or sets application-defined value that specifies what value Complete
will have when the operation is complete.
Total64
Gets or sets application-defined value that specifies what value Complete
will have when the operation is complete.
Events
I've decided to make only two events - OnUserCancelled
and OnBeforeProgressUpdate
.
OnBeforeProgressUpdate
is fired just before the dialog is about to call the callback function, so the program can update text messages if required.
OnUserCancelled
is fired when a user has clicked "Cancel" button.
Using the code
Using the .NET wrapper is pretty much straight forward (so I hope anyway):
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() {
System.Threading.Thread.Sleep( 2000 );
Random rand = new Random();
return (uint)rand.Next( 150, 500 );
}
private uint DoStep() {
System.Threading.Thread.Sleep( 250 );
this._step += 13;
return this._step;
}
Points of Interest
There are a few things that can/need be done, including a way for loading custom resource instead of hard-coded shell32.dll. Later, it can be transformed to a true .NET control with all fancy designer stuff >:).