38
votes

Do WinForms textboxes have any properties that make an embedded button, at the end of the box, possible?

Something like the favorites button on the Chrome address box:

enter image description here

I've also seen something like the following in some Excel forms:

enter image description here


EDIT

I've followed Hans Passant's answer with the addition of a click event handler and it seem to work ok:

protected override void OnLoad(EventArgs e) {
    var btn = new Button();
    btn.Size = new Size(25, textBoxFolder.ClientSize.Height + 2);
    btn.Location = new Point(textBoxFolder.ClientSize.Width - btn.Width, -1);
    btn.Cursor = Cursors.Default;
    btn.Image = Properties.Resources.arrow_diagright;
    btn.Click += btn_Click;     
    textBoxFolder.Controls.Add(btn);
    // Send EM_SETMARGINS to prevent text from disappearing underneath the button
    SendMessage(textBoxFolder.Handle, 0xd3, (IntPtr)2, (IntPtr)(btn.Width << 16));
    base.OnLoad(e);
}

[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

private void btn_Click(object sender, EventArgs e) {
    MessageBox.Show("hello world");
}
7
@Highcore, you're a hopeless one-trick pony. Please add the [winforms] tag to your ignored tags, I don't want to see your wpf rants anymore. - Hans Passant
@HighCore I have noted to you before the same thing that Hans is saying. I strongly recommend you simply avoid WinForms questions from now, on. - Andrew Barber
@HansPassant .... I'm just back from leave now and catching up with questions - looks like you good people scared him off! - whytheq

7 Answers

61
votes

Getting the button inside the TextBox just requires adding it to the box' Controls collection. You'll also need to do something reasonable to prevent the text inside the box disappearing underneath the button; that requires a wee bit of pinvoke. Like this:

    protected override void OnLoad(EventArgs e) {
        var btn = new Button();
        btn.Size = new Size(25, textBox1.ClientSize.Height + 2);
        btn.Location = new Point(textBox1.ClientSize.Width - btn.Width, -1);
        btn.Cursor = Cursors.Default;
        btn.Image = Properties.Resources.star;
        textBox1.Controls.Add(btn);
        // Send EM_SETMARGINS to prevent text from disappearing underneath the button
        SendMessage(textBox1.Handle, 0xd3, (IntPtr)2, (IntPtr)(btn.Width << 16));
        base.OnLoad(e);  
    }

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

Looked like this while I tested the right margin (should have picked a prettier bitmap):

enter image description here

11
votes

Here's the answer wrapped in a TextBox subclass.

public class ButtonTextBox : TextBox {
    private readonly Button _button;

    public event EventHandler ButtonClick { add { _button.Click += value; } remove { _button.Click -= value; } }

    public ButtonTextBox() {
        _button = new Button {Cursor = Cursors.Default};
        _button.SizeChanged += (o, e) => OnResize(e);
        this.Controls.Add(_button); 
    }

    public Button Button {
        get {
            return _button;
        }
    }

    protected override void OnResize(EventArgs e) {
        base.OnResize(e);
        _button.Size = new Size(_button.Width, this.ClientSize.Height + 2);
        _button.Location = new Point(this.ClientSize.Width - _button.Width, -1);
        // Send EM_SETMARGINS to prevent text from disappearing underneath the button
        SendMessage(this.Handle, 0xd3, (IntPtr)2, (IntPtr)(_button.Width << 16));
    }

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

}
5
votes

I saw in Reflector that Control contains "SendMessage(int,int,int)" method and I found another way.

using System;
using System.Reflection;
using System.Windows.Forms;

static class ControlExtensions
{
    static BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
    static Type[] SendMessageSig = new Type[] { typeof(int), typeof(int), typeof(int) };

    internal static IntPtr SendMessage(this Control control, int msg, int wparam, int lparam)
    {
        MethodInfo MethodInfo = control.GetType().GetMethod("SendMessage", flags, null, SendMessageSig, null);

        return (IntPtr)MethodInfo.Invoke(control, new object[] { msg, wparam, lparam });
    }
}

Now by overriding WndProc in ButtonTextBox we can achieve desired effect.

public class ButtonTextBox : TextBox
{
    Button button;

    public ButtonTextBox()
    {
        this.button = new Button();
        this.button.Dock = DockStyle.Right;
        this.button.BackColor = SystemColors.Control;
        this.button.Width = 21;
        this.Controls.Add(this.button);
    }

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);

        switch (m.Msg)
        {
            case 0x30:
                int num = this.button.Width + 3;
                this.SendMessage(0xd3, 2, num << 16);
                return;
        }
    }
}

And I think this is much safer way.

4
votes

If you are willing to add a reference to another library, you could consider using the Krypton Toolkit (available at https://github.com/ComponentFactory/Krypton). The basic toolkit that you should be able to use for free (without the ribbons, navigator, or workspace functionality) does allow you to add "button specs" to various controls (including text boxes) which appear visually just as you describe.

2
votes

Just a small expansion on the accepted solution, to get the button to look and act properly a few tweaks are needed. Here are tweaks for a search box:

    private static readonly int SEARCH_BUTTON_WIDTH = 25;

    private void ConfigureSearchBox()
    {
        var btn = new Button();
        btn.Size = new Size(SEARCH_BUTTON_WIDTH, searchBox.ClientSize.Height + 2);
        btn.Dock = DockStyle.Right;
        btn.Cursor = Cursors.Default;
        btn.Image = Properties.Resources.Find_5650;
        btn.FlatStyle = FlatStyle.Flat;
        btn.ForeColor = Color.White;
        btn.FlatAppearance.BorderSize = 0;
        btn.Click += btn_Click;
        searchBox.Controls.Add(btn);
        this.AcceptButton = btn;
        // Send EM_SETMARGINS to prevent text from disappearing underneath the button
        SendMessage(searchBox.Handle, 0xd3, (IntPtr)2, (IntPtr)(btn.Width << 16));
    }

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

    private void btn_Click(object sender, EventArgs e)
    {
        MessageBox.Show("hello world");
    }
1
votes

No. In order to do something like that you need to create your own User Control. It can be easily put together from a text box and button. The difficulty is that if you want similar properties to the text box, you would need to create all them. In the end it is a lot of code.

1
votes

A minor addition on the nice idea of Hans Passant - if the textbox is resizable, you could add a binding to the underlying button so that it's Location gets adjusted on ClientSize changes as well:

    //Adjust the Location of the button on Size Changes of the containing textBox.
    var binding = new Binding("Location", textBox1, "ClientSize");
    binding.Format += (s, e) => e.Value = e.Value is Size ? new Point(((Size)e.Value).Width - btn.Width, -1) : new Point(0, 0);
    btn.DataBindings.Add(binding);