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

Asynchronous Progress Using Microsoft.DirectX.AudioVideoPlayback in C#

0.00/5 (No votes)
3 Dec 2010CPOL6 min read 35.8K   1.3K  
Custom windows control that mimics Windows 7 diming & asynchronous aero progress animation
Asynchronous DirectX TreeView

Introduction

Recently, I needed a treeview with checkboxes 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.

Animated GIF Banner Maker

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

C#
protected</span /> override</span /> void</span /> WndProc(ref</span /> Message m) {
   if</span /> (IsAsyncBusy) {
      if</span /> (m.Msg == 0x112 /*</span /> WM_SYSCOMMAND */</span />) {
         int</span /> w = m.WParam.ToInt32();
 
         if</span /> (w == 0xf120 /*</span /> SC_RESTORE */</span /> || w == 0xf030 
                         /*</span /> SC_MAXIMIZE */</span /> || w == 0xf020 
                         /*</span /> SC_MINIMIZE */</span />)
            return</span />; //</span /> short circuit
</span /> 
      } else</span /> if</span /> (m.Msg == 0xa3 /*</span /> WM_NCLBUTTONDBLCLK */</span />)
         return</span />; //</span /> short circuit
</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.

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

C#
private</span /> void</span /> DemoForm_Load(object</span /> sender, EventArgs e) {
//</span />----------------------------------------------------------------------------------------
</span />//</span />..If you're persisting their selections, then you need to add them 
</span />//</span /> before initializing the TreeViewEx control. You can use the 
</span />//</span />..	List<string> "Selections" property defined within the TreeViewEx control...
</span />//</span />----------------------------------------------------------------------------------------
</span />	this</span />.tvwExplorer.Selections.Add(@"</span />C:\Windows\addins"</span />);
//</span />----------------------------------------------------------------------------------------
</span />//</span />..	...or you can manage you own List<string> & set/get the property as follows...
</span />//</span />----------------------------------------------------------------------------------------
</span />//</span />	List<string> mySelections = new List<String>();
</span />//</span />	mySelections.Add(@"C:\Windows\addins");
</span />//</span />	this.tvwExplorer.Selections = mySelections;
</span />//</span />----------------------------------------------------------------------------------------
</span />//</span />..	...and when you need to get it back...
</span />//</span />----------------------------------------------------------------------------------------
</span />//</span />	mySelections = this.tvwExplorer.Selections;
</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.

C#
public</span /> void</span /> Open(String</span /> fileName, Size size, Boolean</span /> continous) {
	if</span /> (mdxAviPlayer != null</span />) {mdxAviPlayer.Dispose();}
//</span />------------------------------------------------------------------------------------
</span />//</span />..Within the Visual Studio IDE the following error is thrown when 
</span />//</span />executing "mdxAviPlayer = new Video(fileName)"...
</span />//</span />------------------------------------------------------------------------------------
</span />//</span />..	LoadLock was detected. DLL 'C:\Windows\assembly\GAC\
</span />//</span /> 	Microsoft.DirectX\1.0.2902.0__31bf3856ad364e35\Microsoft.DirectX.dll' 
</span />//</span />..	is attempting managed execution inside OS Loader lock. 
</span />//</span />	Do not attempt to run managed code inside a DllMain or image 
</span />//</span />..	initialization function since doing so can cause the application to hang.
</span />//</span />------------------------------------------------------------------------------------
</span />//</span />..The only work around I've found is the following, since this is an error 
</span />//</span /> being thrown from the OS to the MDA... 
</span />//</span />------------------------------------------------------------------------------------
</span />//</span />..	Within the Visual Studio IDE select "Debug | Exceptions". 
</span />//</span />..	Expand "Managed Debug Assistants". 
</span />//</span />..	Deselect "Thrown" for "Loader Lock".
</span />//</span />..	Select the "OK" button.
</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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)