Bind TextBox alla pressione del tasto Invio


108

L'associazione di dati predefinita TextBoxè TwoWaye il testo viene assegnato alla proprietà solo quando TextBoxperde il suo focus.

Esiste un modo XAML semplice per eseguire l'associazione dati quando si preme il Entertasto TextBox?. So che è abbastanza facile da fare nel codice dietro, ma immagina se questo TextBoxè all'interno di qualche complesso DataTemplate.

Risposte:


137

Puoi diventare un approccio XAML puro creando un comportamento associato .

Qualcosa come questo:

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();
        }
    }
}

Quindi nel tuo XAML imposti la InputBindingsManager.UpdatePropertySourceWhenEnterPressedPropertyproprietà su quella che vuoi aggiornare quando Enterviene premuto il tasto. Come questo

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

(Devi solo assicurarti di includere un riferimento xmlns clr-namespace per "b" nell'elemento radice del tuo file XAML che punta a qualsiasi spazio dei nomi in cui inserisci InputBindingsManager).


11
Nel tentativo di salvare i futuri lettori alcuni minuti di giocherellare, l'ho appena usato nel mio progetto e l'esempio XAML fornito sopra non ha funzionato correttamente. La fonte veniva aggiornata ad ogni cambio di personaggio. La modifica di "UpdateSourceTrigger = PropertyChanged" in "UpdateSourceTrigger = Explicit" ha risolto il problema. Ora funziona tutto come desiderato.
ihake

1
@ihake: Penso che la tua modifica consigliata impedirà anche l'aggiornamento sulla messa a fuoco persa
VoteCoffee

4
UpdateSourceTrigger = PropertyChanged potrebbe essere scomodo quando si digita un numero reale. Ad esempio "3." causa un problema quando viene associato a un float. Consiglierei di non specificare UpdateSourceTrigger (o l'impostazione su LostFocus) oltre al comportamento allegato. Questo dà il meglio di entrambi i mondi.
David Hollinshead

2
Lavoro eccellente! Tiny nit-pick: quando si UpdatePropertySourceWhenEnterPressedpassa da un valore valido a un valore valido diverso, si annulla l'iscrizione e ci si iscrive nuovamente PreviewKeyDownall'evento inutilmente. Invece, tutto ciò di cui hai bisogno è controllare se lo e.NewValueè nullo meno. In caso contrario null, iscriviti; in caso contrario, se null, quindi annullare l'iscrizione.
Nicholas Miller

1
Adoro questa soluzione, grazie per averla postata! Per chiunque abbia bisogno di allegare questo comportamento a numerose caselle di testo nella tua applicazione, ho pubblicato un'estensione a questa risposta su come puoi ottenerlo molto facilmente. Vedi il post qui
Nik

50

È così che ho risolto questo problema. Ho creato un gestore di eventi speciali che è entrato nel codice sottostante:

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(); }
    }
}

Quindi l'ho appena aggiunto come gestore di eventi KeyUp in XAML:

<TextBox Text="{Binding TextValue1}" KeyUp="TextBox_KeyEnterUpdate" />
<TextBox Text="{Binding TextValue2}" KeyUp="TextBox_KeyEnterUpdate" />

Il gestore eventi usa il suo senderriferimento per fare in modo che la propria associazione venga aggiornata. Poiché il gestore eventi è autonomo, dovrebbe funzionare in un DataTemplate complesso. Questo gestore di eventi può ora essere aggiunto a tutte le caselle di testo che richiedono questa funzionalità.


8
Qualcosa mi dice che questo è un post sottovalutato. Molte risposte sono popolari perché sono "XAML-y". Ma questo sembra risparmiare spazio nella maggior parte delle occasioni.
j riv

3
+1 Questo ti consente anche di lasciare da solo UpdateSourceTrigger nel caso in cui tu abbia già faticosamente ottenuto che i tuoi collegamenti TextBox si comportino già nel modo desiderato (con stili, convalida, twoway, ecc.), Ma attualmente non riceverai input dopo aver premuto Invio .
Jamin

2
Questo è l'approccio più pulito che ho trovato e secondo me dovrebbe essere contrassegnato come risposta. Aggiunto un ulteriore controllo null sul mittente, un controllo su Key.Return (il tasto Invio sulla mia tastiera restituisce Key.Return) e Keyboard.ClearFocus () per rimuovere lo stato attivo dal TextBox dopo l'aggiornamento della proprietà di origine. Ho apportato modifiche alla risposta che è in attesa di revisione tra pari.
Oystein

2
D'accordo con i commenti sopra. Ma per me l'evento KeyDown era più appropriato
Allender

1
Ho usato KeyBindingin XAML per attivare questo metodo perché la mia interfaccia utente ha un controllo "predefinito" che cattura il tasto Invio. È necessario catturarlo all'interno di questa casella di testo per impedire che si propaghi nella struttura dell'interfaccia utente al controllo "predefinito".
Ragazzo CAD,

