Winforms Textbox - Using Ctrl-Backspace to Delete

2019-01-17 00:40发布

I have a Winforms dialog that contains among other controls a TextBox that allows a single line of input. I would like to allow the user to be able to press Ctrl-Backspace to delete an entire word. This is not the default behaviour with the out-of-the-box TextBox; I get a rectangle character, rather than having the word deleted.

I have confirmed the ShortcutsEnabled property is set to True.

I did find that I can use a RichTextBox rather than a TextBox to get the behaviour I want. The problem with this is that the apperance of the RichTextBox (border in particular) is different from that of the TextBox, and I don't need or want the ability to mark up text.

So my question is how to best handle this situation? Is there some property on the TextBox that I am missing? Or is it best to use the RichTextBox, update the appearance so it is consistent, and disable markup of the text?

I am relatively happy to write the code to handle the KeyDown and KeyPress events explicity if there is no better way, but thought it was worth checking first.

11条回答
孤傲高冷的网名
2楼-- · 2019-01-17 00:44

I had problems with these approaches:

  • Replacing .Text has scrolling issues with large texts.
  • Doing SendKeys.SendWait("^+{LEFT}{BACKSPACE}") in textBox.KeyDown event handler was not stable at all for me.
  • Using .Cut() changes the clipboard (but works fine otherwise).

Looking at the .NET reference source what .Cut() does lead me to the following solution: Select the text in the TextBox and then use WM_CLEAR to clear it. Seems to work fine and it's not sending artificial key press events.

class CtrlBackspaceSupport
{
    TextBox textBox;
    public CtrlBackspaceSupport(TextBox textBox)
    {
        this.textBox = textBox;
        textBox.KeyDown += new KeyEventHandler(textBox_KeyDown);
    }

    [DllImport("user32.dll", SetLastError = true)]
    static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
    const int WM_CLEAR = 0x0303;

    void textBox_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Control && e.KeyCode == Keys.Back)
        {   // Ctrl+Backspace -> remove till word border before cursor
            e.SuppressKeyPress = true;
            if (0 == textBox.SelectionLength && textBox.SelectionStart > 1)
            {   // nothing selected
                var text = textBox.Text;
                int indexOfSpace = text.LastIndexOf(' ', textBox.SelectionStart - 2);
                if (-1 != indexOfSpace)
                {   // found something
                    indexOfSpace++;
                    textBox.Select(indexOfSpace, textBox.SelectionStart - indexOfSpace);
                    SendMessage(new HandleRef(textBox, textBox.Handle).Handle, WM_CLEAR, 0, 0);
                }
            }
        }
    }
}
查看更多
Fickle 薄情
3楼-- · 2019-01-17 00:47

Old question, but I just stumbled upon an answer that doesn't require any extra code.

Enable autocompletion for the textbox and CTRL-Backspace should work as you want it to.

CTRL-Backspace deleting whole word to the left of the caret seems to be a 'rogue feature' of the autocomplete handler. That's why enabling autocomplete fixes this issue.

Source 1 | Source 2

--

You can enable the auto complete feature with setting the AutoCompleteMode and AutoCompleteSource to anything you like (for instance; Suggest and RecentlyUsedList)

查看更多
爷的心禁止访问
4楼-- · 2019-01-17 00:52

Regex was made for this. Use it.

    private void TextBox_KeyDown(object sender, KeyEventArgs e)
    {
        TextBox box = (TextBox)sender;
        if (e.KeyData == (Keys.Back | Keys.Control))
        {
            if (!box.ReadOnly && box.SelectionLength == 0)
            {
                RemoveWord(box);
            }
            e.SuppressKeyPress = true;
        }
    }

    private void RemoveWord(TextBox box)
    {
        string text = Regex.Replace(box.Text.Substring(0, box.SelectionStart), @"(^\W)?\w*\W*$", "");
        box.Text = text + box.Text.Substring(box.SelectionStart);
        box.SelectionStart = text.Length;
    }
查看更多
三岁会撩人
5楼-- · 2019-01-17 00:53

DWF and giangurgolo, thanks for your information provided. Below a refined version of it. Note that it also considers ComboBox, as that has the very same issue as TextBox. Also note that shortcuts are only active if configuration of TextBox or ComboBox allow so.

TextBoxEx:

public class TextBoxEx : TextBox
{
    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
        // Attention:
        // Similar code exists in ComboBoxEx.ProcessCmdKey().
        // Changes here may have to be applied there too.

        if (ShortcutsEnabled)
        {
            if (keyData == (Keys.Control | Keys.Back))
            {
                if (!ReadOnly)
                {
                    if (SelectionStart > 0)
                    {
                        int i = (SelectionStart - 1);

                        // Potentially trim white space:
                        if (char.IsWhiteSpace(Text, i))
                            i = (StringEx.StartIndexOfSameCharacterClass(Text, i) - 1);

                        // Find previous marker:
                        if (i > 0)
                            i = StringEx.StartIndexOfSameCharacterClass(Text, i);
                        else
                            i = 0; // Limit i as it may become -1 on trimming above.

                        // Remove until previous marker or the beginning:
                        Text = Text.Remove(i, SelectionStart - i);
                        SelectionStart = i;
                        return (true);
                    }
                    else
                    {
                        return (true); // Ignore to prevent a white box being placed.
                    }
                }
            }
            else if (keyData == (Keys.Control | Keys.A))
            {
                if (!ReadOnly && Multiline)
                {
                    SelectAll();
                    return (true);
                }
            }
        }

