2016-09-23 1 views
0

Même après avoir examiné une multitude de propositions de solution, je ne peux pas obtenir une simple liaison bidirectionnelle dans xaml pour fonctionner. J'ai une fenêtre, un dataContext et une application. A) pendant l'exécution du constructeur de l'application, la fenêtre (initialisée et .Show -d dans le même constructeur) apparaît, mais n'est pas mise à jour du tout, même si je bascule la valeur de la case à cocher dans mon code C# quelques fois; B) lorsque le constructeur de l'application a fini, la fenêtre est mise à jour exactement une fois; Je l'ai configuré de sorte que si je clique sur la case à cocher dans la fenêtre, le gestionnaire d'événements dans App (lié à la notification de changement de propriété DataContext) devrait augmenter la taille d'une liste de chaînes, qui est également affichée. L'augmentation de la liste se produit correctement dans le code, mais n'est pas reflétée dans la fenêtre.Liaison bidirectionnelle WPF ne fonctionnant pas sur CheckBox et la liste <string>

Résumé:

  • entrée utilisateur dans la fenêtre atteins le code C# App bien: je peux agir sur les changements de case à cocher, etc.
  • le sens inverse ne fonctionne pas: chaque fois que les éléments sont modifiés DataContext via le code, la fenêtre n'est pas automatiquement mise à jour, même si iNotifyProperty est implémenté et exécuté.

Ce que je me attends est que:

a) pendant le fonctionnement du constructeur App et permet de basculer la valeur CheckBox, la fenêtre doit tenir compte des changements de réglage/compensation de la tique sur la boîte; B) une fois le constructeur de l'application terminé, chaque fois que je passe de CheckBox de FALSE à TRUE, le NameList est ajouté avec une nouvelle chaîne. Je m'attendrais à ce que la liste dans la fenêtre augmente en conséquence et affiche automatiquement le contenu NameList complet et ajouté.

Observations:

  • j'essaie de faire en sorte que le DataContext sur la fenêtre est réglée avant d'appeler InitializeComponent sur la fenêtre. Ne fait pas vraiment une différence, malheureusement ...
  • je reçois un indice unique dans VS dans le fichier MainWindow.xaml: le chemin CheckBox ainsi que la liaison ListBox NameList sont annotés avec Cannot resolve symbol due to unknown DataContext Cependant, lorsque l'application constructeur termine la fenêtre est mis à jour et lorsque je clique sur le CheckBox, l'événement NotifyProperty correct est déclenché. Cela me dit que les liaisons d'exécution devraient fonctionner ... apparemment seulement à sens unique, pas à deux voies cependant.

MainWindow.xaml:

<Window x:Class="StatisticsEvaluation.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="MainWindow" Height="350" Width="525"> 
<Grid> 
    <StackPanel Orientation="Vertical"> 

     <CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}" Content="CheckBox" /> 

     <ListBox ItemsSource="{Binding NameList, Mode=TwoWay}"> 
      <ListBox.ItemTemplate> 
       <DataTemplate> 
        <StackPanel> 
         <TextBlock Text="{Binding}"/> 
        </StackPanel> 
       </DataTemplate> 
      </ListBox.ItemTemplate> 
     </ListBox> 

     <TextBlock FontSize="18" FontFamily="Arial" Foreground="Black" Text="TextBlock" Visibility="Visible" /> 

    </StackPanel> 
</Grid> 

MainWindow.xaml.cs:

namespace StatisticsEvaluation 
{ 
/// <summary> 
/// Interaction logic for MainWindow.xaml 
/// </summary> 
public partial class MainWindow : Window 
{ 
    public MainWindow() 
    {    
    } 
} 

}

L'application et DataContext:

namespace StatisticsEvaluation 
{ 
    public class DataContextClass : INotifyPropertyChanged 
    { 
     private bool isChecked; 

     public bool IsChecked 
     { 
      get 
      { 
       return isChecked; 
      } 

      set 
      { 
       isChecked = value; 
       OnPropertyChanged("IsChecked"); 
      } 
     } 

     private List<string> nameList; 

