2013-06-04 12 views
8

Nel seguente semplice applicazione WPF un TextBox è impostato per aggiornare una proprietà quando il focus si perde da quella di controllo, in questo modoTextBox LostFocus non viene generato quando si fa clic su alcuni altri controlli

<DockPanel> 
    <ToolBar DockPanel.Dock="Top"> 
     <Button>Test</Button> 
    </ToolBar> 
    <TextBox Text="{Binding MyString}" /> 
</DockPanel> 

public MainWindow() 
{ 
    InitializeComponent(); 
    DataContext = this; 
} 

public string MyString 
{ 
    get { return _myString; } 
    set { _myString = value; } 
} 

Tuttavia quando corro questa applicazione, inserire del testo nella casella di testo e quindi fare clic sul pulsante "Test" il mio punto di interruzione sulla proprietà MyString è non sollevato, inoltre non viene generato alcun gestore di eventi per l'evento FocusLost. Questi eventi vengono sollevati solo quando la concentrazione viene persa dal controllo tramite altri mezzi (ad esempio, la finestra è chiusa).

Questo è causa di problemi per noi come in realtà il pulsante "Test" contiene la logica che si basa sulla proprietà MyString in fase di aggiornamento.

Come è possibile verificare che l'evento FocusLost sia stato generato correttamente e che il binding sia aggiornato mentre faccio clic sul pulsante "Test"? Sembra che il problema sia in qualche modo causato dall'uso dello ToolBar, poiché la sostituzione dello ToolBar con un pulsante standard non comporta questo comportamento.

risposta

2

In questo caso la casella di testo in realtà non perde la messa a fuoco logica e quindi l'evento non viene mai generato: in sostanza, desidero l'evento LostKeyboardFocus e non l'evento LostFocus per attivare l'aggiornamento.

Questo problema è simile a WPF: Data bound TabControl doesn't commit changes when new tab is selected e c'è un elemento di connessione Microsoft per esso here con un numero di potenziali soluzioni, tuttavia ho risolto questo problema utilizzando una proprietà associata in questo modo.

public static readonly DependencyProperty BindOnLostKeyboardFocusProperty = 
    DependencyProperty.RegisterAttached("BindOnLostKeyboardFocus", typeof(bool), typeof(MainWindow), new PropertyMetadata(default(bool), BindOnLostKeyboardFocusChanged)); 

private static void BindOnLostKeyboardFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) 
{ 
    var control = o as UIElement; 
    if (control != null) 
    { 
     if ((bool) e.NewValue) 
     { 
      control.AddHandler(LostKeyboardFocusEvent, new RoutedEventHandler(ControlLostKeyboardFocus)); 
     } 
     else 
     { 
      control.RemoveHandler(LostKeyboardFocusEvent, new RoutedEventHandler(ControlLostKeyboardFocus)); 
     } 
    } 
} 

private static void ControlLostKeyboardFocus(object sender, RoutedEventArgs e) 
{ 
    var control = (UIElement)sender; 
    control.RaiseEvent(new RoutedEventArgs(LostFocusEvent)); 
} 

Questo significa semplicemente che ogni volta LostKeyboardFocus viene sollevato per quel controllo, va avanti e solleva un ulteriore LostFocus evento che l'attacco si aggiornare. Il suo usato in questo modo

<TextBox Text="{Binding Test}" LostKeyboardFocus="UIElement_OnLostKeyboardFocus" local:MainWindow.BindOnLostKeyboardFocus="True" /> 
2

La tua struttura collegata fa un paio di ipotesi:

  • che nulla dipende la distinzione tra i LostKeyboardFocus e LostFocus eventi
  • che le associazioni sull'elemento è attaccato risponderà effettivamente all'evento LostFocus (potrebbe avere UpdateSourceTrigger.Explicit)

Invece, si potrebbe elencare le associazioni sull'elemento e chiamare direttamente UpdateSource:

private void CommitBindings(DependencyObject element) { 
    var localValueEnumerator = element.GetLocalValueEnumerator(); 
    while (localValueEnumerator.MoveNext()) { 
     var entry = localValueEnumerator.Current; 
     if (BindingOperations.IsDataBound(element, entry.Property)) { 
      var bindingExpression = (BindingExpressionBase)entry.Value; 
      bindingExpression.UpdateSource(); 
     } 
    } 
} 

Inoltre, invece di gestire ciascun TextBox singolarmente, si potrebbe gestire il contenitore e utilizzare OldFocus per ottenere l'elemento effettivo che ha perso il focus della tastiera.

0

il seguente comportamento risolverà questo:

public class TextBoxUpdateOnLostKeyboardFocusBehavior : Behavior<TextBox> 
{ 
    protected override void OnAttached() 
    { 
     if (AssociatedObject != null) 
     { 
      base.OnAttached(); 
      AssociatedObject.LostKeyboardFocus += OnKeyboardLostFocus; 
     } 
    } 

    protected override void OnDetaching() 
    { 
     if (AssociatedObject != null) 
     { 
      AssociatedObject.LostKeyboardFocus -= OnKeyboardLostFocus; 
      base.OnDetaching(); 
     } 
    } 

    private void OnKeyboardLostFocus(object sender, KeyboardFocusChangedEventArgs e) 
    { 
     var textBox = sender as TextBox; 

     if (textBox != null && e.NewFocus == null) 
     { 
      // Focus on the closest focusable ancestor 
      FrameworkElement parent = (FrameworkElement) textBox.Parent; 
      while (parent is IInputElement && !((IInputElement) parent).Focusable) 
      { 
       parent = (FrameworkElement) parent.Parent; 
      } 

      DependencyObject scope = FocusManager.GetFocusScope(textBox); 
      FocusManager.SetFocusedElement(scope, parent); 
     } 
    } 
} 

È possibile collegare al vostro TextBox come segue:

 <TextBox> 
      <i:Interaction.Behaviors> 
       <behaviors1:TextBoxUpdateOnLostKeyboardFocusBehavior /> 
      </i:Interaction.Behaviors>    
     </TextBox> 
Problemi correlati