WPF Tutorial - Implementing IScrollInfo

Skill

WPF Tutorial - Implementing IScrollInfo

Posted in:

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms (ScrollableControl). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today.

So what is IScrollInfo? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer. Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control. In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollViewer.

If you don't, you go from something that looks like this:

Wrap Panel with no horizontal scrollbar.

To something that looks like this:

Wrap Panel with horizontal scrollbar.

And that kind of really defeats the purpose of a wrap panel.

The problem with disabling the horizontal scroll bar altogether is a situation like this:

Wrap Panel with item bigger than width of panel.

In that case, you would really like a horizontal scroll bar to be there, but not change the wrapping behavior. And to get that behavior, you have to write your own custom scroll logic using IScrollInfo.

Ok, time to dive into the code. First, let's take a look at what methods IScrollInfo requires us to implement:

public class AnimatedWrapPanel : IScrollInfo
{
  public void LineDown(){ }

  public void LineLeft(){ }

  public void LineRight(){ }

  public void LineUp(){ }

  public void MouseWheelDown() { }

  public void MouseWheelLeft() { }

  public void MouseWheelRight() { }

  public void MouseWheelUp() { }

  public void PageDown() { }

  public void PageLeft() { }

  public void PageRight() { }

  public void PageUp() { }

  public ScrollViewer ScrollOwner { get; set; }

  public bool CanHorizontallyScroll { get; set; }

  public bool CanVerticallyScroll { get; set; }

  public double ExtentHeight { get; }

  public double ExtentWidth { get; }

  public double HorizontalOffset { get; }

  public double VerticalOffset { get; }

  public double ViewportHeight { get; }

  public double ViewportWidth { get; }

  public Rect MakeVisible(Visual visual, Rect rectangle)
  { }

  public void SetHorizontalOffset(double offset)
  { }

  public void SetVerticalOffset(double offset)
  { }
}

Wow! Thats quite a lot of stuff there. But don't worry - almost all of it is your basic simple fill in the blank. For instance, take all the 'Up', 'Down', 'Left', 'Right' methods. Those methods give you fine grained control over how much your panel will scroll when the user clicks the up/down buttons on the scroll bar, or scrolls their mouse wheel. But for our purposes, they can be filled in pretty easily:

public class AnimatedWrapPanel : IScrollInfo
{
  private const double LineSize = 16;
  private const double WheelSize = 3 * LineSize;

  public void LineDown()
  { SetVerticalOffset(VerticalOffset + LineSize); }

  public void LineUp()
  { SetVerticalOffset(VerticalOffset - LineSize); }

  public void LineLeft()
  { SetHorizontalOffset(HorizontalOffset - LineSize); }

  public void LineRight()
  { SetHorizontalOffset(HorizontalOffset + LineSize); }

  public void MouseWheelDown()
  { SetVerticalOffset(VerticalOffset + WheelSize); }

  public void MouseWheelUp()
  { SetVerticalOffset(VerticalOffset - WheelSize); }

  public void MouseWheelLeft()
  { SetHorizontalOffset(HorizontalOffset - WheelSize); }

  public void MouseWheelRight()
  { SetHorizontalOffset(HorizontalOffset + WheelSize); }

  public void PageDown()
  { SetVerticalOffset(VerticalOffset + ViewportHeight); }

  public void PageUp()
  { SetVerticalOffset(VerticalOffset - ViewportHeight); }

  public void PageLeft()
  { SetHorizontalOffset(HorizontalOffset - ViewportWidth); }

  public void PageRight()
  { SetHorizontalOffset(HorizontalOffset + ViewportWidth); }

  public ScrollViewer ScrollOwner { get; set; }

  public bool CanHorizontallyScroll { get; set; }

  public bool CanVerticallyScroll { get; set; }

  public double ExtentHeight { get; }

  public double ExtentWidth { get; }

  public double HorizontalOffset { get; }

  public double VerticalOffset { get; }

  public double ViewportHeight { get; }

  public double ViewportWidth { get; }

  public Rect MakeVisible(Visual visual, Rect rectangle)
  { }

