none
ファイルに関連付けられたアイコンのパスを取得したい RRS feed

  • 質問

  • まず目的としては、C#で.exeのアイコンを48*48で取得してWPFで描画することです。

    前提条件は使用できる情報は .exeのフルパスのみです。

    今までSystem.Drawing.Icon.ExtractAssociatedIconで取得しても
    複数のサイズがひとつの.icoにまとまっているため32*32でしか取得できず。

    SHGetFileInfoなどを使用しても同様。

    System.Drawing.Icon(ico_path, 48, 48)なら48*48のアイコンを取得できましたがこれではだめ。

    .exeに関連付けられた.icoのパスの取得方法などを知っている、

    もしくは.exeから48*48のアイコンを取得できる方法を知っていたら、ぜひともご教示ください。

    2014年8月13日 15:22

回答

  • API Code Packを使うという手も
    #Windows API Code Packが元あったところから消えているのでnugetに上がっているのを拾ってくるとかしないと入手できないですが。

    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <DockPanel>
            <Button Click="Button_Click" DockPanel.Dock="Top" Content="Test"/>
            <Image x:Name="image1" Stretch="None"  />
        </DockPanel>
    </Window>
    using System;
    using System.Windows;
    using System.Windows.Media.Imaging;
    namespace WpfApplication1
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
    
                if (dlg.ShowDialog() ?? false)
                {
                    BitmapSource bs = null;
                    //Windows API Code Pack 1.1のMicrosoft.WindowsAPICodePack.dllとMicrosoft.WindowsAPICodePack.Shell.dllを参照しておく
                    var sf = Microsoft.WindowsAPICodePack.Shell.ShellFile.FromFilePath(dlg.FileName);
                    var thum = sf.Thumbnail;
    
                    if (Math.Max(thum.MediumBitmapSource.Width, thum.MediumBitmapSource.Height) == 48)
                    {
                        bs = thum.MediumBitmapSource;
                    }
                    else if (Math.Max(thum.LargeBitmapSource.Width, thum.LargeBitmapSource.Height) == 48)
                    {
                        bs = thum.LargeBitmapSource;
                    }
                    else
                    {
                        thum.CurrentSize = new System.Windows.Size(48, 48);
                        bs = thum.BitmapSource;
                    }
    
                    this.image1.Source = bs;
                }
            }
        }
    }

    なお、今回の質問のEXEだと普通は無いですが、ファイルに関連付けされたアイコンは動的に変更されることがあります。たとえば、画像ファイルはその画像から動的に生成されたアイコンが得られたりします。なので、そういう動的なアイコン画像が欲しい場合は表示されるicoファイルというのは存在していないということになります。

    あと、アイコンは48*48といっても32bitカラーだったり8bitカラーだったりと色深度の異なる画像が複数埋め込まれていることがありますが、そういったことを考慮する必要があるなら、Iconファイルから抽出する必要があるかもしれません。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)


    • 編集済み gekkaMVP 2014年8月14日 5:58
    • 回答としてマーク U Plus 2014年8月17日 17:28
    2014年8月14日 5:58
  • Icon.ExtractAssociatedIcon()は内部で使われているExtractAssociatedIcon()の時点でサイズ固定なんですね、失礼しました。

    お詫びといっては何ですが、GetIconImage()を書いてみました。

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct SHFILEINFO {
        public IntPtr hIcon;
        public int iIcon;
        public int dwAttributes;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string szDisplayName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string szTypeName;
    }
    const int SHGFI_ICON = 0x000000100;
    const int SHIL_EXTRALARGE = 2;
    static readonly Guid IID_IImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
    
    [DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, int uFlags);
    [DllImport("Shell32.dll", PreserveSig = false)]
    static extern void SHGetImageList(int iImageList, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
    [DllImport("Comctl32.dll")]
    static extern IntPtr ImageList_GetIcon(IntPtr himl, int i, int flags);
    [DllImport("Comctl32.dll")]
    static extern bool ImageList_Destroy(IntPtr himl);
    [DllImport("User32.dll", SetLastError = true)]
    static extern bool DestroyIcon(IntPtr hIcon);
    
    static Bitmap GetIconImage(string fileName) {
        var fi = new SHFILEINFO();
        var result = SHGetFileInfo(fileName, 0, ref fi, Marshal.SizeOf(typeof(SHFILEINFO)), SHGFI_ICON);
        Debug.Assert(result != IntPtr.Zero);
    
        var himl = IntPtr.Zero;
        var hicon = IntPtr.Zero;
        try {
            SHGetImageList(SHIL_EXTRALARGE, IID_IImageList, out himl);
            hicon = ImageList_GetIcon(himl, fi.iIcon, 0);
            Debug.Assert(hicon != IntPtr.Zero);
    
            return Icon.FromHandle(hicon).ToBitmap();
        }
        finally {
            if (hicon != IntPtr.Zero)
                DestroyIcon(hicon);
            if (himl != IntPtr.Zero)
                ImageList_Destroy(himl);
        }
    }

    IconクラスでなくBitmapクラスを返す理由は、Iconインスタンス使用後にHandleを解放する必要があるため、.Clone()なりが必要なため、どうせサイズ固定ならBitmapでいいやと。

    アイコンのサイズは分かりづらいですがSHGetImageList()の第1引数で決定しています。

    • 回答としてマーク U Plus 2014年8月17日 17:44
    2014年8月14日 6:52

すべての返信

  • 試していませんが、Iconコンストラクタにはサイズを指定できるので、

    var original = Icon.ExtractAssociatedIcon(file);
    var icon = new Icon(original, 48, 48);

    ではだめですか?

    2014年8月14日 1:31
  • 以下が参考になりそうです。

    ファイルの拡張子に関連づけられたアイコンを表示したい
    http://bbs.wankuma.com/index.cgi?mode=al2&namber=52027&KLOG=87


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2014年8月14日 2:06
    モデレータ
  • API Code Packを使うという手も
    #Windows API Code Packが元あったところから消えているのでnugetに上がっているのを拾ってくるとかしないと入手できないですが。

    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <DockPanel>
            <Button Click="Button_Click" DockPanel.Dock="Top" Content="Test"/>
            <Image x:Name="image1" Stretch="None"  />
        </DockPanel>
    </Window>
    using System;
    using System.Windows;
    using System.Windows.Media.Imaging;
    namespace WpfApplication1
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
    
                if (dlg.ShowDialog() ?? false)
                {
                    BitmapSource bs = null;
                    //Windows API Code Pack 1.1のMicrosoft.WindowsAPICodePack.dllとMicrosoft.WindowsAPICodePack.Shell.dllを参照しておく
                    var sf = Microsoft.WindowsAPICodePack.Shell.ShellFile.FromFilePath(dlg.FileName);
                    var thum = sf.Thumbnail;
    
                    if (Math.Max(thum.MediumBitmapSource.Width, thum.MediumBitmapSource.Height) == 48)
                    {
                        bs = thum.MediumBitmapSource;
                    }
                    else if (Math.Max(thum.LargeBitmapSource.Width, thum.LargeBitmapSource.Height) == 48)
                    {
                        bs = thum.LargeBitmapSource;
                    }
                    else
                    {
                        thum.CurrentSize = new System.Windows.Size(48, 48);
                        bs = thum.BitmapSource;
                    }
    
                    this.image1.Source = bs;
                }
            }
        }
    }

    なお、今回の質問のEXEだと普通は無いですが、ファイルに関連付けされたアイコンは動的に変更されることがあります。たとえば、画像ファイルはその画像から動的に生成されたアイコンが得られたりします。なので、そういう動的なアイコン画像が欲しい場合は表示されるicoファイルというのは存在していないということになります。

    あと、アイコンは48*48といっても32bitカラーだったり8bitカラーだったりと色深度の異なる画像が複数埋め込まれていることがありますが、そういったことを考慮する必要があるなら、Iconファイルから抽出する必要があるかもしれません。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)


    • 編集済み gekkaMVP 2014年8月14日 5:58
    • 回答としてマーク U Plus 2014年8月17日 17:28
    2014年8月14日 5:58
  • Icon.ExtractAssociatedIcon()は内部で使われているExtractAssociatedIcon()の時点でサイズ固定なんですね、失礼しました。

    お詫びといっては何ですが、GetIconImage()を書いてみました。

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct SHFILEINFO {
        public IntPtr hIcon;
        public int iIcon;
        public int dwAttributes;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string szDisplayName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string szTypeName;
    }
    const int SHGFI_ICON = 0x000000100;
    const int SHIL_EXTRALARGE = 2;
    static readonly Guid IID_IImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
    
    [DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, int uFlags);
    [DllImport("Shell32.dll", PreserveSig = false)]
    static extern void SHGetImageList(int iImageList, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
    [DllImport("Comctl32.dll")]
    static extern IntPtr ImageList_GetIcon(IntPtr himl, int i, int flags);
    [DllImport("Comctl32.dll")]
    static extern bool ImageList_Destroy(IntPtr himl);
    [DllImport("User32.dll", SetLastError = true)]
    static extern bool DestroyIcon(IntPtr hIcon);
    
    static Bitmap GetIconImage(string fileName) {
        var fi = new SHFILEINFO();
        var result = SHGetFileInfo(fileName, 0, ref fi, Marshal.SizeOf(typeof(SHFILEINFO)), SHGFI_ICON);
        Debug.Assert(result != IntPtr.Zero);
    
        var himl = IntPtr.Zero;
        var hicon = IntPtr.Zero;
        try {
            SHGetImageList(SHIL_EXTRALARGE, IID_IImageList, out himl);
            hicon = ImageList_GetIcon(himl, fi.iIcon, 0);
            Debug.Assert(hicon != IntPtr.Zero);
    
            return Icon.FromHandle(hicon).ToBitmap();
        }
        finally {
            if (hicon != IntPtr.Zero)
                DestroyIcon(hicon);
            if (himl != IntPtr.Zero)
                ImageList_Destroy(himl);
        }
    }

    IconクラスでなくBitmapクラスを返す理由は、Iconインスタンス使用後にHandleを解放する必要があるため、.Clone()なりが必要なため、どうせサイズ固定ならBitmapでいいやと。

    アイコンのサイズは分かりづらいですがSHGetImageList()の第1引数で決定しています。

    • 回答としてマーク U Plus 2014年8月17日 17:44
    2014年8月14日 6:52
  • gekkaさん返信ありがとうございます。
    こちらから返信が遅れて申し訳ありません。

    Windows API Code Packを取得し、アイコン画像の48*48の抽出確認しました。

    なるほどアイコンはサイズ以外にもカラービットの異なる画像が埋め込まれている可能性もあるのですね。

    .exeファイルからの画像抽出だけでなく、アイコンについての説明もありがとうございます。
    非常に参考になりました。

    今後はWindowsAPICodePackなどのライブラリにも目を向けてみようと思います。

    それではこのコードをしっかり理解し、利用させて頂きます。
    本当にありがとうございました。

    2014年8月17日 17:27
  • 佐祐理さん 返信ありがとうございます。
    返信が遅れて申し訳ありません。

    アイコン画像のBitmapでの取得確認しました。

    こちらから質問してるのにお詫びだなんてとんでもない
    っと思いながらもGetIconImageを作って頂いたことに感謝しています。

    SHGetImageList この関数、実はいくつかのサイトを巡回してるときに見かけたのですが、まったく意味が解らずスルーしていたものです。

    佐裕理さんに書いていただいたサンプルとじっくりにらめっこしながら理解していこうと思います。

    スレッド[C++ テンプレートの明示的特殊化]の時に引き続き、今回もとても参考になる返信ありがとうございます。
    今後も何かありましたらよろしくお願いします。

    2014年8月17日 17:43