c# - Enlazar TextBox en presionar Enter
wpf xaml (12)
El enlace de datos predeterminado en TextBox
es TwoWay
y confirma el texto en la propiedad solo cuando TextBox
pierde su foco.
¿Hay alguna forma sencilla de XAML para hacer que el enlace de datos suceda cuando presiono la tecla Enter en el TextBox
? Sé que es bastante fácil de hacer en el código subyacente, pero imagine si este TextBox
está dentro de una compleja DataTemplate
.
Aquí hay un enfoque que para mí parece bastante sencillo, y más fácil que agregar un AttachedBehaviour (que también es una solución válida). Usamos el UpdateSourceTrigger predeterminado (LostFocus para TextBox), y luego agregamos un InputBinding a la tecla Enter, ligado a un comando.
El xaml es el siguiente
<TextBox Grid.Row="0" Text="{Binding Txt1}" Height="30" Width="150">
<TextBox.InputBindings>
<KeyBinding Gesture="Enter"
Command="{Binding UpdateText1Command}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}},Path=Text}" />
</TextBox.InputBindings>
</TextBox>
Entonces los métodos de comando son
Private Function CanExecuteUpdateText1(ByVal param As Object) As Boolean
Return True
End Function
Private Sub ExecuteUpdateText1(ByVal param As Object)
If TypeOf param Is String Then
Txt1 = CType(param, String)
End If
End Sub
Y el TextBox está vinculado a la propiedad
Public Property Txt1 As String
Get
Return _txt1
End Get
Set(value As String)
_txt1 = value
OnPropertyChanged("Txt1")
End Set
End Property
Hasta ahora, esto parece funcionar bien y capta el evento Enter Key en el TextBox.
Así es como resolví este problema. Creé un controlador de eventos especial que entró en el código detrás:
private void TextBox_KeyEnterUpdate(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
TextBox tBox = (TextBox)sender;
DependencyProperty prop = TextBox.TextProperty;
BindingExpression binding = BindingOperations.GetBindingExpression(tBox, prop);
if (binding != null) { binding.UpdateSource(); }
}
}
Luego acabo de agregar esto como un controlador de eventos KeyUp en el XAML:
<TextBox Text="{Binding TextValue1}" KeyUp="TextBox_KeyEnterUpdate" />
<TextBox Text="{Binding TextValue2}" KeyUp="TextBox_KeyEnterUpdate" />
El controlador de eventos usa su referencia de sender
para hacer que su propio enlace se actualice. Como el controlador de eventos es autónomo, debería funcionar en una plantilla de datos compleja. Este controlador de un evento ahora se puede agregar a todos los cuadros de texto que necesitan esta característica.
En caso de que esté utilizando MultiBinding con su TextBox, necesita usar el método BindingOperations.GetMultiBindingExpression
lugar de BindingOperations.GetBindingExpression
.
// Get the correct binding expression based on type of binding
//(simple binding or multi binding.
BindingExpressionBase binding =
BindingOperations.GetBindingExpression(element, prop);
if (binding == null)
{
binding = BindingOperations.GetMultiBindingExpression(element, prop);
}
if (binding != null)
{
object value = element.GetValue(prop);
if (string.IsNullOrEmpty(value.ToString()) == true)
{
binding.UpdateTarget();
}
else
{
binding.UpdateSource();
}
}
Esta no es una respuesta a la pregunta original, sino más bien una extensión de la respuesta aceptada por @Samuel Jack. Hice lo siguiente en mi propia aplicación, y admiré la elegancia de la solución de Samuel. Es muy limpio y muy reutilizable, ya que se puede usar en cualquier control, no solo en el TextBox
. Pensé que esto debería ser compartido con la comunidad.
Si tiene una ventana con miles de TextBoxes
que requieren actualizar el origen de enlace al TextBoxes
, puede adjuntar este comportamiento a todos ellos incluyendo el XAML a continuación en sus Resources
Window
lugar de adjuntarlo a cada cuadro de texto. Primero debes implementar el comportamiento adjunto según la publicación de Samuel , por supuesto.
<Window.Resources>
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Setters>
<Setter Property="b:InputBindingsManager.UpdatePropertySourceWhenEnterPressed" Value="TextBox.Text"/>
</Style.Setters>
</Style>
</Window.Resources>
Siempre puede limitar el alcance, si es necesario, colocando el Estilo en los Recursos de uno de los elementos secundarios de la Ventana (es decir, una Grid
) que contiene los Campos de texto de destino.
Esto funciona para mí:
<TextBox
Text="{Binding Path=UserInput, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Return"
Command="{Binding Ok}"/>
</TextBox.InputBindings>
</TextBox>
Más simple, simplemente configure UpdateSourceTrigger
en PropertyChanged
en el enlace de su TextBox
sin agregar nada en codebehind. Solo así:
<TextBox Text="{Binding Path=BoundProperty, UpdateSourceTrigger=PropertyChanged}"/>
Esto funciona para mi.
No creo que haya una forma "pura XAML" de hacer lo que describes. Puede configurar un enlace para que se actualice cada vez que el texto en un cuadro de texto cambie (en lugar de cuando el cuadro de texto pierda el foco) configurando la propiedad UpdateSourceTrigger , como esta:
<TextBox Name="itemNameTextBox"
Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}" />
Si configura UpdateSourceTrigger como "Explícito" y luego maneja el evento PreviewKeyDown de TextBox (buscando la tecla Intro), entonces puede lograr lo que quiere, pero requeriría código subyacente. Tal vez algún tipo de propiedad adjunta (similar a mi propiedad EnterKeyTraversal) funcione para usted.
Personalmente, creo que tener una extensión de marcado es un enfoque más limpio.
public class UpdatePropertySourceWhenEnterPressedExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new DelegateCommand<TextBox>(textbox => textbox.GetBindingExpression(TextBox.TextProperty).UpdateSource());
}
}
<TextBox x:Name="TextBox"
Text="{Binding Text}">
<TextBox.InputBindings>
<KeyBinding Key="Enter"
Command="{markupExtensions:UpdatePropertySourceWhenEnterPressed}"
CommandParameter="{Binding ElementName=TextBox}"/>
</TextBox.InputBindings>
</TextBox>
Puede crear fácilmente su propio control heredando de TextBox y reutilizarlo en todo su proyecto.
Algo similar a esto debería funcionar:
public class SubmitTextBox : TextBox
{
public SubmitTextBox()
: base()
{
PreviewKeyDown += new KeyEventHandler(SubmitTextBox_PreviewKeyDown);
}
void SubmitTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
BindingExpression be = GetBindingExpression(TextBox.TextProperty);
if (be != null)
{
be.UpdateSource();
}
}
}
}
Puede haber una forma de evitar este paso, pero de lo contrario deberías enlazar así (usando Explícito):
<custom:SubmitTextBox
Text="{Binding Path=BoundProperty, UpdateSourceTrigger=Explicit}" />
Puede hacerse un enfoque XAML puro mediante la creación de un comportamiento adjunto .
Algo como esto:
public static class InputBindingsManager
{
public static readonly DependencyProperty UpdatePropertySourceWhenEnterPressedProperty = DependencyProperty.RegisterAttached(
"UpdatePropertySourceWhenEnterPressed", typeof(DependencyProperty), typeof(InputBindingsManager), new PropertyMetadata(null, OnUpdatePropertySourceWhenEnterPressedPropertyChanged));
static InputBindingsManager()
{
}
public static void SetUpdatePropertySourceWhenEnterPressed(DependencyObject dp, DependencyProperty value)
{
dp.SetValue(UpdatePropertySourceWhenEnterPressedProperty, value);
}
public static DependencyProperty GetUpdatePropertySourceWhenEnterPressed(DependencyObject dp)
{
return (DependencyProperty)dp.GetValue(UpdatePropertySourceWhenEnterPressedProperty);
}
private static void OnUpdatePropertySourceWhenEnterPressedPropertyChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
UIElement element = dp as UIElement;
if (element == null)
{
return;
}
if (e.OldValue != null)
{
element.PreviewKeyDown -= HandlePreviewKeyDown;
}
if (e.NewValue != null)
{
element.PreviewKeyDown += new KeyEventHandler(HandlePreviewKeyDown);
}
}
static void HandlePreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
DoUpdateSource(e.Source);
}
}
static void DoUpdateSource(object source)
{
DependencyProperty property =
GetUpdatePropertySourceWhenEnterPressed(source as DependencyObject);
if (property == null)
{
return;
}
UIElement elt = source as UIElement;
if (elt == null)
{
return;
}
BindingExpression binding = BindingOperations.GetBindingExpression(elt, property);
if (binding != null)
{
binding.UpdateSource();
}
}
}
Luego, en su XAML, establece la propiedad InputBindingsManager.UpdatePropertySourceWhenEnterPressedProperty
en la que desea actualizar cuando se presiona la tecla Intro . Me gusta esto
<TextBox Name="itemNameTextBox"
Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}"
b:InputBindingsManager.UpdatePropertySourceWhenEnterPressed="TextBox.Text"/>
(Solo necesita asegurarse de incluir una referencia xmlns clr-namespace para "b" en el elemento raíz de su archivo XAML apuntando al espacio de nombres en el que coloca el InputBindingsManager).
Respondí aquí con bastante elegancia utilizando comportamientos adjuntos, mi método preferido para casi cualquier cosa.
WPF cómo hacer que cuadro de texto pierda el foco después de presionar enter
Si combina las soluciones de Ben y Ausadmin, obtendrá una solución muy amigable con MVVM:
<TextBox Text="{Binding Txt1, Mode=TwoWay, UpdateSourceTrigger=Explicit}">
<TextBox.InputBindings>
<KeyBinding Gesture="Enter"
Command="{Binding UpdateTextBoxBindingOnEnterCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}}}" />
</TextBox.InputBindings>
</TextBox>
... lo que significa que está pasando el propio TextBox
como el parámetro para el Command
.
Esto lleva a que su Command
vea así (si está utilizando una implementación de estilo DelegateCommand
en su máquina virtual):
public bool CanExecuteUpdateTextBoxBindingOnEnterCommand(object parameter)
{
return true;
}
public void ExecuteUpdateTextBoxBindingOnEnterCommand(object parameter)
{
TextBox tBox = parameter as TextBox;
if (tBox != null)
{
DependencyProperty prop = TextBox.TextProperty;
BindingExpression binding = BindingOperations.GetBindingExpression(tBox, prop);
if (binding != null)
binding.UpdateSource();
}
}
Esta implementación de Command
se puede utilizar para cualquier TextBox
y, lo mejor de todo, sin código en el código subyacente, aunque es posible que desee colocarlo en su propia clase para que no haya dependencias en System.Windows.Controls
en su VM. Depende de cuán estrictas sean sus pautas de código.