Enlazar TextBox al presionar la tecla Enter

108

El enlace de datos predeterminado TextBoxes TwoWayy confirma el texto a la propiedad solo cuandoTextBox pierde su enfoque.

¿Existe alguna forma XAML fácil de hacer que el enlace de datos ocurra cuando presiono la Entertecla TextBox?. Sé que es bastante fácil de hacer en el código subyacente, pero imagina si esto TextBoxestá dentro de algún complejo DataTemplate.

Jobi Joy
fuente

Respuestas:

137

Puede crear un enfoque XAML puro creando 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 InputBindingsManager.UpdatePropertySourceWhenEnterPressedPropertypropiedad en la que desea actualizar cuando Enterse presiona la tecla. Me gusta esto

<TextBox Name="itemNameTextBox"
         Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}"
         b:InputBindingsManager.UpdatePropertySourceWhenEnterPressed="TextBox.Text"/>

(Solo debe asegurarse de incluir una referencia xmlns clr-namespace para "b" en el elemento raíz de su archivo XAML que apunte al espacio de nombres en el que colocó InputBindingsManager).

Samuel Jack
fuente
11
En un esfuerzo por ahorrar a los futuros lectores unos minutos de manipulación, acabo de usar esto en mi proyecto y el XAML de muestra proporcionado anteriormente no funcionó correctamente. La fuente se actualizó con cada cambio de personaje. Cambiar "UpdateSourceTrigger = PropertyChanged" a "UpdateSourceTrigger = Explicit" solucionó el problema. Ahora todo funciona como se desea.
Hago
1
@ihake: Creo que su cambio recomendado también evitará la actualización en el enfoque perdido, aunque
VoteCoffee
4
UpdateSourceTrigger = PropertyChanged puede ser un inconveniente al escribir un número real. Por ejemplo, "3". causa un problema cuando se une a un flotador. Recomiendo no especificar UpdateSourceTrigger (o configurar LostFocus) además del comportamiento adjunto. Esto da lo mejor de ambos mundos.
David Hollinshead
2
¡Excelente trabajo! Minúsculo detalle: cuando UpdatePropertySourceWhenEnterPressedcambia de un valor válido a un valor válido diferente, se cancela la suscripción y se vuelve a suscribir al PreviewKeyDownevento innecesariamente. En cambio, todo lo que debe necesitar es verificar si lo e.NewValuees nullo no. Si no es así null, suscríbete; de lo contrario, si null, anule la suscripción.
Nicholas Miller
1
Me encanta esta solución, ¡gracias por publicarla! Para cualquiera que necesite adjuntar este comportamiento a numerosos TextBoxes en su aplicación, publiqué una extensión a esta respuesta sobre cómo puede lograrlo muy fácilmente. Vea la publicación aquí
Nik
50

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 senderreferencia para hacer que su propio enlace se actualice. Dado que el controlador de eventos es autónomo, debería funcionar en un DataTemplate complejo. Este controlador de eventos ahora se puede agregar a todos los cuadros de texto que necesitan esta función.

Ben
fuente
8
Algo me dice que esta es una publicación subestimada. Muchas respuestas son populares porque son "XAML-y". Pero este parece ahorrar espacio en la mayoría de las ocasiones.
j riv
3
+1 Esto también le permite dejar solo UpdateSourceTrigger en caso de que ya haya logrado que sus enlaces TextBox se comporten de la manera que desea (con estilos, validación, bidireccional, etc.), pero actualmente simplemente no recibirá entrada después de presionar Enter .
Jamin
2
Este es el enfoque más limpio que he encontrado y, en mi opinión, debería marcarse como la respuesta. Se agregó una verificación nula adicional en el remitente, una verificación en Key.Return (la tecla Enter en mi teclado devuelve Key.Return) y Keyboard.ClearFocus () para eliminar el foco del TextBox después de actualizar la propiedad de origen. Modifiqué la respuesta que está pendiente de revisión por pares.
Oystein
2
De acuerdo con los comentarios anteriores. Pero para mí el evento KeyDown fue más apropiado
Allender
1
He utilizado KeyBindingen el XAML para disparar este método porque mi interfaz de usuario tiene un control de "defecto" que llama la tecla enter. Debe capturarlo dentro de este TextBox para evitar que se propague por el árbol de la interfaz de usuario hasta el control "Predeterminado".
CAD bloke
46

