5

App Silverlight 3 con un controllo TabControl associato a ObservableCollection utilizzando un IValueConverter. Iniziale il collegamento funziona (convertitore chiamato) all'avvio dell'app. Le modifiche, Clear() o Add(), alla raccolta associata non si riflettono nel convertitore TabControl ... non chiamato.Silverlight TabControl associato a ObservableCollection <string> non si aggiorna quando la raccolta è cambiata

nota: il ListBox associato riflette le modifiche alla raccolta associata mentre TabControl non lo fa.

Idee?

/jhd


Il XAML vincolante ...

<UserControl.Resources> 
    <local:ViewModel x:Key="TheViewModel"/> 
    <local:TabConverter x:Key="TabConverter" /> 
</UserControl.Resources> 
<StackPanel DataContext="{StaticResource TheViewModel}"> 
    <ListBox ItemsSource="{Binding Classnames}" /> 
    <controls:TabControl x:Name="TheTabControl" 
     ItemsSource="{Binding Classnames, Converter={StaticResource TabConverter}, ConverterParameter=SomeParameter}"/> 
    <Button Click="Button_Click" Content="Change ObservableCollection" /> 
</StackPanel> 

Il ViewModel ...

namespace DatabindingSpike 
{ 
    public class ViewModel 
    { 
     private ObservableCollection<string> _classnames = new ObservableCollection<string>(); 

     public ViewModel() 
     { 
      _classnames.Add("default 1 of 2"); 
      _classnames.Add("default 2 of 2"); 
     } 

     public ObservableCollection<string> Classnames 
     { 
      get { return _classnames; } 
      set { _classnames = value; } 
     } 
    } 
} 

Il convertitore (per completezza) ...

namespace DatabindingSpike 
{ 
    public class TabConverter : IValueConverter 
    { 
     public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
     { 
      var source = value as ObservableCollection<string>; 
      if (source == null) 
       return null; 

      var param = parameter as string; 
      if (string.IsNullOrEmpty(param) || param != "SomeParameter") 
       throw new NotImplementedException("Null or unknow parameter pasased to the tab converter"); 

      var tabItems = new List<TabItem>(); 
      foreach (string classname in source) 
      { 
       var tabItem = new TabItem 
            { 
             Header = classname, 
             Content = new Button {Content = classname} 
            }; 
       tabItems.Add(tabItem); 
      } 

      return tabItems; 
     } 

     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
     { 
      throw new NotImplementedException(); 
     } 
    } 
} 
+0

C'è qualche possibilità la modalità predefinita è OneTime? Proverà a impostare la modalità in modo esplicito. /jhd –

+0

Provato modalità esplicita = OneWay, nessuna gioia. Userò l'evento CollectionChanged e reimposterò TabControl.ItemsSource finché non avrò trovato un modo migliore./jhd –

+0

Ho creato il controllo scheda estesa che funziona correttamente con la classe ObservableCollection. http://vortexwolf.wordpress.com/2011/04/09/silverlight-tabcontrol-with-data-binding/ – vorrtex

risposta

0

Esporre

public ObservableCollection<TabItem> Classnames 
{ 
    get { return _classnames; } 
    set { _classnames = value; } 
} 

Se si esegue il debug ValueConverter vedrete che non viene chiamato tutte le volte che si pensa che è .

+0

Stai dicendo che il ValueConverter non dovrebbe essere chiamato quando la sorgente associata cambia? Questo può essere corretto ma è controintuitivo per me. Dato che ViewModel è attualmente nel progetto Data/spazio dei nomi preferirei non fare riferimento a S.W.C per ottenere TabItem, ma è una soluzione migliore di quella che ho ora. Grazie./jhd –

+0

Hi Graeme, il ValueConverter non è stato chiamato (dopo il binding iniziale) perché non ho implementato INotifyPropertyChanged su ViewModel. Vedi la mia risposta qui sotto. –

0

Il problema potrebbe essere che ValueConverter restituisce uno List<TabItem> anziché uno ObservableCollection<TabItem>. Prova questo cambio di linea e vedi se aiuta.

+0

Una buona presa, purtroppo la modifica non ha alcun effetto [evidente]. Il database non è sottoscritto alla fonte, Nomi di classe nel caso? –

3

Aggiornamento 8/19

La risposta concisa è che devi implementare INotifyPropertyChanged sul modello vista e informare gli ascoltatori quando la proprietà/Collezione è cambiato.

Implementare INotifyPropertyChanged sul ViewModel

* implement the interface INotifyPropertyChanged 
* define the event (public event PropertyChangedEventHandler PropertyChanged) 
* subscribe to the CollectionChanged event (Classnames.CollectionChanged += ...) 
* fire the event for listeners 

