Wcf Silverlight Combobox数据绑定竞争条件

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

在我寻求开发一款漂亮的数据驱动silverlight应用程序的过程中,我似乎不断遇到一些需要解决的种族问题。下面是最新的一个。任何帮助都将不胜感激

后端有两个表:一个是组件,一个是制造商。每个组件都有一个制造商。完全不是一种不寻常的外键查找关系

我使用Silverlight,通过WCF服务访问数据。我将调用Components_Get(id)以获取当前组件(查看或编辑),并调用Manufacturers_GetAll()以获取制造商的完整列表以填充组合框的可能选择。然后,我将组合框上的SelectedItem绑定到当前组件的制造商,并将组合框上的ItemSource绑定到可能的制造商列表。像这样:

<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控件,以了解在异步调用成为规范的情况下,这将发生。但在那之前,我需要一个方法来修复它

以下是我想到的一些选择:

  • 消除缓存或全面启用缓存,以再次掩盖问题。不太好,因为这会再次失败。我真的不愿意把它藏起来
  • 创建一个将为我执行同步的中间对象(这应该在ItemsControl本身中完成)。它将接受and项和ItemsList,然后在两者都已到达时输出and ItemWithItemsList属性。我会将ComboBox绑定到结果输出,这样它就永远不会先得到一个项目再得到另一个项目。我的问题是,这似乎是一种痛苦,但它将确保比赛条件不会再次发生
  • 有什么想法/意见吗

    FWIW:为了其他人的利益,我会在这里发布我的解决方案


    @乔:非常感谢你的回复。我知道只需要从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");
                    }
                }
            }