  public void SetHorizontalOffset(double offset)
  { }

  public void SetVerticalOffset(double offset)
  { }
}

Just set up some constants for the amount to scroll per line and per wheel click, and away we go! Thats over half the methods down already. Now let's take care of some of those pesky properties:

public class AnimatedWrapPanel : IScrollInfo
{
  private const double LineSize = 16;
  private const double WheelSize = 3 * LineSize;

  private bool _CanHorizontallyScroll;
  private bool _CanVerticallyScroll;
  private ScrollViewer _ScrollOwner;
  private Vector _Offset;
  private Size _Extent;
  private Size _Viewport;

  public void LineDown()
  { SetVerticalOffset(VerticalOffset + LineSize); }

  public void LineUp()
  { SetVerticalOffset(VerticalOffset - LineSize); }

  public void LineLeft()
  { SetHorizontalOffset(HorizontalOffset - LineSize); }

  public void LineRight()
  { SetHorizontalOffset(HorizontalOffset + LineSize); }

  public void MouseWheelDown()
  { SetVerticalOffset(VerticalOffset + WheelSize); }

  public void MouseWheelUp()
  { SetVerticalOffset(VerticalOffset - WheelSize); }

  public void MouseWheelLeft()
  { SetHorizontalOffset(HorizontalOffset - WheelSize); }

  public void MouseWheelRight()
  { SetHorizontalOffset(HorizontalOffset + WheelSize); }

  public void PageDown()
  { SetVerticalOffset(VerticalOffset + ViewportHeight); }

  public void PageUp()
  { SetVerticalOffset(VerticalOffset - ViewportHeight); }

  public void PageLeft()
  { SetHorizontalOffset(HorizontalOffset - ViewportWidth); }

  public void PageRight()
  { SetHorizontalOffset(HorizontalOffset + ViewportWidth); }

  public ScrollViewer ScrollOwner
  {
    get { return _ScrollOwner; }
    set { _ScrollOwner = value; }
  }

  public bool CanHorizontallyScroll
  {
    get { return _CanHorizontallyScroll; }
    set { _CanHorizontallyScroll = value; }
  }

  public bool CanVerticallyScroll
  {
    get { return _CanVerticallyScroll; }
    set { _CanVerticallyScroll = value; }
  }

  public double ExtentHeight
  { get { return _Extent.Height; } }

  public double ExtentWidth
  { get { return _Extent.Width; } }

  public double HorizontalOffset
  { get { return _Offset.X; } }

  public double VerticalOffset
  { get { return _Offset.Y; } }

  public double ViewportHeight
  { get { return _Viewport.Height; } }

  public double ViewportWidth
  { get { return _Viewport.Width; } }

  public Rect MakeVisible(Visual visual, Rect rectangle)
  { }

  public void SetHorizontalOffset(double offset)
  { }

  public void SetVerticalOffset(double offset)
  { }
}

Pretty much we just needed to set up backing fields for all those properties. The property names are pretty self explanatory - "Extent" is the total size of the panel, while "Viewport" is the amount that is visible on screen. "Offset" is the amount that the viewport is offset from 0,0 - i.e., how far scrolled down/right we are.

What is left are the more complicated parts of the interface. First, let's fill out the SetHorizontalOffset and SetVerticalOffset calls:

public void SetHorizontalOffset(double offset)
{
  offset = Math.Max(0, Math.Min(offset, ExtentWidth - ViewportWidth));
  if (offset != _Offset.Y)
  {
    _Offset.X = offset;
    InvalidateArrange();
  }
}

public void SetVerticalOffset(double offset)
{
  offset = Math.Max(0, Math.Min(offset, ExtentHeight - ViewportHeight));
  if (offset != _Offset.Y)
  {
    _Offset.Y = offset;
    InvalidateArrange();
  }
}

In both cases, we force the offset into a valid range, and then if it is different than the current offset, we set it as the new offset and invalidate the arrange of the panel (so that the items on the panel will get moved appropriately).

