Windows users are familiar with the Blue Screen of Death, or BSOD. ASP.NET developers know all about the Yellow Screen of Death (YSOD, naturally). Silverlight development has its own iconic sign of catastrophic failure: the White Screen of Death. Actually, I’m stretching that a little, because I suppose that based on how you have the styles set up for the page hosting the Silverlight 2 plug-in, it could be the Chartreuse Screen of Death, or the Cornflower Blue Screen of Death.
Whatever, the symptoms are the same: something mysterious trashes the plug-in and it dies, and stops rendering to the piece of browser window real-estate it owns. No exception is thrown, and the fault typically happens after you return control to the framework from some operation that later proves to have been ill-conceived. Finding the problem is therefore a classic exercise in cutting stuff out until it goes away. That’s what I did when I ran into an interesting issue with a ComboBox recently. Take a look at this code:
<UserControl x:Class="SilverTests.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300"> <Grid x:Name="LayoutRoot" Background="White"> <StackPanel Orientation="Horizontal"> <ComboBox x:Name="TestCombo" Width="100" Height="20" /> <Button x:Name="Test" Click="Test_Click" Width="100" Height="20" Content="Test"/> </StackPanel> </Grid> </UserControl>
Just a simple user control with a ComboBox and a Button we can click to make stuff happen. Here is the code-behind for the control:
using System; using System.Windows; using System.Windows.Controls; namespace SilverTests { public partial class Page : UserControl { public Page() { InitializeComponent(); Random rand = new Random(); for ( int i = 0; i < 20; i++ ) TestCombo.Items.Add( new ComboBoxItem() { Content = rand.Next().ToString() } ); TestCombo.SelectedIndex = rand.Next(20); } private void Test_Click(object sender, RoutedEventArgs e) { Random rand = new Random(); TestCombo.Items.Clear(); for (int i = 0; i < 20; i++) TestCombo.Items.Add(new ComboBoxItem() { Content = rand.Next().ToString() }); TestCombo.SelectedIndex = rand.Next(20); } } }
As you can see this is very straightforward. The constructor for the control adds twenty random numbers to the ComboBox.Items collection, and then randomly selects one of them. The click event handler for the button does the same thing again, but first it calls TestCombo.Items.Clear() to get rid of the old list.
If you run this code and click the ‘Test’ button repeatedly without opening the list you will see that the contents are being regenerated and the selected item updated, and you can keep on like that presumably forever or until your computer dies. If you open the list, however, the next time you click ‘Test’ you’ll get a WSOD. I was fairly puzzled by this, once I narrowed it down (which wasn’t easy because I was working on much more complicated stuff at the time, and felt sure the answer must there, and not in some simple operations on a control).
After some conversation on the Silverlight.net forums Mark Salsbery figured out that you could prevent the failure by using a combination of InvalidateArrange() and UpdateLayout(), as shown:
using System; using System.Windows; using System.Windows.Controls; namespace SilverTests { public partial class Page : UserControl { public Page() { InitializeComponent(); Random rand = new Random(); for ( int i = 0; i < 20; i++ ) TestCombo.Items.Add( new ComboBoxItem() { Content = rand.Next().ToString() } ); TestCombo.SelectedIndex = rand.Next(20); } private void Test_Click(object sender, RoutedEventArgs e) { Random rand = new Random(); TestCombo.Items.Clear(); TestCombo.InvalidateArrange(); TestCombo.UpdateLayout(); for (int i = 0; i < 20; i++) TestCombo.Items.Add(new ComboBoxItem() { Content = rand.Next().ToString() }); TestCombo.SelectedIndex = rand.Next(20); } } }
Why this works is a mystery to me, and I guess to Mark as well. He did mention that this can still fail if you click it very fast, although I haven’t reproduced that. Amanda Wang from MS offered the suggestion that InvalidateArrange() made Items.Clear() unnecessary, but so far my testing doesn’t bear that out. However, she also mentioned that the update to the ComboBox takes place asynchronously after the UpdateLayout() call is made, so I suspect some issue of timing or thread contention is at the root of the problem. So, no real resolution yet, but at least a reasonable work-around if you bang into this issue. If there are more revelations forthcoming I will post an update.
More that a year later and you’ve just helped me, had the same problem with the treeview control. Thanks man!
Wow, I have been looking for the culprit this whole day. I had the same problem with a treeview control. Many thanks!!!!