当前位置:  开发笔记 > 开发工具 > 正文

Silverlight Combobox数据绑定竞争条件

如何解决《SilverlightCombobox数据绑定竞争条件》经验,为你挑选了1个好方法。

在我开发一个漂亮的数据驱动的Silverlight应用程序的过程中,我似乎不断遇到某种需要解决的竞争条件.最新的一个在下面.任何帮助,将不胜感激.

后端有两个表:一个是组件,一个是制造商.每个组件都有一个制造商.根本不是一个不寻常的外键查找关系.

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


    


    
        
            
                
                    
                
            
        
    

这是最好的工作时间,直到我变得聪明并做了一点客户端缓存组件(我计划为制造商打开).当我打开组件的缓存并获得缓存命中时,所有数据都将正确存在于对象中,但SelectedItem将无法绑定.原因是,在Silverlight中调用是异步的,并且在缓存的帮助下,在制造商之前不会返回Component.因此,当SelectedItem尝试在ItemsSource列表中找到Components.Current.Manufacturer时,它不存在,因为此列表仍然为空,因为尚未从WCF服务加载Manufacturers.All.同样,如果我关闭组件缓存,它会再次运行,但感觉不对 - 就像我很幸运时机正在运转.正确的修复恕我直言,MS用于修复ComboBox/ItemsControl控件,以了解这将发生Asynch调用是常态.但在那之前,我需要一种方法来解决它...

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

    消除缓存或全面打开它以再次掩盖问题.不好恕我直言,因为这将再次失败.不太愿意把它扫回地毯下.

    创建一个中间对象,为我做同步(应该在ItemsControl本身完成).当两者都到达时,它将接受和Item和ItemsList,然后输出和ItemWithItemsList属性.我会将ComboBox绑定到结果输出,以便它永远不会在另一个之前得到一个项目.我的问题是,这似乎是一种痛苦,但它会确保竞争条件不再发生.

任何想法/评论?

FWIW:我会在这里发布我的解决方案,以造福他人.

@Joe:非常感谢你的回复.我知道只需要从UI线程更新UI.这是我的理解,我想我已经通过调试器确认了这一点,在SL2中,服务参考生成的代码为您解决了这个问题.即当我调用Manufacturers_GetAll_Asynch()时,我通过Manufacturers_GetAll_Completed事件获得结果.如果查看生成的服务引用代码,它可确保从UI线程调用*Completed事件处理程序.我的问题不是这个,而是我做了两个不同的调用(一个用于制造商列表,一个用于引用制造商id的组件),然后将这两个结果绑定到一个ComboBox.它们都绑定在UI线程上,问题是如果列表在选择之前没有到达那里,

另请注意,如果您只是以错误的顺序设置ItemSource和SelectedItem,这仍然是一个问题!

另一个更新:虽然仍然存在组合框竞争条件,但我发现了一些有趣的东西.您永远不应该从该属性的"getter"中生成PropertyChanged事件.示例:在我的类型为ManufacturerData的SL数据对象中,我有一个名为"All"的属性.在Get {}中,它会检查它是否已被加载,如果没有,它会加载它:

public class ManufacturersData : DataServiceAccessbase
{
    public ObservableCollection 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 all = IsoStorageManager.GetDataTransferObjectFromCache>(mAllManufacturersIsoStoreFilename);
            if (null != all)
            {
                UpdateAll(all);
                mCurrentlyLoadingAll = false;
            }
            else
            {
                Web.SystemBuilderClient sbc = GetSystemBuilderClient();
                sbc.Manufacturers_GetAllCompleted += new EventHandler(sbc_Manufacturers_GetAllCompleted);
                sbc.Manufacturers_GetAllAsync(); ;
            }
        }
    }
    private void UpdateAll(ObservableCollection 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>(e.Result.Records, mAllManufacturersIsoStoreFilename);
        }
        else
            OnWebServiceError(e.Error);
        mCurrentlyLoadingAll = false;
    }

}

请注意,此代码在"缓存命中"时失败,因为它将在All {Get {}}方法中为"All"生成PropertyChanged事件,这通常会导致绑定系统再次调用All {get {}}. .我从ScottGu博客发布的方式复制了这种创建可绑定silverlight数据对象的模式,它总体上对我很有帮助,但是这样的东西使得它非常棘手.幸运的是,修复很简单.希望这有助于其他人.



1> caryden..:

好的我找到了答案(使用很多Reflector来弄清楚ComboBox是如何工作的).

在设置SelectedItem之后设置ItemSource时存在问题.当发生这种情况时,Combobx将其视为选择的完全重置并清除SelectedItem/SelectedIndex.你可以在System.Windows.Controls.Primitives.Selector(ComboBox的基类)中看到这个:

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();
}

注意最后一种情况 - 重置...当你加载一个新的ItemSource时,你最终会在这里被任何SelectedItem/SelectedIndex吹走?!?!

那么解决方案最终非常简单.我只是将错误的ComboBox子类化,并为此方法提供和覆盖如下.虽然我确实需要添加:

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;
}

所有这一切都是(a)保存旧的SelectedItem然后(b)检查是否在ItemsChanged之后,如果我们没有做出选择并且旧的SelectedItem存在于新列表中......那么......选择它!

推荐阅读
LEEstarmmmmm
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有