2010-10-10 5 views
0

Je travaille sur une application où les objets Repository sont affichés via un DataTemplate qui contient une version modified d'un TextBox, qui prend en charge la liaison à la SelectionStart, SelectionLength et VerticalOffset.WPF- problème avec TextBox dans DataTemplate

Le DataTemplate ressemble à ceci:

<DataTemplate DataType="{x:Type m:Repository}"> 
<controls:ModdedTextBox 
x:Name="textBox" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" 
BindableSelectionStart="{Binding SelectionStart, UpdateSourceTrigger=PropertyChanged}" 
BindableSelectionLength="{Binding SelectionLength, UpdateSourceTrigger=PropertyChanged}" 
BindableVerticalOffset="{Binding VerticalOffset, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> 
</DataTemplate> 

Le problème est que lorsque je change la Repository qui est actuellement en cours d'affichage; le SelectionStart, SelectionLength et VerticalOffset tous semblent se mettre à 0, même lorsque les propriétés de l'objet ne sont pas Repository 0.

Je pense que ce qui se passe dans l'instant avant que le texte est affiché lorsque la SelectionStart, SelectionLength et VerticalOffset ne peut pas être supérieur à 0. Cela ne définit pas uniquement les propriétés réelles du TextBox à zéro, mais met également à jour les liaisons et définit les propriétés de l'objet Repository sur zéro.

Y a-t-il un moyen de prévenir cela?

--Edit--

Je ne sais pas si l'affichage des liens dl à des projets est un non-non ou non sur le SO, mais voici un lien vers un projet que j'ai créé pour démontrer le problème J'ai: http://dl.dropbox.com/u/1520079/RepositoryProblemDemo.zip

Lorsque vous exécutez la démonstration application de la sélection, vous pouvez cliquer sur le bouton « Switch référentiel » pour changer le référentiel qui est affiché dans la zone de texte. Si vous regardez à droite de la zone de texte, les propriétés du référentiel actuel sont toutes mises à zéro lorsque vous passez à l'autre.

Une différence entre cette démo et mon application actuelle est que dans mes applications, les dépôts seront activés via des raccourcis clavier, pas un bouton.

+0

Pouvez-vous publier des informations sur la manière dont vos propriétés de dépendance Bindable *** sont créées/liées aux propriétés de sélection sous-jacentes? –

+0

Vos liaisons doivent-elles être bidirectionnelles et déclenchées sur PropertyChanged, ou pourriez-vous vous en sortir avec OneWay ou LostFocus? –

+0

J'ai lié à cette question: http://stackoverflow.com/questions/1175618/how-to-bind-selectionstart-property-of-text-box/1849539#1849539 – Justin

Répondre

1

Le problème est dû au fait que les liaisons sont évaluées en série, et lorsque la propriété Text est modifié, il provoque toutes les informations de sélection à supprimer (vous pouvez voir cela en mettant des points d'arrêt sur vos ModdedTextBox gestionnaires d'événements). Comme les liaisons BindableSelection ... sont toujours actives à ce stade, les informations de sélection sont réinitialisées.

Selon le comportement exact que vous voulez il y a probablement un moyen de contourner ce problème, mais vous devez savoir un peu plus en détail ...

Modifier en réponse aux commentaires: Cette solution ISN 'exactement répondant à votre question initiale, et ce n'est probablement pas une bonne pratique, mais il fonctionne au moins ...

Essayez de modifier votre ModdedTextBox de sorte qu'au lieu d'exposer les propriétés pouvant être liées pour l'information de sélection, exposer un seul DP du type de référentiel et se lient à ce que:

<local:ModdedTextBox 
       x:Name="textBox" 
       Repository="{Binding CurrentRepository}" 
       TextWrapping="Wrap" 
       /> 

poignée Ensuite, l'événement a changé sur votre DP pour régler la Propriétés de la zone de texte:

public static DependencyProperty RepositoryProperty = 
       DependencyProperty.Register("Repository", 
       typeof(Repository), typeof(ModdedTextBox), new PropertyMetadata(null, OnRepositoryChanged)); 

    public Repository Repository 
    { 
     get { return (Repository)base.GetValue(RepositoryProperty); } 
     set { base.SetValue(RepositoryProperty, value); } 
    } 

    private static void OnRepositoryChanged(DependencyObject senderObject, DependencyPropertyChangedEventArgs e) 
    { 
     var sender = (ModdedTextBox)senderObject; 
     var oldRepository = e.OldValue as Repository; 
     var newRepository = e.NewValue as Repository; 
     if (oldRepository != null) 
     { 
      oldRepository.Text = sender.Text; 
      oldRepository.SelectionStart = sender.SelectionStart; 
      //etc 
     } 

     if (newRepository != null) 
     { 
      sender.Text = newRepository.Text; 
      sender.SelectionStart = newRepository.SelectionStart; 
      //etc 
     } 
    } 

Ceci supprime essentiellement la nature sérielle de l'évaluation de liaison.

Remarque: Vous pouvez également obtenir la même chose en utilisant les propriétés attachées, ce qui serait mieux que sous-classer TextBox, mais c'est plus proche de vos tentatives originales, donc je pense que c'est plus facile à expliquer!