migliore,

/jhd


ViewModel aggiornamento al precedente ... ValueConverter ora ha invitato tutte le modifiche al Proprietà/Collezione

public class ViewModel : INotifyPropertyChanged 
{ 
    private readonly ObservableCollection<string> _classnames = new ObservableCollection<string>(); 

    public ViewModel() 
    { 
     Classnames.CollectionChanged += Classnames_CollectionChanged; 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    private void Classnames_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     NotifyPropertyChanged("Classnames"); 
    } 

    private void NotifyPropertyChanged(string info) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) 
     { 
      foreach (PropertyChangedEventHandler d in handler.GetInvocationList()) 
      { 
        d(this, new PropertyChangedEventArgs(info)); 
      } 
     } 
    } 

    public ObservableCollection<string> Classnames 
    { 
     get { return _classnames; } 
    } 
} 

vincolanti XAML ...

<UserControl.Resources> 
    <local:ViewModel x:Key="TheViewModel"/> 
    <local:TabConverter x:Key="TabConverter" /> 
</UserControl.Resources> 

<StackPanel DataContext="{StaticResource TheViewModel}"> 
    <ListBox ItemsSource="{Binding Classnames}" /> 
    <controls:TabControl x:Name="TheTabControl" 
     ItemsSource="{Binding Classnames, Converter={StaticResource TabConverter}, ConverterParameter={StaticResource TheViewModel}}"/> 
    <Button Click="Button_Click" Content="Change Classnames" /> 
</StackPanel> 

Il ValueConverter (sostanzialmente invariata

public class TabConverter : IValueConverter 
    { 
     public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
     { 
      var source = value as ObservableCollection<string>; 
      if (source == null) 
       return null; 

      //also sorted out the binding syntax to pass the ViewModel as a parameter 
      var viewModel = parameter as ViewModel; 
      if (viewModel == null) 
       throw new ArgumentException("ConverterParameter must be ViewModel (e.g. ConverterParameter={StaticResource TheViewModel}"); 

      var tabItems = new List<TabItem>(); 
      foreach (string classname in source) 
      { 
       // real code dynamically loads controls by name 
       var tabItem = new TabItem 
            { 
             Header = "Tab " + classname, 
             Content = new Button {Content = "Content " + classname} 
            }; 
       tabItems.Add(tabItem); 
      } 

      return tabItems; 
     } 

     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
     { 
      throw new NotImplementedException(); 
     } 
    } 
+0

nota: non si desidera inviare l'evento PropertyChanged su ogni evento CollectionChanged nel codice reale ... è necessario generare un evento PropertyChanged dopo aver terminato la manipolazione della raccolta di origine./JHD –

3

Mi rendo conto che questa è una domanda un po 'vecchio, a questo punto, ma non so che qualcuno ha spiegato perché è necessario per eseguire l'operazione INotifyPropertyChanged sulla proprietà associata sul modello della vista

Lo stesso oggetto Items deve essere associato a ObservableCollection per gli eventi di modifica della raccolta per consentire a ItemsControl di rivalutare. Il tuo convertitore restituisce una raccolta distinta di elenchi (o osservabili) ogni volta che viene richiamata piuttosto che mantenere una sola ObservableCollection e aggiungere elementi ad essa. Pertanto, queste raccolte non hanno mai modificato la raccolta degli eventi su di esse ... sono sempre nuove, ogni volta che l'associazione viene rifatta.

Raising PropertyChanged forza il binding a essere rivalutato e riesegue il convertitore, restituendo una raccolta distinta e riflettendo le modifiche.

Ritengo che un approccio migliore potrebbe essere la conversione in ViewModel piuttosto che in un convertitore. Esporre una ObservableCollection di TabItem a cui si collega direttamente e che si modifica in posizione. TabControl dovrebbe quindi visualizzare le modifiche apportate direttamente alla raccolta senza la necessità di aumentare PropertyChanged e rivalutare l'intera associazione.

[Edit - Aggiunto il mio approccio] ViewModel: public class TabSampleViewModel { _tabItems ObservableCollection private = new ObservableCollection();

public TabSampleViewModel() 
    { 
     AddTabItem("Alpba"); 
     AddTabItem("Beta"); 
    } 

    public ObservableCollection<TabItem> TabItems 
    { 
     get 
     { 
      return _tabItems; 
     } 
    } 

    public void AddTabItem(string newTabItemName) 
    { 
     TabItem newTabItem = new TabItem(); 

     newTabItem.Header = newTabItemName; 
     newTabItem.Content = newTabItemName; 

     TabItems.Add(newTabItem); 
    } 
} 

Vista: < controlli: TabControl ItemsSource = "{Binding TabItems}"/>