Silverlight ComboBox 绑定 null 值错误修复






4.82/5 (7投票s)
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
,也不会发生这种情况。
在撰写本文时,微软还没有官方的修复程序。
解决方案
此解决方案的源代码以及示例实现已附加到此项目中。
这个问题几乎中断了我最近开发的一个应用程序,我们想出了一个到目前为止一直很有效的解决方案。这个问题直到开发进行得很顺利时才显现出来,所以我们对解决方案的标准是:
- 它必须普遍适用于所有 ComboBox。 (我们不能为一个 ComboBox 以一种方式修复,为另一个 ComboBox 以另一种方式修复……维护噩梦!)
- 它只需要最少的代码更改即可实现。(我们已经有很多代码了,我们希望尽可能少地重构它们。)
我为这个问题提出的解决方案是,在内存中保留 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
事件,并在缓存中存储当前绑定,或者如果 SelectedValue
为 null
,则检查我们是否有该控件的缓存绑定并重新应用它。
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 为新控件。我还认为我们使用的订阅方法侵入性较小,如果需要,更容易撤销。
结论
我相信这是解决此问题的一个“两害相权取其轻”的解决方案。它并不完美,但可以轻松地应用于现有应用程序,使其问题消失。它对我们很有用,我希望其他人也能发现它有用。
我也希望微软能直接修复这个错误,这样我就可以扔掉所有这些代码了。