gpt4 book ai didi

wcf - Silverlight Combobox 数据绑定(bind)竞争条件

转载 作者:行者123 更新时间:2023-12-04 14:07:59 26 4
gpt4 key购买 nike

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

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

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

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

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

以下是我想到的一些选项:
  • 消除缓存或全面打开它以再次掩盖问题。不好恕我直言,因为这将再次失败。不太愿意把它扫回地毯下。
  • 创建一个中间对象来为我进行同步(应该在 ItemsControl 本身中完成)。它会接受 Item 和 ItemsList ,然后在两者都到达时输出 ItemWithItemsList 属性。我会将 ComboBox 绑定(bind)到结果输出,以便它永远不会在另一个之前获得一个项目。我的问题是这看起来很痛苦,但它可以确保竞争条件不会再次发生。

  • 任何想法/评论?

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

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

    另请注意,这仍然是一个问题 if you just set the ItemSource and the SelectedItem in the wrong order !!!

    另一个更新:
    虽然仍然存在组合框竞争条件,但我发现了其他有趣的东西。您应该 从不 从该属性的“getter”中生成一个 PropertyChanged 事件。示例:在我的 ManufacturerData 类型的 SL 数据对象中,我有一个名为“All”的属性。在 Get{} 中,它检查是否已加载,如果未加载,则按如下方式加载:
    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;
    }

    }

    注意这个代码 失败 在“缓存命中”上,因为它会从 All { Get {}} 方法中为“All”生成一个 PropertyChanged 事件,这通常会导致绑定(bind)系统再次调用 All {get{}}...我复制了这个模式从 ScottGu 的博客帖子中创建可绑定(bind)的 Silverlight 数据对象的方法,它总体上对我很有帮助,但像这样的东西使它变得非常棘手。幸运的是,修复很简单。希望这对其他人有帮助。

    最佳答案

    好的,我找到了答案(使用大量 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 存在于新列表中......好吧......选择它!

    关于wcf - Silverlight Combobox 数据绑定(bind)竞争条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/596047/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com