What's left on the interface is MakeVisible, which is what gets called to scroll an item into view. The code in there is just a bunch of math to calculate new scroll offsets - I'm not going to walk through it, but you can check it out in the full code farther down the tutorial.

So the interface is fully implemented. But sadly, that isn't enough - we still have to do things like calculate the Viewport and the Extent, as well as modify the MeasureOverride and ArrangeOverride to deal with their own scroll behavior.

If you take a look at the code in Creating a Custom Panel Control, the following code might look very familiar. This is because I tried to modify the code for the original animated wrap panel as little as possible - see if you can spot the changes:

protected override Size MeasureOverride(Size availableSize)
{
  double curX = 0, curY = 0, curLineHeight = 0, maxLineWidth = 0;
  foreach (UIElement child in Children)
  {
    child.Measure(InfiniteSize);

    if (curX + child.DesiredSize.Width > availableSize.Width)
    { //Wrap to next line
      curY += curLineHeight;
      curX = 0;
      curLineHeight = 0;
    }

    curX += child.DesiredSize.Width;
    if (child.DesiredSize.Height > curLineHeight)
    { curLineHeight = child.DesiredSize.Height; }
   
    if (curX > maxLineWidth)
    { maxLineWidth = curX; }
  }

  curY += curLineHeight;

  VerifyScrollData(availableSize, new Size(maxLineWidth, curY));

  return _Viewport;
}

protected override Size ArrangeOverride(Size finalSize)
{
  if (this.Children == null || this.Children.Count == 0)
  { return finalSize; }

  TranslateTransform trans = null;
  double curX = 0, curY = 0, curLineHeight = 0, maxLineWidth = 0;

  foreach (UIElement child in Children)
  {
    trans = child.RenderTransform as TranslateTransform;
    if (trans == null)
    {
      child.RenderTransformOrigin = new Point(0, 0);
      trans = new TranslateTransform();
      child.RenderTransform = trans;
    }

    if (curX + child.DesiredSize.Width > finalSize.Width)
    { //Wrap to next line
      curY += curLineHeight;
      curX = 0;
      curLineHeight = 0;
    }

    child.Arrange(new Rect(0, 0,
      child.DesiredSize.Width, child.DesiredSize.Height));

    trans.BeginAnimation(TranslateTransform.XProperty,
      new DoubleAnimation(curX - HorizontalOffset, _AnimationLength),
      HandoffBehavior.Compose);
    trans.BeginAnimation(TranslateTransform.YProperty,
      new DoubleAnimation(curY - VerticalOffset, _AnimationLength),
      HandoffBehavior.Compose);

    curX += child.DesiredSize.Width;
    if (child.DesiredSize.Height > curLineHeight)
    { curLineHeight = child.DesiredSize.Height; }

    if (curX > maxLineWidth)
    { maxLineWidth = curX; }
  }

  curY += curLineHeight;
  VerifyScrollData(finalSize, new Size(maxLineWidth, curY));

  return finalSize;
}

MeasureOverride is almost identical to the old code, except for two things. One, we keep track of the max row width, to correctly calculate the horizontal extent of the panel. Two, we have a call to VerifyScrollData at the end of the method - a method we have not seen yet (but will be taking a look at soon).

ArrangeOverride has a couple more changes. Again, we are keeping track of the max row width, and calling VerifyScrollData. But we are also modifying the positions at which the items are placed by the amount of the scroll offset. This is because since we are in charge of scrolling behavior, we are also in charge of making sure items are place correctly according to the scrolling behavior.

Ok, now for that VerifyScrollData method:

protected void VerifyScrollData(Size viewport, Size extent)
{
  if (double.IsInfinity(viewport.Width))
  { viewport.Width = extent.Width; }

  if (double.IsInfinity(viewport.Height))
  { viewport.Height = extent.Height; }

  _Extent = extent;
  _Viewport = viewport;

  _Offset.X = Math.Max(0,
    Math.Min(_Offset.X, ExtentWidth - ViewportWidth));
  _Offset.Y = Math.Max(0,
    Math.Min(_Offset.Y, ExtentHeight - ViewportHeight));

  if (ScrollOwner != null)
  { ScrollOwner.InvalidateScrollInfo(); }
}

