Welcome to MSDN Blogs Sign in | Join | Help

jfo's coding

adventures in windows forms and wpf
Building a SplitButton

The following sample creates a split button that you can use on your form.  It makes use of the ButtonRenderer class to custom draw a button.

Sample code for adding a SplitButton:

SplitButton sb = new SplitButton();
sb.ShowSplit = true;
sb.ContextMenuStrip = new ContextMenuStrip();
sb.ContextMenuStrip.Items.Add("one");
sb.ContextMenuStrip.Items.Add("two");
sb.ContextMenuStrip.Items.Add("three");
this.Controls.Add(sb);

Code for SplitButton:

using System;

using System.Collections.Generic;

using System.Text;

using System.Windows.Forms;

using System.Windows.Forms.VisualStyles;

using System.Drawing;

using System.ComponentModel;

 

namespace Microsoft.Samples {

    public class SplitButton : Button {

        private PushButtonState _state;

        private const int PushButtonWidth = 14;

        private static int BorderSize = SystemInformation.Border3DSize.Width * 2;

        private bool skipNextOpen = false;

        private Rectangle dropDownRectangle = new Rectangle();

        private bool showSplit = true;

 

        public SplitButton() {

            this.AutoSize = true;

        }

        [DefaultValue(true)]

        public bool ShowSplit {

            set {

                if (value != showSplit) {

                    showSplit = value;

                    Invalidate();

                    if (this.Parent != null) {

                        this.Parent.PerformLayout();

                    }

                }

            }

        }

 

        private PushButtonState State {

            get {

                return _state;

            }

            set {

                if (!_state.Equals(value)) {

                    _state = value;

                    Invalidate();

                }

            }

        }

 

        public override Size GetPreferredSize(Size proposedSize) {

            Size preferredSize = base.GetPreferredSize(proposedSize);

            if (showSplit && !string.IsNullOrEmpty(Text) && TextRenderer.MeasureText(Text, Font).Width + PushButtonWidth > preferredSize.Width) {

                return preferredSize + new Size(PushButtonWidth + BorderSize * 2, 0);

            }

            return preferredSize;

        }

 

        protected override bool IsInputKey(Keys keyData) {

            if (keyData.Equals(Keys.Down) && showSplit) {

                return true;

            }

            else {

                return base.IsInputKey(keyData);

            }

        }

 

        protected override void OnGotFocus(EventArgs e) {

            if (!showSplit) {

                base.OnGotFocus(e);

                return;

            }

 

            if (!State.Equals(PushButtonState.Pressed) && !State.Equals(PushButtonState.Disabled)) {

                State = PushButtonState.Default;

            }

        }

 

        protected override void OnKeyDown(KeyEventArgs kevent) {

            if (showSplit) {

                if (kevent.KeyCode.Equals(Keys.Down)) {

                    ShowContextMenuStrip();

                }

                else if (kevent.KeyCode.Equals(Keys.Space) && kevent.Modifiers == Keys.None) {

                    State = PushButtonState.Pressed;

                }

            }

 

            base.OnKeyDown(kevent);

        }

 

        protected override void OnKeyUp(KeyEventArgs kevent) {

            if (kevent.KeyCode.Equals(Keys.Space)) {

                if (Control.MouseButtons == MouseButtons.None) {

                    State = PushButtonState.Normal;

                }

            }

            base.OnKeyUp(kevent);

        }

 

        protected override void OnLostFocus(EventArgs e) {

            if (!showSplit) {

                base.OnLostFocus(e);

                return;

            }

            if (!State.Equals(PushButtonState.Pressed) && !State.Equals(PushButtonState.Disabled)) {

                State = PushButtonState.Normal;

            }

        }

 

        protected override void OnMouseDown(MouseEventArgs e) {

            if (!showSplit) {

                base.OnMouseDown(e);

                return;

            }

 

            if (dropDownRectangle.Contains(e.Location)) {

                ShowContextMenuStrip();

            }

            else {

                State = PushButtonState.Pressed;

            }

        }

 

        protected override void OnMouseEnter(EventArgs e) {

            if (!showSplit) {

                base.OnMouseEnter(e);

                return;

            }

 

            if (!State.Equals(PushButtonState.Pressed) && !State.Equals(PushButtonState.Disabled)) {

                State = PushButtonState.Hot;

            }

        }

 

        protected override void OnMouseLeave(EventArgs e) {

            if (!showSplit) {

                base.OnMouseLeave(e);

                return;

            }

 

            if (!State.Equals(PushButtonState.Pressed) && !State.Equals(PushButtonState.Disabled)) {

                if (Focused) {

                    State = PushButtonState.Default;

                }

                else {

                    State = PushButtonState.Normal;

                }

            }

        }

 

        protected override void OnMouseUp(MouseEventArgs mevent) {

            if (!showSplit) {

                base.OnMouseUp(mevent);

                return;

            }

 

            if (ContextMenuStrip == null || !ContextMenuStrip.Visible) {

                SetButtonDrawState();

                if (Bounds.Contains(Parent.PointToClient(Cursor.Position)) && !dropDownRectangle.Contains(mevent.Location)) {

                    OnClick(new EventArgs());

                }

            }

        }

 

