Top / .NET備忘録 / 01.TextBox / 03.Aero での背景色

Aero が適用された環境では、Enabled = False のときの背景色が、BackColor プロパティに代入しただけでは制御できません。

Enabled プロパティがオーバーライドできれば比較的楽に実装できるのですが、残念ながら virtual ではありません。

かといって、new してしまうと、Control や TextBox にキャストしたときの値とずれてしまう可能性があります。

なので、Enabled プロパティが False のときは、オーナードローすることにします。

ControlStyles.UserPaint を切り替えるのはコストが高いため、WM_PAINT/WM_PRINTCLIENT をキャッチし、Enabled = False ならオーナードローします。

また、ちらつきを防ぐため WM_ERASEBKGND を無視します。

private const int WM_PAINT = 0x000F;
private const int WM_ERASEBKGND = 0x0014;
private const int WM_PRINTCLIENT = 0x0318;

protected override void WndProc(ref Message m) {
    switch (m.Msg) {
        case WM_PAINT:
            if (Enabled) {
                base.WndProc(ref m);
            } else {
                WmPaint(ref m);
            }
            break;

        case WM_PRINTCLIENT:
            if (Enabled) {
                base.WndProc(ref m);
            } else {
                WmPrintClient(ref m);
            }
            break;

        case WM_ERASEBKGND:
            if (Enabled) {
                base.WndProc(ref m);
            } else {
                // 無視する
            }
            break;

        default:
            base.WndProc(ref m);
            break;
    }
}

Enabled プロパティは、親の Enabled プロパティが False なら、自分のプロパティがどのように設定されているかは関係なく、False を返します。 自分に設定された値を取得するには、残念ながら、Reflection を使用するしかありません。

 // コントロール内部の Enabled プロパティを取得

 internal const int STATE_ENABLED = 0x00000004;
 static MethodInfo GetStateMethodInfo = null;
 
 private bool InnerEnabled
 {
     get 
     {
         if (GetStateMethodInfo == null)
         {
             GetStateMethodInfo = typeof(Control).GetMethod("GetState", BindingFlags.Instance | BindingFlags.NonPublic);
         }
         return (bool)GetStateMethodInfo.Invoke(this, new object[] { STATE_ENABLED });
     }
 }

WM_PAINT では、BeginPaint でデバイスコンテキストハンドルを取得し、Graphics オブジェクトを作成して描画し、EndPaint で後始末します。

その際、System.Drawing.BufferedGraphicsManager クラスを使ってダブルバッファ処理を行います。

WParam にデバイスコンテキストハンドル(HDC)をセットすると、それに対して描画するというコントロールの仕様があるので少し注意です。

[StructLayout(LayoutKind.Sequential)]
private struct PAINTSTRUCT {
    public IntPtr hdc;
    public bool fErase;
    public int rcPaint_left;
    public int rcPaint_top;
    public int rcPaint_right;
    public int rcPaint_bottom;
    public bool fRestore;
    public bool fIncUpdate;
    public int reserved1;
    public int reserved2;
    public int reserved3;
    public int reserved4;
    public int reserved5;
    public int reserved6;
    public int reserved7;
    public int reserved8;
}

[DllImport("user32.dll")]
private static extern IntPtr BeginPaint(HandleRef hWnd, ref PAINTSTRUCT lpPaint);

[DllImport("user32.dll")]
private static extern bool EndPaint(HandleRef hWnd, ref PAINTSTRUCT lpPaint);

