Click here to Skip to main content
15,887,027 members
Articles / Desktop Programming / Windows Forms
Article

TdhCursorFactory - A Class to Provide Animated and Color Cursors to DotNET Applications

Rate me:
Please Sign up or sign in to vote.
4.65/5 (16 votes)
1 Nov 2007CPOL9 min read 58K   649   38   23
This article describes a class to manage animated and color cursors and make them available for use in .NET applications. The cursors may derive from embedded resources, from standard cursors, or from files read from disk.
Screenshot - TdhCursorFactory.gif

Introduction

The .NET System.Windows.Forms.Cursor class doesn't natively support animated or color cursors. (And, if the constructor doesn't throw an exception when passed a color cursor, the colors vanish). But, one can use the LoadCursorFromFileW function of "user32.dll" to get around this constraint.

The 'TdhCursorFactory' Class

The TdhCursorFactory class is built on a basis primarily of code published in this MSDN article: Using Colored and Animated Cursors; and secondarily Bingzhe Quan's Code Project article: A scrollable, zoomable, and scalable picture box. But, then it takes off into parts unknown.

The class is static; its primary purpose is to create .NET usable color and animated cursors. As an added bonus, it can maintain an array of Cursors for easy access by the program making use of the library. The Cursors stored in this array may be used to "override" the standard Cursors of the System.Windows.Forms.Cursors class.

A Note On The LoadCursorFromFileW Function

The LoadCursorFromFileW function of "user32.dll" requires its argument to be the name of an actual file on disk (I was unable to find if there is a similar function which can take a memory stream). Thus, when the methods of TdhCursorFactory must make use of the LoadCursorFromFileW function as part of the process of extracting a Cursor object from the embedded resources (i.e. for animated cursors or color non-animated cursors), the resource is first extracted into a memory stream, which is written to a temporary file. This temporary file is then passed as the argument to the LoadCursorFromFileW function (and deleted afterward).

On the other hand, a black-and-white non-animated cursor may be passed as a memory stream directly to the [new System.Windows.Forms.Cursor(stream)] constructor. By default, the TdhCursorFactory class attempts this with cursors having the ".CUR" extension.

So, effectively, the only real advantage to making animated cursors or color non-animated cursors embedded resources of your project is that since they are "inside" the assembly, they won't get lost.

Using The Class

To use TdhCursorFactory as is, add a reference in your project to the class library 'TDHCursorFactory.dll.' The namespace used in this library is:

C#
using TDHControls.TDHCursorFactory;
//
{
    // Sample usage:
    TdhCursorFactory.SetCursor(TdhCursorFactory.Cursors._Busy, 
        @"C:\SomePath\SomeCursor.ani");
    TdhCursorFactory.SetCursor(TdhCursorFactory.Cursors.WaitCursor, true,
        "Cursors.Cow_chewing.ani", true);
    //  
    // The following statement will return the Cursor loaded from
    // "C:\SomePath\SomeCursor.ani"  
    this.Cursor = TdhCursorFactory.GetCursor(TdhCursorFactory.Cursors._Busy);
    // 
    // At the moment, the following statement will return the 
    // "Cursors.Cow_chewing.ani" animated cursor 

    // (from the library's embedded resources)
    this.Cursor = TdhCursorFactory.GetCursor(
        TdhCursorFactory.Cursors.WaitCursor);
    //  
    TdhCursorFactory.UnsetCursor(TdhCursorFactory.Cursors.WaitCursor);
    // The following statement will now return the standard  
    // System.Windows.Forms.Cursors.WaitCursor Cursor object 
    this.Cursor = TdhCursorFactory.GetCursor(
        TdhCursorFactory.Cursors.WaitCursor);
    //   
    // There is no "EarlyBird.ani" in the Demo project's embedded resources  
    // ("Demo_Cursors" is the name of a folder under the Demo project)  
    // Thus, the following statement will have no effect upon the Cursor  
    // to which the enum value TdhCursorFactory.Cursors._Busy refers 
    TdhCursorFactory.SetCursor(TdhCursorFactory.Cursors._Busy, true,
        "Demo_Cursors.EarlyBird.ani", true);
    //  
    // There is a "Drum.ani" in the Demo project's embedded resources.  
    // However, this statement will not find it (the correct full-name is
    // "Demo_Cursors.Drum.ani")  
    TdhCursorFactory.SetCursor(TdhCursorFactory.Cursors._Busy, true,
        "Drum.ani", true);
    // The following statement will find the "Cursors.Drum.ani"
    // embedded resource  
    // in the Demo project's resources manifest.  
    TdhCursorFactory.SetCursor(TdhCursorFactory.Cursors._Busy, true,
        "Drum.ani", false);
    //   
    // The .FromFile() method always makes use of the LoadCursorFromFileW 

    // function of "user32.dll"  
    this.Cursor = TdhCursorFactory.FromFile(@"C:\SomePath\SomeCursor.cur",
        false);
    //   
    // The .FromResource() method makes use of   
    // * either the LoadCursorFromFileW function of "user32.dll"   
    // * or the the [new System.Windows.Forms.Cursor(stream)] constructor.   
    // If the extension is ".ANI," or if the first argument is 'true,'   
    // the LoadCursorFromFileW function will be used.   
    // Otherwise, an attempt is first made to use  
    // the [new System.Windows.Forms.Cursor(stream)] constructor.   
    // If that attempt fails, the LoadCursorFromFileW function is used.   
    this.Cursor = TdhCursorFactory.FromResource(false, 
        "Demo_Cursors.SomeCursor.cur", true, false);
}