No creo que haya ninguna forma "XAML puro" de hacer lo que estás describiendo. Puede configurar un enlace para que se actualice cada vez que cambia el texto en un TextBox (en lugar de cuando el TextBox pierde el foco) estableciendo la propiedad UpdateSourceTrigger , así:

<TextBox Name="itemNameTextBox"
    Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}" />

Si configura UpdateSourceTrigger en "Explicit" y luego maneja el evento PreviewKeyDown de TextBox (buscando la tecla Enter), entonces podría lograr lo que desea, pero requeriría código subyacente. Tal vez algún tipo de propiedad adjunta (similar a mi EnterKeyTraversal propiedad) woudld trabajo para usted.

Matt Hamilton
fuente
3
Esta respuesta puede ser más simple que la marcada como respuesta, pero tiene algunas limitaciones. Imagen que desea hacer algún tipo de verificación en la función de conjunto de la propiedad vinculada (verificando si la entrada es válida). No desea llamar a esa función de verificación después de cada tecla que presione el usuario, ¿verdad (especialmente cuando la función lleva algún tiempo)?
sth_Weird
25

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ía enlazar así (usando Explicit):

<custom:SubmitTextBox
    Text="{Binding Path=BoundProperty, UpdateSourceTrigger=Explicit}" />
Ryan Versaw
fuente
9
En WPF / Silverlight, nunca debe usar la herencia: desordena los estilos y no es tan flexible como los comportamientos adjuntos. Por ejemplo, con Attached Behaviors puede tener tanto Watermark como UpdateOnEnter en el mismo cuadro de texto.
Mikhail
14

Si combina las soluciones de Ben y ausadmin, obtendrá una solución muy compatible 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 TextBoxpropio como parámetro al Command.

Esto hace que se Commandvea así (si está utilizando una DelegateCommandimplementación de estilo en su VM):

    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 Commandimplementación se puede usar para cualquier TextBoxy, lo mejor de todo, sin código en el código subyacente, aunque es posible que desee ponerlo en su propia clase para que no haya dependencias System.Windows.Controlsen su VM. Depende de cuán estrictas sean las pautas de su código.

toadflakz
fuente
Agradable. Muy limpio. Si se trata de un enlace múltiple, es posible que deba utilizar BindingOperations.GetBindingExpressionBase (tBox, prop); y luego binding.UpdateTarget ();
VoteCoffee
4

Aquí hay un enfoque que me 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, vinculado 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 detecta el evento Enter Key en TextBox.

ausadmin
fuente
4

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 me asombró 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 elTextBox . Pensé que esto debería compartirse con la comunidad.

Si tiene una ventana con mil y TextBoxestodas requieren actualizar la fuente de enlace en Enter, puede adjuntar este comportamiento a todas ellas al incluir el XAML a continuación en su en Window Resourceslugar de adjuntarlo a cada TextBox. Primero debe 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, a Grid) que contiene los TextBoxes de destino.

Nik
fuente
2

En caso de que esté usando MultiBinding con su TextBox, debe usar el BindingOperations.GetMultiBindingExpressionmétodo en 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();
     }
}
Akjoshi
fuente
1

Esto funciona para mi:

        <TextBox                 
            Text="{Binding Path=UserInput, UpdateSourceTrigger=PropertyChanged}">
            <TextBox.InputBindings>
                <KeyBinding Key="Return" 
                            Command="{Binding Ok}"/>
            </TextBox.InputBindings>
        </TextBox>
Valery
fuente
0

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>
yo
fuente
0

Una solución diferente (creo que no usa xaml pero todavía bastante limpia).

class ReturnKeyTextBox : TextBox
{
    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);
        if (e.Key == Key.Return)
            GetBindingExpression(TextProperty).UpdateSource();
    }
}
Hauk
fuente