This page discusses how to create and use your own cursors.
In addition to this page, I have the following
Cursors are images with a designated hot spot (tip of the arrow, center of a bull's eye, ...) and are controlled by a pointing device (like a mouse). Windows provides a number of default cursors (arrow, hourglass, hand, etc) and programs can add their own. They can be stored as *.cur files or placed in the resource section of an exe or a dll file.
Windows identifies a cursor with a handle, Delphi apps use an index into the TScreen.Cursors[] "array" (actually, a linked list of TCursorRec records) to get the handles. In the "array", the predefined Windows cursors have negative indices and your custom cursors should have positive (reduces conflicts). The only real problem will be if you have more than one dcu with cursors. In that case, you need to do something to avoid conflicts.
Note: | Besides reducing conflicts, when the application terminates, the Screen object automatically releases all cursors with positive indices. |
Using the built-in cursors is trivial and well documented, however, creating and including custom cursors is very poorly documented. I assume that you are supposed to use a provided tool and just follow the instructions. Unfortunately, the only tool I have is fairly old and does not support any of the new features.
What Microsoft says
Cursors can be either monochrome or color, and either static or animated. |
Microsoft does not appear to have an answer. I was not able to determine a single technical detail. They don't even bother to suggest what size to use!
After many hours of digging, I stumbled onto the following CURSORDIR help in the Windows 32 Developers Reference supplied with Delphi 5.
The CURSORDIR structure contains the dimensions of an individual cursor image in a resource group. Width Specifies the width of the cursor, in pixels. Acceptable values are 16, 32, and 64. Height Specifies the height of the cursor, in pixels. Acceptable values are 16, 32, and 64. |
The LoadCursor help (same source) says
The LoadCursor function searches the cursor resource most appropriate for the cursor for the current display device. The cursor resource can be a color or monochrome bitmap. |
Steps to create a cursor
How to create Target_Cursor.res using Delphi 5 Image Editor
|
Note that this tool only supports a black and white image, to add colors, you can handle the mouse movements yourself and draw anything you like.
Using the cursor
In one of your source files (it does not matter which one) include the resource file. Do not use an asterisk - in the resource statement, the asterisk is not a generic wildcard. Instead, it is replaced by the name of the source file.
{$R Cursor_Custom_Name.res} {$R C:\[full path]\xxCursor_CustomName.res} // use a path if necessary |
var cur: Integer; // sometimes this needs to be available later procedure TForm1.FormCreate(Sender: TObject); begin cur := 0; // very important when adding a cursor LoadCursor_xx (cur, 'MCCURSOR_TARGET'); // the name is not case sensitive Some_Control.cursor := cur; end; procedure LoadCursor_xx(var CursorID: Integer; CursorName: String); var i, xx, yy : integer ; begin if CursorID = 0 then begin // If the ID is not assigned, find an empty index yy := Screen.Cursors[0] ; // This speeds up the loop for i:=1 to 6000 do begin // 6000 is just a big number, 10 should be enough xx := Screen.Cursors[i]; // This makes trouble shooting easier if xx = yy then begin // if Screen.Cursors[0] = Screen.Cursors[i] CursorID := i; // This is an unused value break; end; end ; end; // if CursorID = 0 if CursorID = 0 then exit; // This is just a safety, it should never exit Screen.Cursors[CursorID] := LoadCursor(HInstance, PChar(CursorName)); end; |
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var p : Tpoint; begin p.x := 15; // in this case, the hotspot is 15,15 p.y := 15; p := Image1.ClientToScreen(p); // the hotspot in screen coordinates SetCursorPos(p.x, p.y ); // move the mouse to that position Screen.Cursor := cur; // this is the custom cursor Image1.Visible := false; // hide the image end; procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin Screen.Cursor := crDefault; Image1.Visible := true; end; |
Displaying the cursor on a form
Button1: TButton; Image1 : TImage; procedure TForm1.Button1Click(Sender: TObject); begin Image1.Picture.Icon.ReleaseHandle; // required before assigning a handle Image1.Picture.Icon.Handle := Screen.Cursors[Button1.Cursor]; end; |
Image1.Picture.Icon.Handle := Screen.Cursors[-3]; // same as crCross Image1.Picture.Icon.Handle := Screen.Cursors[crCross]; // built-in constant = -3 |
Finding the hotspot
BOOL GetIconInfo( HICON hIcon, // icon handle PICONINFO piconinfo // address of icon structure ); |
typedef struct _ICONINFO { // ii BOOL fIcon; DWORD xHotspot; DWORD yHotspot; HBITMAP hbmMask; HBITMAP hbmColor; } ICONINFO; |
type PIconInfo = ^TIconInfo; {$EXTERNALSYM _ICONINFO} _ICONINFO = packed record fIcon : BOOL; xHotspot: DWORD; yHotspot: DWORD; hbmMask : HBITMAP; hbmColor: HBITMAP; end; TIconInfo = _ICONINFO; {$EXTERNALSYM ICONINFO} ICONINFO = _ICONINFO; |
procedure TForm1.Button4Click(Sender: TObject); var IconInfo : TIconInfo ; begin GetIconInfo(Screen.Cursors[SpinEdit1.Value], IconInfo); SpinEdit2.Value := IconInfo.xHotspot; SpinEdit3.Value := IconInfo.yHotspot; end; |
Using TIconInfo and CreateIconIndirect, it is possible to create a cursor from an icon - just set fIcon false and define the hotspot. If fIcon if true, then the values you enter will be ignored and the hotspot will be the center.
procedure TForm1.Button6Click(Sender: TObject); var IconInfo : TIconInfo ; cur : HICON; begin GetIconInfo(Application.Icon.Handle, IconInfo); IconInfo.fIcon := false; // required to set the hotspot away from the center IconInfo.xHotspot := 15; IconInfo.yHotspot := 15; cur := CreateIconIndirect(IconInfo); Screen.Cursors[99] := cur; Button6.Cursor := 99; end; |
Adding a cursor to a component
implementation var myCursor :integer; // this must be located before it is used // The control's constructor must be after the variable is defined constructor TmyControl.Create(AOwner: TComponent); begin inherited; FPanel := TPanel.Create(self); FPanel.Cursor := myCursor; end; initialization // must be here so only one copy will be loaded myCursor := 0; // 0 means create a new index value Local_LoadCursor(myCursor, 'myCursor'); |