Bei meiner Suche nach einer schönen datengesteuerten Silverlight-App stehe ich immer wieder vor einer Art von Race-Condition, die es zu bewältigen gilt. Die neueste ist unten. Jede Hilfe wäre willkommen.Silverlight Combobox Databinding Race Zustand
Sie haben zwei Tabellen auf der Rückseite: eins ist Komponenten und eins ist Hersteller. Jede Komponente hat einen Hersteller. Überhaupt keine ungewöhnliche Fremdschlüssel-Lookup-Beziehung.
I Silverlight, ich greife auf Daten über den WCF-Dienst zu. Ich werde Components_Get (ID) aufrufen, um die Current-Komponente (zum Anzeigen oder Bearbeiten) und einen Aufruf von Manufacters_GetAll() aufzurufen, um die vollständige Liste der Hersteller zu erhalten, die die möglichen Auswahlen für eine ComboBox auffüllen. Dann binde ich das SelectedItem auf der ComboBox an den Hersteller für die aktuelle Komponente und die ItemSource auf der ComboBox an die Liste der möglichen Hersteller. dies wie:
<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>
Das funktionierte gut für die längste Zeit, bis ich einen kleinen Client-seitiges Caching der Komponente klug und tat bekam (die ich auch für die Hersteller einschalten geplant). Wenn ich das Caching für die Komponente aktivierte und einen Cache-Treffer erhielt, waren alle Daten in den Objekten korrekt vorhanden, aber SelectedItem konnte nicht binden. Der Grund dafür ist, dass die Aufrufe in Silverlight asynchron sind und mit dem Vorteil der Zwischenspeicherung die Komponente nicht vor den Herstellern zurückgegeben wird. Wenn das SelectedItem also versucht, den Components.Current.Manufacturer in der ItemsSource-Liste zu finden, ist es nicht vorhanden, da diese Liste noch leer ist, da Manufacters.All noch nicht aus dem WCF-Dienst geladen wurde. Wenn ich das Komponenten-Caching wieder ausschalte, funktioniert es wieder, aber es fühlt sich FALSCH an - als ob ich gerade Glück hätte, dass das Timing funktioniert. Die richtige Korrektur IMHO ist für MS, um das ComboBox/ItemsControl-Steuerelement zu beheben, um zu verstehen, dass dies geschehen wird, da Asynch-Aufrufe die Norm sind. Aber bis dahin muss ich ein Bedürfnis einen Weg, yo es zu beheben ...
Hier einige Optionen, die ich gedacht habe von:
- das Caching beseitigen oder schalten Sie ihn auf der ganzen Linie auf einmal wieder zu maskieren das Problem. Nicht gut, IMHO, denn das wird wieder scheitern. Nicht wirklich bereit, es unter den Teppich kehren.
- Erstellen Sie ein Zwischenobjekt, das die Synchronisierung für mich tun würde (das sollte in ItemsControl selbst durchgeführt werden). Es würde und Item und eine ItemsList und dann Ausgabe und ItemWithItemsList-Eigenschaft akzeptieren, wenn beide ein angekommen sind. Ich würde die ComboBox an die resultierende Ausgabe binden, so dass es nie ein Element vor dem anderen bekommen würde. Mein Problem ist, dass dies wie ein Schmerz aussieht, aber es wird sicherstellen, dass die Race-Bedingung nicht wieder auftritt.
Irgendwelche Gedanken/Kommentare?
FWIW: Ich werde hier meine Lösung zum Wohle anderer posten.
@Joe: Vielen Dank für die Antwort. Ich bin mir bewusst, dass die Benutzeroberfläche nur über den UInthread aktualisiert werden muss. Es ist mein Verständnis und ich glaube, ich habe dies durch den Debugger bestätigt, dass in SL2 der von der Service-Referenz generierte Code das für Sie erledigt. Wenn ich Manufactrers_GetAll_Asynch() aufrufen, erhalte ich das Ergebnis über das Manufacturers_GetAll_Completed-Ereignis. Wenn Sie in den generierten Service-Referenzcode schauen, wird sichergestellt, dass der Ereignishandler * Completed vom UI-Thread aufgerufen wird. Mein Problem ist nicht das, es ist, dass ich zwei verschiedene Aufrufe (eine für die Herstellerliste und eine für die Komponente, die auf eine ID eines Herstellers verweist) mache und dann beide Ergebnisse an eine einzelne ComboBox binden. Sie binden beide an den UI-Thread. Das Problem besteht darin, dass die Auswahl ignoriert wird, wenn die Liste vor der Auswahl nicht angezeigt wird.
Beachten Sie auch, dass diese if you just set the ItemSource and the SelectedItem in the wrong order immer noch ein Problem ist !!!
Ein weiteres Update: Zwar gibt es noch die Combobox Race-Bedingung ist, entdeckte ich etwas anderes interessant. Sie sollten nie ein PropertyChanged-Ereignis aus dem "Getter" für diese Eigenschaft generieren. Beispiel: In meinem SL-Datenobjekt vom Typ ManufacturerData habe ich eine Eigenschaft namens "All". Im Get {} überprüft er, ob es geladen ist, wenn es nicht es so lädt:
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;
}
}
Beachten Sie, dass dieser Code FAILS auf einem „Cache-Treffer“, weil es ein Property Ereignis erzeugen für "All" aus der All {Get {}} - Methode heraus, die normalerweise dazu führen würde, dass das Binding-System alle {get {}} erneut aufruft ... Ich habe dieses Muster der Erstellung von bindefähigen Silverlight-Datenobjekten aus einem Blog von ScottGu kopiert und es hat mir insgesamt gut gedient, aber solche Sachen machen es ziemlich schwierig. Zum Glück ist die Lösung einfach. Hoffe das hilft jemand anderem.
Diese Lösung ist eine gemeinsame Lösung. Lange habe ich nach einer allgemeineren Lösung gesucht, die alle Selector-Steuerelemente abdeckt; nicht nur ComboBoxen, und dies ohne jegliche Kontrolle. Es gibt eine Möglichkeit, dies mit Verhaltensweisen zu tun. Diese vorgeschlagene Lösung funktioniert auch in UWP und wahrscheinlich WPF: http://stackoverflow.com/questions/36003805/uwp-silverlight-combobox-selector-itemssource-selecteditem-race-condition-solu –