Introduction
Recently, I needed a treeview
with checkbox
es to allow the end user the ability to select folders & files and show the "windows" icon associated to them. Since enumerating directories can be painful, I also wanted to display an aero type of indicator over the treeview
, without disabling the entire form.
Background
I'm certainly no genius, and I find the older I get, the more forgetful I am. I often search CodeProject for tidbits that will help me solve a problem I'm working on or refresh my memory on something I've done...and have forgotten. Since I've been a member since 2006, I guess it's time I give a bit of something back! This is not the full implementation of what I'm working on, but it is one of the controls I've created as my template for the other controls to implement.
While searching CodeProject, I found the article Obtaining (and managing) file and folder icons using SHGetFileInfo in C#, submitted by Paul Ingles, which is a very good article on obtaining the "windows" icon. I also found the article AJAX-style Asynchronous Progress Dialog for WinForms, submitted by Nathan B. Evans, which is a very good article on implementing an aero type indicator over a Windows Form. I highly recommend that you review both articles.
While I liked the work that Nathan did with his animation, I wanted to use pre-built .gif images within the control instead of overlaying the entire form, and I wanted the ability to use the same animation that I've used elsewhere. Implementing the animated .gifs in the PictureBox
control was futile when a background process was initiated. This led me to use a .avi, like Windows does, and thus the Microsoft.DirectX.AudioVideoPlayback
which I have used before for other solutions.
The first problem I had to solve was converting the animated .gif to a .avi. For this, I used the Animated GIF Banner Maker, which is software designed for making animated GIF banner files from graphic files. It supports the following image formats: *.bmp, *.gif, *.jpg, *.tga, *.png, *.ico, *.wmf, *.emf, *.sgi, *.dib, *.icb, *.pcx, *.pcd, *.psp, *.pbm, *.pgm, *.psd, *.ppm, *.psg, *.vst, *.vda, *.tif, *.wbmp, *.rle.
The neat thing about this tool is that it has a utility for converting an animated GIF into an AVI, and I used it to create the AVI include within the source code. Here is the link to their product website, Animated GIF Banner Maker, where you can download a trial. You don't need to purchase it to run the GIF to AVI conversion utility, however, if you need to create an animated GIF... give the trial a go!
There are three controls that make this all work:
- AviPlayerEx.cs. Handles all of the logic for Playing, Stopping & Resetting the
Microsoft.DirectX.AudioVideoPlayback
. - ImageListsEx.cs. Handles all of the logic for retrieving & caching the Windows icons returned from the API calls.
I used Nathan's code as follows (Nathan's copyright is included in the source code):
- I used his snapshoting logic from the
BeginAsyncIndication
method in his AsyncBaseDialog.cs class to create the Gdi32.GetSnapshot()
method. I created this method in the Gdi32.cs file so that I can use this logic with other controls I have created. Since I didn't want the effect on the entire form, I created the TreeViewEx
control, which contains the TreeView
and a PictureBox
. The method takes 2 parameters, the "source" control (in this case, the TreeViewEx.TreeView
control, "ctlTreeView
") and the "target" control (in this case, the TreeViewEx.PictureBox
control "ctlSnapShot
"). - I changed the
Opacity
of the FillBrush
from 225
to 128
to make it a bit more transparent. - I switched from using a
Panel
to using a PictureBox
in the TreeViewEx
control. The TreeViewEx.PictureBox
is 2x2 pixels smaller than the TreeViewEx.TreeView
and offset from its location by 1x1 pixel. When I show the snapshot graphic in the TreeViewEx.PictureBox
, the TreeViewEx.TreeView
simply appears to disable with no shift, retaining the border from the TreeViewEx.TreeView
. The TreeViewEx.PictureBox
also prevents them from executing any other action on the TreeViewEx.TreeView
until the process has completed, and cleanly hides any flickering that occurs while working with the TreeViewEx.TreeView
. - I also did not use his
Blur
or Grayscale
methods, so I removed the logic from my method.
- TreeViewEx.cs. Handles all of the logic for enumerating the drives, folders & files, using the other controls to store icons & show progress.
I used Paul 's code as follows:
- I combined Paul's logic from both classes into my ImageListsEx.cs control class. While I only use the
SmallImageList
for this demo, I can use the same ImageListsEx
control for a ListView
by simply initializing the control with IconSize.Multiple
, and setting the ListView
to use both the SmallImageList
and LargeImageList
properties. - I modified the
GetFolderIcon()
to use the folders path instead of "null
" so that I could get any "special" icons that Windows might be using for a folder instead of the generic open & closed icons. Again, these are cached. - I added the
GetDriveIcon()
method to retrieve the registered Windows icon for each drive type. Again, these are cached. - I removed the "
_extensionList
" HashTable
and used the same functionality provided by the ImageList.Images
ImageListCollection
.
Using the Code
Implementing the control is fairly easy, simply add the AviPlayerEx.cs, ImageListsEx.cs and TreeViewEx.cs files to your project or build them into their own DLL. Drag the TreeViewEx.cs control onto your Form and hookup the following events:
OnBusyStateChanged
. This is a custom delegate BooleanHandler (Boolean value)
event handler that raises a True
when the TreeViewEx
begins processing a request and False
when it has completed the request. This allows you to add custom handling to change control state if you need.
private</span /> void</span /> BusyStateChanged(Boolean</span /> busy) {
this</span />.btnShow.Enabled = this</span />.btnClose.Enabled = !busy;
}
OnPublishException
. This is a custom delegate ExceptionHandler (Exception exception)
event handler that passes an exception from the TreeViewEx
to the parent for processing, logging or display.
private</span /> void</span /> PublishException(Exception exception) {
MessageBox.Show(exception.Message);
}
OnPublishStatus
. This is a custom delegate TextHandler (String value)
event handler that passes string
information from the TreeViewEx
to the parent for displaying what action the TreeViewEx
is currently performing.
private</span /> void</span /> PublishStatus(String</span /> text) {
this</span />.staStatus.Text = text;
}
You will need to implement the WndProc(ref Message m)
that Nathan uses to handle the user resizing, maximizing, minimizing, restoring, or double-clicking the title bar of the Form
...no sense re-inverting the wheel...and his code works!
protected</span /> override</span /> void</span /> WndProc(ref</span /> Message m) {
if</span /> (IsAsyncBusy) {
if</span /> (m.Msg == 0x112 </span />) {
int</span /> w = m.WParam.ToInt32();
if</span /> (w == 0xf120 </span /> || w == 0xf030
</span /> || w == 0xf020
</span />)
return</span />;
</span />
} else</span /> if</span /> (m.Msg == 0xa3 </span />)
return</span />;
</span /> }
base</span />.WndProc(ref</span /> m);
}
You will also need to handle the Cancel
event in the FormClosing(object sender, FormClosingEventArgs e)
method to stop them from closing the form while the TreeViewEx
is processing a request.
private</span /> void</span /> DemoForm_FormClosing(object</span /> sender, FormClosingEventArgs e) {
e.Cancel = this</span />.tvwExplorer.IsAsyncBusy;
}
And lastly, you need to call the TreeViewEx.Initialize()
method within the Form_Load
method of your form to get things rolling.
private</span /> void</span /> DemoForm_Load(object</span /> sender, EventArgs e) {
</span />
</span />
</span />
</span />
</span /> this</span />.tvwExplorer.Selections.Add(@"</span />C:\Windows\addins"</span />);
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span /> this</span />.tvwExplorer.Initialize();
Application.DoEvents();
}
To really "see" it in action, you'll need to drill down to the Windows\System32 folder.
Points of Interest
When running the AviPlayerEx.cs control within the Microsoft Visual Studio IDE, it may throw a LoadLock
was detected error. I have commented the code where this occurs and how to "fix" it. It does not throw the error when you run the executable.
public</span /> void</span /> Open(String</span /> fileName, Size size, Boolean</span /> continous) {
if</span /> (mdxAviPlayer != null</span />) {mdxAviPlayer.Dispose();}
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span />
</span /> mdxAviPlayer = new</span /> Video(fileName);
if</span /> (continous) {mdxAviPlayer.Ending += new</span /> EventHandler(Ending);}
mdxAviPlayer.Owner = this</span />; this</span />.Size = size;
mfContinous = continous;
}
Thank You
Thanks for reading! If you have any modifications, bug fixes or enhancements to the controls that you'd like to share, please post your source snippets and/or ideas in the comments section.
History
- December 1, 2010 - Initial release