2012年11月29日木曜日

[C#] 画像描画と背景透過

[共通] 当 Blog の投稿に関する基本事項について
→ http://fuunyan.blogspot.jp/2012/09/blog.html


画像描画と銘打っていますが、実のところ先行して投稿した下記2記事の続きと言ってもいいかもしれません。

[C#] ListView のカラム部分に画像表示

[C#] ListView のカラム部分に画像表示(其の2:HDITEM版)

其の2の中で、実は背景が透過していないため色の指定次第で画像背景が目立ってしまうと問題定義しておいたことに対する対策方法となります。
タイトルを引き続き利用しようかとも考えたのですが、基本的に内容が異なるために別タイトルとしました。

前置きはこのくらいにして本題に入りましょう。
今回は前提無しです。
でいきなりコードとなります(説明はのちほど)
// OdrIdx:表示する数字
// Odr   :三角形の向き
private Bitmap CreateSortOrderBmp(int OdrIdx, int Odr)
{
    // やっていること
    // 背景塗りつぶし → ソート順により三角形作成 → フォント描画(上下中央右寄せ)
    Bitmap img = new Bitmap(16, 16);
    Graphics g = Graphics.FromImage(img);

    // 背景を塗りつぶし(透過色)
    g.FillRectangle(new SolidBrush(this.TransparencyKey), g.VisibleClipBounds); // ①

    // 三角描画
    Point[] pnt = new Point[4];
    switch (Odr)
    {
        case 1: // 上三角
            pnt = new Point[4] { new Point(4, 2), new Point(0, 10), new Point(8, 10), new Point(4, 2) };
            break;
        case 2: // 下三角
            pnt = new Point[4] { new Point(4, 13), new Point(0, 5), new Point(8, 5), new Point(4, 13) };
            break;
        case 0: // 非表示化
        default:
            // 無印化するので描画処理をやらずに終了
            g.Dispose();
            return img;
    }
    g.FillPolygon(Brushes.Gray, pnt);   // 先に塗りつぶし
    g.DrawPolygon(Pens.Black, pnt);     // 次に、線描画

    // 文字を描画
    Font fnt = new Font("MS ゴシック", 8, FontStyle.Bold);
    StringFormat sfmt = new StringFormat();

    sfmt.Alignment = StringAlignment.Far;           // 横の表示位置
    sfmt.LineAlignment = StringAlignment.Center;    // 縦の表示位置

    g.DrawString(OdrIdx.ToString(), fnt, Brushes.Red, new RectangleF(new PointF(0, 0), new SizeF(16, 16)), sfmt);

    // 描画作業が完了したのでグラフィッククラスを解放
    g.Dispose();

    // 作成した画像を戻り値に
    return img;
}
御覧の通り、今回はメソッドとなります。
画像描画を書いたことある人には、なんてことない内容となっています。
やりたかったことは、リストのカラムをクリックすることでソートするので、それの昇順と降順、選択順番を明記するために画像を表示しようと考えていました。
そんなこんなの描画部分についてはソースを見ていただくとして、今回メインとなる背景透過の部分について。
ズバリ①の部分です。
コメントにも書いてあるのでわかるかと思いますが対象画像の背景をフォームで透過色と指定されている色で塗りつぶしています。
こうすることでカラム部分に表示しても昇降を示す三角と数字以外は表示されないことになります。
あとはそうして作成した Bitmap を ImageList の該当する部分に登録してあげれば対策完了です。
当然ですが ImageList の項目数 = カラム数と言うことになるので、他にもアイコンやらを使う予定がある場合はあらかじめ ImageList の配置仕様を検討しておく必要があると思います。

あと当たり前の話ですが、事前に ImageList に画像を登録して、それを利用する場合は登録する画像の背景を this.TransparencyKey で指定する色と同じにしておく必要があるので注意です。

[C#] ListView のカラム部分に画像表示(其の2:HDITEM版)

[共通] 当 Blog の投稿に関する基本事項について
→ http://fuunyan.blogspot.jp/2012/09/blog.html


先日、ほぼ同様のタイトルでカラムに画像を表示する方法を投稿しました。
[C#] ListView のカラム部分に画像表示
この時は ImageList を利用して表示する方法となっていましたが、今回は別の方法で画像を表示してみたいと思います。

表示パターンが確定していて動作中に変更が必要無い場合は、画像ファイルを準備した状態で前回の方法を利用してもらうのが一番だと思います。
しかし状況により表示内容を変更したい(動的に描画する)場合は、少々不便です。
確かに該当する ImageList の内容を書き換えてしまえば前回の方法で表示することは可能ですが、それだと ImageList を利用して準備している意味がありません。

そこで今回は動的に描画した画像をカラムに表示させてみたいと思います。
(妙に説明調になっているのは、自分が忘れそうだからです)
Header Control (Windows)


まずは宣言。
// 前回も宣言したもの
using System.Runtime.InteropServices;

public const UInt32 LVM_GETHEADER = 0x101F;
public const int HDF_BITMAP_ON_RIGHT = 0x1000;

[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, UInt32 wParam, UInt32 lParam);

// 以下前回より追加宣言したもの
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct HDITEM
{
 public uint mask;
 public int cxy;
 public IntPtr pszText;
 public IntPtr hbm;
 public int cchTextMax;
 public int fmt;
 public int lParam;
 public int iImage;
 public int iOrder;
 public uint type;
 public IntPtr pvFilter;
 public uint state;
}

public const int HDF_BITMAP = 0x2000;
public const uint HDI_FORMAT = 0x0004;
public const uint HDI_BITMAP = 0x0010;
public const uint HDM_FIRST = 0x1200;
public const uint HDM_GETITEM = HDM_FIRST + 3;
public const uint HDM_SETITEM = HDM_FIRST + 4;

[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, UInt32 wParam, ref HDITEM lParam);
前回と似たような感じですが、新規宣言したものがあります。
相変わらず情報源は CommCtrl.h となっています。

次はコード本体です。
今回もボタン押下イベントに書いています。
IntPtr colHeader;

// リストビューのカラム部分のハンドルを取得
colHeader = SendMessage(listView1.Handle, LVM_GETHEADER, (UInt32)0, (UInt32)0);

// 設定対象のカラム情報を取得
HDITEM hdItem = new HDITEM();
hdItem.mask = HDI_FORMAT;
SendMessage(colHeader, HDM_GETITEM, 0, ref hdItem);

// 設定対象のカラム情報を変更、反映
hdItem.mask = HDI_FORMAT | HDI_BITMAP;
hdItem.fmt |= HDF_BITMAP | HDF_BITMAP_ON_RIGHT;
Bitmap bmp = CreateSortOrderBmp(1, 1);
hdItem.hbm = bmp.GetHbitmap(SystemColors.Window);
SendMessage(colHeader, HDM_SETITEM, 0, ref hdItem);

/* 参考:Header_GetItem と Header_SetItem の定義内容
#define Header_GetItem(hwndHD, i, phdi) \
    (BOOL)SNDMSG((hwndHD), HDM_GETITEM, (WPARAM)(int)(i), (LPARAM)(HD_ITEM *)(phdi))
            
#define Header_SetItem(hwndHD, i, phdi) \
    (BOOL)SNDMSG((hwndHD), HDM_SETITEM, (WPARAM)(int)(i), (LPARAM)(const HD_ITEM *)(phdi))
*/
CreateSortOrderBmp(1, 1)の中でBMP画像を作成しています。
利用される場合は、その辺を適当に変更していただければいいかと。

このコードでリストのカラムテキストの右側に画像を表示することはできるのですが、少し後に問題があることに気付きました。
ほとんど保護色になっていて気付かなかったのですが、背景色が透過していないため、画面の色指定次第で画像部分が目立って表示されることがあります。
それなりに考えた背景色にしておけば気づかれない可能性はありますが、完璧を求めるならばこの方法では駄目だということになります。
その辺の対策は ImageList を利用した前回のパターンで解消できます。

じゃ何でこの記事投稿したんだ?って話になるのですが、そこはそれメモです。
せっかく調べたのに消すのはもったいないですからね。

上記理由から、特に理由がないようでしたら前回か次回(予定)の投稿を参照してください。

2012年11月22日木曜日

[C#] 文字列まわりの基本

[共通] 当 Blog の投稿に関する基本事項について
→ http://fuunyan.blogspot.jp/2012/09/blog.html


2016/03/18 ⇒ 追記

今更ながらに文字列まわりの基本をいくつか確認したのでメモしておく。

●シングルクォーテーション、ダブルクォーテーション
シングルが文字でダブルが文字列。
・・・ホント今更。
これだけだと、ちょっとおバカさんなので他にも。

文字コード指定は\uXXXXで指定。
文字列の場合「逐語的文字列リテラル」というのがあってエスケープシーケンスを無視させる書式がある。
シングルやダブルの前に @ をつければいい。
string str = @"aaa\nbbb\nccc";
Console.Write(str);
@ を使わない場合
aaa
bbb
ccc
と表示されるが @ があると
aaa\nbbb\nccc
と表示される。
ちなみに複数行にわたっての記述に対応していて
string str = @"この下の行も文字列扱いになる。
string str = ""ここも文字列扱い"";
ここまで文字列";
Console.WriteLine(str);
これを実行すると 3 行にわたり文書が表示される。
対象になる行の改行、スペースもそのまま表示されるのでコード上ではインデントで空白を入れたくてもいれられないので注意(入れちゃうとその分しっかりと空白が表示されます)


●文字列が null かどうか情報参照元
いろいろ手段がある。
string str = "ほげほげ";
if(str == "") Console.write("空白です");
if(str == string.Enpry) Console.write("空白です");
if(str.Equals("")) Console.write("空白です");
if(str.Equals(string.Enpry)) Console.write("空白です");
if(string.IsNullOrEmpty(str)) Console.write("空白です");
if(str.Length == 0 ) Console.write("空白です");
どれで判断しても結果として空白かどうかの判断ができる。
対象が空白の場合に、ただ空白か否かの判定をする場合は str.Length == 0, String.Equals, == "" の順が早いようです(詳しくは情報参照元やMSDNにて)
ただ普段使いとしては IsNullOrEmpty がいいようです(MS的に推奨されているとのこと)

余談だが調べている時にnull合体演算子というものがあるのを知った。
string str = null;
str = str ?? "nullです";
?? の左辺が null でないなら左辺値を null であるなら右辺値を返すらしい(詳しくはMSDNを参照のこと)
使い方次第ではすっきりしたコードを書くことができそう。


●デバッグ時のログ出力
デバッグ中とかにブレークするほどではないけど値を確認したい場合に出力画面に結果を表示する方法。
System.Diagnostics.Trace.WriteLine("");


●デバッグ時の時間計測
動作時間を計測したい場合はStopwatchを利用すると便利。
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
sw.Stop();
sw.Restart();
sw.Reset();


以上、確認事項メモでした。

2012年11月20日火曜日

[C#] ListView のカラム部分に画像表示

更新履歴:
2012/11/29:宣言部分で DLL の宣言が抜けていたので追記。

[共通] 当 Blog の投稿に関する基本事項について
→ http://fuunyan.blogspot.jp/2012/09/blog.html


タイトル通り ListView のカラム部分に画像を表示する方法です。
ただしカラム文字列の左に表示するだけなら SmallImageList にImageList を登録して Columns.ImageIndex を設定するだけで表示できるので、取り立てて記事にするほどでもありません。
←これは簡単

今回記事にした理由は、文字列の右側に画像を表示しようとしているためです。
たとえば列でソートする機能を作成した時や、何らかの印を表示したい時などに便利かと。
ではさっそく。

環境は Form 上に Button, ListView, ImageList をそれぞれ一つずつ配置しています。
ImageList には適当に画像を登録しておいてください。
カラムに登録するサイズなのでフォントサイズにもよりますが、縦16 x 横8 程度が見栄えいいかと。
とりあえず、この投稿では以下のような感じで登録しています。

参照したサイトは以下の通り。
●PRB: Cannot Assign Images to a ColumnHeader Control in a Windows Forms ListView Control in Visual C# .NET
●Common Control Versions (Windows)
●LVCOLUMN structure (Windows)

おまけの類似項目
●HDITEM structure (Windows)

とりあえず宣言部分です
using System.Runtime.InteropServices;

public const UInt32 LVM_GETHEADER = 4127;
public const UInt32 HDM_SETIMAGELIST = 4616;
public const UInt32 LVM_SETCOLUMN = 4122;
public const uint LVCF_FMT = 1;
public const uint LVCF_IMAGE = 16;
public const int LVCFMT_IMAGE = 2048;

// ListView のカラム情報設定用構造体
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Auto)]
public struct LVCOLUMN
{
    public uint mask;
    public int fmt;
    public int cx;
    public IntPtr pszText;
    public int cchTextMax;
    public int iSubItem;
    public int iImage;
    public int iOrder;
}

// 以下 fmt で利用する定義で参照元に無かったやつ( CommCtrl.h より定義参照)
// ComCtl32.dll ver4.70 以降でのみ利用可能らしい
// ただし 4.70 というのは Internet Explorer 3.0 って事なので今時では制約にならない
public const int HDF_IMAGE = 0x0800;      // Same as LVCFMT_IMAGE
public const int HDF_BITMAP_ON_RIGHT = 0x1000;      // Same as LVCFMT_BITMAP_ON_RIGHT
// おまけ:参考サイトのコメント欄にて発見。隠し?項目らしいです(CommCtrl.h には定義あり)
public const int HDF_SORTUP = 0x0400;      // Windows7 Explorer のソート順表示(上)
public const int HDF_SORTDOWN = 0x0200;      // Windows7 Explorer のソート順表示(下)

[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, UInt32 wParam, UInt32 lParam);

[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 msg, UInt32 wParam, ref LVCOLUMN lParam);
覚書みたいなものはコメントにゴチャゴチャ書いているので省略します。

次はボタン押下イベントです。
IntPtr colHeader;
int i;

// リストビューのカラム部分のハンドルを取得
colHeader = SendMessage(listView1.Handle, LVM_GETHEADER, (UInt32)0, (UInt32)0);

// カラム部のハンドルにイメージリストを割り当て
SendMessage(colHeader, HDM_SETIMAGELIST, (UInt32)0, (UInt32)imageList1.Handle);

// カラムの設定
for (i = 0; i < listView1.Columns.Count; i++)
{
    // Use the LVM_SETCOLUMN message to set the column's image index. 
    LVCOLUMN col;
    //  col.mask: include LVCF_FMT | LVCF_IMAGE 
    col.mask = LVCF_FMT | LVCF_IMAGE;

    // LVCFMT_IMAGE 
    col.fmt = LVCFMT_IMAGE | HDF_BITMAP_ON_RIGHT;
//    col.fmt = LVCFMT_IMAGE | HDF_SORTUP;

    // 表示するイメージの番号(仮に固定値で ImageList の 1 番目をしていしている)
    col.iImage = 1;

    //  Initialize the rest to zero.
    col.pszText = (IntPtr)0;
    col.cchTextMax = 0;
    col.cx = 0;
    col.iSubItem = 0;
    col.iOrder = 0;

    // Send the LVM_SETCOLUMN message.
    // The column that we are assigning the image to is defined in the third parameter.
    SendMessage(listView1.Handle, LVM_SETCOLUMN, (UInt32)i, ref col);
}
まぁ何というか、ほぼ MS サイトのコードまる写しです、はい。
col.fmt 部分の設定がキモになっています。
上記通りにやればリンクしてある ImageList の 1 番目の画像が表示されます。
コメントになっているように記述すると Windows7 の explorer の詳細表示部分のソート表記と同じにできます。
左:LVCFMT_IMAGE | HDF_BITMAP_ON_RIGHT
右:LVCFMT_IMAGE | HDF_SORTUP

何にしろ画像は表示できたので目的達成です。