« .NETでリソースにアクセスする | トップページ | OLEドラッグ&ドロップ対応にする(テキスト編) »

2011年1月19日 (水)

C#でカラーカーソル/アニメーションカーソルを使用する

アプリケーションで自作カーソルを使用する場合、過去の記事「.NETでリソースにアクセスする」で示した事を行なえば、OKです。しかし1つ問題があります。この方法はモノクロでないカーソルやアニメーションカーソルには適用出来ません。
ヘルプの Cursor クラスの説明で、アニメーションカーソルやカラーカーソルはサポートしていないと明記されています。つまり、元々サポートされていないという事になります。私にとっては、これはかなり意外な事でした。アニメーションカーソルやカラーカーソルは確か Windows95 では既にサポートされていた筈ですからね。

では、絶対に使えないかというと、そうでも無いようです。Win32API を使用すれば可能な様です。という事で、今回はカラーカーソル及びアニメーションカーソルを実現する記述を説明します。

1.カーソルの取得&設定
カーソルの取得は、Win32API の LoadImage で行ないます。アニメーションカーソルも特別な意識をする必要がなく、そのまま取り込めます。Win32リソースからカーソルを取得する場合は、LoadImage でアプリケーションのインスタンスハンドルを指定する必要がありますが、ここも含めて下記のように指定すればOKです。

// リソースの値を文字列で指定する場合の LoadImage
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr LoadImage(IntPtr hinst, string lpszName, uint uType,
    int cxDesired, int cyDesired, uint fuLoad);
// リソースの値を数値で指定する場合の LoadImage
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr LoadImage(IntPtr hinst, IntPtr lpszName, uint uType,
    int cxDesired, int cyDesired, uint fuLoad);