        protected override void OnPaint(PaintEventArgs pevent) {

            base.OnPaint(pevent);

 

            if (!showSplit) {

                return;

            }

 

            Graphics g = pevent.Graphics;

            Rectangle bounds = this.ClientRectangle;

 

            // draw the button background as according to the current state.

            if (State != PushButtonState.Pressed && IsDefault && !Application.RenderWithVisualStyles) {

                Rectangle backgroundBounds = bounds;

                backgroundBounds.Inflate(-1, -1);

                ButtonRenderer.DrawButton(g, backgroundBounds, State);

 

                // button renderer doesnt draw the black frame when themes are off =(

                g.DrawRectangle(SystemPens.WindowFrame, 0, 0, bounds.Width - 1, bounds.Height - 1);

 

            }

            else {

                ButtonRenderer.DrawButton(g, bounds, State);

            }

            // calculate the current dropdown rectangle.

            dropDownRectangle = new Rectangle(bounds.Right - PushButtonWidth - 1, BorderSize, PushButtonWidth, bounds.Height - BorderSize * 2);

 

            int internalBorder = BorderSize;

            Rectangle focusRect =

                new Rectangle(internalBorder,

                              internalBorder,

                              bounds.Width - dropDownRectangle.Width - internalBorder,

                              bounds.Height - (internalBorder * 2));

 

           

            bool drawSplitLine = (State == PushButtonState.Hot || State == PushButtonState.Pressed || !Application.RenderWithVisualStyles);

 

            if (RightToLeft == RightToLeft.Yes) {

                dropDownRectangle.X = bounds.Left + 1;

                focusRect.X = dropDownRectangle.Right;

                if (drawSplitLine) {

                    // draw two lines at the edge of the dropdown button

                    g.DrawLine(SystemPens.ButtonShadow, bounds.Left + PushButtonWidth, BorderSize, bounds.Left + PushButtonWidth, bounds.Bottom - BorderSize);

                    g.DrawLine(SystemPens.ButtonFace, bounds.Left + PushButtonWidth + 1, BorderSize, bounds.Left + PushButtonWidth + 1, bounds.Bottom - BorderSize);

                }

            }

            else {

                if (drawSplitLine) {

                    // draw two lines at the edge of the dropdown button

                    g.DrawLine(SystemPens.ButtonShadow, bounds.Right - PushButtonWidth, BorderSize, bounds.Right - PushButtonWidth, bounds.Bottom - BorderSize);

                    g.DrawLine(SystemPens.ButtonFace, bounds.Right - PushButtonWidth - 1, BorderSize, bounds.Right - PushButtonWidth - 1, bounds.Bottom - BorderSize);

                }

 

            }

 

            // Draw an arrow in the correct location

            PaintArrow(g, dropDownRectangle);

 

            // Figure out how to draw the text

            TextFormatFlags formatFlags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter;

 

            // If we dont' use mnemonic, set formatFlag to NoPrefix as this will show ampersand.

            if (!UseMnemonic) {

                formatFlags = formatFlags | TextFormatFlags.NoPrefix;

            }

            else if (!ShowKeyboardCues) {

                formatFlags = formatFlags | TextFormatFlags.HidePrefix;

            }

 

            if (!string.IsNullOrEmpty(this.Text)) {

                TextRenderer.DrawText(g, Text, Font, focusRect, SystemColors.ControlText, formatFlags);

            }

 

            // draw the focus rectangle.

 

            if (State != PushButtonState.Pressed && Focused) {

                ControlPaint.DrawFocusRectangle(g, focusRect);

            }

        }

 

        private void PaintArrow(Graphics g, Rectangle dropDownRect) {

            Point middle = new Point(Convert.ToInt32(dropDownRect.Left + dropDownRect.Width / 2), Convert.ToInt32(dropDownRect.Top + dropDownRect.Height / 2));

 

            //if the width is odd - favor pushing it over one pixel right.

            middle.X += (dropDownRect.Width % 2);

 

            Point[] arrow = new Point[] { new Point(middle.X - 2, middle.Y - 1), new Point(middle.X + 3, middle.Y - 1), new Point(middle.X, middle.Y + 2) };

 

            g.FillPolygon(SystemBrushes.ControlText, arrow);

        }

 

        private void ShowContextMenuStrip() {

            if (skipNextOpen) {

                // we were called because we're closing the context menu strip

                // when clicking the dropdown button.

                skipNextOpen = false;

                return;

            }

            State = PushButtonState.Pressed;

 

            if (ContextMenuStrip != null) {

                ContextMenuStrip.Closing += new ToolStripDropDownClosingEventHandler(ContextMenuStrip_Closing);

                ContextMenuStrip.Show(this, new Point(0, Height), ToolStripDropDownDirection.BelowRight);

            }

        }

 

        void ContextMenuStrip_Closing(object sender, ToolStripDropDownClosingEventArgs e) {

            ContextMenuStrip cms = sender as ContextMenuStrip;

            if (cms != null) {

                cms.Closing -= new ToolStripDropDownClosingEventHandler(ContextMenuStrip_Closing);

            }

 

            SetButtonDrawState();

 

            if (e.CloseReason == ToolStripDropDownCloseReason.AppClicked) {

                skipNextOpen = (dropDownRectangle.Contains(this.PointToClient(Cursor.Position)));

            }

        }

 

 

        private void SetButtonDrawState() {

            if (Bounds.Contains(Parent.PointToClient(Cursor.Position))) {

                State = PushButtonState.Hot;

            }

            else if (Focused) {

                State = PushButtonState.Default;

            }

            else {

                State = PushButtonState.Normal;

            }

        }

    }

 

}

 

Posted: Thursday, November 10, 2005 3:31 PM by jfoscoding

Attachment(s): SplitButton.cs

Comments

jfo's coding said:

David was kind enough to point out some rough spots in the SplitButton sample, so I've gone ahead...
# February 28, 2006 11:33 PM

Thomas Williams said:

I don't know why I was thinking about this the other day - but it came into my head that I really like...
# July 5, 2006 12:04 AM
New Comments to this post are disabled
Page view tracker