     public List<string> NameList 
     { 
      get 
      { 
       return nameList; 
      } 

      set 
      { 
       nameList = value; 
       OnPropertyChanged("NameList"); 
      } 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 

     public void OnPropertyChanged(string propertyName) 
     { 
      var handler = PropertyChanged; 
      if (handler != null) 
       handler(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 


    /// <summary> 
    /// Interaction logic for App.xaml 
    /// </summary> 

    public partial class App : Application 
    { 
     private MainWindow MyWindow { get; set; } 

     private DataContextClass MyDataContext{ get; set; } 

     private void HandleDataContextPropertyChange(object sender, PropertyChangedEventArgs e) 
     { 
      // If the CheckBox was just toggled to TRUE, increase the NameList 
      // with an additional name and call OnPropertyChanged on it ... 
      // hoping that this would trigger a Window UI update - but no luck ! 

      if ((e.PropertyName == "IsChecked") && MyDataContext.IsChecked) 
      { 
       var randomProvider = new Random(); 
       MyDataContext.NameList.Add(randomProvider.Next().ToString()); 
       MyDataContext.OnPropertyChanged("NameList"); 
      } 
     } 

     public App() 
     { 
      MyDataContext = new DataContextClass(); 
      MyDataContext.PropertyChanged += HandleDataContextPropertyChange; 

      MyWindow = new MainWindow {DataContext = MyDataContext}; 
      MyWindow.InitializeComponent(); 
      MyWindow.Show(); 

      MyDataContext.NameList = new List<string>(); 
      MyDataContext.NameList.Add("FirstName"); 
      MyDataContext.NameList.Add("SecondName"); 
      MyDataContext.NameList.Add("ThirdName"); 

      MyDataContext.IsChecked = true; 
      Thread.Sleep(3000); 
      MyDataContext.IsChecked = false; 
      Thread.Sleep(3000); 
      MyDataContext.IsChecked = true; 
     }  
    } 
} 

Quand je commence l'application de la fenêtre suivante apparaît, une fois que le constructeur App frappe .Show:

enter image description here

Une fois le constructeur de l'appli h comme terminée, la fenêtre est mis à jour une fois, mais jamais plus tard, quel que soit le nombre de chaînes sont ajoutées à NameList:

enter image description here

Toutes les idées, pourquoi mes deux sens de liaison ne fonctionne que dans un sens?

+1

Je vous suggère de supprimer tout ce bizarre choses '' dans MyDataContext' de app' et juste créer le viewmodel dans le constructeur MainWindow ou quelque chose. En outre, utilisez toujours 'ObservableCollection ' pour les collections qui seront liées aux contrôles dans WPF. Ne pas utiliser la liste. Il ne déclenche pas d'événements lorsque son contenu change. –

+2

'ItemsSource =" {Lister NameList, Mode = TwoWay} "' n'a pas de sens.Un ItemsControl ne change jamais sa propriété 'ItemsSource', de sorte que le réglage' Mode = TwoWay' n'a jamais d'effet. En outre, 'MyDataContext.OnPropertyChanged (" NameList ");' est ignoré silencieusement car l'instance NameList ne change pas. Utilisez donc une ObservableCollection. – Clemens

+0

Messieurs - merci! En effet, mon erreur principale était de ne pas utiliser une ObservableCollection. J'ai également enlevé le 'Mode = TwoWay' sur la liaison NameList et - comme vous l'avez mentionné, Clemens - il n'avait en effet aucun effet (et fonctionnait parfaitement sans que le mode TwoWay ne soit explicitement défini). Merci d'avoir fait remarquer cela. Ed, je vais essayer la création de viewmodel dans MainWindow. Mon code pour l'instant était plus un contrôle de faisabilité pour moi-même. Cela (et mes connaissances limitées WPF) ont conduit à la structure inhabituelle. Je vais corriger cela. – Woelund

Répondre

0

Si une collection liée n'implémente pas INotifyCollectionChanged (par exemple ObservableCollection<T>), vous obtiendrez un comportement incohérent ou inexistant lors de la tentative de mise à jour de la vue. J'ai remarqué que la liste serait effectivement mise à jour lorsque j'effleurerais la molette de défilement de la souris après avoir basculé l'état de vérification sur vrai. En outre, comme @Clemens a dit, votre liaison ItemsSource devrait être Mode=TwoWay parce que c'est le seul mode qui a du sens. En outre, vous devriez utiliser une collection conforme INotifyCollectionChanged car vous pouvez rencontrer une fuite [1] si vous n'effacez pas la liaison lorsque vous avez terminé. Ce n'est pas un problème dans votre application à guichet unique, mais il vaut la peine de le mentionner maintenant.

Quant au IsChecked entre sommeils basculement, ma supposition est que Thread.Sleep se passe sur le thread d'interface utilisateur (et donc l'attacher vers le haut), de sorte que vous avez 6 secondes de temps mort où PropertyChanged est inutile. J'ai pu résoudre cela avec ce qui suit (en supposant que le bon type de collecte est utilisé):

private async void Toggle() 
{ 
    MyDataContext.IsChecked = true; 
    await Task.Delay(3000); 
    MyDataContext.IsChecked = false; 
    await Task.Delay(3000); 
    MyDataContext.IsChecked = true; 
} 

et un appel à Toggle() à la fin du constructeur App. Malheureusement, l'application a tenté de modifier la collection à partir d'un thread différent qui ne fonctionne pas. Vous pouvez ensuite résoudre que avec quelque chose de ridicule comme:

 ... 
     Toggle(Application.Current.Dispatcher); 
    } 

    private async void Toggle(System.Windows.Threading.Dispatcher d) 
    { 
     d.Invoke(() => { MyDataContext.IsChecked = true; }); 
     await Task.Delay(3000); 
     d.Invoke(() => { MyDataContext.IsChecked = false; }); 
     await Task.Delay(3000); 
     d.Invoke(() => { MyDataContext.IsChecked = true; }); 
    } 

mais qui vient d'appliquer une mauvaise structure de votre programme. EDIT: J'ai oublié de mentionner qu'utiliser async/await a l'avantage supplémentaire de libérer le thread UI; il ne verrouille plus votre fenêtre entière entre les états de vérification.

Je vous suggère de séparer votre code dans les bons fichiers, puis séparer la logique dans les emplacements appropriés. Votre HandleDataContextPropertyChange peut avoir lieu à l'intérieur du setter pour IsChecked, moins l'appel Notify.

[1] https://blog.jetbrains.com/dotnet/2014/09/04/fighting-common-wpf-memory-leaks-with-dotmemory/

+0

Merci, Brandon - fonctionne comme un charme! Merci beaucoup pour l'effort - très apprécié. J'ai maintenant vu aussi que déplacer la molette de la souris vers le bas a mis à jour la liste ... intéressant. Bien sûr, ma principale erreur était la non-utilisation de ObservableCollection. J'ai également essayé de créer un nouveau thread à la fin du constructeur de l'App pour basculer l'indicateur IsChecked, mais j'ai rencontré le même problème que vous mentionnez (collection non modifiable à partir d'un thread différent). Toggling via le répartiteur (j'apprends des choses tous les jours :-)) fonctionne bien. Mon code est un contrôle de faisabilité - améliorera la structure comme vous le suggérez. – Woelund