Winforms Textbox - Использование Ctrl-Backspace для удаления всего слова - PullRequest
30 голосов
/ 14 июля 2009

У меня есть диалоговое окно Winforms, которое содержит среди других элементов управления TextBox, который допускает одну строку ввода. Я хотел бы позволить пользователю иметь возможность нажать Ctrl-Backspace, чтобы удалить все слово. Это не стандартное поведение с готовым TextBox; Я получаю символ rectangle вместо удаления слова.

Я подтвердил, что для свойства ShortcutsEnabled установлено значение True.

Я обнаружил, что могу использовать RichTextBox вместо TextBox, чтобы получить желаемое поведение. Проблема в том, что внешний вид RichTextBox (в частности, границы) отличается от внешнего вида TextBox, и мне не нужна или не нужна возможность разметки текста.

Итак, мой вопрос: как лучше всего справиться с этой ситуацией? Есть ли какое-либо свойство в TextBox, которое мне не хватает? Или лучше использовать RichTextBox, обновить внешний вид, чтобы он был согласованным, и отключить разметку текста?

Я с радостью пишу код для обработки событий KeyDown и KeyPress, если нет лучшего способа, но подумал, что стоит сначала проверить.

Ответы [ 11 ]

23 голосов
/ 29 июля 2009

/ * Обновление: посмотрите также ответ Дамира ниже, возможно, это лучшее решение :) * /

Я бы симулировал Ctrl + Backspace, отправив Ctrl + Shift + Left и Backspace в TextBox. Эффект практически тот же, и нет необходимости вручную обрабатывать текст элемента управления. Вы можете достичь этого с помощью этого кода:

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);
    }
}

Вы также можете изменить файл app.config, чтобы заставить класс SendKey использовать более новый метод отправки ключей:

<configuration>
  <appSettings>
    <add key="SendKeys" value="SendInput" />
  </appSettings>
</configuration>
22 голосов
/ 16 мая 2015

Старый вопрос, но я наткнулся на ответ, который не требует никакого дополнительного кода.

Включить автозаполнение для текстового поля, и CTRL-Backspace должен работать так, как вы хотите.

CTRL-Backspace - удаление целого слова слева от каретки, похоже, является мошеннической функцией 'обработчика автозаполнения. Вот почему включение автозаполнения устраняет эту проблему.

Источник 1 | Источник 2

-

Вы можете включить функцию автозаполнения, установив AutoCompleteMode и AutoCompleteSource на что угодно (например, Suggest и RecentlyUsedList)

7 голосов
/ 01 марта 2013

Хотя переопределение ProcessCmdKey работает хорошо и все, оно ограничивается только одной итерацией Ctrl + Backspace, главным образом потому, что использование SendWait имитирует нажатие клавиши, и если вы удерживаете нажатой клавишу Ctrl при повторном нажатии Backspace, только система кажется, распознает нажатие клавиши Backspace. Если бы вы регистрировали нажатия клавиш переопределения, вы бы нашли набор дополнительных клавиш, которые вы никогда не нажимали.

Альтернативный подход состоит в том, чтобы явно управлять внешним видом текстового поля в переопределении ProcessCmdKey, а не отправлять больше ключей в систему. Это также можно легко применить к Ctrl + Delete.

Я включил несколько общих "точек остановки" для поведения Ctrl + Backspace и использовал оператор switch в отличие от RegEx. Они никогда не чувствуют себя достаточно чистыми, и я обычно скучаю по персонажу

Если вы видите какие-либо проблемы с моим кодом, пожалуйста, дайте мне знать. Удачи всем, кто все еще озадачен этой загадкой!