        return (base.ProcessCmdKey(ref msg, keyData));
    }
}

ComboxBoxEx:

public class ComboBoxEx : ComboBox
{
    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
        // Attention:
        // Similar code exists in TextBoxEx.ProcessCmdKey().
        // Changes here may have to be applied there too.

        if (keyData == (Keys.Control | Keys.Back))
        {
            if (DropDownStyle != ComboBoxStyle.DropDownList)
            {
                if (SelectionStart > 0)
                {
                    int i = (SelectionStart - 1);

                    // Potentially trim white space:
                    if (char.IsWhiteSpace(Text, i))
                        i = (StringEx.StartIndexOfSameCharacterClass(Text, i) - 1);

                    // Find previous marker:
                    if (i > 0)
                        i = StringEx.StartIndexOfSameCharacterClass(Text, i);
                    else
                        i = 0; // Limit i as it may become -1 on trimming above.

                    // Remove until previous marker or the beginning:
                    Text = Text.Remove(i, SelectionStart - i);
                    SelectionStart = i;
                    return (true);
                }
                else
                {
                    return (true); // Ignore to prevent a white box being placed.
                }
            }
        }

        return (base.ProcessCmdKey(ref msg, keyData));
    }
}

String auxiliary (e.g. static class StringEx):

/// <summary>
/// Returns the start index of the same character class.
/// </summary>
/// <param name="str">The <see cref="string"/> object to process.</param>
/// <param name="startIndex">The search starting position.</param>
/// <returns>
/// The zero-based index position of the start of the same character class in the string.
/// </returns>
public static int StartIndexOfSameCharacterClass(string str, int startIndex)
{
    int i = startIndex;

    if (char.IsWhiteSpace(str, i)) // Includes 'IsSeparator' (Unicode space/line/paragraph
    {                              // separators) as well as 'IsControl' (<CR>, <LF>,...).
        for (/* i */; i >= 0; i--)
        {
            if (!char.IsWhiteSpace(str, i))
                return (i + 1);
        }
    }
    else if (char.IsPunctuation(str, i))
    {
        for (/* i */; i >= 0; i--)
        {
            if (!char.IsPunctuation(str, i))
                return (i + 1);
        }
    }
    else if (char.IsSymbol(str, i))
    {
        for (/* i */; i >= 0; i--)
        {
            if (!char.IsSymbol(str, i))
                return (i + 1);
        }
    }
    else
    {
        for (/* i */; i >= 0; i--)
        {
            if (char.IsWhiteSpace(str, i) || char.IsPunctuation(str, i) || char.IsSymbol(str, i))
                return (i + 1);
        }
    }

    return (0);
}
查看更多
手持菜刀,她持情操
6楼-- · 2019-01-17 00:57

/* Update: Please look also at Damir’s answer below, it’s probably a better solution :) */

I would simulate Ctrl+Backspace by sending Ctrl+Shift+Left and Backspace to the TextBox. The effect is virtually the same, and there is no need to manually process control’s text. You can achieve it using this code:

class TextBoxEx : TextBox
{
    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
        if (keyData == (Keys.Control | Keys.Back))
        {
            SendKeys.SendWait("^+{LEFT}{BACKSPACE}");
            return true;
        }
        return base.ProcessCmdKey(ref msg, keyData);
    }
}

You can also modify the app.config file to force the SendKey class to use newer method of sending keys:

<configuration>
  <appSettings>
    <add key="SendKeys" value="SendInput" />
  </appSettings>
</configuration>
查看更多
兄弟一词,经得起流年.
7楼-- · 2019-01-17 00:57

This is what I landed up using, it also handles multi line textboxes

private void HandleCtrlBackspace_KeyDown(object sender, KeyEventArgs e) {
  switch (e.KeyData) {
    case (Keys.Back | Keys.Control):
      e.SuppressKeyPress = true;
      TextBox textbox = (TextBox)sender;
      int i;
      if (textbox.SelectionStart.Equals(0)) {
        return;
      }
      int space = textbox.Text.LastIndexOf(' ', textbox.SelectionStart - 1);
      int line = textbox.Text.LastIndexOf("\r\n", textbox.SelectionStart - 1);
      if (space > line) {
        i = space;
      } else {
        i = line;
      }
      if (i > -1) {
        while (textbox.Text.Substring(i - 1, 1).Equals(' ')) {
          if (i.Equals(0)) {
            break;
          }
          i--;
        }
        textbox.Text = textbox.Text.Substring(0, i) + textbox.Text.Substring(textbox.SelectionStart);
        textbox.SelectionStart = i;
      } else if (i.Equals(-1)) {
        textbox.Text = textbox.Text.Substring(textbox.SelectionStart);
      }
      break;
  }
}
查看更多
登录 后发表回答