• 追加された行はこの色です。
  • 削除された行はこの色です。
#title(.NETプログラミング研究 第5号)

#navi(.NET プログラミング研究)

#contents

*.NETプログラミング研究 第5号 [#k277db53]

**注意事項 [#y3b522fa]

今回はコードが長いため、VB.NETのコードのみを紹介します。ご了承ください。C#のコードについては、私のサイトで紹介するかもしれません。

**ピンポイントリンク [#q3e43a7a]

***メニューにアイコンを表示する2 [#yb112a6f]

前回メニュにアイコンを表示する方法についてその基礎を説明しましたが、結局いくつかの問題が残ってしまいました。今回は前回作成したImageMenuItemクラスをより使えるものに改善させます。

前回のImageMenuItemクラスの欠点として主に次の2点が挙げられます。

+メニュー項目が無効になっているとき(EnabledがFalseになっているとき)の処理
+チェックが付いているとき(CheckedがTrueのとき)の処理

まず1番目の問題ですが、これには文字列を立体的に灰色表示する方法と、画像を立体的に灰色表示する方法の2つのテクニックが必要になります。

多分これが一般的なのではないかと思いますが、まずはAPIを使う方法を紹介します。Win32 APIのDrawState関数を使うことにより、無効状態の文字列や画像を描画できます。以下にDrawState関数を使った簡単な使用例を示します。

(下記コード内のDrawStateString関数で描画した文字列の位置は、同じ位置にGraphics.DrawStringメソッドで描画した文字列の位置よりわずかに左側にずれてしまいます。DrawState関数を使うのであれば、すべてにおいてDrawState関数を使って描画すべきかもしれません。)

#code(vbnet){{
'API宣言
Private Declare Function DrawState Lib "user32" Alias "DrawStateA" _
	(ByVal hDC As IntPtr, _
	ByVal hBrush As Integer, _
	ByVal lpDrawStateProc As Integer, _
	ByVal lParam As IntPtr, _
	ByVal wParam As Integer, _
	ByVal n1 As Integer, _
	ByVal n2 As Integer, _
	ByVal n3 As Integer, _
	ByVal n4 As Integer, _
	ByVal un As Integer) As Integer
Private Declare Function DrawStateString Lib "user32" _
	Alias "DrawStateA" (_
	ByVal hDC As IntPtr, _
	ByVal hBrush As Integer, _
	ByVal lpDrawStateProc As Integer, _
	ByVal lpString As String, _
	ByVal cbStringLen As Integer, _
	ByVal n1 As Integer, _
	ByVal n2 As Integer, _
	ByVal n3 As Integer, _
	ByVal n4 As Integer, _
	ByVal un As Integer) As Integer

Private Const DST_COMPLEX = &H0
Private Const DST_TEXT = &H1
Private Const DST_PREFIXTEXT = &H2
Private Const DST_ICON = &H3
Private Const DST_BITMAP = &H4

Private Const DSS_NORMAL = &H0
Private Const DSS_UNION = &H10
Private Const DSS_DISABLED = &H20
Private Const DSS_MONO = &H80
Private Const DSS_RIGHT = &H8000

'メニュー項目に表示させるアイコン
Private _icon As New Icon("open.ico")

'メニュー項目の描画
Private Sub MenuItem2_DrawItem(ByVal sender As System.Object, _
        ByVal e As System.Windows.Forms.DrawItemEventArgs) _
        Handles MenuItem2.DrawItem
    '無効状態のアイコンを描画
    Dim hdc As IntPtr = e.Graphics.GetHdc()
    If Not (_icon Is Nothing) Then
        DrawState(hdc, 0, 0, _icon.Handle, 0, e.Bounds.Left + 2, _
            e.Bounds.Top + (e.Bounds.Height - _icon.Height) \ 2, _
            16, 16, DST_ICON Or DSS_DISABLED)
    End If

    '無効状態の文字列を描画
    DrawStateString(hdc, 0, 0, sender.Text, LenB(sender.Text), _
        e.Bounds.Left + 22, _
        e.Bounds.Top + _
        (e.Bounds.Height - SystemInformation.MenuFont.Height) \ 2, _
        e.Bounds.Width, e.Bounds.Height, _
        DST_PREFIXTEXT Or DSS_DISABLED)
        
    '開放
    e.Graphics.ReleaseHdc(hdc)
End Sub

'VB6のLenBの代わりになるかもしれない関数
Private Function LenB(ByVal strString As String) As Integer
    'Shift JISに変換したときに必要なバイト数を返す
    Return System.Text.Encoding.GetEncoding(932). _
        GetByteCount(strString)
End Function
}}

APIを使わずに同じことができないものか調べたところ、それらしいメソッドを見つけました。それは、ControlPaintクラスのDrawImageDisabledメソッドとDrawStringDisabledメソッドです。いかにも「ビンゴ!」という感じの名前をしています。次にこれらのメソッドの使用例を示します。

#code(vbnet){{
'無効状態のアイコンを描画
ControlPaint.DrawImageDisabled(e.Graphics, _
    _image, x, y, SystemColors.Menu)

'無効状態の文字列を描画
ControlPaint.DrawStringDisabled(e.Graphics, _
    text, SystemInformation.MenuFont, _
    SystemColors.ControlLightLight, _
    New RectangleF(point.X, point.Y, _
		e.Bounds.Width, e.Bounds.Height), _
    sf)
}}

