Winforms文本框 – 使用Ctrl-Backspace删除整个单词
我有一个Winforms对话框,其中包含一个允许单行输入的TextBox。 我想允许用户能够按Ctrl-Backspace删除整个单词。 这不是开箱即用的TextBox的默认行为; 我得到一个矩形字符,而不是删除单词。
我已确认ShortcutsEnabled
属性设置为True
。
我确实发现我可以使用RichTextBox而不是TextBox来获取我想要的行为。 这个问题是RichTextBox的外观(特别是边框)与TextBox的外观不同,我不需要或不想要标记文本的能力。
所以我的问题是如何最好地处理这种情况? 我错过了TextBox上的一些属性吗? 或者最好使用RichTextBox,更新外观以使其一致,并禁用文本的标记?
如果没有更好的方法,我很乐意编写处理KeyDown和KeyPress事件明确的代码,但认为值得先检查一下。
/ *更新:请看下面Damir的答案,这可能是一个更好的解决方案:) * /
我会通过将Ctrl + Shift + Left和Backspace发送到TextBox来模拟Ctrl + Backspace。 效果几乎相同,无需手动处理控件的文本。 您可以使用以下代码实现它:
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类使用更新的发送键的方法:
老问题,但我偶然发现了一个不需要任何额外代码的答案。
启用文本框的自动完成function,CTRL-Backspace应该按照您的意愿工作。
CTRL-Backspace删除插入符号左侧的整个单词似乎是自动完成处理程序的“ 流氓function ”。 这就是启用自动完成function修复此问题的原因。
来源1 | 来源2
–
您可以通过将AutoCompleteMode
和AutoCompleteSource
设置为您喜欢的任何内容来启用自动完成function(例如, Suggest
和RecentlyUsedList
)
我不确定如果没有自定义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 = ""; } }
虽然ProcessCmdKey覆盖function很好,但它只限于Ctrl + Backspace的一次迭代,主要是因为使用SendWait模仿击键,如果你在按Backspace时再按住Ctrl键,系统似乎只能识别按下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); } } }
这是我使用的,它还处理多行文本框
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; } }
这就是你去的方式:)
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 = ""; } } }
这很好用:
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; }
正则表达式是为此而做的。 用它。
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; }
我在VB中回答而不是C#cuz我在VB中寻找这个解决方案却找不到一个,但是这些C#响应帮助我解决了这个问题:-D
在模块中创建此子
Public Sub ctrl_bksp(ByRef t As TextBox) Dim ss As Integer = t.SelectionStart Dim sl As Integer = t.SelectionLength Dim tl As Integer = t.TextLength '//Split either side of selection start Dim strPre As String = Strings.Left(t.Text, tl - (tl - ss)) Dim strPost As String = Strings.Right(t.Text, tl - ss - sl) '//Get Last Space Location in StrPre Dim s As Integer = Strings.InStrRev(RTrim(strPre), " ") strPre = Strings.Left(strPre, s) t.Text = strPre & strPost t.SelectionStart = s End Sub
然后你可以在任何文本框的KeyPress事件中调用这个子:
Private Sub Textbox1_KeyPress(sender As Object, e As System.Windows.Forms.KeyPressEventArgs) Handles Textbox1.KeyPress Select Case e.KeyChar Case Chr(127) '//Ctrl+Backspace e.Handled = True Call ctrl_bksp(Textbox1) End Select End Sub
无论选择在字符串中的哪个位置,以及是否选择了文本,这都会有效,并且响应非常好!
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):
/// /// Returns the start index of the same character class. /// /// The object to process. /// The search starting position. /// /// The zero-based index position of the start of the same character class in the string. /// 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' (, ,...). 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); }
我有这些方法的问题:
- 替换.Text具有大文本的滚动问题。
- 在textBox.KeyDown事件处理程序中执行SendKeys.SendWait(“^ + {LEFT} {BACKSPACE}”)对我来说根本不稳定。
- 使用.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); } } } } }