Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Silverlight Combobox Binding Null Value Bug Fix

0.00/5 (No votes)
29 Jun 2011 1  
A solution for the Silverlight combobox dropping its bindings after setting a null value.

Summary

This article lays out a solution to a bug with the Silverlight ComboBox bindings. You may notice that occasionally your data bindings on a ComboBox stop working and they do not update their source, or the ComboBox value can't be set. This may be due to the bug documented on MS Connect here: http://connect.microsoft.com/VisualStudio/feedback/details/597689/silverlight-ComboBox-binding-drops-after-null-binding

 

UPDATE:  The bug listing on MS Connect (the link above) is marked as fixed, but there is no information on how to obtain the fix or how if it is part of an update or patch.   I am still experiencing the issue.

Problem  

This bug occurs when you are using the SelectedValuePath property on your ComboBox and at some point during runtime the property that the SelectedValue is bound to is set to null, thus the SelectedValue is set to null. Once the SelectedValue is set to null, the binding is dropped and your ComboBox will not update the bound property and the SelectedValue will not be updated when the bound property is changed. In fact, you can inspect the bindings of the ComboBox before and after the SelectedValue is set to null and see that before there is a binding present, and then after the bindings are null.

This is clearly not expected behavior. If you do not use the SelectedValuePath property but instead set the SelectedValue directly, this will not occur, even when you set the SelectedValue to null.

At the time of this writing, there is no official fix from Microsoft.

Solution

The source code for this solution with a sample implementation is attached to this project.

This problem was nearly a show stopper for an application I worked on recently and we came up with a solution that has served us well so far. This problem did not become evident until development was well under way, so the criteria that we had for a solution was:

  1. It had to work universally for all the ComboBoxes. (We could not fix it one way for one ComboBox and another way for a different one .. maintenance nightmare!)
  2. It had to require minimal code changes to implement. (We had a lot of code in place, and we wanted to refactor as little of it as possible.)

The solution that I have come up with for this problem is to keep a cache of the ComboBox bindings in memory and if the SelectedValue is ever set to null, pull the binding out of the cache and re-apply the binding to the ComboBox.

The cache is implemented as a static hash table with ComboBoxes identified by their hashcode.

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

We subscribe to the SelectedValueChanged event of the ComboBox and either store the current binding in the cache, or if the SelectedValue is null, check if we have a cached binding for this control and re-apply it.

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

The trickiest part was to find all of the ComboBoxes on the page without having to manually subscribe them (ugh!). To do this, we recursively traverse the Visual UI Tree of the page and return a list of all the ComboBoxes.

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

Because a lot of ComboBoxes are loaded dynamically or exist in data grid cells, we do not always know when they will be created, but we need them to participate in our solution no matter when they are created. In order to ensure that dynamically created controls were found, we execute our tree traversal every time the page layout is updated.

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

So, ultimately in order to apply this solution across our application, we only have to add the page layout updated event (like above) to every page that we need this solution on.

Issues

I do not love this solution. It gets the job done, but it adds a lot of unnecessary operations. Because of the need to support dynamically created controls, we perform the tree traversal and search every time the root page layout is updated. I found out during debugging that this event is fired a lot, basically every time a control on the page changes shape. I wish I had some sort of "control added" event to use instead of Layout Updated, because the vast majority of the time, the ComboBox search is performed unnecessarily.

Also, although this has not been an issue for us, if you dynamically change the ComboBox's bindings at runtime, it is possible that you could end up in a situation where you overwrite a new binding with an old one because it is re-applied from the cache.

Other Solutions

Another solution was suggested on the MS Connect bug page which implements an inherited ComboBox control with a fix for the bindings. I have not tried this solution although I trust that it works. I chose not to use this method as I did not want to have to change all of our existing ComboBoxes to the new control. I also thought that the subscription method we used was less intrusive and would be easier to back out of if need be.

Conclusion

I believe this is a "best of all evils" solution to this problem. It is not perfect, but it can be applied easily to an existing application to make the problem just go away. It has served us well, I hope someone else finds it useful too.

I also hope that Microsoft just fixes the bug, and I can throw all of this code away.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here