+0

Je ne vois pas comment je peux résoudre ce problème. Pourriez-vous entrer dans plus de détails? Avez-vous besoin de plus d'infos de moi? – Justin

+0

Je ne suis pas sûr que cela puisse être "réparé" exactement, car il se comporte comme vous vous y attendez. Ce que je voulais dire, c'est qu'il y a peut-être un autre moyen d'atteindre les résultats souhaités - qu'est-ce que vous voulez faire avec les informations de sélection? –

+0

Je veux qu'il soit en mesure de restaurer les informations de sélection, lorsque je passe d'un dépôt à l'autre. – Justin

0

Voici une réécriture de l'autre solution. Celui-ci prend en compte la propriété de texte n'étant pas liée avant les autres propriétés.


using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Windows; 
using System.Windows.Controls; 

namespace WpfApplication1 
{ 
    public class SelectionBindingTextBox : TextBox 
    { 
     public static readonly DependencyProperty BindableSelectionStartProperty = 
      DependencyProperty.Register(
      "BindableSelectionStart", 
      typeof(int), 
      typeof(SelectionBindingTextBox), 
      new PropertyMetadata(OnBindableSelectionStartChanged)); 

     public static readonly DependencyProperty BindableSelectionLengthProperty = 
      DependencyProperty.Register(
      "BindableSelectionLength", 
      typeof(int), 
      typeof(SelectionBindingTextBox), 
      new PropertyMetadata(OnBindableSelectionLengthChanged)); 

     private bool isBindingComplete = false; 

     public SelectionBindingTextBox() 
      : base() 
     { 
      this.SelectionChanged += this.OnSelectionChanged; 
      this.TextChanged += this.OnTextChanged; 
     } 

     public int BindableSelectionStart 
     { 
      get 
      { 
       return (int)this.GetValue(BindableSelectionStartProperty); 
      } 

      set 
      { 
       this.SetValue(BindableSelectionStartProperty, value); 
      } 
     } 

     public int BindableSelectionLength 
     { 
      get 
      { 
       return (int)this.GetValue(BindableSelectionLengthProperty); 
      } 

      set 
      { 
       this.SetValue(BindableSelectionLengthProperty, value); 
      } 
     } 


     private static void OnBindableSelectionStartChanged(DependencyObject dependencyObject, 
      DependencyPropertyChangedEventArgs args) 
     { 
      var textBox = dependencyObject as SelectionBindingTextBox; 

      if (textBox.isBindingComplete) 
      { 
       textBox.SetupSelection(); 
      } 
     } 

     private static void OnBindableSelectionLengthChanged(DependencyObject dependencyObject, 
      DependencyPropertyChangedEventArgs args) 
     { 
      var textBox = dependencyObject as SelectionBindingTextBox; 
      if (textBox.isBindingComplete) 
      { 
       textBox.SetupSelection(); 
      } 
     } 

     private void OnSelectionChanged(object sender, RoutedEventArgs e) 
     { 
      if (isBindingComplete) 
      { 
       this.BindableSelectionStart = this.SelectionStart; 
       this.BindableSelectionLength = this.SelectionLength; 
      } 
     } 

     private void OnTextChanged(object sender, RoutedEventArgs e) 
     { 
      if (!isBindingComplete) 
      { 
       SetupSelection(); 
      } 
      isBindingComplete = true; 
     } 

     private void SetupSelection() 
     { 
      // this.Focus(); 
      this.SelectionLength = this.BindableSelectionLength; 
      this.SelectionStart = this.BindableSelectionStart; 
     } 
    } 
} 

+0

La zone de texte est toujours ciblée. – Justin

0

La mise à jour de la liaison dépend de l'ordre d'évaluation de WPF ou de Silverlight Engine, vos mises à jour SelectionStart et SelectionEnd sont mises à jour avant le texte, donc lorsque le texte est modifié, SelectionStart et SelectionEnd sont toutes deux remises à zéro.

La seule façon est d'accrocher pour l'événement TextChanged et actualiser les liaisons de SelectionStart et selectionEnd ou WPF vous pouvez étendre textbox comme suivre

public class MyTextBox : TextBox{ 

    protected override OnTextChanged(TextChangedEventArgs e){ 
     BindingExpression be = this.GetBindingExpression(SelectionStartProperty); 
     if(be!=null){ 
      be.UpdateTarget(); 
     } 
     be = this.GetBindingExpression(SelectionEndProperty); 
     if(be!=null){ 
      be.UpdateTarget(); 
     } 
     be = this.GetBindingExpression(VerticalOffsetProperty); 
     if(be!=null){ 
      be.UpdateTarget(); 
     } 
    } 

} 

il y a bien ici une astuce, vous avez encore changer ci-dessus logique pour tenir dans votre logique parce que chaque mise à jour du texte mettra à jour la liaison, de sorte que vous devez savoir quand actualiser ces liaisons. Parce que cela échouera constamment à changer la valeur de votre zone de texte en cours d'exécution car le texte sera modifié et la sélection ne sera plus que la sélection précédente.