The TdhCursorFactory class was written (and compiled) using VS2002 (.NET 1.0) with the intention that the source code be readily available to other developers regardless of the .NET version they are using.

The members of TdhCursorFactory's interface are:

  • Cursors — The items in the array of Cursors managed by the class are accessed via the TdhCursorFactory.Cursors enum (see below for a list of members).

  • public static System.Windows.Forms.Cursor GetCursor(TdhCursorFactory.Cursors whichCursor)
    This method returns a System.Windows.Forms.Cursor object from the array of cursors managed by the class, as named by the 'whichCursor' value. If that particular cursor has not been set, the method returns the standard System.Windows.Forms.Cursors.Default object.

  • public static void InitCursor_X(TdhCursorFactory.Cursors whichCursor)
    This method initializes the "sub-array" of Cursors referenced by the 'whichCursor' enum value (at this time, only the TdhCursorFactory.Cursors._X_Random_Busy enum member is so defined) to a set of pre-defined Cursor objects obtained from the class's resources manifest. The method is not executed automatically by the class's constructor, but rather if it is desired that these cursors be loaded, the method must be explicitly called by the main program.

  • public static void SetCursor(TdhCursorFactory.Cursors whichCursor, bool forceAsColor, string cursorName, bool caseSensitive)
    This method sets the managed Cursor named by 'whichCursor' to a System.Windows.Forms.Cursor object obtained from the embedded Resources of either: 1) the calling assembly, or 2) the TdhCursorFactory class' assembly. If the 'cursorName' value cannot be found in either assembly (or if it is not a valid Cursor), the method will leave the managed Cursor as is. The 'caseSensitive' value determines whether the 'cursorName' value must be given exactly. When this value is false, the class will examine all the object names contained in the assembly's resources manifest. The value of 'forceAsColor' allows the developer using the class to force ".CUR" files to be loaded via the LoadCursorFromFileW function of "user32.dll." (Cursors with the ".ANI" extension are always loaded via this function).

  • public static void SetCursor(TdhCursorFactory.Cursors whichCursor, string fileName)
    This method sets the managed Cursor named by 'whichCursor' to the System.Windows.Forms.Cursor object read from the file with the path given in 'fileName.' If the given path does not resolve to a valid Cursor, the method will leave the managed Cursor as is.

  • public static void SetCursor(TdhCursorFactory.Cursors whichCursor, System.Windows.Forms.Cursor theCursor)
    This method sets the managed Cursor named by 'whichCursor' to the System.Windows.Forms.Cursor object passed as 'theCursor.'

  • public static void UnsetCursor(TdhCursorFactory.Cursors whichCursor)
    This method releases the managed Cursor named by 'whichCursor.'

  • public static System.Windows.Forms.Cursor FromFile(string fileName, bool allowNull)
    This method returns a System.Windows.Forms.Cursor object read from the file with the path given in 'fileName.' If the given path does not resolve to a valid Cursor, the method will return either a null object or the System.Windows.Forms.Cursors.Default object, depending on the value of 'allowNull.'

  • public static System.Windows.Forms.Cursor FromResource(bool forceAsColor, string cursorName, bool caseSensitive, bool allowNull)
    This method returns a System.Windows.Forms.Cursor object obtained from the embedded Resources of either: 1) the calling assembly, or 2) the TdhCursorFactory class' assembly. If the 'cursorName' value cannot be found in either assembly (or if it is not a valid Cursor), the method will return either a null object or the System.Windows.Forms.Cursors.Default object, depending on the value of 'allowNull.' The 'caseSensitive' value determines whether the 'cursorName' value must be given exactly. When this value is false, the class will examine all the object names contained in the assembly's resources manifest. The value of 'forceAsColor' allows the developer using the class to force ".CUR" files to be loaded via the LoadCursorFromFileW function of "user32.dll." (Cursors with the ".ANI" extension are always loaded via this function).

  • public static System.Collections.ArrayList ResourcesManifest(bool forCallingAssembly)
    This method returns an ArrayList (of string objects) containing the fully qualified names of the ".ANI" and ".CUR" items in the resources manifest of either the calling assembly or the 'TDHCursorFactory.dll' assembly.


