Top / .NET備忘録 / 99.小ネタ / 08.DropDownButton2

DropDownButton を VBTextBox の中に入れて使ってみます。

Imports VB6Control

Public Class DropDownTextBox
    Inherits VBTextBox

    Private WithEvents button As DropDownButton

    Public Sub New()
        button = New DropDownButton()
        button.Width = SystemInformation.HorizontalScrollBarArrowWidth
        button.Dock = DockStyle.Right
        button.Cursor = Cursors.Arrow
        Controls.Add(button)
    End Sub

    Private Sub button_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles button.Click
        MessageBox.Show("Click!!")
    End Sub

End Class

VBComboBox に比べると、あまり美しくありません。

dropdownbutton1.gif

いろいろ思案した結果、自分自身ではなく、親コントロールに対して描画することにしました。

Aero なドロップダウンボタンは

        private static readonly VisualStyleElement ComboBoxElement
                                    = VisualStyleElement.ComboBox.DropDownButton.Normal;
        private const int AeroDropDownRButtonPart = 6;
        private static readonly VisualStyleElement AeroDropDownRButtonElement
                                    = VisualStyleElement.CreateElement(ComboBoxElement.ClassName,
                                                                       AeroDropDownRButtonPart,
                                                                       ComboBoxElement.State);

と、VisualStyleElement を作成して描画します。

using System;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using VB6Control.Core;

namespace VB6Control
{
    public class DropDownButton2 : Button
    {
        Control m_Owner = null;
        ParentNativeWindow m_ParentNativeWindow = null;
        bool m_MouseIsDown = false;
        bool m_MouseIsOver = false;

        VisualStyleRenderer m_VisualStyleRenderer = null;
        private static readonly VisualStyleElement ComboBoxElement
                                    = VisualStyleElement.ComboBox.DropDownButton.Normal;
        private const int AeroDropDownRButtonPart = 6;
        private static readonly VisualStyleElement AeroDropDownRButtonElement
                                    = VisualStyleElement.CreateElement(ComboBoxElement.ClassName,
                                                                       AeroDropDownRButtonPart,
                                                                       ComboBoxElement.State);

        public DropDownButton2() {
            base.Cursor = Cursors.Arrow;
            SetStyle(ControlStyles.Selectable, false);
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
        }

        public DropDownButton2(Control owner)
            : this() {
            base.Width = SystemInformation.HorizontalScrollBarArrowWidth;
            base.Dock = DockStyle.Right;
            owner.Controls.Add(this);
        }

        protected override void OnParentChanged(EventArgs e) {
            base.OnParentChanged(e);
            this.Owner = base.Parent;
        }

        private Control Owner {
            get {
                return m_Owner;
            }
            set {
                if (!object.ReferenceEquals(value, Owner)) {
                    if (Owner != null) {
                        Owner.HandleCreated -= OwnerHandleCreated;
                        Owner.FontChanged -= OwnerFontChanged;
                        Owner.Resize -= OwnerResize;
                        ReleaseParentWindow();
                    }
                    m_Owner = value;
                    if (Owner != null) {
                        Owner.HandleCreated += OwnerHandleCreated;
                        Owner.FontChanged += OwnerFontChanged;
                        Owner.Resize += OwnerResize;
                        if (Owner.IsHandleCreated) {
                            AssignParentWindow();
                        }
                    }
                }
            }
        }

        private IntPtr OwnerHandle {
            get {
                if (Owner != null && Owner.IsHandleCreated) {
                    return Owner.Handle;
                } else {
                    return IntPtr.Zero;
                }
            }
        }

        private HandleRef OwnerHandleRef {
            get {
                return new HandleRef(Owner, OwnerHandle);
            }
        }

        private void OwnerHandleCreated(object sender, EventArgs e) {
            AssignParentWindow();
            OwnerFontChanged(sender, e);
        }

        // Font が変更されたら、FontHeight プロパティをセットする
        // (AutoSize = true の場合、高さが再計算されます。
        private void OwnerFontChanged(object sender, EventArgs e) {
            if (Owner.AutoSize) {
                PropertyInfo pi = Owner.GetType().GetProperty("FontHeight", BindingFlags.NonPublic | BindingFlags.Instance);
                pi.SetValue(Owner, Owner.Font.Height + 1, null);
            }
        }