public class TextBoxEx : TextBox
{
    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
        if (keyData == (Keys.Back | Keys.Control))
        {
            for (int i = this.SelectionStart - 1; i > 0; i--)
            {
                switch (Text.Substring(i, 1))
                {    //set up any stopping points you want
                    case " ":
                    case ";":
                    case ",":
                    case "/":
                    case "\\":                        
                        Text = Text.Remove(i, SelectionStart - i);
                        SelectionStart = i;
                        return true;
                    case "\n":
                        Text = Text.Remove(i - 1, SelectionStart - i);
                        SelectionStart = i;
                        return true;
                }
            }
            Clear();        //in case you never hit a stopping point, the whole textbox goes blank
            return true;
        }
        else
        {
            return base.ProcessCmdKey(ref msg, keyData);
        }
    }  
}
6 голосов
/ 14 июля 2009

Я не уверен, что это возможно без пользовательского события KeyDown или KeyPress, хотя работает следующий код:

private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
    if ((e.KeyCode == Keys.Back) && e.Control)
    {
        e.SuppressKeyPress = true;
        int selStart = textBox1.SelectionStart;
        while (selStart > 0 && textBox1.Text.Substring(selStart - 1, 1) == " ")
        {
            selStart--;
        }
        int prevSpacePos = -1;
        if (selStart != 0)
        {
            prevSpacePos = textBox1.Text.LastIndexOf(' ', selStart - 1);
        }
        textBox1.Select(prevSpacePos + 1, textBox1.SelectionStart - prevSpacePos - 1);
        textBox1.SelectedText = "";
    }
}
3 голосов
/ 23 октября 2009

Это то, что я использовал, оно также обрабатывает многострочные текстовые поля

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;
  }
}
2 голосов
/ 14 ноября 2012

Это хорошо работает:

static Regex RegExWholeWord = new Regex(@"(\r\n|[^A-Za-z0-9_\r\n]+?|\w+?) *$", RegexOptions.Compiled);

В ключах, используйте

var m = RegExWholeWord.Match(textbox.Text, 0, textbox.SelectionStart);
if (m.Success)
{
    textbox.Text = textbox.Text.Remove(m.Index, m.Length);
    textbox.SelectionStart = m.Index;
}
2 голосов
/ 14 июля 2009

Это путь, которым вы идете :)

private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
    //if ctrl+bcksp
    if (e.KeyChar == 127)
    {
        //if not last word
        if (textBox1.Text.Split (' ').Count() > 1)
        {
            //remoce last word form list and put it back together (gotta love lambda)
            textBox1.Text = textBox1.Text.Split (' ').Take (textBox1.Text.Split (' ').Count() - 1).Aggregate ((a,b) => a + " " + b);
            //set selection at the end
            textBox1.SelectionStart = textBox1.Text.Length;
        }
        else if (textBox1.Text.Split (' ').Count() == 1)
        {
            textBox1.Text = "";
        }
    }
}
1 голос
/ 19 ноября 2017

Regex был сделан для этого. Используйте это.

    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;
    }
0 голосов
/ 05 марта 2018

У меня были проблемы с этими подходами:

  • Замена .Text имеет проблемы с прокруткой больших текстов.
  • Выполнение SendKeys.SendWait ("^ + {LEFT} {BACKSPACE}") в обработчике события textBox.KeyDown для меня вообще не было стабильным.
  • Использование .Cut () изменяет буфер обмена (но работает нормально).

Глядя на справочный источник .NET, что .Cut () приводит меня к следующему решению: выделите текст в TextBox и затем используйте WM_CLEAR, чтобы очистить его. Кажется, работает нормально, и он не отправляет события искусственного нажатия клавиши.

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);
                }
            }
        }
    }
}
0 голосов
/ 31 августа 2017

DWF и giangurgolo , спасибо за предоставленную информацию. Ниже доработана его версия. Обратите внимание, что он также учитывает ComboBox, поскольку имеет ту же проблему, что и TextBox. Также обратите внимание, что ярлыки активны, только если это разрешено конфигурацией TextBox или ComboBox.

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));
    }
}

Вспомогательная строка (например, статический класс 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...