As written/assembled, the 'TDHCursorFactory.dll' assembly contains a number of cursors as embedded resources. Since the class as written is geared towards my overall project, you may well want to eliminate many (or all) of these; and you may want to add your own cursors as resources ... aren't you glad you have the source code?

  • Cursors.arrow_l.cur
  • Cursors.arrow_l_blue.cur
  • Cursors.arrow_l_red-outline.cur
  • Cursors.BlindMouse.ani
  • Cursors.busy_l.cur
  • Cursors.counter.ani
  • Cursors.Cow_chewing.ani
  • Cursors.EarlyBird.ani
  • Cursors.FlyingPig.ani
  • Cursors.HamsterWheel.ani
  • Cursors.HamtonJPig3.cur
  • Cursors.HorseRider.ani
  • Cursors.PanToolCursor.cur
  • Cursors.PanToolCursorMouseDown.cur
  • Cursors.PorkyPig.cur


The TdhCursorFactory.Cursors enum is the means by which the array of Cursors internally managed by the TdhCursorFactory class are accessed. This has both advantages and disadvantages. The main disadvantage is that the only way to add items to the enum is to recompile the class. The advantages are legion, including: the TdhCursorFactory class manages the set of cursors your program is using for you, all you need do is initially set them; you may "override" (or not, as you wish) the standard cursors of the System.Windows.Forms.Cursors class -- your main program's code will be given a valid cursor object in either case; once you are satisfied with *your* version of the TdhCursorFactory.Cursors enum, your version of the TdhCursorFactory class will be both flexible enough to be useful for any program needing color and/or animated cursors and simultaneously specific to your own needs.