        // サイズが変更されたとき、マージンをセットする
        private void OwnerResize(object sender, EventArgs e) {
            IntPtr margins = NativeMethods.SendMessage(new HandleRef(this, OwnerHandle),
                                                        NativeMethods.EM_GETMARGINS,
                                                        IntPtr.Zero, IntPtr.Zero);
            int leftMargin = NativeMethods.LOWORD(margins);
            // 左マージン+垂直スクロールバーの幅を右マージンにします。
            int rightMargin = leftMargin + SystemInformation.HorizontalScrollBarArrowWidth;
            IntPtr wParam = (IntPtr)(NativeMethods.EC_RIGHTMARGIN | NativeMethods.EC_LEFTMARGIN);
            IntPtr lParam = (IntPtr)(NativeMethods.MAKEDWORD(rightMargin, leftMargin));
            NativeMethods.SendMessage(new HandleRef(this, OwnerHandle),
                                        NativeMethods.EM_SETMARGINS, wParam, lParam);
        }

        // NativeWindow を継承したクラスで親コントロールをサブクラス化する
        private void AssignParentWindow() {
            m_ParentNativeWindow = new ParentNativeWindow(this);
            m_ParentNativeWindow.AssignHandle(OwnerHandle);
        }

        // サブクラス化の解除
        private void ReleaseParentWindow() {
            if (m_ParentNativeWindow != null) {
                m_ParentNativeWindow.ReleaseHandle();
            }
            m_ParentNativeWindow = null;
        }

        // 親コントロールをサブクラス化するクラス
        private class ParentNativeWindow : NativeWindow
        {
            private DropDownButton2 m_Owner;

            public ParentNativeWindow(DropDownButton2 owner) {
                m_Owner = owner;
            }

