« ファイルに関連づけされたアイコンをImageクラスで取得 | トップページ | 「アイコンの変更」ダイアログを使ってみる。 »

2010年12月 1日 (水)

ファイルからアイコンを抽出して、ImageListへ登録

怒涛のアイコンネタ、第3弾です。

今回は、指定されたファイルからアイコンを抽出して、それを ImageList に登録する汎用的な関数を公開します。
指定対象となるファイルは、アイコンリソースが含まれたファイル(実行ファイル/DLLファイル、アイコンライブラリファイル、アイコンファイル)です。

では、そのソースコードです。

using System;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace Acha_ya.SampleApplication
{
    /// <summary>
    /// ExtractIconImageList の概要の説明です。
    /// </summary>
    public class IconUtility
    {
        // ExtractIconEx 複数の引数指定方法により、オーバーロード定義する。
        [DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
        private static extern uint ExtractIconEx(string lpszFile, int nIconIndex,
            IntPtr[] phiconLarge, IntPtr[] phiconSmall, uint nIcons);
        [DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
        private static extern uint ExtractIconEx(string lpszFile, int nIconIndex,
            IntPtr[] phiconLarge, IntPtr phiconSmall, uint nIcons);
        [DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
        private static extern uint ExtractIconEx(string lpszFile, int nIconIndex,
            IntPtr phiconLarge, IntPtr[] phiconSmall, uint nIcons);
        [DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
        private static extern uint ExtractIconEx(string lpszFile, int nIconIndex,
            IntPtr phiconLarge, IntPtr phiconSmall, uint nIcons);
        // DestroyIcon の定義
        [DllImport("User32.dll", CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool DestroyIcon(IntPtr hIcon);
        /// <summary>
        /// 指定したパスのDLLの指定インデックスのアイコンをイメージリストに追加する。
        /// </summary>
        /// <param name="dllPath">指定するアイコンを含んだファイルパス</param>
        /// <param name="iconIndex">アイコンインデックス</param>
        /// <param name="orderImageList">アイコンを追加するイメージリスト</param>
        /// <returns>成功時 true、失敗時 false</returns>
        public static bool ImageAddIcon(string dllPath, int iconIndex, ImageList orderImageList)
        {
            IntPtr[] hLargeIcon = new IntPtr[1] {IntPtr.Zero};
            IntPtr[] hSmallIcon = new IntPtr[1] {IntPtr.Zero};
            try
            {
                if (ExtractIconEx(dllPath, iconIndex, hLargeIcon, hSmallIcon, 1) < 1)
                    return false;
                if (orderImageList.ImageSize.Width <= SystemInformation.SmallIconSize.Width &&
                    orderImageList.ImageSize.Height <= SystemInformation.SmallIconSize.Height)
                {
                    if (hSmallIcon[0] != IntPtr.Zero)
                    {
                        using (Icon smallIcon = Icon.FromHandle(hSmallIcon[0]))
                            orderImageList.Images.Add(smallIcon);
                    }
                }
                else
                {
                    if (hLargeIcon[0] != IntPtr.Zero)
                    {
                        using (Icon largeIcon = Icon.FromHandle(hLargeIcon[0]))
                            orderImageList.Images.Add(largeIcon);
                    }
                }
                return true;
            }
            finally
            {
                foreach (IntPtr ptr in hLargeIcon)
                    if (ptr != IntPtr.Zero) DestroyIcon(ptr);
                foreach (IntPtr ptr in hSmallIcon)
                    if (ptr != IntPtr.Zero) DestroyIcon(ptr);
            }
        }

        /// <summary>
        /// 指定したパスのDLLのアイコン全てをイメージリストに追加する。
        /// </summary>
        /// <param name="dllPath">指定するアイコンを含んだファイルパス</param>
        /// <param name="orderImageList">アイコンを追加するイメージリスト</param>
        /// <returns>成功時 true、失敗時 false</returns>
        public static bool ImageAddIcon(string dllPath, ImageList orderImageList)
        {
            // 指定ファイルに格納されたアイコン総数の取得
            uint iconCnt = ExtractIconEx(dllPath, -1, IntPtr.Zero, IntPtr.Zero, 1);
            if (iconCnt == 0) return false;

            IntPtr[] hIcon = new IntPtr[iconCnt];
            try
            {
                // アイコンハンドル&ハンドル数の取得
                uint getCnt;
                if (orderImageList.ImageSize.Width <= SystemInformation.SmallIconSize.Width &&
                    orderImageList.ImageSize.Height <= SystemInformation.SmallIconSize.Height)
                    getCnt = ExtractIconEx(dllPath, 0, IntPtr.Zero, hIcon, iconCnt);
                else
                    getCnt = ExtractIconEx(dllPath, 0, hIcon, IntPtr.Zero, iconCnt);
                if (getCnt < 1) return false;   // アイコンがなければ終了
                for (int idx = 0; idx < getCnt; ++idx)
                {
                    if (hIcon[idx] != IntPtr.Zero)
                    {
                        using (Icon icon = Icon.FromHandle(hIcon[idx]))
                            orderImageList.Images.Add(icon);
                    }
                }
                return true;
            }
            finally
            {
                foreach (IntPtr ptr in hIcon)
                    if (ptr != IntPtr.Zero) DestroyIcon(ptr);
            }
        }
    }
}

ここでは、指定した ImageList の ImageSize により、大きいアイコンと小さいアイコンのどちらを格納するかを判断する様にしています。当初、両方のアイコンを2つの ImageList にそれぞれ登録する様なものを考えていたのですが、そうすると、アイコンハンドル取得用の ExtractIconEx の戻り値で、アイコンファイルでは 1、DLL等では存在するアイコン数の2倍の数値が返って来ます。変な回避策を行なうよりは、この方が良いかと判断しています。まあ、このあたりは必要に応じて、それぞれ自由に改良してもらって結構です。
それから、指定ファイルから特定のアイコンだけを抽出する場合は、そのインデックス値を指定する必要がありますが、これは、最初のインデックス値が 0 です。アイコンファイルのときは、1つしかありませんから、0 を指定して下さい。

また、指定する ImageList は、ColorDepth の指定に注意して下さい。例えば shell32.dll のアイコンを抽出しようとする場合、アルファチャンネルを含む32bitのアイコンが含まれてるので、ColorDepth.Depth32Bit にしてないと、下記の図の様に、アイコンまわりに黒い枠が出たりします。必ず ColorDepth.Depth32Bit を指定して下さい。

本来のアイコン表示
まわりに黒枠の出るアイコン表示

また、これは shell32.dll のバージョン6以降でないと、32bitアイコンに対応してない様なので、Visual Style 対応にする必要があります。C#2005以降で作成したプログラムは、デフォルトで Application.EnableVisualStyles(); の行が入っているので、問題が起きる可能性は低いですが、C#2003では注意する必要があります。

※C#2003でコンパイルする場合の注意点
今回、最後に添付しているテストAP上では、
Application.EnableVisualStyles();
の行を追加するだけで、うまく表示出来たのですが、過去にはそれだけではうまくいかなかった事を経験しています。完全に対応する場合は、マニフェストファイルを埋め込むのが確実だと思います。
簡単にマニフェストファイルを埋め込むフリーソフトとしては、ExeStyleXP がお薦めです。この名前で検索すれば、ダウンロード出来るサイトはすぐ見つかる筈です。

では、最後にこのソースとこれをテストする簡単なテストAP等を、CAB形式で圧縮して置いておきます。
「ExtractIcon.cab」をダウンロード


« ファイルに関連づけされたアイコンをImageクラスで取得 | トップページ | 「アイコンの変更」ダイアログを使ってみる。 »

C# Tips」カテゴリの記事

コメント

コメントを書く

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

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

トラックバック


この記事へのトラックバック一覧です: ファイルからアイコンを抽出して、ImageListへ登録:

« ファイルに関連づけされたアイコンをImageクラスで取得 | トップページ | 「アイコンの変更」ダイアログを使ってみる。 »