Published articles on other web sites*

Published articles on other web sites*

ScrollToBottom extension method in ScrollViewer - Silverlight 4

ScrollViewer is one among the commonly used Silverlight control. When you develop Silverlight application, at some time, you'll come across using ScrollViewer. Using ScrollViewer is straightforward in most of the cases. In some scenarios (like displaying chat history messages, stock history update), we might want to bind values to elements inside the ScrollViewer and then scroll to the bottom in order to make the latest message appear in the visible region.

ScrollToBottom() is one among the several extension methods available for ScrollViewer which helps us to achieve scrolling vertically to the end of the ScrollViewer content. While using this ScrollToBottom() method some developers won't see the ScrollViewer scrolls upto the bottom. So, why it is like that what need to be done to make it work? Let us dive into the implementation of ScrollToBottom() method and find out the solution.

Snippet 1: (ScrollToBottom() implementation)

public static void ScrollToBottom(this ScrollViewer viewer)
{
   if (viewer == null)
   {
       throw new ArgumentNullException("viewer");
   }

   viewer.ScrollToVerticalOffset(viewer.ExtentHeight);
}

As most of us might guess, it scrolls the veritical offset to the
height of the content inside the ScrollViewer. Actually the values for properties like ScrollViewer.ExtentHeight, ScrollViewer.VerticalOffset are not recalculated immediately. It seems that they placed such restrictions due to performance considerations.

We can force to recalculate and update values of related properties by calling the UpdateLayout() method of the ScrollViewer. Anyhow be sure to call the UpdateLayout() only required. Please view the remarks section here at msdn documentation.

Ultimately, calling the UpdateLayout() method on the ScrollViewer before ScrollToBottom() is called will solve the obstacle.

Following is a sample snippet to help you understand the above specified scenario. Try executing the following snippet and inspect the value of ExtentHeight(and related properties) property by inserting the breakpoint in the call to UpdateLayout() method.

Snippet 2:
ScrollViewerTest.xaml

<UserControl x:Class="TestSilverlightApplication1.ScrollViewerTest"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   mc:Ignorable="d"
   d:DesignHeight="500" d:DesignWidth="500">
   <Grid Background="DeepSkyBlue">
       <Grid x:Name="LayoutRoot" Background="SkyBlue" Height="300" Width="400">
           <Grid.RowDefinitions>
               <RowDefinition></RowDefinition>
               <RowDefinition Height="Auto"></RowDefinition>
               <RowDefinition Height="70"></RowDefinition>
               <RowDefinition Height="Auto"></RowDefinition>
               <RowDefinition Height="70"></RowDefinition>
               <RowDefinition></RowDefinition>
           </Grid.RowDefinitions>
           <StackPanel>
               <TextBlock Text="ScrollToBottom Check" HorizontalAlignment="Center" FontWeight="Bold" ></TextBlock>

           </StackPanel>
           <TextBlock Grid.Row="1" Text="Enter multiline text here......"></TextBlock>
           <ScrollViewer Grid.Row="2">
               <TextBox AcceptsReturn="True" Text="{Binding SampleTextString, Mode=TwoWay}"></TextBox>
           </ScrollViewer>
           <Button Content="...and click here...." Click="ScrollDownCheck_Click" Width="150" Grid.Row="3" Margin="10"></Button>
           <ScrollViewer Grid.Row="4" Name="TestScrollControl">
               <TextBox AcceptsReturn="True" Name="TestTextBox" Text="{Binding AlteredTextString}"></TextBox>
           </ScrollViewer>
       </Grid>
   </Grid>
</UserControl>

ScrollViewerTest.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;

namespace TestSilverlightApplication1
{
   public partial class ScrollViewerTest : UserControl, INotifyPropertyChanged
   {
       public ScrollViewerTest()
       {
           InitializeComponent();
           InitControls();
       }

       private string sampleTextString;

       public string SampleTextString
       {
           get { return sampleTextString; }
           set
           {
               sampleTextString = value;
               NotifyPropertyChanged("SampleTextString");
           }
       }

       private string alteredTextString;

       public string AlteredTextString
       {
           get { return alteredTextString; }
           set
           {
               alteredTextString = value;
               NotifyPropertyChanged("AlteredTextString");
           }
       }
       
       private void ScrollDownCheck_Click(object sender, RoutedEventArgs e)
       {
           ManipulateScrollDownCheck();
       }

       private void InitControls()
       {
           this.DataContext = this;
       }

       private void ManipulateScrollDownCheck()
       {
           this.AlteredTextString = this.SampleTextString;
           //Insert breakpoint here and validate the value of
           //ExtentHeight before and after the execution of UpdateLayout().
           TestScrollControl.UpdateLayout();
           TestScrollControl.ScrollToBottom();
       }

       #region INotifyPropertyChanged Members

       protected void NotifyPropertyChanged(string propertyName)
       {
           if (PropertyChanged != null)
           {
               PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
           }
       }

       public event PropertyChangedEventHandler PropertyChanged;

       #endregion
   }
}

You can infer from the above example that the call to UpdateLayout() is made before calling ScrollToBottom() and after assinging the value to the property bound to the content(UIElement contained inside the ScrollViewer) of the ScrollViewer. Since developers will face such problem only at certain workflows, call the UpdateLayout() only when you encounter that problem.

Reference
http://msdn.microsoft.com/en-us/library/system.windows.controls.scrollviewer.scrolltoverticaloffset(v=vs.95).aspx
http://msdn.microsoft.com/en-us/library/system.windows.controls.scrollviewer.scrolltobottom.aspx
https://epiinfo.svn.codeplex.com/svn/Silverlight.Controls.Toolkit/Controls.Toolkit/Common/ScrollViewerExtensions.cs

No hay comentarios:

Publicar un comentario