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

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

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

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

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

OnEnabledChanged メソッドをオーバーライドし、ControlStyles.UserPaint を切り替えます。

注意しなければならないのは、Font です。

ControlStyles.UserPaint = True にすると、Font が Dispose() されるため、ウインドウハンドルが再作成されるような変更(TextAlign の変更など)を行うと、文字の描画がおかしくなります。

OnFontChanged を呼び出しておきましょう。

 protected override void OnEnabledChanged(EventArgs e)
 {
     base.OnEnabledChanged(e);
     SetStyle(ControlStyles.UserPaint, !base.Enabled);
     base.OnFontChanged(EventArgs.Empty);
 }

OnPaint / OnPaintBackground をオーバーライドし、描画します。

 protected override void OnPaint(PaintEventArgs e)
 {
     base.OnPaint(e);
     DrawTextBox(e.Graphics);
 }
 
 protected override void OnPaintBackground(PaintEventArgs pevent)
 {
     base.OnPaintBackground(pevent);
     pevent.Graphics.Clear(BackColor);
 }

忘れてはいけないのが、枠線の描画です。 WM_NCPAINT/WM_PAINT/WM_ERASEBKGND をキャッチして描画します。

 private const int
 WM_NCPAINT = 0x0085,
 WM_PAINT = 0x000F,
 WM_ERASEBKGND = 0x0014;
 
 protected override void WndProc(ref Message m)
 {
     switch (m.Msg)
     {
         case WM_NCPAINT:
         case WM_PAINT:
         case WM_ERASEBKGND:
             base.WndProc(ref m);
             if (!Enabled) DrawTextBoxBorder();
             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 });
     }
 }

描画部分のコーディングです。

 private void DrawTextBox(Graphics g)
 {
     // 描画位置を調整してますが根拠がありません(T_T)
     Rectangle face = new Rectangle(Point.Empty, this.Size);
     switch (this.BorderStyle)
     {
         case BorderStyle.Fixed3D:
             face.X += 1;
             face.Y += 1;
             face.Width -= 6;
             break;
 
         case BorderStyle.FixedSingle:
             face.X += 2;
             face.Y += 2;
             face.Width -= 4;
             face.Height -= 4;
             break;
     }
 
     // 描画色は内部的に保持している Enabled プロパティに従う
 
     Color foreColor;
     if (InnerEnabled)
         foreColor = base.ForeColor;
     else
         foreColor = SystemColors.ControlDark;
 
     TextFormatFlags flags = CreateTextFormatFlags();
     string text = base.Text;
     if ((!string.IsNullOrEmpty(text)) && ((int)PasswordChar != 0))
     {
         text = new string(PasswordChar, text.Length);
     }
     TextRenderer.DrawText(g, text, this.Font, face, foreColor, flags);
 }
 
 private TextFormatFlags CreateTextFormatFlags()
 {
     TextFormatFlags format = TextFormatFlags.NoPrefix | TextFormatFlags.ExpandTabs
                            | TextFormatFlags.NoClipping | TextFormatFlags.NoPadding;
     if (!this.Multiline)
     {
         format |= TextFormatFlags.SingleLine;
     }
     else if (this.WordWrap)
     {
         format |= TextFormatFlags.WordBreak;
     }
 
     switch (this.TextAlign)
     {
         case HorizontalAlignment.Center:
             format |= TextFormatFlags.HorizontalCenter;
             break;
 
         case HorizontalAlignment.Right:
             format |= TextFormatFlags.Right;
             break;
 
         default:
             format |= TextFormatFlags.Left;
             break;
     }
     if (this.RightToLeft == RightToLeft.Yes)
     {
         format |= TextFormatFlags.RightToLeft;
     }
     return format;
 }
 
 [DllImport("user32")]
 private static extern IntPtr GetWindowDC(HandleRef hWnd);
 
 [DllImport("user32.dll")]
 public static extern int ReleaseDC(HandleRef hwnd, IntPtr hDC);
 
 private void DrawTextBoxBorder()
 {
     HandleRef href = new HandleRef(this, this.Handle);
     IntPtr hDC = GetWindowDC(href);
 
     if (hDC != IntPtr.Zero)
     {
         using (Graphics g = Graphics.FromHdc(hDC))
         {
             PrintBorder(g, new Rectangle(Point.Empty, this.Size), this.BorderStyle, Border3DStyle.SunkenOuter);
         }
         ReleaseDC(href, hDC);
     }
 }
 
 private void PrintBorder(Graphics graphics, Rectangle bounds, BorderStyle style, Border3DStyle b3dStyle)
 {
     switch (style)
     {
         case BorderStyle.FixedSingle:
             ControlPaint.DrawBorder(graphics, bounds, SystemColors.WindowFrame, ButtonBorderStyle.Solid);
             break;
         case BorderStyle.Fixed3D:
             if (Application.RenderWithVisualStyles)
                 ControlPaint.DrawBorder(graphics, bounds, SystemColors.ControlDark, ButtonBorderStyle.Solid);
             else
                 ControlPaint.DrawBorder3D(graphics, bounds, b3dStyle);
             break;
         case BorderStyle.None:
             break;
         default:
             Debug.Fail("Unsupported border style.");
             break;
     }
 }

親ウインドウの Enabled プロパティが False のとき、子ウインドウの Enabled プロパティを True から False に変えても、OnEnabledChanged イベントが発生しないので、表示が変わりません。

False を代入した後は、再描画する必要がありそうです。 もしくは、Enabled プロパティを new して OnEnabledChanged を呼びます。

 public new virtual bool Enabled
 {
     get { return base.Enabled; }
     set 
     {
         bool parentEnabled = Parent != null ? Parent.Enabled : true ;
         bool innerEnabled = InnerEnabled;
         base.Enabled = value;
         if (!parentEnabled && !value && innerEnabled)
             OnEnabledChanged(EventArgs.Empty);
     }
 }

 後日談

描画位置については EM_GETRECT を送信すればよかったようです。

[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

private int EM_GETRECT = 0x00B2;

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

private void DrawTextBox(Graphics g)
{
    RECT rc = new RECT();
    IntPtr result = SendMessage(new HandleRef(this, this.Handle),
                                              EM_GETRECT, IntPtr.Zero, ref rc);
    Rectangle face = Rectangle.FromLTRB(rc.left, rc.top, rc.right, rc.bottom);



トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   最終更新のRSS
Last-modified: 2013-06-10 (月) 14:07:38 (1595d)