The members of the TdhCursorFactory.Cursors enum are :

  • There are a number of members for custom-defined Cursors. By default, the class constructor extracts embedded resources from its assembly for these items, though the main program can replace (or release) any of them at any time. These custom definitions are, of course, geared towards my overall project; you may want to add or remove custom definitions.
    • Cursors._Busy
    • Cursors._Grab
    • Cursors._GrabRelease
    • Cursors._Normal
    • Cursors._X_Random_Busy - a special member; it (randomly) accesses the elements of a "sub-array" of Cursors
  • The remainder of members may be used to "override" the standard Cursors of the System.Windows.Forms.Cursors class. Any to which a Cursor has not been explicitly set (via the [ SetCursor() ] methods) will return (via the [ GetCursor() ] method) the corresponding standard Cursor:
    • Cursors.AppStarting
    • Cursors.Arrow
    • Cursors.Cross
    • Cursors.Default
    • Cursors.Hand
    • Cursors.Help
    • Cursors.HSplit
    • Cursors.IBeam
    • Cursors.No
    • Cursors.NoMove2D
    • Cursors.NoMoveHoriz
    • Cursors.NoMoveVert
    • Cursors.PanEast
    • Cursors.PanNE
    • Cursors.PanNorth
    • Cursors.PanNW
    • Cursors.PanSE
    • Cursors.PanSouth
    • Cursors.PanSW
    • Cursors.PanWest
    • Cursors.SizeAll
    • Cursors.SizeNESW
    • Cursors.SizeNS
    • Cursors.SizeNWSE
    • Cursors.SizeWE
    • Cursors.UpArrow
    • Cursors.VSplit
    • Cursors.WaitCursor

History

  • 2007 October 26: Submission of TdhCursorFactory ver 1.0.001 to The Code Project.
  • 2007 October 27: ver 1.0.002
    • Modified the SetCursor(TdhCursorFactory.Cursors whichCursor, string cursorName, bool caseSensitive) method.
      Previously, if the string cursorName value could not be found in the resources manifest (or did not resolve to a valid Cursor), the method would set the array item referenced by the TdhCursorFactory.Cursors whichCursor value to a null object. Now, there will be no effect.
    • Created the public static System.Collections.ArrayList ResourcesManifest(bool forCallingAssembly) method.
  • 2007 October 29: ver 1.0.003
    • Modified the methods: FromResource() and (one signature of) SetCursor() (sorry about that!)
      - Added the argument "bool forceAsColor" to allow the developer using the class to force ".CUR" files to be loaded via the LoadCursorFromFileW function of "user32.dll." The value of this argument is relevant only for Cursors with the ".CUR" extension; Cursors with the ".ANI" extension are always loaded via the LoadCursorFromFileW function of "user32.dll."
  • 2007 October 29: ver 1.1.000
    • Added the special '_X_Random_Busy' member to the TdhCursorFactory.Cursors enum.
      The '_X_Random_Busy' member may be used to access a "sub-array" of Cursors -- It randomly points to one of the Cursors which have been loaded into the "sub-array." Individual Cursors may be loaded into this "sub-array" via the existing [ SetCursor() ] methods; the [ GetCursor() ] method will randomly return one of the Cursors loaded into the "sub-array."
    • Created the [ InitCursor_X() ] method.
      This method initializes the "sub-array" of Cursors referenced by the enum value TdhCursorFactory.Cursors._X_Random_Busy to a set of pre-defined Cursor objects obtained from the class' resources manifest.

