Nella mia ricerca per sviluppare una graziosa app Silverlight basata sui dati, mi sembra di imbattersi continuamente in una sorta di condizione di gara che deve essere risolta. L'ultimo è qui sotto. Qualsiasi aiuto sarebbe apprezzato.Silverlight Combobox Condizioni di gara per la legatura dei dati
Hai due tabelle sul back-end: una è Componenti e una è Produttori. Ogni componente ha UN produttore. Niente affatto una strana relazione di ricerca di chiavi estranee.
I Silverlight, accesso ai dati tramite il servizio WCF. Effettuerò una chiamata a Components_Get (id) per ottenere il componente Corrente (da visualizzare o modificare) e una chiamata a Manufacturers_GetAll() per ottenere l'elenco completo dei produttori per popolare le possibili selezioni per un ComboBox. Quindi lego il SelectedItem sul ComboBox al produttore per il componente corrente e il ItemSource sul ComboBox all'elenco dei possibili produttori. in questo modo:
<UserControl.Resources>
<data:WebServiceDataManager x:Key="WebService" />
</UserControl.Resources>
<Grid DataContext={Binding Components.Current, mode=OneWay, Source={StaticResource WebService}}>
<ComboBox Grid.Row="2" Grid.Column="2" Style="{StaticResource ComboBoxStyle}" Margin="3"
ItemsSource="{Binding Manufacturers.All, Mode=OneWay, Source={StaticResource WebService}}"
SelectedItem="{Binding Manufacturer, Mode=TwoWay}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}" Style="{StaticResource DefaultTextStyle}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
Questo ha funzionato grande per il tempo più lungo, fino a quando ho avuto intelligente e fatto un po 'di caching lato client del componente (che ho programmato accendere per i produttori pure). Quando ho attivato la memorizzazione nella cache per il Component e ho ottenuto un hit della cache, tutti i dati sarebbero presenti negli oggetti correttamente, ma il SelectedItem non sarebbe riuscito a eseguire il binding. La ragione di ciò è che le chiamate sono asincrone in Silverlight e con il vantaggio della memorizzazione nella cache, il componente non viene restituito prima dei produttori. Quindi, quando l'oggetto SelectedItem tenta di trovare Components.Current.Manufacturer nell'elenco ItemsSource, non è lì, perché questo elenco è ancora vuoto perché Manufacturers.All non è ancora stato caricato dal servizio WCF. Di nuovo, se spengo la cache del componente, funziona di nuovo, ma mi sembra sbagliato - come se fossi solo fortunato che il tempismo stia funzionando. La correzione corretta IMHO è per MS per risolvere il controllo ComboBox/ItemsControl per capire che questo accadrà con le chiamate Asynch che sono la norma. Ma fino ad allora, ho bisogno di un bisogno di un modo yo fissarlo ...
Qui ci sono alcune opzioni che ho pensato:
- eliminare il caching o accenderlo su tutta la linea per mascherare ancora una volta il problema. Non buono IMHO, perché questo fallirà di nuovo. Non proprio disposto a spazzarlo di nuovo sotto il tappeto.
- Creare un oggetto intermedio che esegua la sincronizzazione per me (che dovrebbe essere eseguito in ItemsControl stesso). Accetterebbe e Item e un ItemsList e quindi l'output e la proprietà ItemWithItemsList quando entrambi sono arrivati. Vorrei legare il ComboBox all'output risultante in modo che non potesse mai ottenere un oggetto prima dell'altro. Il mio problema è che questo sembra un dolore, ma farà in modo che la condizione di gara non si ripresenti.
Qualche idea/commenti?
FWIW: posterò la mia soluzione qui a beneficio degli altri.
@Joe: Grazie mille per la risposta. Sono consapevole della necessità di aggiornare l'interfaccia utente solo dal thread dell'interfaccia utente. È una mia comprensione e penso di aver confermato questo tramite il debugger che in SL2, il codice generato dalla Service Reference si prende cura di questo per voi. Quando chiamo Manufacturers_GetAll_Asynch(), ottengo il risultato attraverso l'evento Manufacturer_GetAll_Completed. Se si guarda all'interno del codice di riferimento del servizio che viene generato, si assicura che il * gestore di eventi completato sia chiamato dal thread dell'interfaccia utente. Il mio problema non è questo, è che faccio due chiamate diverse (una per l'elenco produttori e una per il componente che fa riferimento a un id di un produttore) e quindi associare entrambi questi risultati a un singolo ComboBox. Entrambi si collegano al thread dell'interfaccia utente, il problema è che se la lista non arriva prima della selezione, la selezione viene ignorata.
Si noti che questo è ancora un problema if you just set the ItemSource and the SelectedItem in the wrong order !!!
Un altro aggiornamento: Mentre c'è ancora la condizione della competizione combo, ho scoperto qualcos'altro di interessante. È necessario MAI generare un evento PropertyChanged dall'interno del "getter" per quella proprietà. Esempio: nel mio oggetto dati SL di tipo ManufacturerData, ho una proprietà chiamata "All". Nella Get {} controlla per vedere se è stato caricato, se non lo carica in questo modo:
public class ManufacturersData : DataServiceAccessbase
{
public ObservableCollection<Web.Manufacturer> All
{
get
{
if (!AllLoaded)
LoadAllManufacturersAsync();
return mAll;
}
private set
{
mAll = value;
OnPropertyChanged("All");
}
}
private void LoadAllManufacturersAsync()
{
if (!mCurrentlyLoadingAll)
{
mCurrentlyLoadingAll = true;
// check to see if this component is loaded in local Isolated Storage, if not get it from the webservice
ObservableCollection<Web.Manufacturer> all = IsoStorageManager.GetDataTransferObjectFromCache<ObservableCollection<Web.Manufacturer>>(mAllManufacturersIsoStoreFilename);
if (null != all)
{
UpdateAll(all);
mCurrentlyLoadingAll = false;
}
else
{
Web.SystemBuilderClient sbc = GetSystemBuilderClient();
sbc.Manufacturers_GetAllCompleted += new EventHandler<hookitupright.com.silverlight.data.Web.Manufacturers_GetAllCompletedEventArgs>(sbc_Manufacturers_GetAllCompleted);
sbc.Manufacturers_GetAllAsync(); ;
}
}
}
private void UpdateAll(ObservableCollection<Web.Manufacturer> all)
{
All = all;
AllLoaded = true;
}
private void sbc_Manufacturers_GetAllCompleted(object sender, hookitupright.com.silverlight.data.Web.Manufacturers_GetAllCompletedEventArgs e)
{
if (e.Error == null)
{
UpdateAll(e.Result.Records);
IsoStorageManager.CacheDataTransferObject<ObservableCollection<Web.Manufacturer>>(e.Result.Records, mAllManufacturersIsoStoreFilename);
}
else
OnWebServiceError(e.Error);
mCurrentlyLoadingAll = false;
}
}
Si noti che questo codice NON su una "cache hit", perché genererà un evento PropertyChanged per "Tutti" dal metodo All {Get {}} che normalmente causerebbe al Sistema Binding di chiamare All {get {}} di nuovo ... Ho copiato questo modello di creazione di oggetti dati Silverlight associabili da un blog ScottGu postando e mi è servito molto bene, ma roba del genere lo rende piuttosto complicato. Fortunatamente la soluzione è semplice. Spero che questo aiuti qualcun altro.
Questa soluzione è comune. Per molto tempo, ho cercato una soluzione più generica che copra tutti i controlli Selector; non solo ComboBoxes, e lo fa senza ereditare da alcun controllo. C'è un modo per farlo con i comportamenti. Questa soluzione proposta funziona anche in UWP e probabilmente in WPF: http://stackoverflow.com/questions/36003805/uwp-silverlight-combobox-selector-itemssource-selecteditem-race-condition-solu –