            [DebuggerStepThrough]
            protected override void WndProc(ref Message m) {
                switch (m.Msg) {
                    case NativeMethods.WM_NCDESTROY:
                        base.WndProc(ref m);
                        m_Owner.ReleaseParentWindow();
                        break;

                    case NativeMethods.WM_PAINT:
                        base.WndProc(ref m);
                        if (m_Owner.IsPaintParent) {
                            m_Owner.DrawButtonParent();
                        }
                        break;

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

        protected bool MouseIsDown {
            get { return m_MouseIsDown; }
            set {
                if (m_MouseIsDown != value) {
                    m_MouseIsDown = value;
                    InvalidateInternal();
                }
            }
        }

        protected bool MouseIsOver {
            get { return m_MouseIsOver; }
            set {
                if (m_MouseIsOver != value) {
                    m_MouseIsOver = value;
                    InvalidateInternal();
                }
            }
        }

        protected override void OnEnabledChanged(EventArgs e) {
            base.OnEnabledChanged(e);
            InvalidateInternal();
        }

        protected override void OnMouseCaptureChanged(EventArgs e) {
            base.OnMouseCaptureChanged(e);
            m_MouseIsOver = false;
            m_MouseIsDown = false;
            InvalidateInternal();
        }

        protected override void OnMouseDown(MouseEventArgs mevent) {
            base.OnMouseDown(mevent);
            if (mevent.Button == MouseButtons.Left)
                MouseIsDown = true;
        }

        protected override void OnMouseHover(EventArgs e) {
            base.OnMouseHover(e);
            MouseIsOver = true;
        }

        protected override void OnMouseLeave(EventArgs e) {
            base.OnMouseLeave(e);
            MouseIsOver = false;
        }

        protected override void OnMouseUp(MouseEventArgs mevent) {
            base.OnMouseUp(mevent);
            MouseIsDown = false;
        }

        // VisualStyle が有効で、テキストボックスの BorderStyle = Fixed3D のとき、
        // 親コントロールに対して描画し、自身は描画しない
        private bool IsPaintParent {
            get {
                TextBox txt = Owner as TextBox;
                return (txt != null &&
                        Application.RenderWithVisualStyles &&
                        txt.BorderStyle == BorderStyle.Fixed3D);
            }
        }

        // 再描画要求
        private void InvalidateInternal() {
            if (IsPaintParent) {

                // 枠を描画してほしいので RedrawWindow で WM_NCPAINT と WM_PAINT を送る
                NativeMethods.RedrawWindow(OwnerHandleRef, IntPtr.Zero, IntPtr.Zero,
                                           NativeMethods.RDW.RDW_INVALIDATE |
                                           NativeMethods.RDW.RDW_FRAME |
                                           NativeMethods.RDW.RDW_NOERASE 
                                           );

            } else {
                base.Invalidate();
            }
        }

        [DebuggerStepThrough]
        protected override void WndProc(ref Message m) {
            switch (m.Msg) {
                case NativeMethods.WM_PAINT:
                case NativeMethods.WM_ERASEBKGND:
                case NativeMethods.WM_NCPAINT:
                    if (!IsPaintParent) {
                        base.WndProc(ref m);
                    } else {
                        base.DefWndProc(ref m);
                    }
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }

        [DebuggerStepThrough]
        protected override void OnPaint(PaintEventArgs pevent) {
            if (!IsPaintParent) {
                DrawButton(pevent.Graphics);
            }
        }

        // 自身にボタンを描画
        private void DrawButton(Graphics g) {
            if (Application.RenderWithVisualStyles) {
                DrawVisualStyleButton(g, this.ClientRectangle, this.ClientRectangle);
            } else {
                ButtonState bsState;
                if (!this.Enabled)
                    bsState = ButtonState.Inactive;
                else {
                    if (MouseIsDown)
                        bsState = ButtonState.Pushed;
                    else
                        bsState = ButtonState.Normal;
                }
                ControlPaint.DrawComboButton(
                                        g,
                                        this.ClientRectangle,
                                        bsState);
            }
        }

        // 親コントロールにボタンを描画
        private void DrawButtonParent() {
            IntPtr dc = NativeMethods.GetWindowDC(OwnerHandleRef);
            if (dc != IntPtr.Zero) {
                using (Graphics g = Graphics.FromHdc(dc)) {
                    // ボタンの表面
                    int width = SystemInformation.HorizontalScrollBarArrowWidth;
                    int height = Owner.Height;
                    int x = Owner.Width - width;
                    int y = 0;
                    Rectangle buttonFace = new Rectangle(x, y, width, height);

                    // 背景色で塗りつぶすエリア
                    // 枠を消してしまわないよう調整
                    Rectangle buttonBack = buttonFace;
                    buttonBack.X -= 2;
                    buttonBack.Y++;
                    buttonBack.Height -= 2;

                    DrawVisualStyleButton(g, buttonFace, buttonBack);
                }
                NativeMethods.ReleaseDC(OwnerHandleRef, dc);
            }
        }

        // VisualStyle のボタンを描画
        private void DrawVisualStyleButton(Graphics g, Rectangle buttonFace, Rectangle buttonBack) {

            // VisualStyleElement を決定
            VisualStyleElement element;
            if (IsPaintParent) {
                // Aero をサポートしているかチェック
                if (VisualStyleRenderer.IsElementDefined(AeroDropDownRButtonElement)) {
                    element = AeroDropDownRButtonElement;
                } else {
                    element = ComboBoxElement;
                }
            } else {
                element = ComboBoxElement;
            }

            // ステータスに応じた VisualStyleRenderer を作成
            ComboBoxState cbState;
            if (!this.Enabled)
                cbState = ComboBoxState.Disabled;
            else {
                if (MouseIsDown)
                    cbState = ComboBoxState.Pressed;
                else if (MouseIsOver)
                    cbState = ComboBoxState.Hot;
                else
                    cbState = ComboBoxState.Normal;
            }
            if (m_VisualStyleRenderer == null)
                m_VisualStyleRenderer = new VisualStyleRenderer(element.ClassName, element.Part, (int)cbState);
            else
                m_VisualStyleRenderer.SetParameters(element.ClassName, element.Part, (int)cbState);

            // 背景色で塗りつぶす
            g.FillRectangle(SystemBrushes.ButtonHighlight, buttonBack);

            // ボタンを描画
            m_VisualStyleRenderer.DrawBackground(g, buttonFace);
        }
    }
}

API の部分は、NativeMethods.cs にまとめています。

同じように、VBTextBox の中に入れて・・・

Imports VB6Control

Public Class DropDownTextBox2
    Inherits VBTextBox

    Private WithEvents button As DropDownButton2

    Public Sub New()
        button = New DropDownButton2(Me)
    End Sub

    Private Sub button_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button.Click
        MessageBox.Show("Click!!")
    End Sub

End Class

こんな感じになります。

dropdownbutton2.gif



トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   最終更新のRSS
Last-modified: 2015-02-20 (金) 01:51:38 (975d)