License

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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHow to embed the cursors in resources? Pin
mosquets21-Nov-07 7:53
mosquets21-Nov-07 7:53 
AnswerRe: How to embed the cursors in resources? Pin
Ilíon21-Nov-07 22:53
Ilíon21-Nov-07 22:53 
GeneralRe: How to embed the cursors in resources? Pin
mosquets22-Nov-07 12:14
mosquets22-Nov-07 12:14 
GeneralRe: How to embed the cursors in resources? Pin
Ilíon22-Nov-07 21:00
Ilíon22-Nov-07 21:00 
GeneralLoading from Embedded Resource Pin
Jakka Prihatna1-Nov-07 21:12
Jakka Prihatna1-Nov-07 21:12 
GeneralRe: Loading from Embedded Resource Pin
Ilíon2-Nov-07 2:04
Ilíon2-Nov-07 2:04 
GeneralRe: Loading from Embedded Resource Pin
Jakka Prihatna4-Nov-07 15:56
Jakka Prihatna4-Nov-07 15:56 
GeneralRe: Loading from Embedded Resource [modified] Pin
Ilíon5-Nov-07 17:18
Ilíon5-Nov-07 17:18 
GeneralRe: Loading from Embedded Resource Pin
Ilíon6-Nov-07 7:13
Ilíon6-Nov-07 7:13 
GeneralRe: Loading from Embedded Resource Pin
Jakka Prihatna6-Nov-07 22:49
Jakka Prihatna6-Nov-07 22:49 
GeneralGood But Not Stellar Pin
Mark Treadwell30-Oct-07 0:59
professionalMark Treadwell30-Oct-07 0:59 
GeneralRe: Good But Not Stellar Pin
Ilíon1-Nov-07 12:08
Ilíon1-Nov-07 12:08 
GeneralRe: Good But Not Stellar Pin
fwsouthern1-Nov-07 15:25
fwsouthern1-Nov-07 15:25 
GeneralRe: Good But Not Stellar Pin
Ilíon1-Nov-07 16:31
Ilíon1-Nov-07 16:31 
GeneralRe: Good But Not Stellar Pin
fwsouthern1-Nov-07 16:36
fwsouthern1-Nov-07 16:36 
GeneralRe: Good But Not Stellar Pin
Ilíon1-Nov-07 16:38
Ilíon1-Nov-07 16:38 
GeneralRe: Good But Not Stellar Pin
Mark Treadwell1-Nov-07 16:46
professionalMark Treadwell1-Nov-07 16:46 
Touchy just by looking at the length, and I have not even read the comment yet, so I agree with [fwsouthern]. I offered several examples of why I thought you had not offered a general purpose library, despite it being in the "C# - General" section, but here are some more specifics. I guess I assumed you knew what I was referring to since you wrote the code.

I said "it is way too special case." In your constructor static TdhCursorFactory(), all the loading of cursors make it specific case. They support your sample. Your sample should load them into the library, not the other way around. The "special processing" in public static void SetCursor() is another place to remove specific case code.

I said "It contains workarounds ("special processing" in the author's words) to handle specific sample cursor files." In public static void SetCursor() you have special case handling of your example cursors. You should not be handling specific cursor files in the library. In the sample code, certainly, but not in the library.

I said "'Easter Eggs' with different behavior depending on who is logged on." We really do not care what you want displayed for crunion or angie as is included in the static TdhCursorFactory() constructor.

I said "poor exception handling." You only have a try / catch block in public static System.Windows.Forms.Cursor FromFile() that just eats exceptions. Any time I see catch {} in a code review it raises red flags. You throw no exceptions to communicate back to the user (a developer) that something went amiss. This is important for library code. Your goal is not to hide everything, only the errors which may come from expected events which are outside of your control.

I said "lots of commented out code that makes it look like work is still in progress." Not counting the GetResource class at the end, there are about 50 source lines commented out with about 400 or so active lines. (I split out your public enum Cursors and internal class iGetResource into separate files to simplify the main file.) I know commenting style and region usage are personal tastes, but your commenting technique lends to the impression you are commenting out additional lines of code, as do your region names. In the end, it was, and is, my impression when I examined your code.

I said "potentially questionable use of mutexes." I used the wrong term here. I meant to refer to all the lock blocks creating critical sections. While I have not run the code is detail, quite a bit of it is in critical sections. That raises another flag for me, hence my inclusion of the word "questionable".

OK, now I will read your comment and see what you have to say.

Mark Treadwell
Special Enhancements

AnswerRe: Good But Not Stellar Pin
Mark Treadwell3-Nov-07 16:01
professionalMark Treadwell3-Nov-07 16:01 
NewsRe: Good But Not Stellar Pin
Ilíon3-Nov-07 18:18
Ilíon3-Nov-07 18:18 
GeneralRe: Good But Not Stellar Pin
Mark Treadwell3-Nov-07 19:18
professionalMark Treadwell3-Nov-07 19:18 
NewsRe: Good But Not Stellar Pin
Ilíon3-Nov-07 18:43
Ilíon3-Nov-07 18:43 
General5 Thumbs Up! Pin
Ri Qen-Sin27-Oct-07 3:35
Ri Qen-Sin27-Oct-07 3:35 
GeneralRe: 5 Thumbs Up! [modified] Pin
Ilíon27-Oct-07 6:30
Ilíon27-Oct-07 6:30 

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.