C# - ちょー雑な方法でFormのアイコンを生成する (Windows)

# 画面キャプチャ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/bfae8418-1de6-abd8-eee2-8e59d8bd316c.png)

# サンプルコード - 修正版

```cs

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

class IconMakeTest:Form
{
    class NativeMethods
    {
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public extern static bool DestroyIcon(IntPtr handle);
    } 

    IconMakeTest()
    {
        HandleCreated+=(sender,e)=>{GetHiconExample();};
    }
    
    static string[] iconDot = new string[]{
        "................",
        ".###...##...##..",
        "..#...#..#.#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#....#..#.",
        "..#...#..#.#..#.",
        ".###...##...##..",
        "................",
    };

    void GetHiconExample()
    {
        Bitmap bmp = new Bitmap(16,16);
        using ( Graphics g = Graphics.FromImage(bmp) ) {
            g.Clear(Color.White);
        }
        for(int y=0;y<16;y++){
            for(int x=0;x<16;x++){
                if (iconDot[y][x]=='#') {
                    bmp.SetPixel(x,y,Color.Black);
                }
            }
        }

        IntPtr Hicon = bmp.GetHicon();
        Icon newIcon;
        newIcon = Icon.FromHandle(Hicon);

        this.Icon = newIcon;
        NativeMethods.DestroyIcon(newIcon.Handle);
    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new IconMakeTest());
    }
}
```

# 説明

string配列でドット絵を表現していて、BitmapへSetPixelで描画し、GetHiconメソッドでIconへのポインタを取得し、Icon.FromHandleメソッドで取得したハンドルをFormのIconプロパティにセットしている。


# `HandleCreated`イベントで呼び出している理由

`HandleCreated`イベントが発生したときに`GetHiconExample`を呼んでいるのは、以下の理由のため。

最初は、`Form`のコンストラクタで`GetHiconExample`を呼ぶコードを書いていた。それで実行すると、下記の例外が発生した。

```

System.ObjectDisposedException: 破棄されたオブジェクトにアクセスできません。
オブジェクト名 'Icon' です。
```

どうやら`DestroyIcon`したあとに`Icon`が生成されているようである。

ILSpyで色々調べてみたところ、`Form`の`Icon`プロパティに代入(setアクセス)したときに、内部で`UpdateWindowIcon`が呼ばれるのだが、`IsHandleCreated`が`true`でないと、このときに`Icon`が生成(`WM_SETICON`)されないようになっている。

```cs:Formの内部コード抜粋

private void UpdateWindowIcon(bool redrawFrame)
{
    if (!base.IsHandleCreated)
    {
        return;
    }
    :
    SendMessage(128, 1, icon.Handle); // 128 = WM_SETICON
```

なので、以下の順になっていないといけない。
1. `HandleCreated`イベント発生
2. `Form.Icon`へのハンドルのset
3. `DestroyIcon`の実行

# 参考サイト

1. https://docs.microsoft.com/en-us/dotnet/api/system.drawing.icon.fromhandle?view=netframework-4.8
2. https://dobon.net/vb/dotnet/graphics/saveimage.html

## おまけ - 参考サイト 1 のサンプルコードの完全版

参考サイト 1 のサンプルコードは一部しかかかれておらず、そのままでは実行できないため、
`GetHicon_Example`メソッドと`DestroyIcon`以外を付け足した。

```cs

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

class IconTest : Form
{
    [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = CharSet.Auto)]
    extern static bool DestroyIcon(IntPtr handle);

    IconTest()
    {
        Paint += (sender,e)=>{GetHicon_Example(e);};
    }

    private void GetHicon_Example(PaintEventArgs e)
    {

        // Create a Bitmap object from an image file.
        Bitmap myBitmap = new Bitmap(@"c:\FakePhoto.jpg");

        // Draw myBitmap to the screen.
        e.Graphics.DrawImage(myBitmap, 0, 0);

        // Get an Hicon for myBitmap.
        IntPtr Hicon = myBitmap.GetHicon();

        // Create a new icon from the handle. 
        Icon newIcon = Icon.FromHandle(Hicon);

        // Set the form Icon attribute to the new icon.
        this.Icon = newIcon;

        // You can now destroy the icon, since the form creates
        // its own copy of the icon accessible through the Form.Icon property.
        DestroyIcon(newIcon.Handle);

    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new IconTest());
    }
}
```

`DestroyIcon`のWindowsAPIの説明を見ても.Netでの使用を考慮していない説明になっており、このコードの`DestroyIcon`を呼び出しているとこのコメントが参考になった。