2008-12-08 5 views
4

Je rencontre un problème avec un ObservableCollection qui obtient de nouveaux éléments mais ne reflète pas ces modifications dans un ListView. J'ai assez de bizarreries dans la façon dont je suis en train de mettre en œuvre cela que j'ai du mal à déterminer quel est le problème.ObservableCollection <T> ne pas mettre à jour l'interface utilisateur

Mon ObservableCollection est mis en œuvre ainsi:

public class MessageList : ObservableCollection<LobbyMessage> 
{ 
    public MessageList(): base() 
    { 
     Add(new LobbyMessage() { Name = "System", Message = "Welcome!" }); 
    } 
} 

je stocke la collection dans une propriété statique (de sorte que son facilement accessible à partir de plusieurs contrôles utilisateur):

static public MessageList LobbyMessages { get; set; } 

En cas OnLoad de mon La principale NavigationWindow j'ai la ligne suivante:

ChatHelper.LobbyMessages = new MessageList(); 

Mon X AML dans le UserControl où le ListView est situé se lit comme:

<ListBox IsSynchronizedWithCurrentItem="True" 
     ItemsSource="{Binding Mode=OneWay}" 
     x:Name="ListBoxChatMessages" 
     d:UseSampleData="True" 
     ItemTemplate="{DynamicResource MessageListTemplate}" 
     IsEnabled="True"> 
     <ListBox.DataContext> 
     <Magrathea_Words_Tools:MessageList/> 
     </ListBox.DataContext> 
    </ListBox> 

Le message initial que j'ai ajouté dans le constructeur apparaît dans l'interface utilisateur très bien.

Maintenant, la façon dont j'ajoute de nouveaux éléments à la collection provient d'un CallBack provenant d'un service WCF. J'ai eu ce code fonctionnant dans une application WinForms et il était nécessaire de marshall le rappel au fil de l'interface utilisateur donc j'ai laissé ce code en place. Voici une version abrégée de la méthode:

Helper.Context = SynchronizationContext.Current; 

#region IServiceMessageCallback Members 

/// <summary> 
/// Callback handler for when the service has a message for 
/// this client 
/// </summary> 
/// <param name="serviceMessage"></param> 
public void OnReceivedServiceMessage(ServiceMessage serviceMessage) 
{ 
    // This is being called from the WCF service on it's own thread so 
    // we have to marshall the call back to this thread. 
    SendOrPostCallback callback = delegate 
    { 
     switch (serviceMessage.MessageType) 
     { 
      case MessageType.ChatMessage: 
       ChatHelper.LobbyMessages.Add(
         new LobbyMessage() 
         { 
          Name = serviceMessage.OriginatingPlayer.Name, 
          Message = serviceMessage.Message 
         }); 
       break; 

      default: 
       break; 
     } 
    }; 

    Helper.Context.Post(callback, null); 
} 

Pendant que je débogage peux voir la collection se mis à jour avec des messages du service, mais l'interface utilisateur ne reflète pas ces ajouts.

Des idées sur ce qui me manque pour que le ListView reflète ces nouveaux éléments de la collection?

Répondre

4

J'ai résolu ce problème.

Ni la propriété statique, ni le contexte des données entrantes n'avaient un rapport avec le problème (ce qui semble évident avec le recul).

Le code XAML qui a été généré à partir de Expression Blend n'était pas à la hauteur de la tâche pour une raison quelconque. Tout ce que j'ai fait pour que cela fonctionne, c'est d'assigner ItemSource à la collection en C#.

ListBoxChatMessages.ItemsSource = ChatHelper.LobbyMessages.Messages; 

Mon XAML est maintenant plus simplifié.

<ListBox IsSynchronizedWithCurrentItem="True" 
     ItemsSource="{Binding Mode=OneWay}" Background="#FF1F1F1F" 
     Margin="223,18.084,15.957,67.787" x:Name="ListBoxChatMessages" 
     ItemTemplate="{DynamicResource MessageListTemplate}" 
     IsEnabled="True"/> 

Je suis un peu confus quant à la raison pour laquelle cela fonctionne. Je lisais les articles MSDN sur la façon de lier des données dans WPF et ils comprenaient plusieurs objets de liaison, référençant des propriétés sur l'objet, etc. Je ne comprends pas pourquoi ils sont allés à tous les problèmes lorsqu'une ligne de code dans le constructeur UserControl fait le tour très bien.

+0

Si je comprends bien, l'intention de MS en WPF était de faire des choses comme mettre des sources contraignantes (comme la propriété ItemsSource) un paramètre de données statiques dans le XAML, plutôt que dans le code. Un développeur plus expérimenté refait mon programme complexe avec presque aucun C#, et seulement un peu plus de xaml. xaml> code. –

0

Vous devez créer votre classe poco dans l'outil ObservableCollection INotifyPropertyChanged.

Exemple:

<viewModels:LocationsViewModel x:Key="viewModel" /> 
. 
. 
.  
<ListView 
    DataContext="{StaticResource viewModel}" 
    ItemsSource="{Binding Locations}" 
    IsItemClickEnabled="True" 
    ItemClick="GroupSection_ItemClick" 
    ContinuumNavigationTransitionInfo.ExitElementContainer="True"> 

    <ListView.ItemTemplate> 
     <DataTemplate> 
      <StackPanel Orientation="Horizontal"> 
       <TextBlock Text="{Binding Name}" Margin="0,0,10,0" Style="{ThemeResource ListViewItemTextBlockStyle}" /> 
       <TextBlock Text="{Binding Latitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="0,0,5,0"/> 
       <TextBlock Text="{Binding Longitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="5,0,0,0" /> 
      </StackPanel> 
     </DataTemplate> 
    </ListView.ItemTemplate> 
</ListView> 

public class LocationViewModel : BaseViewModel 
{ 
    ObservableCollection<Location> _locations = new ObservableCollection<Location>(); 
    public ObservableCollection<Location> Locations 
    { 
     get 
     { 
      return _locations; 
     } 
     set 
     { 
      if (_locations != value) 
      { 
       _locations = value; 
       OnNotifyPropertyChanged(); 
      } 
     } 
    } 
} 

public class Location : BaseViewModel 
{ 
    int _locationId = 0; 
    public int LocationId 
    { 
     get 
     { 
      return _locationId; 
     } 
     set 
     { 
      if (_locationId != value) 
      { 
       _locationId = value; 
       OnNotifyPropertyChanged(); 
      } 
     } 
    } 

    string _name = null; 
    public string Name 
    { 
     get 
     { 
      return _name; 
     } 
     set 
     { 
      if (_name != value) 
      { 
       _name = value; 
       OnNotifyPropertyChanged(); 
      } 
     } 
    } 

    float _latitude = 0; 
    public float Latitude 
    { 
     get 
     { 
      return _latitude; 
     } 
     set 
     { 
      if (_latitude != value) 
      { 
       _latitude = value; 
       OnNotifyPropertyChanged(); 
      } 
     } 
    } 

    float _longitude = 0; 
    public float Longitude 
    { 
     get 
     { 
      return _longitude; 
     } 
     set 
     { 
      if (_longitude != value) 
      { 
       _longitude = value; 
       OnNotifyPropertyChanged(); 
      } 
     } 
    } 
} 

public class BaseViewModel : INotifyPropertyChanged 
{ 
    #region Events 
    public event PropertyChangedEventHandler PropertyChanged; 
    #endregion 

    protected void OnNotifyPropertyChanged([CallerMemberName] string memberName = "") 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(memberName)); 
     } 
    } 
} 
Questions connexes