2009-02-18 18 views
92

Il database predefinito su TextBox è TwoWay e impegna il testo nella proprietà solo quando TextBox ha perso il focus.Bind TextBox su tasto Invio premere

Esiste un modo semplice XAML per rendere possibile l'associazione dati quando premo il tasto Immettere su TextBox?. So che è piuttosto facile farlo nel codice, ma immagina se questo si trova all'interno di un complesso DataTemplate.

risposta

121

È possibile creare un approccio XAML puro creando uno attached behaviour.

Qualcosa di simile a 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(); 
     } 
    } 
} 

Poi, nel tuo XAML si imposta la proprietà InputBindingsManager.UpdatePropertySourceWhenEnterPressedProperty per quello che si desidera aggiornare quando il Enter viene premuto chiave. Ti piace questa

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

(Hai solo bisogno di assicurarsi di includere un riferimento CLR-namespace xmlns per "b" nell'elemento radice del file XAML indicando che mai spazio dei nomi si inserisce l'InputBindingsManager in).

+0

ho provato ma sembra webbrowser non aggiorna la sua fonte. qualsiasi cosa mi sia sfuggita? non ho gestito nessun evento presumo legando 2 way, il suo evento di gestione automatica. ho sbagliato? Grazie. –

+7

Nel tentativo di salvare i lettori del futuro alcuni minuti di giocherellare, l'ho usato nel mio progetto e l'esempio XAML sopra riportato non funzionava correttamente. La fonte è stata aggiornata ad ogni cambio di personaggio. La modifica di "UpdateSourceTrigger = PropertyChanged" su "UpdateSourceTrigger = Explicit" ha risolto il problema. Ora funziona tutto come desiderato. – ihake

+0

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

45

Non credo che ci sia un modo "puro XAML" per fare quello che stai descrivendo. È possibile impostare un binding in modo che aggiorna ogni volta che il testo in una casella di testo cambia (piuttosto che quando il controllo TextBox perde lo stato attivo) impostando la proprietà UpdateSourceTrigger, in questo modo:

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

Se si imposta UpdateSourceTrigger a "Explicit" e poi ha gestito l'evento PreviewKeyDown del TextBox (cercando il tasto Invio), quindi è possibile ottenere ciò che si desidera, ma richiederebbe code-behind. Forse una sorta di proprietà associata (simile alla mia proprietà EnterKeyTraversal) funziona per te.

+2

Questa risposta può essere più semplice di quella contrassegnata come risposta, ma presenta alcune limitazioni. Immagine che vuoi fare un qualche tipo di controllo nella funzione set della proprietà associata (controllando se l'input è valido). Non si desidera richiamare la funzione di controllo dopo ogni tasto premuto dall'utente (in particolare quando la funzione richiede del tempo). –

25

È possibile creare facilmente il proprio controllo ereditato 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(); 
      } 
     } 
    } 
} 

Ci può essere un modo per aggirare questo passo, ma per il resto si dovrebbe legare in questo modo (usando Explicit):

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

In WPF/Silverlight non si dovrebbe mai usare l'ereditarietà - esso mette in disordine gli stili e non è flessibile come i comportamenti associati. Ad esempio con i comportamenti associati è possibile avere sia Watermark che UpdateOnEnter nella stessa casella di testo. – Mikhail

2

Nel caso in cui stai usando MultiBinding con il tuo TextBox devi usare il metodo BindingOperations.GetMultiBindingExpression 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(); 
    } 
} 
33

Ecco come ho risolto questo problema. Ho creato un gestore evento speciale che è andato in codice dietro:

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

Poi ho solo aggiunto questo come un gestore di eventi KeyUp nel XAML:

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

Il gestore di eventi utilizza la sua sender riferimento a causare è proprio vincolante per essere aggiornato. Poiché il gestore eventi è autonomo, dovrebbe funzionare in un DataTemplate complesso. Questo unico gestore di eventi può ora essere aggiunto a tutte le caselle di testo che richiedono questa funzionalità.

+4

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

+1

+1 Questo ti permette anche di lasciare da solo UpdateSourceTrigger nel caso in cui hai già faticosamente fatto in modo che i tuoi binding TextBox si comportino come vuoi (con stili, validazione, twoway, ecc.), Ma al momento non riceveranno input dopo aver premuto Accedere. – Jamin

+0

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

4

Ecco un approccio che a me sembra abbastanza semplice, e più semplice che aggiungere un AttachedBehaviour (che è anche una soluzione valida). Utilizziamo l'UpdateSourceTrigger predefinito (LostFocus per TextBox) e quindi aggiungiamo un InputBinding alla chiave Invio, associato a un comando.

XAML è la 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> 

Poi 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 la casella di testo è associato alla proprietà

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

3

Più semplice, è sufficiente impostare UpdateSourceTrigger a PropertyChanged nell'associazione TextBox senza aggiungere nulla in codebehind. Proprio come questo:

<TextBox Text="{Binding Path=BoundProperty, UpdateSourceTrigger=PropertyChanged}"/> 

funziona per me.

+0

Funziona, ma si aggiornerà ad ogni cambio di carattere – VoteCoffee

11

Se si combinano sia Ben e soluzioni di ausadmin, si finisce con una soluzione amichevole molto 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 si sta passando il TextBox stesso come il parametro alla Command.

Questo porta alla tua Command simile a questo (se si sta utilizzando un'implementazione in stile DelegateCommand nel vostro 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(); 
     } 
    } 

Questo Command implementazione può essere utilizzato per qualsiasi TextBox e meglio di tutti nessun codice nel code-behind anche se potresti voler inserire questo nella propria classe quindi non ci sono dipendenze su System.Windows.Controls nella tua VM. Dipende da quanto siano rigorose le linee guida del codice.

+0

Nice. Molto pulito. Se è coinvolta una multibinding, potrebbe essere necessario utilizzare BindingOperations.GetBindingExpressionBase (tBox, prop); e quindi binding.UpdateTarget(); – VoteCoffee

0

questo funziona per me:

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

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

Se si dispone di una finestra con un migliaio di TextBoxes che tutti richiedono per aggiornare l'origine Legatura in brossura con Enter, è possibile collegare questo comportamento a tutti loro includendo il codice XAML basso nella WindowResources piuttosto che collegandolo a ogni TextBox. Innanzitutto è necessario implementare il comportamento allegato come da Samuel's post, 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> 

Si può sempre limitare il campo di applicazione, se necessario, mettendo lo stile nelle Risorse di uno degli elementi secondari della finestra (vale a dire una Grid) che contiene le caselle di testo di destinazione.

Problemi correlati