Silverlight Combobox-Binding race-condition
In meinem Bestreben zu entwickeln, eine ziemlich datengesteuerten silverlight-app, ich scheine immer wieder an eine Art race-condition, die umgangen werden muss. Die neueste ist unten. Jede Hilfe würde geschätzt werden.
Haben Sie zwei Tabellen auf die back-end: ein is-Komponenten und einer ist-Herstellern. Jede Komponente hat EINEN Hersteller. Gar nicht ungewöhnlich, foreign key lookup-Beziehung.
Ich Silverlight habe ich Zugriff auf die Daten via WCF service. Ich werde einen Anruf tätigen, um Components_Get(id), um die Aktuelle Komponente (anzeigen oder Bearbeiten) und ein Aufruf Manufacturers_GetAll (), um die komplette Liste der Hersteller, die zum Auffüllen der möglichen Auswahl für ein Kombinationsfeld. Ich habe dann Binden Sie die SelectedItem auf die ComboBox-Komponente auf die Hersteller, die für die Aktuelle Komponente und die ItemSource auf das ComboBox-Steuerelement die Liste der möglichen Hersteller. wie diese:
<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>
Dieser funktioniert großartig für die längste Zeit, bis ich clever und habe ein kleines client-side caching der Komponente (die ich geplante Wende für die Hersteller wie auch). Als ich mich umdrehte, Zwischenspeicherung, die für die Komponente ein, und ich bekam einen cache-Treffer, die alle von den Daten wäre es in die Objekte korrekt, aber die SelectedItem-fehlschlagen würde, zu Binden. Der Grund dafür ist, dass die Aufrufe Asynchron sind in Silverlight und mit dem Vorteil der Zwischenspeicherung, die Komponente wird nicht zurückgegeben wird, bevor der Hersteller. Also, wenn Sie die SelectedItem-versucht zu finden, die Komponenten.Aktuelle.Hersteller in der ItemsSource-Liste-es gibt Sie nicht, weil diese Liste ist noch leer, denn die Hersteller.Alle noch nicht geladenen von den WCF-Dienst noch. Wieder, wenn ich schalten Sie die Komponente-caching, funktioniert es wieder, aber es fühlt sich FALSCH an - wie bin ich nur immer Glück, dass das timing funktioniert. Der richtige fix IMHO ist für MS zu beheben ComboBox/ItemsControl-Steuerelement zu verstehen, dass dies geschehen WIRD, mit Asynch anrufen die norm. Aber bis dahin brauche ich einen brauchen einen Weg yo es beheben...
Hier sind einige Möglichkeiten, die ich gedacht haben:
- Beseitigen Sie die caching-oder schalten Sie ihn über das board noch einmal-Maske das problem. Nicht Gut, IMHO, denn es wird wieder scheitern. Nicht wirklich bereit zu kehren es unter den Teppich.
- Erstellen Sie ein Zwischenobjekt, das die Synchronisierung für mich (das sollte getan werden, im ItemsControl selbst). Sie akzeptieren würden und Item und ein ItemsList und dann auf Ausgang und ItemWithItemsList Eigenschaft, wenn beide eine angekommen. Ich würde Binden Sie die ComboBox-Komponente auf die resultierende Ausgabe, so dass es nie ein Element vor dem anderen. Mein problem ist, dass dies scheint wie ein Schmerz, aber es wird sicherstellen, dass die race-condition nicht wieder auftreten.
Jede thougnts/Kommentare?
FWIW: ich poste meine Lösung hier für das wohl anderer.
@Joe: vielen Dank für die Antwort. Ich bin der Notwendigkeit bewusst, für die Aktualisierung der Benutzeroberfläche nur vom UI-thread. Es ist mein Verständnis, und ich glaube, ich habe bestätigt dies durch den debugger, der in SL2, der code generiert, durch den die Service-Referenz übernimmt dies für Sie. d.h. wenn ich Anrufe, Manufacturers_GetAll_Asynch(), bekomme ich das Ergebnis durch die Manufacturers_GetAll_Completed Veranstaltung. Wenn Sie sich die Service-Referenz-code, der generiert wird, es sorgt dafür, dass die *Completed-Ereignis-handler wird aufgerufen, aus dem UI-thread. Mein problem ist nicht dieses, es ist, dass ich zwei verschiedene Anrufe (eines für die Hersteller-Liste und eine für die Komponente mit Verweis auf eine id eines Herstellers) und Binden Sie diese beiden Ergebnisse zu einem einzigen ComboBox. Beide Binden an den UI-thread, das problem ist, dass wenn die Liste nicht bekommen, es vor der Auswahl, wird die Auswahl ignoriert.
Beachten Sie auch, dass dies immer noch ein problem wenn Sie nur die ItemSource und das SelectedItem in der falschen Reihenfolge!!!
Weiteres Update:
Es gibt zwar immer noch die combobox-race-Bedingung, entdeckte ich etwas anderes Interessantes. Sollten Sie NIE genrate ein PropertyChanged-Ereignis in der "getter" für diese Eigenschaft. Beispiel: in meinem SL-Daten-Objekt von Typ ManufacturerData, habe ich eine Eigenschaft namens "Alle". In der Get{} es wird überprüft, um zu sehen, wenn es geladen wurde, wenn nicht lädt es so:
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 FEHL auf einem "cache-hit", weil es erzeugt ein PropertyChanged-Ereignis für "Alle" innerhalb der Alle { Get {}} Methode, die normalerweise dazu führen würde das Bindungs-System zu nennen, Alle {get{}} wieder...ich kopiert dieses Muster zu erstellen bindbare silverlight-Daten-Objekte aus einem ScottGu blog-posting Weg zurück und es war mir insgesamt gut, aber Zeug wie das macht es ziemlich knifflig. Zum Glück ist die Lösung ist einfach. Hoffe, dies hilft jemand anderes.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ok, ich habe die Antwort gefunden (mit einer Menge Reflektor, um herauszufinden, wie die ComboBox funktioniert).
Das problem besteht, wenn die ItemSource ist nach dem SelectedItem eingestellt ist. Wenn dies geschieht, die Combobx sieht es wie ein komplettes Rücksetzen des Auswahl-und löscht die SelectedItem - /SelectedIndex. Sie sehen dies hier in die System.Windows.Controls.Primitiven.Selektor (die Basisklasse für die ComboBox):
Hinweis: der Letzte Fall - die reset...Beim laden einer neuen ItemSource Sie am Ende hier und jeden SelectedItem/SelectedIndex ruft weggeblasen?!?!
Nun, die Lösung war ziemlich einfach am Ende. ich habe gerade Unterklassen der fehlgeleiteten ComboBox zur Verfügung gestellt und überschreiben Sie diese Methode wie folgt. Zwar wollte ich hinzufügen :
Alle, die dies tut, ist (a) speichern aus der alten SelectedItem und dann (b) prüfen Sie, dass, wenn nach der ItemsChanged, wenn wir haben keine Auswahl vorgenommen, und die alten SelectedItem existiert in der neuen Liste...naja...Es Ausgewählt!
War ich empört, als ich das erste lief in dieses problem, aber ich dachte mir, es musste ein Weg, um es. Mein bester Versuch bisher ist detailliert in der post.
http://blogs.msdn.com/b/kylemc/archive/2010/06/18/combobox-sample-for-ria-services.aspx
War ich ziemlich glücklich, wie es verengt die syntax, um etwas wie das folgende.
Kyle
Ich kämpfte mich durch dieses gleiche Problem beim erstellen von cascading comboboxes, und stolperte über einen blog-post von jemandem, der eine einfache, aber überraschend fix. Rufen Sie UpdateLayout() nach der Einstellung .ItemsSource aber vor der Einstellung der SelectedItem. Diese erzwingen, müssen Sie den code zu blockieren, bis das binding abgeschlossen ist. Ich bin mir nicht genau sicher, warum es behebt, aber ich habe nicht erlebt das race-Bedingung wieder da...
Quelle dieser info: http://compiledexperience.com/Blog/post/Gotcha-when-databinding-a-ComboBox-in-Silverlight.aspx
Es ist nicht klar aus deinem post, ob Sie sich bewusst sind, dass müssen Sie ändern, UI-Elemente auf dem UI-thread - oder werden Sie Probleme haben. Hier ist ein kurzes Beispiel erstellt einen hintergrund-thread, das ändert ein Textfeld mit der aktuellen Zeit.
Den Schlüssel MyTextBox.Dispather.BeginInvoke-in-Seite.xaml.cs.
Seite.xaml:
Seite.xaml.cs:
So, wenn Sie wollen, um die Dinge asynchron, Sie haben die Kontrolle.Dispatcher.BeginInvoke zu Benachrichtigen, UI element, dass Sie einige neue Daten.
Eher als das erneute binden die ItemsSource-jedes mal, es wäre einfacher für Sie es zu binden an eine ObservableCollection<> und dann rufen Sie Clear ()) und Add(...) alle Elemente. Diese Art der Bindung nicht zurückgesetzt.
Andere gotcha ist, dass das ausgewählte Element MUSS eine Instanz der Objekte in der Liste. Ich habe einen Fehler gemacht als ich dachte, die abgefragt Liste für das Standard-Element wurde behoben, wurde aber neu generiert bei jedem Aufruf. Somit ist die aktuelle war anders, hätte es einen DisplayPath Eigenschaft, dass war die gleiche wie Sie ein Element der Liste.
Könnten Sie noch Holen Sie sich die aktuellen Artikel-ID (oder was auch immer eindeutig definiert), rebind die Kontrolle und dann finden Sie in der gebundenen Liste das Element mit der gleichen ID und binden Sie dieses Element als das aktuelle.
Falls Sie hier ankommen, da haben Sie eine Combobox-Auswahl-problem, Bedeutung, nichts geschieht, wenn Sie auf Ihren Artikel in der Liste. Beachten Sie, dass die folgenden Tipps könnten auch Ihnen helfen:
1/stellen Sie sicher, dass Sie nicht Benachrichtigen Sie etwas in den Fall, Sie wählen Sie ein Element
2/stellen Sie sicher, dass das Element, das Sie auswählen, ist immer noch in der zugrunde liegenden Datenquelle in Fall, dass Sie es löschen, durch Unfall
Ich machte beide Fehler 😉