Wcf Silverlight Combobox数据绑定竞争条件
在我寻求开发一款漂亮的数据驱动silverlight应用程序的过程中,我似乎不断遇到一些需要解决的种族问题。下面是最新的一个。任何帮助都将不胜感激 后端有两个表:一个是组件,一个是制造商。每个组件都有一个制造商。完全不是一种不寻常的外键查找关系 我使用Silverlight,通过WCF服务访问数据。我将调用Components_Get(id)以获取当前组件(查看或编辑),并调用Manufacturers_GetAll()以获取制造商的完整列表以填充组合框的可能选择。然后,我将组合框上的SelectedItem绑定到当前组件的制造商,并将组合框上的ItemSource绑定到可能的制造商列表。像这样:Wcf Silverlight Combobox数据绑定竞争条件,wcf,silverlight,data-binding,silverlight-2.0,Wcf,Silverlight,Data Binding,Silverlight 2.0,在我寻求开发一款漂亮的数据驱动silverlight应用程序的过程中,我似乎不断遇到一些需要解决的种族问题。下面是最新的一个。任何帮助都将不胜感激 后端有两个表:一个是组件,一个是制造商。每个组件都有一个制造商。完全不是一种不寻常的外键查找关系 我使用Silverlight,通过WCF服务访问数据。我将调用Components_Get(id)以获取当前组件(查看或编辑),并调用Manufacturers_GetAll()以获取制造商的完整列表以填充组合框的可能选择。然后,我将组合框上的Selec
<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>
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;
}
}
这在很长一段时间内都非常有效,直到我变得聪明,对组件进行了一点客户端缓存(我也计划为制造商启用)。当我为组件打开缓存并获得缓存命中时,所有数据都将正确地存在于对象中,但SelectedItem将无法绑定。原因是,在Silverlight中调用是异步的,并且由于缓存的好处,组件不会在制造商之前返回。因此,当SelectedItem尝试在ItemsSource列表中查找Components.Current.Manufacturer时,它不在那里,因为此列表仍然为空,因为Manufacturers.All尚未从WCF服务加载。同样,如果我关闭组件缓存,它会再次工作,但感觉不对劲——好像我只是幸运地发现了时间的安排。正确的修复IMHO是让MS修复ComboBox/ItemsControl控件,以了解在异步调用成为规范的情况下,这将发生。但在那之前,我需要一个方法来修复它
以下是我想到的一些选择:
@乔:非常感谢你的回复。我知道只需要从UI线程更新UI。我的理解是,我想我已经通过调试器确认了这一点,即在SL2中,服务引用生成的代码为您解决了这一问题。i、 e.当我调用Manufacturers\u GetAll\u Asynch()时,我通过Manufacturers\u GetAll\u Completed事件获得结果。如果查看生成的服务引用代码,它将确保从UI线程调用*已完成事件处理程序。我的问题不是这个,而是我进行了两个不同的调用(一个用于制造商列表,另一个用于引用制造商id的组件),然后将这两个结果绑定到一个组合框。它们都绑定在UI线程上,问题是如果列表没有在选择之前到达,那么选择将被忽略 还请注意,这仍然是一个问题 另一更新: 虽然仍然存在组合框竞争条件,但我发现了其他有趣的东西。您应该从不从该属性的“getter”中生成PropertyChanged事件。示例:在ManufacturerData类型的SL数据对象中,我有一个名为“All”的属性。在Get{}中,它检查是否已加载,如果未加载,则按如下方式加载:
<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>
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;
}
}
公共类制造商数据:DataServiceAccess数据库
{
公众可观察收集所有
{
得到
{
如果(!全部加载)
LoadAllManufacturersAsync();
返回购物中心;
}
专用设备
{
购物中心=价值;
不动产变更(“全部”);
}
}
私有void LoadAllManufacturersAsync()
{
如果(!mCurrentlyLoadingAll)
{
mCurrentlyLoadingAll=true;
//检查此组件是否加载到本地隔离存储中,如果没有,请从webservice获取它
ObservableCollection all=IsoStorageManager.GetDataTransferObjectFromCache(MallManufacturerSSOStoreFileName);
如果(空!=全部)
{
更新全部;
mCurrentlyLoadingAll=false;
}
其他的
{
Web.SystemBuilderClient sbc=GetSystemBuilderClient();
sbc.Manufacturers_GetAllCompleted+=新事件处理程序(sbc_Manufacturers_GetAllCompleted);
sbc.Manufacturers_GetAllAsync();
}
}
}
私有void UpdateAll(ObservableCollection all)
{
全部=全部;
AllLoaded=true;
}
私有void sbc_Manufacturers_GetAllCompleted(对象发送方,hookitupright.com.silverlight.data.Web.Manufacturers_GetAllCompletedEventArgs e)
{
如果(e.Error==null)
{
更新所有记录(如结果记录);
IsoStorageManager.CacheDataTransferObject(例如,Result.Records、mAllManufacturersIsoStoreFilename);
}
其他的
OnWebServiceError(即错误);
mCurrentlyLoadingAll=false;
}
}
请注意,此代码FA
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
int selectedIndex = this.SelectedIndex;
bool flag = this.IsInit && this._initializingData.IsIndexSet;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (!this.AddedWithSelectionSet(e.NewStartingIndex, e.NewStartingIndex + e.NewItems.Count))
{
if ((e.NewStartingIndex <= selectedIndex) && !flag)
{
this._processingSelectionPropertyChange = true;
this.SelectedIndex += e.NewItems.Count;
this._processingSelectionPropertyChange = false;
}
if (e.NewStartingIndex > this._focusedIndex)
{
return;
}
this.SetFocusedItem(this._focusedIndex + e.NewItems.Count, false);
}
return;
case NotifyCollectionChangedAction.Remove:
if (((e.OldStartingIndex > selectedIndex) || (selectedIndex >= (e.OldStartingIndex + e.OldItems.Count))) && (e.OldStartingIndex < selectedIndex))
{
this._processingSelectionPropertyChange = true;
this.SelectedIndex -= e.OldItems.Count;
this._processingSelectionPropertyChange = false;
}
if ((e.OldStartingIndex <= this._focusedIndex) && (this._focusedIndex < (e.OldStartingIndex + e.OldItems.Count)))
{
this.SetFocusedItem(-1, false);
return;
}
if (e.OldStartingIndex < selectedIndex)
{
this.SetFocusedItem(this._focusedIndex - e.OldItems.Count, false);
}
return;
case NotifyCollectionChangedAction.Replace:
if (!this.AddedWithSelectionSet(e.NewStartingIndex, e.NewStartingIndex + e.NewItems.Count))
{
if ((e.OldStartingIndex <= selectedIndex) && (selectedIndex < (e.OldStartingIndex + e.OldItems.Count)))
{
this.SelectedIndex = -1;
}
if ((e.OldStartingIndex > this._focusedIndex) || (this._focusedIndex >= (e.OldStartingIndex + e.OldItems.Count)))
{
return;
}
this.SetFocusedItem(-1, false);
}
return;
case NotifyCollectionChangedAction.Reset:
if (!this.AddedWithSelectionSet(0, base.Items.Count) && !flag)
{
this.SelectedIndex = -1;
this.SetFocusedItem(-1, false);
}
return;
}
throw new InvalidOperationException();
}
public class FixedComboBox : ComboBox
{
public FixedComboBox()
: base()
{
// This is here to sync the dep properties (OnSelectedItemChanged is private is the base class - thanks M$)
base.SelectionChanged += (s, e) => { FixedSelectedItem = SelectedItem; };
}
// need to add a safe dependency property here to bind to - this will store off the "requested selectedItem"
// this whole this is a kludgy wrapper because the OnSelectedItemChanged is private in the base class
public readonly static DependencyProperty FixedSelectedItemProperty = DependencyProperty.Register("FixedSelectedItem", typeof(object), typeof(FixedComboBox), new PropertyMetadata(null, new PropertyChangedCallback(FixedSelectedItemPropertyChanged)));
private static void FixedSelectedItemPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
FixedComboBox fcb = obj as FixedComboBox;
fcb.mLastSelection = e.NewValue;
fcb.SelectedItem = e.NewValue;
}
public object FixedSelectedItem
{
get { return GetValue(FixedSelectedItemProperty); }
set { SetValue(FixedSelectedItemProperty, value);}
}
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (-1 == SelectedIndex)
{
// if after the base class is called, there is no selection, try
if (null != mLastSelection && Items.Contains(mLastSelection))
SelectedItem = mLastSelection;
}
}
protected object mLastSelection = null;
}
<ComboBox Name="AComboBox"
ItemsSource="{Binding Data, ElementName=ASource}"
SelectedItem="{Binding A, Mode=TwoWay}"
ex:ComboBox.Mode="Async" />
public string SelectedItem
{
get
{
return this.selectedItem;
}
set
{
if (this.selectedItem != value)
{
this.selectedItem = value;
//this.OnPropertyChanged("SelectedItem");
}
}
}