46

Non credo che esista un modo "puro XAML" per fare ciò che stai descrivendo. È possibile configurare un'associazione in modo che si aggiorni ogni volta che il testo in un TextBox cambia (anziché quando il TextBox perde lo stato attivo ) impostando la proprietà UpdateSourceTrigger , in questo modo:

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

Se imposti UpdateSourceTrigger su "Explicit" e quindi gestisci l'evento PreviewKeyDown del TextBox (cercando il tasto Invio), potresti ottenere ciò che desideri, ma richiederebbe il code-behind. Forse una sorta di proprietà associata (simile al mio EnterKeyTraversal proprietà) di lavoro woudld per voi.


3
Questa risposta potrebbe essere più semplice di quella contrassegnata come risposta, ma presenta alcune limitazioni. Immagine a cui vuoi fare un qualche tipo di controllo nella funzione set della proprietà associata (controllando se l'input è valido). Non vuoi chiamare quella funzione di controllo dopo ogni tasto premuto dall'utente, vero (specialmente non quando la funzione richiede un po 'di tempo)?
sth_Weird

25

Puoi facilmente creare il tuo controllo ereditando da TextBox e riutilizzarlo in tutto il progetto.

Qualcosa di simile a questo dovrebbe funzionare:

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();
            }
        }
    }
}

Potrebbe esserci un modo per aggirare questo passaggio, ma altrimenti dovresti eseguire il binding in questo modo (usando Explicit):

<custom:SubmitTextBox
    Text="{Binding Path=BoundProperty, UpdateSourceTrigger=Explicit}" />

9
In WPF / Silverlight non dovresti mai usare l'ereditarietà: altera gli stili e non è flessibile come i comportamenti allegati. Ad esempio con Attached Behaviors puoi avere sia Watermark che UpdateOnEnter sulla stessa casella di testo.
Mikhail

14

Se combini le soluzioni di Ben e ausadmin, ti ritroverai con una soluzione molto amichevole 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>

... il che significa che stai passando lo TextBoxstesso come parametro al file Command.

Questo ti porta a Commandsembrare così (se stai usando DelegateCommandun'implementazione in stile nella tua 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();
        }
    }

Questa Commandimplementazione può essere utilizzata per qualsiasi TextBoxe, soprattutto, nessun codice nel code-behind, anche se potresti volerlo inserire nella sua classe in modo che non ci siano dipendenze System.Windows.Controlsnella tua VM. Dipende da quanto sono rigide le linee guida del codice.


Bello. Molto pulito. Se è coinvolto un multibinding, potrebbe essere necessario utilizzare BindingOperations.GetBindingExpressionBase (tBox, prop); e quindi binding.UpdateTarget ();
Votazione Caffè

4

Ecco un approccio che a me sembra abbastanza diretto, e più facile che aggiungere un AttachedBehaviour (che è anche una valida soluzione). Usiamo il valore predefinito UpdateSourceTrigger (LostFocus per TextBox), quindi aggiungiamo un InputBinding al tasto Invio, associato a un comando.

Il xaml è il seguente

       <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>

Quindi i metodi di comando sono

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

E il TextBox è associato alla proprietà

 Public Property Txt1 As String
    Get
        Return _txt1
    End Get
    Set(value As String)
        _txt1 = value
        OnPropertyChanged("Txt1")
    End Set
End Property

Finora questo sembra funzionare bene e cattura l'evento Enter Key nel TextBox.


4

Questa non è una risposta alla domanda originale, ma piuttosto un'estensione della risposta accettata da @Samuel Jack. Ho fatto quanto segue nella mia domanda ed ero sbalordito dall'eleganza della soluzione di Samuel. È molto pulito e molto riutilizzabile, poiché può essere utilizzato su qualsiasi controllo, non solo su TextBox. Ho pensato che questo dovrebbe essere condiviso con la comunità.

Se hai una finestra con mille TextBoxesche richiedono tutti di aggiornare l'origine del binding in invio, puoi associare questo comportamento a tutte includendo il codice XAML di seguito nel tuo Window Resourcesinvece di collegarlo a ogni TextBox. Per prima cosa devi implementare il comportamento allegato come da post di Samuel , ovviamente.

<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>

È sempre possibile limitare l'ambito, se necessario, inserendo lo stile nelle risorse di uno degli elementi figlio della finestra (ad esempio a Grid) che contiene i TextBox di destinazione.


2

Nel caso in cui utilizzi MultiBinding con il tuo TextBox, devi usare il BindingOperations.GetMultiBindingExpressionmetodo invece di 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();
     }
}

1

Questo funziona per me:

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


0

Personalmente penso che avere un'estensione di markup sia un approccio più pulito.

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>

0

Una soluzione diversa (non usando xaml ma credo comunque abbastanza pulita).

class ReturnKeyTextBox : TextBox
{
    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);
        if (e.Key == Key.Return)
            GetBindingExpression(TextProperty).UpdateSource();
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.