It is this function that sets the viewport and extent fields. It also coerces the offsets to be within the correct ranges (changes to the extent/viewport can make a previously valid offset incorrect). Finally, if there is a scroll owner currently attached, we call InvalidateScrollInfo.
This makes sure that the scrolviewer is displaying the right ranges and positions for the scrollbars.

And that is it for implementing your own implementation of IScrollInfo. Here is all the code together:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace AnimatedWrapPanel
{
  public class AnimatedWrapPanel : Panel, IScrollInfo
  {
    private static Size InfiniteSize =
      new Size(double.PositiveInfinity, double.PositiveInfinity);
    private const double LineSize = 16;
    private const double WheelSize = 3 * LineSize;

    private bool _CanHorizontallyScroll;
    private bool _CanVerticallyScroll;
    private ScrollViewer _ScrollOwner;
    private Vector _Offset;
    private Size _Extent;
    private Size _Viewport;

    private TimeSpan _AnimationLength = TimeSpan.FromMilliseconds(200);

    protected override Size MeasureOverride(Size availableSize)
    {
      double curX = 0, curY = 0, curLineHeight = 0, maxLineWidth = 0;
      foreach (UIElement child in Children)
      {
        child.Measure(InfiniteSize);

        if (curX + child.DesiredSize.Width > availableSize.Width)
        { //Wrap to next line
          curY += curLineHeight;
          curX = 0;
          curLineHeight = 0;
        }

        curX += child.DesiredSize.Width;
        if (child.DesiredSize.Height > curLineHeight)
        { curLineHeight = child.DesiredSize.Height; }
       
        if (curX > maxLineWidth)
        { maxLineWidth = curX; }
      }

      curY += curLineHeight;

      VerifyScrollData(availableSize, new Size(maxLineWidth, curY));

      return _Viewport;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
      if (this.Children == null || this.Children.Count == 0)
      { return finalSize; }

      TranslateTransform trans = null;
      double curX = 0, curY = 0, curLineHeight = 0, maxLineWidth = 0;

      foreach (UIElement child in Children)
      {
        trans = child.RenderTransform as TranslateTransform;
        if (trans == null)
        {
          child.RenderTransformOrigin = new Point(0, 0);
          trans = new TranslateTransform();
          child.RenderTransform = trans;
        }

        if (curX + child.DesiredSize.Width > finalSize.Width)
        { //Wrap to next line
          curY += curLineHeight;
          curX = 0;
          curLineHeight = 0;
        }

        child.Arrange(new Rect(0, 0,
          child.DesiredSize.Width, child.DesiredSize.Height));

        trans.BeginAnimation(TranslateTransform.XProperty,
          new DoubleAnimation(curX - HorizontalOffset, _AnimationLength),
          HandoffBehavior.Compose);
        trans.BeginAnimation(TranslateTransform.YProperty,
          new DoubleAnimation(curY - VerticalOffset, _AnimationLength),
          HandoffBehavior.Compose);

        curX += child.DesiredSize.Width;
        if (child.DesiredSize.Height > curLineHeight)
        { curLineHeight = child.DesiredSize.Height; }

        if (curX > maxLineWidth)
        { maxLineWidth = curX; }
      }

      curY += curLineHeight;
      VerifyScrollData(finalSize, new Size(maxLineWidth, curY));

      return finalSize;
    }
   
    #region Movement Methods
    public void LineDown()
    { SetVerticalOffset(VerticalOffset + LineSize); }

    public void LineUp()
    { SetVerticalOffset(VerticalOffset - LineSize); }

    public void LineLeft()
    { SetHorizontalOffset(HorizontalOffset - LineSize); }

    public void LineRight()
    { SetHorizontalOffset(HorizontalOffset + LineSize); }

    public void MouseWheelDown()
    { SetVerticalOffset(VerticalOffset + WheelSize); }

    public void MouseWheelUp()
    { SetVerticalOffset(VerticalOffset - WheelSize); }

    public void MouseWheelLeft()
    { SetHorizontalOffset(HorizontalOffset - WheelSize); }

    public void MouseWheelRight()
    { SetHorizontalOffset(HorizontalOffset + WheelSize); }

    public void PageDown()
    { SetVerticalOffset(VerticalOffset + ViewportHeight); }

    public void PageUp()
    { SetVerticalOffset(VerticalOffset - ViewportHeight); }

    public void PageLeft()
    { SetHorizontalOffset(HorizontalOffset - ViewportWidth); }

    public void PageRight()
    { SetHorizontalOffset(HorizontalOffset + ViewportWidth); }
    #endregion

    public ScrollViewer ScrollOwner
    {
      get { return _ScrollOwner; }
      set { _ScrollOwner = value; }
    }

    public bool CanHorizontallyScroll
    {
      get { return _CanHorizontallyScroll; }
      set { _CanHorizontallyScroll = value; }
    }

    public bool CanVerticallyScroll
    {
      get { return _CanVerticallyScroll; }
      set { _CanVerticallyScroll = value; }
    }

    public double ExtentHeight
    { get { return _Extent.Height; } }

    public double ExtentWidth
    { get { return _Extent.Width; } }

    public double HorizontalOffset
    { get { return _Offset.X; } }

    public double VerticalOffset
    { get { return _Offset.Y; } }

    public double ViewportHeight
    { get { return _Viewport.Height; } }

    public double ViewportWidth
    { get { return _Viewport.Width; } }

    public Rect MakeVisible(Visual visual, Rect rectangle)
    {
      if (rectangle.IsEmpty || visual == null
        || visual == this || !base.IsAncestorOf(visual))
      { return Rect.Empty; }

      rectangle = visual.TransformToAncestor(this).TransformBounds(rectangle);

      Rect viewRect = new Rect(HorizontalOffset,
        VerticalOffset, ViewportWidth, ViewportHeight);
      rectangle.X += viewRect.X;
      rectangle.Y += viewRect.Y;
      viewRect.X = CalculateNewScrollOffset(viewRect.Left,
        viewRect.Right, rectangle.Left, rectangle.Right);
      viewRect.Y = CalculateNewScrollOffset(viewRect.Top,
        viewRect.Bottom, rectangle.Top, rectangle.Bottom);
      SetHorizontalOffset(viewRect.X);
      SetVerticalOffset(viewRect.Y);
      rectangle.Intersect(viewRect);
      rectangle.X -= viewRect.X;
      rectangle.Y -= viewRect.Y;

      return rectangle;
    }

    private static double CalculateNewScrollOffset(double topView,
      double bottomView, double topChild, double bottomChild)
    {
      bool offBottom = topChild < topView && bottomChild < bottomView;
      bool offTop = bottomChild > bottomView && topChild > topView;
      bool tooLarge = (bottomChild - topChild) > (bottomView - topView);

      if (!offBottom && !offTop)
      { return topView; } //Don't do anything, already in view

      if ((offBottom && !tooLarge) || (offTop && tooLarge))
      { return topChild; }

      return (bottomChild - (bottomView - topView));
    }

    protected void VerifyScrollData(Size viewport, Size extent)
    {
      if (double.IsInfinity(viewport.Width))
      { viewport.Width = extent.Width; }

      if (double.IsInfinity(viewport.Height))
      { viewport.Height = extent.Height; }

      _Extent = extent;
      _Viewport = viewport;

      _Offset.X = Math.Max(0,
        Math.Min(_Offset.X, ExtentWidth - ViewportWidth));
      _Offset.Y = Math.Max(0,
        Math.Min(_Offset.Y, ExtentHeight - ViewportHeight));

      if (ScrollOwner != null)
      { ScrollOwner.InvalidateScrollInfo(); }
    }

    public void SetHorizontalOffset(double offset)
    {
      offset = Math.Max(0,
        Math.Min(offset, ExtentWidth - ViewportWidth));
      if (offset != _Offset.Y)
      {
        _Offset.X = offset;
        InvalidateArrange();
      }
    }

    public void SetVerticalOffset(double offset)
    {
      offset = Math.Max(0,
        Math.Min(offset, ExtentHeight - ViewportHeight));
      if (offset != _Offset.Y)
      {
        _Offset.Y = offset;
        InvalidateArrange();
      }
    }
  }
}

