65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight ComboBox 绑定 null 值错误修复

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (7投票s)

2011年 5月 27日

CPOL

5分钟阅读

viewsIcon

32825

downloadIcon

579

Silverlight ComboBox 在设置空值后丢失绑定的解决方案。

摘要

本文介绍了一个 Silverlight ComboBox 绑定错误的解决方案。您可能会注意到,有时 ComboBox 上的数据绑定会停止工作,它们不会更新源,或者 ComboBox 的值无法设置。这可能是由于 Microsoft Connect 上记录的错误:http://connect.microsoft.com/VisualStudio/feedback/details/597689/silverlight-ComboBox-binding-drops-after-null-binding

 

更新: MS Connect 上的错误列表(上面的链接)已标记为已修复,但没有关于如何获取修复程序的信息,或者它是否是更新或补丁的一部分。我仍然遇到此问题。

问题  

当您在 ComboBox 上使用 SelectedValuePath 属性,并且在运行时绑定到 SelectedValue 的属性被设置为 null 时,就会发生此错误,从而将 SelectedValue 设置为 null。一旦 SelectedValue 设置为 null,绑定就会丢失,您的 ComboBox 将不会更新绑定的属性,当绑定的属性更改时 SelectedValue 也不会更新。事实上,您可以检查 SelectedValue 设置为 null 之前和之后的 ComboBox 绑定,发现之前存在绑定,之后绑定为 null

这显然不是预期的行为。如果您不使用 SelectedValuePath 属性,而是直接设置 SelectedValue,则即使将 SelectedValue 设置为 null,也不会发生这种情况。

在撰写本文时,微软还没有官方的修复程序。

解决方案

此解决方案的源代码以及示例实现已附加到此项目中。

这个问题几乎中断了我最近开发的一个应用程序,我们想出了一个到目前为止一直很有效的解决方案。这个问题直到开发进行得很顺利时才显现出来,所以我们对解决方案的标准是:

  1. 它必须普遍适用于所有 ComboBox。 (我们不能为一个 ComboBox 以一种方式修复,为另一个 ComboBox 以另一种方式修复……维护噩梦!)
  2. 它只需要最少的代码更改即可实现。(我们已经有很多代码了,我们希望尽可能少地重构它们。)

我为这个问题提出的解决方案是,在内存中保留 ComboBox 绑定的缓存,如果 SelectedValue 被设置为 null,则从缓存中取出绑定并将其重新应用于 ComboBox。

缓存实现为一个静态哈希表,ComboBox 由其哈希码标识。

private static Dictionary<int, Binding> _BindingsByHashCode = new Dictionary<int, Binding>();

private static void SaveBinding(int objectHashCode, Binding binding)
{
    if (binding == null)
        return;
    lock (_SyncLock)
    {
        if (_BindingsByHashCode.ContainsKey(objectHashCode))
        {
            _BindingsByHashCode[objectHashCode] = binding;
        }
        else
        {
            _BindingsByHashCode.Add(objectHashCode, binding);
        }
    }
}

private static Binding GetSavedBinding(int objectHashCode)
{
    Binding binding = null;
    lock (_SyncLock)
    {
        if (_BindingsByHashCode.ContainsKey(objectHashCode))
        {
            binding = _BindingsByHashCode[objectHashCode];
        }
    }
    return binding;
}

我们订阅 ComboBox 的 SelectedValueChanged 事件,并在缓存中存储当前绑定,或者如果 SelectedValuenull,则检查我们是否有该控件的缓存绑定并重新应用它。

private static void comboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    ComboBox comboBox = (ComboBox)sender;

    //if the selected value is being set to null
    if (comboBox.SelectedValue == null)
    {
        //get the saved binding and re-apply it
         Binding binding = GetSavedBinding(comboBox.GetHashCode());
         if (binding != null)
         {
             comboBox.SetBinding(ComboBox.SelectedValueProperty, binding);
         }
    }
    else
    {
        //if the binding is not null, save it to the cache
        SaveBinding(comboBox);
    }
}

最棘手的部分是找到页面上的所有 ComboBox,而无需手动订阅它们 (ugh!)。为此,我们递归遍历页面的 Visual UI Tree 并返回所有 ComboBox 的列表。

private static void GetAllComboBoxes(UIElement element, List<ComboBox> comboBoxes)
{
    if (element == null)
        return;

    int childCount = VisualTreeHelper.GetChildrenCount(element);
    for (int i = 0; i < childCount; i++)
    {
        UIElement child = (UIElement)VisualTreeHelper.GetChild(element,i);
        if (child is ComboBox)
        {
            comboBoxes.Add((ComboBox)child);
        }
        else
        {
            GetAllComboBoxes(child, comboBoxes);
        }
     }
}

由于许多 ComboBox 是动态加载的或存在于数据网格单元格中,我们并不总是知道它们何时会被创建,但我们需要它们参与我们的解决方案,无论何时创建。为了确保动态创建的控件被找到,我们在每次页面布局更新时执行我们的树遍历。

public partial class MainPage : UserControl
{
    public MainPage()
    { 
        //subscribe our "fix" code to the Page_LayoutUpdated event
        this.LayoutUpdated += new EventHandler(MainPage_LayoutUpdated);
    }

    void MainPage_LayoutUpdated(object sender, EventArgs e)
    {
        //this fires off a tree traversal search for all comboboxeson the page
        ComboBoxHelper.FixSelectedValueBindings(this);
    }
}

所以,最终为了在我们的应用程序中应用这个解决方案,我们只需要在每个需要此解决方案的页面上添加页面布局更新事件(如上)。

问题

我不喜欢这个解决方案。它能完成工作,但会增加很多不必要的开销。由于需要支持动态创建的控件,我们在每次根页面布局更新时执行树遍历和搜索。我在调试过程中发现,这个事件触发了很多次,基本上每次页面上的控件形状发生变化时都会触发。我希望有一个“控件添加”事件而不是 Layout Updated,因为绝大多数时候,ComboBox 的搜索都是不必要地执行的。

此外,虽然这对我们来说不是问题,但如果您在运行时动态更改 ComboBox 的绑定,您可能会遇到一种情况,即您会用旧的绑定覆盖新的绑定,因为它从缓存中重新应用了。

其他解决方案

MS Connect 错误页面上还提出了另一种解决方案,该方案实现了一个继承的 ComboBox 控件,并修复了绑定。我没有尝试过这个解决方案,尽管我相信它是有效的。我选择不使用这种方法,因为我不想更改我们所有现有的 ComboBox 为新控件。我还认为我们使用的订阅方法侵入性较小,如果需要,更容易撤销。

结论

我相信这是解决此问题的一个“两害相权取其轻”的解决方案。它并不完美,但可以轻松地应用于现有应用程序,使其问题消失。它对我们很有用,我希望其他人也能发现它有用。

我也希望微软能直接修复这个错误,这样我就可以扔掉所有这些代码了。

© . All rights reserved.