我有一个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类使用更新的发送键的方法:
<configuration>
<appSettings>
<add key="SendKeys" value="SendInput" />
</appSettings>
</configuration>
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);
}
我有这些方法的问题:
查看.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);
}
}
}
}
}
虽然ProcessCmdKey覆盖功能很好,但它只限于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);
}
}
}
我不确定如果没有自定义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 = "";
}
}
这是我使用它,它还处理多行文本框
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
无论选择在字符串中的哪个位置,以及是否选择了文本,这都会有效,并且响应非常好!