Now, in order to use a class that implements IScrollInfo, you do have to do one other thing - you have to remember to set the property CanContentScroll to true on the ScrollViewer surrounding the instance of your class. This signifies to the ScrollViewer that the content can control its own scroll behavior - letting the logic that you have written work its magic. So with that property set, your XAML might look something like this:

<Window x:Class="AnimatedWrapPanel.Window1"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:ARP="clr-namespace:AnimatedWrapPanel"
   Title="Animated Wrap Panel Test" Height="300" Width="300">
  <ScrollViewer CanContentScroll="True"
               HorizontalScrollBarVisibility="Auto"
               VerticalScrollBarVisibility="Auto">
    <ARP:AnimatedWrapPanel>
      <Image Source="Images\Aquarium.jpg" Stretch="Uniform"
            Width="100" Margin="5"/>
      <Image Source="Images\Ascent.jpg" Stretch="Uniform"
            Width="50" Margin="5" />
      <Image Source="Images\Autumn.jpg" Stretch="Uniform"
            Width="200" Margin="5"/>
      <Image Source="Images\Crystal.jpg" Stretch="Uniform"
            Width="75" Margin="5"/>
      <Image Source="Images\DaVinci.jpg" Stretch="Uniform"
            Width="125" Margin="5"/>
      <Image Source="Images\Follow.jpg" Stretch="Uniform"
            Width="100" Margin="5"/>
      <Image Source="Images\Friend.jpg" Stretch="Uniform"
            Width="50" Margin="5"/>
      <Image Source="Images\Home.jpg" Stretch="Uniform"
            Width="150" Margin="5"/>
      <Image Source="Images\Moon flower.jpg" Stretch="Uniform"
            Width="100" Margin="5"/>
    </ARP:AnimatedWrapPanel>
  </ScrollViewer>