これで期待通りの結果が得られるかといいますと、そううまくは行きません。ControlPaint.DrawImageDisabledメソッドで描画された画像は立体的ではなく、平面的な画像で描画されます。ControlPaint.DrawStringDisabledメソッドで描画された文字列は立体的にはなりますが、よく見ると文字の色がちょっとだけ違います。(ControlPaint.DrawStringDisabledメソッドの4番目のパラメータ「文字列を描画するためのColor」にどの値を入れても同じにならないようです。)

こうなったら自分でなんとかすることにしましょう。無効状態の文字列は影を縦横に1ドットずらして描画すればいいだけなので、簡単です。

問題は画像の方です。ここでは確実に、元の画像を1ドットずつ調べて、新しく無効状態の画像を作成する方法を考えます。次にその例を示します。

(ここでは白のみを透明にしていますが、これには何の根拠もありません。
bmp.GetPixel(x, y).GetBrightness < 1.0F
の1.0Fを別の値にしたほうがいいのかもしれません。どの値が適当なのかご存知の方がいらっしゃいましたら、ぜひ教えてください。)

#code(vbnet){{
'無効状態の画像を作成する
Private Function CreateDisanabledImage(ByVal img As Image) _
        As Image
    Dim x, y As Integer

    If img Is Nothing Then
        Return Nothing
    End If

    '無効状態の画像 元の画像より縦横1ドットずつ大きくなる
    Dim dbmp As New Bitmap(img.Width + 1, img.Height + 1)
    'もとの画像
    Dim bmp As New Bitmap(img)

    '1ドットずつ調べる
    For y = 0 To bmp.Height - 1
        For x = 0 To bmp.Width - 1
            '元の色が透明か、白を透明にする
            If bmp.GetPixel(x, y).A = &HFF AndAlso _
                    bmp.GetPixel(x, y).GetBrightness < 1.0F Then
                dbmp.SetPixel(x, y, SystemColors.ControlDark)
                dbmp.SetPixel(x + 1, y + 1, _
                    SystemColors.ControlLightLight)
            End If
        Next
    Next

    bmp.Dispose()
    Return dbmp
End Function
}}

次に2番目の問題に移りたいところですが、予想以上に長くなってしまいましたので、今回はこの辺で終わりにさせていただき、続きはまた次回とさせて頂きます。次回こそは完成させたいですね。

**コメント [#m023e7b4]
#comment

//これより下は編集しないでください
#pageinfo([[:Category/.NET]],2003-04-15 (火) 06:00:00,DOBON!,2010-03-20 (土) 02:25:48,DOBON!)


[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]