private void WmPaint(ref Message m) {
    if (m.WParam == IntPtr.Zero) {
        HandleRef hwnd = new HandleRef(this, m.HWnd);
        PAINTSTRUCT ps = new PAINTSTRUCT();
        IntPtr dc = BeginPaint(hwnd, ref ps);
        HandleRef hdc = new HandleRef(this, dc);
        Rectangle clip = Rectangle.FromLTRB(ps.rcPaint_left, ps.rcPaint_top, ps.rcPaint_right, ps.rcPaint_bottom);
        try {
            if (clip.Width > 0 && clip.Height > 0) {
                // ダブルバッファ処理を行う
                var bufferContext = BufferedGraphicsManager.Current;
                using (var bufferedGraphics = bufferContext.Allocate(dc, clip)) {
                    using (Graphics g = bufferedGraphics.Graphics) {
                        DrawDisableTextBox(g, clip);
                        bufferedGraphics.Render();
                    }
                }
            }
        } finally {
            EndPaint(hwnd, ref ps);
        }
    } else {
        // WParam にセットされた HDC に描画する
        DrawDisableTextBox(m.WParam);
    }
}

WM_PRINTCLIENT では、WParam に HDC が入ってくるのでそれに対して描画します。

private void WmPrintClient(ref Message m) {
    DrawDisableTextBox(m.WParam);
}

ClientRectangle に文字列を描画してしまうと少しずれてしまいます。 EM_GETRECT で領域を取得して、その範囲に描画するといい感じになります。

[StructLayout(LayoutKind.Sequential)]
private struct RECT {
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

private const int EM_GETRECT = 0x00B2;

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(HandleRef hWndRef, int Msg, IntPtr wParam, ref RECT lParam);

private Rectangle GetTextFace() {
    var rect = new RECT();
    SendMessage(new HandleRef(this, this.Handle), EM_GETRECT, IntPtr.Zero, ref rect);
    return Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom);
}

描画部分です。
Graphics.DrawString ではなく、TextRenderer.DrawText で描画します。

private void DrawDisableTextBox(IntPtr dc) {
    var clip = this.ClientRectangle;
    if (clip.Width > 0 && clip.Height > 0) {
        using (var g = Graphics.FromHdc(dc)) {
            DrawDisableTextBox(g, clip);
        }
    }
}

private void DrawDisableTextBox(Graphics g, Rectangle clip) {
    g.SetClip(clip);
    g.Clear(BackColor);
    var text = PasswordChar == '\0' ? Text : new string(PasswordChar, TextLength);
    var textColor = InnerEnabled ? ForeColor : DisabledTextColor(BackColor);
    TextRenderer.DrawText(g, text, Font, GetTextFace(), textColor, CreateTextFormatFlags());
}

private static Color DisabledTextColor(Color backColor) {
    Color disabledTextForeColor = SystemColors.ControlDark;
    if (IsDarker(backColor, SystemColors.Control)) {
        disabledTextForeColor = ControlPaint.Dark(backColor);
    }
    return disabledTextForeColor;
}

private static bool IsDarker(Color c1, Color c2) {
    float hc1 = c1.R + c1.G + c1.B;
    float hc2 = c2.R + c2.G + c2.B;
    return (hc1 < hc2);
}

private TextFormatFlags CreateTextFormatFlags() {
    TextFormatFlags format = TextFormatFlags.NoPrefix | TextFormatFlags.ExpandTabs
                           | TextFormatFlags.NoClipping | TextFormatFlags.NoPadding
                           | TextFormatFlags.TextBoxControl;
    if (!this.Multiline) {
        format |= TextFormatFlags.SingleLine;
    } else if (this.WordWrap) {
        format |= TextFormatFlags.WordBreak;
    }
    var textAlign = this.TextAlign;
    if (this.RightToLeft == RightToLeft.Yes) {
        format |= TextFormatFlags.RightToLeft;
        textAlign = RtlTranslateAlignment(textAlign);
    }
    switch (textAlign) {
        case HorizontalAlignment.Center:
            format |= TextFormatFlags.HorizontalCenter;
            break;

        case HorizontalAlignment.Right:
            format |= TextFormatFlags.Right;
            break;

        default:
            format |= TextFormatFlags.Left;
            break;
    }
    return format;
}



トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   最終更新のRSS
Last-modified: 2018-11-28 (水) 22:33:22 (13d)