</Window>

And this leaves you a panel that can scroll horizontally, but only when it needs to:

Resulting panel screenshot

Well, that about wraps it up. As always, you can grab the source for the example below if you want to play with the code on your own. Leave any comments or questions you might have , and I'll do my best to answer them.

Tamás
06/30/2009 - 16:35

Wonderful tutorial! I was looking exactly for this solution to implement logical scrolling on my WrapPanel!

reply

Martin
08/17/2009 - 09:56

Great Work!
helped me a lot. But in the dowloaded code there is a little bug (which is not apparent since you support horizontal layout only): In method SetHorizontalOffset(), the if statement needs to compare to _Offset.X, not to Y.

Greetings Martin

reply

JD
02/27/2010 - 15:00

Have you run into problems getting this to work in a listbox control template? Everything is good except pgup and pgdn buttons do not work.

reply

JD
03/01/2010 - 11:15

I have looked into it further and its related to the StackPanel I'm using inside the AnimatedWrapPanel. I think it does not like when IsItemsHost is true. The PageUp and PageDown methods do not fire when PgDn and PgUp are clicked in that instance.

Is there a reason why this is happening or a way to fix it?

reply

WPF Priya
02/11/2011 - 07:35

Hi I am working on a richtextbox in a document viewer control. I am facing the same problem with documentviewer's scrollviewer. I want the scroll viewer scroll based on the child on the richtextbox that is in focus. will the same logic work for this?

reply

Allen Feng
02/23/2011 - 03:40

That would be the exact problem I'm having - a WrapPanel in a ScrollViewer where vertical scroll disappear and Wrapping functionality is gone.

But ... your code didn't work. It turns out the same as a normal WrapPanel.

reply

Allen Feng
02/23/2011 - 03:55

You code doesn't work if your scrollviewer and wrappanel is inside a stackpanel/grid or any container...

reply

Anonymous
05/03/2011 - 07:15

I am getting the error "Assemble 'AnimatedWrapPanel'" was not found.
Please help me.

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.