[DllImport("user32.dll")]
static extern bool DestroyCursor(IntPtr hCursor);
private IntPtr hMoveCursor;
private IntPtr hMoveAnime;
private IntPtr hCopyCursor;
private IntPtr hCopyAnime;
private IntPtr hLinkCursor;
private IntPtr hLinkAnime;
private void Form1_Load(object sender, System.EventArgs e)
{
    System.Reflection.Module[] m = 
        System.Reflection.Assembly.GetExecutingAssembly().GetModules();
    IntPtr hInstance = Marshal.GetHINSTANCE(m[0]);
    const int IMAGE_CURSOR = 2;
    const int LR_LOADFROMFILE = 0x0010;
    const int LR_DEFAULTSIZE = 0x0040;
    // カラーカーソルを、数値のリソース値で指定して取得
    hMoveCursor = LoadImage(hInstance, new IntPtr(151), IMAGE_CURSOR,
        0, 0, LR_DEFAULTSIZE);
    if (hMoveCursor == IntPtr.Zero)
        label1.Enabled = false;
    else
        label1.Cursor = new Cursor(hMoveCursor);
    // アニメーションカーソルを、数値のリソース値で指定して取得
    hMoveAnime = LoadImage(hInstance, new IntPtr(201), IMAGE_CURSOR,
        0, 0, LR_DEFAULTSIZE);
    if (hMoveAnime == IntPtr.Zero)
        label2.Enabled = false;
    else
        label2.Cursor = new Cursor(hMoveAnime);
    // カラーカーソルを、文字列のリソース値で指定して取得
    hCopyCursor = LoadImage(hInstance, "COPYCUR", IMAGE_CURSOR,
        0, 0, LR_DEFAULTSIZE);
    if (hCopyCursor == IntPtr.Zero)
        label3.Enabled = false;
    else
        label3.Cursor = new Cursor(hCopyCursor);
    // アニメーションカーソルを、文字列のリソース値で指定して取得
    hCopyAnime = LoadImage(hInstance, "COPYANI", IMAGE_CURSOR,
        0, 0, LR_DEFAULTSIZE);
    if (hCopyAnime == IntPtr.Zero)
        label4.Enabled = false;
    else
        label4.Cursor = new Cursor(hCopyAnime);
    // カラーカーソルを、ファイルパスを指定して取得
    hLinkCursor = LoadImage(IntPtr.Zero, @"link.cur", IMAGE_CURSOR,
        0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
    if (hLinkCursor == IntPtr.Zero)
        label5.Enabled = false;
    else
        label5.Cursor = new Cursor(hLinkCursor);
    // アニメーションカーソルを、ファイルパスを指定して取得
    hLinkAnime = LoadImage(IntPtr.Zero, @"link.ani", IMAGE_CURSOR,
        0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
    if (hLinkAnime == IntPtr.Zero)
        label6.Enabled = false;
    else
        label6.Cursor = new Cursor(hLinkAnime);
}

ここでは、リソース値を文字列で指定する場合と、数値で指定する場合、あるいは外部ファイルとして存在するカーソルファイルを指定する場合の3種類と、通常のカーソルとアニメーションカーソルの組み合わせで、計6種類のカーソルを LoadImage しています。

また LoadImage に失敗した場合、カーソル変更対象ラベルの Enabled プロパティを false にしています。実は当初、アニメーションカーソルのリソースが複数あると、1つしか取得出来ない問題に突き当たりました。それで、視覚的にどれが失敗したかすぐ判るようにする為のものです。この件については、後で詳しく述べます。

2.カーソルの破棄
不要になった時点で、カーソルオブジェクトの Dispose と、LoadImage で得たハンドルの破棄(DestroyCursor)を行ないます。

protected override void Dispose( bool disposing )
{
    if( disposing )
    {
        if (components != null) 
        {
            components.Dispose();
        }
        if (hMoveCursor != IntPtr.Zero)
            label1.Cursor.Dispose();
        if (hMoveAnime != IntPtr.Zero)
            label2.Cursor.Dispose();
        if (hCopyCursor != IntPtr.Zero)
            label3.Cursor.Dispose();
        if (hCopyAnime != IntPtr.Zero)
            label4.Cursor.Dispose();
        if (hLinkCursor != IntPtr.Zero)
            label5.Cursor.Dispose();
        if (hLinkAnime != IntPtr.Zero)
            label6.Cursor.Dispose();
    }
    if (hMoveCursor != IntPtr.Zero)
        DestroyCursor(hMoveCursor);
    if (hMoveAnime != IntPtr.Zero)
        DestroyCursor(hMoveAnime);
    if (hCopyCursor != IntPtr.Zero)
        DestroyCursor(hCopyCursor);
    if (hCopyAnime != IntPtr.Zero)
        DestroyCursor(hCopyAnime);
    if (hLinkCursor != IntPtr.Zero)
        DestroyCursor(hLinkCursor);
    if (hLinkAnime != IntPtr.Zero)
        DestroyCursor(hLinkAnime);
    base.Dispose( disposing );
}

ここでは、自動生成される Dispose 処理に追加しています。しかしC#2005で新規に作成した場合だと、Form1.Desiner.cs とかに出来るものなので、こういった場合、別のところで処理した方がいいかもしれません。

3.カーソルをWin32リソースとして埋め込み
Win32API でカーソルを取得しなければならないので、リソースもWin32リソースとして埋め込む必要が出て来ます。勿論外部ファイルとして存在するカーソルを使用するのであれば、この手続きは不要です。
さて、実際の埋め込み方法ですが、これは DOBON.NETの「Win32リソースを実行ファイルに埋め込む」で詳しく紹介されていますので、これを参考にされればいいと思います。それから、アニメーションカーソルのリソーステンプレートへの組み込み方法は、マルチメディアファイルフォーマットの「アニメーションリソースの読み込み」に書いてあります(VC++前提で書かれてますが、参考にするのはリソーステンプレート画面の操作だけですから、何も問題ありません)。

4.アニメーションカーソルの注意点
先程ちょっと触れた様に、当初は問題がありました。Win32リソース中のアニメーションカーソルが1つだけしか存在しない場合は、それが文字列指定でも数値指定でも問題無く表示されます。しかし2つ同時に含ませると一方のリソースしか取り込めません。
相当の試行錯誤を行ないましたが、なかなか原因が判りません。当初から、現象的にプログラムコードは問題無く、ツールで作成したアニメーションカーソル自体に問題がありそうだと推測してはいました。しかし、Microsoftのツール Animates Cursor Editor(Windows95 Resource Kit Utility に入っている)を使ってアニメーションカーソルを作成してみても、結果は変わりません。

原因不明の問題があっては、記事など載せられません。記事をボツにしようとも思っていたのですが、試しに Windows に標準でついてくるアニメーションカーソルをリソースとして組み込むと、複数でも問題無く LoadImage 出来る事が判りました。著作権上、配布の問題があるので、オリジナルのアニメーションカーソルにこだわっていたのが仇となった感じですね。

結局、ここを切り口にして、問題がアニメーションファイル内の先頭5バイト目からの4バイト分の数値に問題がある事が判りました。ここの数値は、全体のデータ長を表わすのですが、先頭の識別子"RIFF"とこのデータ長を格納するサイズは含まないのが正解な様です。つまりファイルサイズ-8バイトが正しい数値の様です。

実は今まで作成していたツールでは、ここがファイルサイズと同じ値を示していたのです。そこで、ここをバイナリエディタで正しい値に修正したものをリソースとして使う様に変更すると、ようやく正常に動きだす様になりました。
もし、この様な現象になったら、真っ先にこのデータ長が正しいかを疑った方がいいです。

このファイルフォーマット関連で参考にしたサイトを挙げておきます。
ani ファイル形式
http://www.syuhitu.org/other/ani.html
AVI RIFF ファイルのリファレンス
http://msdn.microsoft.com/ja-jp/library/cc352264.aspx

また、Animates Cursor Editor(Aniedit)のダウンロードサイトはこちら
http://www.microsofttranslator.com/bv.aspx?ref=SERP&br=ro&mkt=ja-JP&dl=ja&lp=DE_JA&a=http%3a%2f%2fdownload.chip.eu%2fde%2fAniedit_38020.html

簡単なテストではうまくいっていたので、すぐに記事に出来ると思ったのに、まったく予想外の事ではまってしまいました。まあ、最後は原因もわかってよかったのですが、単体だとデータ長が間違っていてもちゃんと動く(ように見える)からタチが悪い。

では最後に、今回の記事に使用したサンプルソース一式をCAB形式で圧縮して、ここに置いておきます。
「ColorCursor.cab」をダウンロード


« .NETでリソースにアクセスする | トップページ | OLEドラッグ&ドロップ対応にする(テキスト編) »

C# Tips」カテゴリの記事

コメント

コメントを書く

コメントは記事投稿者が公開するまで表示されません。

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: C#でカラーカーソル/アニメーションカーソルを使用する:

« .NETでリソースにアクセスする | トップページ | OLEドラッグ&ドロップ対応にする(テキスト編) »