Silverlight: Controls inside ScrollViewer - horizontal resize layout bug and related workaround

Print Content | More

I was updating my sample on how to simulate a Window environment in Silverlight, after having implemented some fixes to the resize functions and having added the support for scrollbars inside a window, I realized a simple test form with a series of textboxes.

During the horizontal resize operation I encountered a very strange behavior: only the first textbox changed its width according to the new windows size...while all the others retained their previous value; when you start a vertical resize operation all the textboxes were redrawn with the correct width.

To be sure it wasn’t my control template’s fault and to try to reproduce this situation I prepared a simple test page, something like:

   1: <ScrollViewer x:Name="ScrollBox" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
   2:  
   3:     <StackPanel MinWidth="300" x:Name="ScrollContent">
   4:         <TextBlock>TextBoxes</TextBlock>
   5:         <TextBox Margin="0,2,0,2" x:Name="t1" />
   6:         <TextBox Margin="0,2,0,2" x:Name="t2" />
   7:         <my:MyTextBox Margin="0,2,0,2" x:Name="mt1" />
   8:         <my:MyTextBox Margin="0,2,0,2" x:Name="mt2" />
   9:         <TextBlock>PasswordBoxes</TextBlock>
  10:         <PasswordBox />
  11:         <PasswordBox />
  12:         <TextBlock>Buttons</TextBlock>
  13:         <Button Content="BTN1" />
  14:         <Button Content="BTN2" />
  15:     </StackPanel>
  16:  
  17: </ScrollViewer>

Some controls inside a ScrollViewer with both scrollbars set on Auto (you can download the whole code at the end of the article). If you open this page in a small browser window and the you resize it horizontally you can see what I was talking about:

ScrollViewControlLayoutBug1 ScrollViewControlLayoutBug2

As an information it seems that the layout arrange operations aren’t performed for the controls that follow the first one inside the children collection of the StackPanel; to see that I have just derived a new TextBox class and overridden the default behavior of the protected ArrangeOverride() function to report in the debug window some information regarding the size of the controls. If you run the example you can see that we have logs only for the first control inside the StackPanel.

I was able to find a workaround for this and it is to set explicitly the size of the first container inside the ScrollViewer: we ca write some code for the ScrollViewr’s SizeChanged() event in which we set the dimensions of the contained element, here’s the snippet.

   1: void ScrollBox_SizeChanged(object sender, SizeChangedEventArgs e)
   2: {
   3:      ScrollContent.Width = Math.Max(ScrollBox.ViewportWidth - 1.0, 0);
   4: }

I have also added a small offset to avoid another graphic artifact: sometimes during shrink operations a disabled horizontal scrollbar used to appear, making the container a little smaller avoid this problem.

I have to admit I haven’t done the same tests in WPF yet.

Example Solution:

 

 



Control, Silverlight, Window, Layout, Scrollviewer, Resize

3 comments

Related Post

  1. #1 da Silverlight: simulate a Windows desktop application - part 3 (Resizable Window) at Guardians Home - Saturday March 2010 alle 01:15

    [...] Silverlight: Controls inside ScrollViewer - horizontal resize layout bug and related workaround [...]

  2. #2 da robolize - Saturday March 2010 alle 01:15

    Thanks! This is exactly what I was looking for. I used your code to create an attach property for ease of use (with MVVM pattern in mind). Here it is. public class ScrollViewerFix { public static readonly DependencyProperty ResizeBugProperty = DependencyProperty.RegisterAttached("ResizeBug", typeof(bool), typeof(ScrollViewerFix), new PropertyMetadata(OnResizeBugChanged)); private static void OnResizeBugChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var sv = d as ScrollViewer; if(sv == null) { return; } if((bool)e.OldValue) { sv.SizeChanged -= ScrollViewer_SizeChanged; } if((bool)e.NewValue) { sv.SizeChanged += ScrollViewer_SizeChanged; } } private static void ScrollViewer_SizeChanged(object sender, SizeChangedEventArgs e) { var sv = (ScrollViewer)sender; var cont = sv.Content as FrameworkElement; if(cont == null) { return; } cont.Width = Math.Max(sv.ViewportWidth - 1.0, 0); } public static bool GetResizeBug(DependencyObject d) { return (bool)d.GetValue(ResizeBugProperty); } public static void SetResizeBug(DependencyObject d, bool value) { d.SetValue(ResizeBugProperty, value); } }

  3. #3 da Craig Shearer - Saturday March 2010 alle 01:15

    Hey, thanks for this. I encountered the same bug, and it's very annoying, but your workaround fixes it for me, so I'm happy :)

All fields are required and you must provide valid data in order to be able to comment on this post.


(will not be published)
(es: http://www.mysite.com)