En mi afán por desarrollar una bonita aplicación Silverlight basada en datos, parece que continuamente me encuentro con algún tipo de condición de carrera que necesita ser solucionada. El último está abajo. Cualquier ayuda sería apreciada.Silverlight Combobox Databinding race condition
Tiene dos tablas en la parte posterior: una es Componentes y una son Fabricantes. Cada componente tiene UN fabricante. En absoluto, una relación de búsqueda de claves extranjeras inusual.
I Silverlight, acceso a los datos a través del servicio WCF. Haré una llamada a Components_Get (id) para obtener el componente actual (para ver o editar) y una llamada a Manufacturers_GetAll() para obtener la lista completa de fabricantes para completar las posibles selecciones para un ComboBox. A continuación, enlace el elemento seleccionado en el ComboBox con el fabricante del componente actual y el ItemSource del ComboBox con la lista de posibles fabricantes. de esta manera:
<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>
Esto funcionó muy bien para el tiempo más largo, hasta que llegué inteligente y hice un poco de almacenamiento en caché del lado del cliente del Componente (que había planeado encender para los fabricantes también). Cuando encendí el caché para el Componente y recibí un golpe de caché, todos los datos estarían allí en los objetos correctamente, pero SelectedItem no se uniría. La razón de esto es que las llamadas son asíncronas en Silverlight y, con la ventaja del almacenamiento en caché, el componente no se devuelve antes de los fabricantes. Por lo tanto, cuando SelectedItem intenta encontrar los Components.Current.Manufacturer en la lista ItemsSource, no está allí, porque esta lista todavía está vacía porque Manufacturers.All aún no se ha cargado desde el servicio WCF. Nuevamente, si apago el caché de Componente, funciona nuevamente, pero se siente EQUIVOCADO, como que estoy teniendo suerte de que el tiempo esté funcionando. La solución correcta en mi humilde opinión es que MS arregle el control ComboBox/ItemsControl para comprender que esto sucederá con las llamadas Asynch siendo la norma. Pero hasta entonces, necesito una necesidad de una manera yo lo fijan ...
Estas son algunas de las opciones que he pensado:
- eliminar la caché o convertirlo en todos los ámbitos para enmascarar una vez más el problema. No es bueno en mi humilde opinión, porque esto fracasará nuevamente. No estoy realmente dispuesto a barrerlo bajo la alfombra.
- Crear un objeto intermediario que haría la sincronización para mí (esto debería hacerse en ItemsControl). Aceptaría una propiedad Item y una lista de Items y luego una salida y ItemWithItemsList cuando ambos hayan llegado. Enlazaría el ComboBox al resultado resultante para que nunca llegue un elemento antes que el otro. Mi problema es que esto parece un dolor, pero asegurará que la condición de carrera no vuelva a ocurrir.
Any thougnts/Comments?
FWIW: Voy a publicar mi solución aquí para el beneficio de los demás.
@Joe: Muchísimas gracias por la respuesta. Soy consciente de la necesidad de actualizar la interfaz de usuario solo desde el hilo de la interfaz de usuario. Tengo entendido y creo que he confirmado esto a través del depurador que en SL2, que el código generado por la referencia de servicio se ocupa de esto por usted. es decir, cuando llamo a Manufacturers_GetAll_Asynch(), obtengo el resultado a través del evento Manufacturers_GetAll_Completed. Si mira dentro del código de referencia de servicio que se genera, se asegura de que el controlador de evento * Completado se invoque desde el hilo de la interfaz de usuario. Mi problema no es esto, es que realizo dos llamadas diferentes (una para la lista de fabricantes y otra para el componente que hace referencia a una identificación de un fabricante) y luego unir ambos resultados a un solo ComboBox. Ambos se unen en el hilo de la interfaz de usuario, el problema es que si la lista no llega antes de la selección, la selección se ignora.
También tenga en cuenta que esto sigue siendo un problema if you just set the ItemSource and the SelectedItem in the wrong order !!!
Otra actualización: Si bien aún existe la condición de carrera de la combobox, descubrí algo más interesante. Debe NUNCA generar un evento PropertyChanged dentro del "captador" para esa propiedad. Ejemplo: en mi objeto de datos SL de tipo ManufacturerData, tengo una propiedad llamada "Todos". En el Get {} comprueba si se ha cargado, si no se carga de esta manera:
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;
}
}
Tenga en cuenta que este código FALLA en un "acierto de caché", ya que generará un evento PropertyChanged para "Todos" desde el método Todo {Obtener {}} que normalmente haría que el Sistema de enlace llame a Todos {get {}} otra vez ... Copié este patrón de creación de objetos de datos silverlight vinculables desde un blog ScottGuard y me ha servido bien en general, pero cosas como esta lo hacen bastante complicado. Por suerte, la solución es simple. Espero que esto ayude a alguien más.
Esta solución es común. Durante mucho tiempo, he estado buscando una solución más genérica que cubra todos los controles Selector; no solo ComboBoxes, y lo hace sin heredar de ningún control. Hay una manera de hacer esto con comportamientos. Esta solución propuesta también funciona en UWP, y probablemente WPF: http://stackoverflow.com/questions/36003805/uwp-silverlight-combobox-selector-itemssource-selecteditem-race-condition-solu –