ComboBox SelectedItem non cambia dopo aver eliminato la raccolta osservabile
-
20-09-2019 - |
Domanda
Sto avendo un problema con un ComboBox
che è destinato a un ObservableCollection
e mi chiedevo se qualcuno può puntare a quello che mi manca.
Ho un ComboBox
che è legato ad un semplice ObservableCollection<string>
. Anche io lego il SelectedIndex
in un OneWay
vincolante per alcune proprietà.
Nella mia domanda ho arrivare a un punto in cui voglio chiarire la raccolta e il ripopolamento con diversi dati e l'impostazione del SelectedIndex
ad un nuovo valore. per qualche motivo vincolante il SelectedIndex
non funziona.
Sto attaccando un po 'Repro del problema:
public partial class Window1 : Window, INotifyPropertyChanged
{
private int j;
public event PropertyChangedEventHandler PropertyChanged;
public Window1()
{
InitializeComponent();
DataContext = this;
Tables = new ObservableCollection<string>();
}
public ObservableCollection<string> Tables { get; set; }
private int _TheIndex;
public int TheIndex
{
get { return _TheIndex; }
set
{
_TheIndex = value;
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("TheIndex"));
}
}
}
private void aaaa(object sender, RoutedEventArgs e)
{
j = (j + 1)%10;
Tables.Clear();
for(int i = 0; i < 10 ; i++)
{
Tables.Add(i.ToString());
}
TheIndex = j;
}
}
il codice XAML è:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<StackPanel>
<ComboBox x:Name="TablesCombobox"
ItemsSource="{Binding Tables}"
SelectedIndex="{Binding TheIndex, Mode=OneWay}"/>
<Button Content="asdasd" Click="aaaa"/>
</StackPanel>
</Grid>
</Window>
Soluzione
Il problema è interamente causato dalla linea Tables.Clear()
nel metodo aaaa()
. Poiché Tables
è una collezione osservabile, cancellando tutti i contenuti della raccolta provoca WPF per aggiornare la visualizzazione con un nuovo elenco vuoto. Poi si cerca di selezionare la voce utilizzando SelectedIndex
attualmente attivo, che non esiste (perché l'elenco è ora vuota). Di conseguenza, il motore di associazione è lasciato con un valore che non può essere applicato, e decide di disattivare e staccare la logica vincolante:
System.Windows.Data Warning: Got PropertyChanged event from Window1 for TheIndex
System.Windows.Data Warning: GetValue at level 0 from Window1 using DependencyProperty(TheIndex): '1'
System.Windows.Data Warning: TransferValue - got raw value '1'
System.Windows.Data Warning: TransferValue - using final value '1'
System.Windows.Data Warning: Deactivate
System.Windows.Data Warning: Replace item at level 0 with {NullDataItem}
System.Windows.Data Warning: Detach
Con il tempo si arriva al 'TheIndex = j;' linea, il legame non è più attivo e non vedere il cambiamento TheIndex, il che significa che indice desiderato non è selezionato.
Ci sono un paio di soluzioni per risolvere questo:
- Non soffiare via l'intera collezione ogni volta. senza cancellare la raccolta, la logica vincolante dei dati ha sempre un indice di scegliere, il che significa che non si stacca.
- Usa un
TwoWay
vincolante Questo funziona perché ora il ComboBox partecipa vincolante.; siTables
chiaro, i tentativi di impostare vincolanti, ma non riesce a trovare l'indice in modo il reset ComboBox alla posizione speciale 'alcun indice' di -1, che poi scrive di nuovo alTheIndex
(il doppio senso parte), che è una valida valore in modo vincolante la logica non si stacca. -
Seleziona alcun indice (-1) prima di cancellare la collezione. Se non è selezionato alcun indice (-1) quando
Tables
scompaia e quindi ComboBox non cerca di applicareSelectedItem
, che significa che non 'vede' la raccolta svuotato e ri-riempita, e pertanto, non si stacchi.private void aaaa(object sender, RoutedEventArgs e) { TheIndex = -1; j = (j + 1)%10; Tables.Clear(); for (int i = 0; i < 10; i++) { Tables.Add(i.ToString()); } TheIndex = j; }
Per le prestazioni, architettonico, e le ragioni di chiarezza, mi raccomando l'opzione 1, anche se mi rendo conto che lo scenario reale può essere più complesse e richiedono qualcosa lungo le linee di 3.
Nota a margine:
Individuazione il motivo dietro questioni vincolanti come questo è ragionevolmente facile quando si utilizzano le tracce di legame come quello postato sopra. accenderli per un singolo legame dichiarando lo spazio dei nomi System.Diagnostics e aggiungendo PresentationTraceSources.TraceLevel=High
to il legame che sta causando problemi:
<Window xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase" />
...
<TextBlock Text="{Binding Path=x, diag:PresentationTraceSources.TraceLevel=High}" />
Altri modi di debug attacchi WPF sono qui .
Altri suggerimenti
So che questa è una vecchia questione, ma ho appena sperimentato questo problema io stesso modo hanno scritto un metodo di supporto basata su l'opzione 1 della risposta @Nicholas Armstrong s' e ho pensato condividere, spero che qualcuno lo troverà utile:
public void refreshDropdownOptions(ObservableCollection<object> OldOptions, ObservableCollection<object> NewOptions)
{
MainWindow application = Application.Current.MainWindow as MainWindow;
int highestCount = 0;
if(OldOptions.Count() > NewOptions.Count())
{
highestCount = OldOptions.Count();
}
else
{
highestCount = NewOptions.Count();
}
for (int i = 0; i < highestCount; i++)
{
if(i < OldOptions.Count() && i < NewOptions.Count())
{// If we have not exceeded the count of either list, copy the new value over the old
application.Dispatcher.Invoke((Action)(() => OldOptions[i] = NewOptions[i]));
}
else if (i < OldOptions.Count() && i >= NewOptions.Count())
{// If we have no more new options remove the old option
application.Dispatcher.Invoke((Action)(() => OldOptions.RemoveAt(i)));
highestCount = OldOptions.Count();
i--;
}
else if (i >= OldOptions.Count() && i < NewOptions.Count())
{// if we have no more old options to replace, add the new option to the end of the collection
application.Dispatcher.Invoke((Action)(() => OldOptions.Add(NewOptions[i])));
}
}
}