c# - teclado - Cuadro de texto de Winforms-Uso de Ctrl-Retroceso para eliminar toda la palabra
tecla re pag y av pag (11)
Tengo un cuadro de diálogo de Winforms que contiene, entre otros controles, un TextBox que permite una sola línea de entrada. Me gustaría permitir que el usuario pueda presionar Ctrl-Retroceso para eliminar una palabra completa. Este no es el comportamiento predeterminado con el TextBox listo para usar; Obtengo un carácter de rectángulo , en lugar de eliminar la palabra.
He confirmado que la propiedad ShortcutsEnabled
está establecida en True
.
Encontré que puedo usar un RichTextBox en lugar de un TextBox para obtener el comportamiento que quiero. El problema con esto es que la apariencia de RichTextBox (borde en particular) es diferente de la de TextBox, y no necesito ni quiero la capacidad de marcar texto.
¿Entonces mi pregunta es cómo manejar mejor esta situación? ¿Hay alguna propiedad en el TextBox que me falta? ¿O es mejor usar RichTextBox, actualizar la apariencia para que sea consistente y deshabilitar el marcado del texto?
Estoy relativamente feliz de escribir el código para manejar los eventos KeyDown y KeyPress de manera explícita si no hay una mejor manera, pero pensé que valía la pena revisar primero.
Este es el camino que vas :)
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 = "";
}
}
}
Esto es lo que obtuve usando, también maneja cuadros de texto de varias líneas
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;
}
}
Esto funciona bien:
static Regex RegExWholeWord = new Regex(@"(/r/n|[^A-Za-z0-9_/r/n]+?|/w+?) *$", RegexOptions.Compiled);
En la tecla de abajo, use
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;
}
Estoy respondiendo en VB en lugar de C # porque estaba buscando esta solución en VB pero no pude encontrar una, pero estas respuestas en C # me ayudaron a resolverlo :-D
Crea este sub en un módulo
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
Luego puedes llamar a este sub desde dentro del evento KeyPress de cualquier cuadro de texto:
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
Esto funcionará sin importar dónde esté la selección dentro de la cadena, y si se selecciona o no el texto, y responde magníficamente.
No estoy seguro de que sea posible sin un evento KeyDown o KeyPress personalizado, aunque el siguiente código funciona:
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 = "";
}
}
Pregunta antigua, pero acabo de encontrar una respuesta que no requiere ningún código adicional.
Habilitar autocompletar para el cuadro de texto y CTRL-Retroceso debería funcionar como lo desea.
CTRL-Retroceso que elimina una palabra entera a la izquierda de la etiqueta parece ser una " característica deshonesta " del controlador autocompletado. Es por eso que habilitar autocompletar soluciona este problema.
-
Puede habilitar la función de autocompletar con la configuración de AutoCompleteMode
y AutoCompleteSource
en cualquier cosa que desee (por ejemplo; Suggest
y Suggest
RecentlyUsedList
)
Regex fue hecho para esto. Utilízalo
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;
}
Si bien la anulación de ProcessCmdKey funciona bien y todo, se limita a una sola iteración de Ctrl + Retroceso, principalmente porque el uso de SendWait imita una pulsación de tecla, y si tuviera que mantener presionada la tecla Ctrl mientras presiona Retroceso de nuevo, el sistema solo parece reconocer la tecla de retroceso que se está presionando. Si tuviera que registrar las pulsaciones de tecla de la anulación, encontraría una colección de teclas adicionales que nunca presionó realmente.
Un enfoque alternativo es administrar explícitamente la apariencia del cuadro de texto en la anulación de ProcessCmdKey, y no enviar más claves al sistema. Esto también podría aplicarse fácilmente a Ctrl + Delete.
He incluido algunos de los "puntos de parada" comunes para el comportamiento de Ctrl + Retroceso, y utilicé una declaración de cambio en lugar de un RegEx. Nunca se sienten lo suficientemente limpios, y por lo general termino perdiendo un personaje.
Si ve algún problema con mi código, por favor hágamelo saber. ¡La mejor de las suertes para cualquier persona que todavía esté confundida por este acertijo!
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);
}
}
}
Tuve problemas con estos enfoques:
- La sustitución de .Texto tiene problemas de desplazamiento con textos grandes.
- Hacer SendKeys.SendWait ("^ + {LEFT} {BACKSPACE}") en el controlador de eventos textBox.KeyDown no fue estable en absoluto para mí.
- El uso de .Cut () cambia el portapapeles (pero funciona bien de lo contrario).
Mirando la fuente de referencia de .NET, lo que .Cut () me lleva a la siguiente solución: seleccione el texto en el TextBox y luego use WM_CLEAR para borrarlo. Parece funcionar bien y no está enviando eventos de pulsación de tecla artificial.
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);
}
}
}
}
}
y giangurgolo , gracias por la información proporcionada. A continuación una versión refinada de la misma. Tenga en cuenta que también considera ComboBox
, ya que tiene el mismo problema que TextBox
. También tenga en cuenta que los accesos directos solo están activos si la configuración de TextBox
o ComboBox
permite.
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));
}
}
Cadena auxiliar (por ejemplo, clase estática 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);
}
/ * Actualización: vea también la respuesta de Damir a continuación, probablemente sea una mejor solución :) * /
Simularía Ctrl + Retroceso enviando Ctrl + Mayús + Izquierda y Retroceso al TextBox. El efecto es prácticamente el mismo, y no es necesario procesar manualmente el texto del control. Puedes conseguirlo utilizando este código:
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);
}
}
También puede modificar el archivo app.config para forzar a la clase SendKey a usar un método más nuevo de envío de claves:
<configuration>
<appSettings>
<add key="SendKeys" value="SendInput" />
</appSettings>
</configuration>