2009-07-20 9 views
1

Dans mon application WPF, j'ai une base de données TextBox et une base de données ItemsControl. Le contenu du ItemsControl est déterminé par le contenu du TextBox. Je veux être en mesure de taper une valeur dans le TextBox, appuyez sur l'onglet et entrez le premier élément dans le ItemsControl (créé à partir de la valeur dans le TextBox). Le problème que j'ai est que l'action de tabulation est évaluée avant que WPF crée les éléments de modèle dans le ItemsControl. Le code suivant illustre ce problème:WPF Tab Into Databound ItemsControl

<Window x:Class="BindingExample.Window1" x:Name="SelfControl" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:loc="clr-namespace:BindingExample" Title="Window1" Height="300" Width="400"> 
    <Window.Resources> 
     <DataTemplate DataType="{x:Type loc:ChildClass}"> 
      <TextBox Text="{Binding Value}" /> 
     </DataTemplate> 
    </Window.Resources> 
    <StackPanel DataContext="{Binding ElementName=SelfControl}" Focusable="False"> 
     <Label Content="Options: A, B, C" /> 
     <TextBox Text="{Binding Object.Value}" /> 
     <ItemsControl Margin="16,0,0,0" ItemsSource="{Binding Object.Children}" Focusable="False"> 
      <ItemsControl.ItemsPanel> 
       <ItemsPanelTemplate> 
        <StackPanel Orientation="Horizontal" /> 
       </ItemsPanelTemplate> 
      </ItemsControl.ItemsPanel> 
     </ItemsControl> 
     <TextBox Text="Box2" /> 
    </StackPanel> 
</Window> 

using System.Collections.Generic; 
using System.ComponentModel; 
using System.Windows; 

namespace BindingExample 
{ 
    public partial class Window1 
    { 
     public static readonly DependencyProperty ObjectProperty = DependencyProperty.Register("Object", typeof(ParentClass), typeof(Window1)); 
     public ParentClass Object 
     { 
      get { return GetValue(ObjectProperty) as ParentClass; } 
      set { SetValue(ObjectProperty, value); } 
     } 

     public Window1() 
     { 
      InitializeComponent(); 
      Object = new ParentClass(); 
     } 
    } 

    public class ParentClass : INotifyPropertyChanged 
    { 
     private string value; 
     public string Value 
     { 
      get { return value; } 
      set { this.value = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Children")); } 
     } 

     public IEnumerable<ChildClass> Children 
     { 
      get 
      { 
       switch (Value.ToUpper()) 
       { 
        case "A": return new ChildClass[] { "A-1", "A-2", "A-2" }; 
        case "B": return new ChildClass[] { "B-1", "B-2", "B-3" }; 
        case "C": return new ChildClass[] { "C-1", "C-2", "C-2" }; 
        default: return new ChildClass[] { "Unknown" }; 
       } 
      } 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 
    } 

    public class ChildClass 
    { 
     public string Value { get; set; } 
     public static implicit operator ChildClass(string value) { return new ChildClass { Value = value }; } 
    } 
} 

Dans ce code, je voudrais saisir « A » dans la première TextBox, onglet de presse, et ont le changement de mise au point à l'enfant TextBox avec le texte "A-1". Au lieu de cela, la mise au point passe à la zone de texte avec le texte "Box2". Comment puis-je atteindre le comportement que je recherche ici?

Note: Comme Julien Poulin a souligné, il est possible de faire ce travail en mettant l'UpdateSourceTrigger sur le TextBox-PropertyChanged. Cela ne fonctionne cependant que si la liaison "en cours de frappe" est acceptable. Dans mon cas, je voudrais également faire le jeu de la valeur et l'onglet avec une seule touche. Existe-t-il un moyen de forcer le ItemsControl à créer ses éléments basés sur des modèles à la demande?

Répondre

1

Essayez de régler la UpdateMode du TextBox-PropertyChanged de sorte que la valeur sous-jacente est définie lorsque vous tapez une nouvelle valeur au lieu du moment où le TextBox désélectionné:

<TextBox Text="{Binding Path=Object.Value, UpdateMode=PropertyChanged}" /> 
+0

Cela fonctionne. Idéalement, cependant, j'aimerais que la liaison se produise lorsque le contrôle perd le focus, pas pendant que l'utilisateur tape. –

+0

Je ne pense pas qu'il sera possible de définir automatiquement le focus sur le TextBox à l'intérieur de ItemsControl car ils ne sont pas encore créés lorsque votre 1er TextBox perd le focus. Vous pourriez écrire du code pour mettre l'accent là où il devrait appartenir, mais il semblerait que ce soit vraiment désordonné. –

0

Voici une solution de rechange. C'est un peu un hack, mais semble fonctionner. Puisque les objets modèles dans le ItemsControl ne sont pas créés avant que l'exécution sur le thread principal ne fasse une pause, ce code attrape l'onglet, met à jour la liaison et définit un temporisateur pour déplacer le focus une fois que les éléments ont une chance d'être créés.

... 
<TextBox Text="{Binding Object.Value}" KeyDown="TextBox_KeyDown" /> 
... 

public partial class Window1 
{ 
    private DispatcherTimer timer; 

    ... 

    private void TextBox_KeyDown(object sender, KeyEventArgs e) 
    { 
     if (e.Key == Key.Tab && e.KeyboardDevice.Modifiers != ModifierKeys.Shift) 
     { 
      e.Handled = true; 
      var textbox = (TextBox)sender; 
      textbox.GetBindingExpression(TextBox.TextProperty).UpdateSource(); 
      (timer = new DispatcherTimer(
       new TimeSpan(100000), // 10 ms 
       DispatcherPriority.Normal, 
       delegate 
       { 
        textbox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); 
        timer.Stop(); 
       }, Dispatcher)).Start(); 
     } 
    } 
} 

Le seul problème que je vois avec ce code est que le ItemsControl peut prendre plus de 10 ms pour remplir, auquel cas Tab serait sauter par-dessus ces éléments. Est-il possible de détecter si la création de ItemsControl éléments s'est produite ou non?

Questions connexes