2012年12月25日火曜日

[Windows] システム周りな話

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


開発言語にとらわれないシステム共通な部分の覚書です。

画面サイズの事
GetSystemMetrics で諸々取得可能
SM_CYSMCAPTION タイトルバー 21pixel
SM_CXSIZEFRAME 枠 8pixel


2012年12月12日水曜日

[C#] WinAPI を呼んでみる

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


以前の投稿内では普通に使っているのですが、特に記載していないようだったので別途投稿してみようと思います。
C# は便利ですが、細かいことをやろうとするとどうしても API に頼ることになります。
では API ってどうやって呼びだすのな部分を今回はネタにしてみました。

using System.Runtime.InteropServices;   // DllImportなど、API呼び出し
namespace CSWinFormTest2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // API を利用するのに必要な宣言(インポートライブラリの宣言)
        [DllImport("user32.dll")]
        // API を利用するのに必要な宣言(インポートライブラリの宣言、オプション指定はこんな感じ)
//        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        // API を利用するのに必要な宣言(関数定義の宣言)
        // static extern は必須。アクセス演算子は任意
        private static extern UInt32 MessageBox(IntPtr hWnd, String lpText, String lpCaption, UInt32 uType);

        // MessageBox() Flags  winuser.h より(一部抜粋)
        private const UInt32 MB_OK                   = 0x00000000;
        private const UInt32 MB_ICONASTERISK         = 0x00000040;
        private const UInt32 MB_ICONINFORMATION      = MB_ICONASTERISK;

        private void button1_Click(object sender, EventArgs e)
        {
            // API の利用方法を確認
            MessageBox(this.Handle, "メッセージ本文", "タイトル部", MB_OK | MB_ICONINFORMATION);

            // 余談:C# では MessageBox.Show があるので、本来上記のような手間は必要はない
            System.Windows.Forms.MessageBox.Show("メッセージ本文", "タイトル部",
                MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }
}
短いので全部まとめて書いています。
まぁコメント見ていただければわかるかと思います。
DllImport で使用するライブラリを指定して関数定義を宣言。
あとは普通に利用するだけです。

ちなみに複数種類の API を利用したい場合は、その関数毎に定義を各必要があります。
オーバーロードするような場合でも DllImport から書く必要があります。
これがめんどくさいので結局 C++/CLI で作るかぁ~?ってなっちゃいます。
まぁ DllImport から関数定義までが一つの宣言だと思ってあきらめるしかありません。

全体的に見れば C# の方が生産性いいはずなので、できるだけ C# でやっていこうかな、っと。

--- 2012/12/27追記 ----------
よく見に行くサイトをいちいち検索するのがめんどくさいので、こちらに転記させていただいた。
毎度お世話になっております(@IT .NET TIPS Win32 APIやDLL関数を呼び出すには?
APIでの型名
(括弧内は対応するC言語の型)
対応するC#の型
(括弧内は.NET Frameworkでの型名)
HANDLE (void *)System.IntPtr
BYTE (unsigned char)byte (System.Byte)
SHORT (short)short (System.Int16)
WORD (unsigned short)ushort (System.UInt16)
INT (int)
LONG (long)
int (System.Int32)
UINT (unsigned int)
DWORD, ULONG (unsigned long)
uint (System.UInt32)
BOOL (long)bool (System.Boolean)
CHAR (char)char (System.Char)
LPSTR (char *)
LPWSTR (wchar_t *)
System.Text.StringBuilder
LPCSTR (const char *)
LPCWSTR (const wchar_t *)
string (System.String)
FLOAT (float)float (System.Single)
DOUBLE (double)double (System.Double)
Win32 APIでの型名と対応するC#の型 WindowsのDLL(Win32 API)と.NET Frameworkとでは型の管理方法が違うため、実際には型の相互変換(マーシャリング)が行われる。なお、BOOL型の実体はLONG型と同じなので、boolの代わりにintを指定することも可能である。

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

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

2012年10月24日水曜日

[java]パッケージについて

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


インナークラスについて勉強中
 ↓
アクセス制御について再度確認
 ↓
パッケージ部分について再確認するためにコード書いてみる
 ↓
見事にハマって実行できないorz ← 投稿の原点

こんな流れでパッケージの書き方、ファイルの置き方どうすればいいのか正しくわからなかったので、いろいろ調べた結果を書き出しておく。
なお私の環境はカレントディレクトリとして D:\Data\java 、システム環境には JAVA_HOME は定義していますが CLASSPATH は定義していません。
D:\Data\java>set JAVA_HOME
JAVA_HOME=C:\Java\jdk1.7.0_07

D:\Data\java>set CLASSPATH
環境変数 CLASSPATH が定義されていません

●定義方法と格納場所
パッケージの作成と言うのは別にむずかしいことはない。
普通にコーディングをしてコンパイルをすればいいだけです。
注意するのは以下の部分

・ファイルの先頭でキーワードを宣言する。
hoge.java
// hoge はパッケージ名です、これが必要
package hoge;

public class testPackage{
 public void show(){
  System.out.println("testPackage Message");
 }
}

・キーワードと同じ名称のフォルダーにファイルを格納する
たとえば上のキーワード宣言をしているファイルを格納するなら hoge フォルダーに入れること。
とりあえずこれだけ。
細かいことを言うとフォルダー名とかはコンパイルオプションでなんとかなりそうですが、一意性の保証など諸々の理由から、イレギュラーな手法については考えず、この方法を利用した方がいいでしょう。

ちなみに個別にコンパイルも当然できますが、呼び出し元をコンパイルすることで関連するパッケージファイルも勝手にコンパイルされるようです。


●呼び出し元での記述方法
さて呼び出し元。
こちらも難しい事はありません。
普通に java のコーディングができるなら、いつも通りコードを記述していくだけです。
hogecall.java
class hogeCall{
 public static void main(String[] ages){
  hoge.testPackage tp = new hoge.testPackage();
  tp.show();
 }
}

ちなみに hoge の記述を省略したいのなら hoge をインポートしてしまうことで実現できます。
import hoge.*;

class hogeCall{
 public static void main(String[] ages){
  testPackage tp = new testPackage();
  tp.show();
 }
}


●具体的な配置
上記二つのファイルの具体的な配置は以下の通り。
+カレントディレクトリ
 |hogecall.java
  +hoge
   |hoge.java

こんな感じです。
カレント配下に hoge ディレクトリ
あとはカレントに hogecall.java 、hoge ディレクトリ内に hoge.java を配置するだけです。
この状態で hogecall.java をコンパイルすれば、ちゃんとメッセージが表示されるはずです。
D:\Data\java>javac hogecall.java

D:\Data\java>java hogecall
testPkgMain Message


実は本題のアクセス制御やらの確認ができていないのですが、まぁ次回にでも。