6
votes

I'm trying to adapt the behaviour of the ListBox for my needs and I ran into several problems

1) How can you programatically set the scrolling position of the ListBox
The ListBox does not provide an accessor to its inner ScrollViewer so that you cannot scroll it to wherever you want.

2) How to accurately set the vertical scrolling (i.e how to have a smooth scrolling) ?
By default, there is no way to scroll the list other by scrolling one complete element at a time (the Listbox will always make sure that the first element is shown entirely)

This behaviour is ok in most cases, but not mine : I want a smooth movement...),

There is a solution to this problem with WPF, but not with Silverlight (see question "is-it-possible-to-implement-smooth-scroll-in-a-wpf-listview" ).

3) How to catch the MouseDown and MouseUp events :
If you subclass the ListBox , you might be able to catch the MouseUp and MouseMove events. However the MouseUp event is never fired (I suspect it is being eaten by the ListBox subelements)

1

1 Answers

8
votes

I have found the answer, so I will answer myself.


1) How to make the ListBox scroll smoothly :
This problem did not happen in SilverLight 2, and it happens only with SilverLight 3, in which the VirtualizedStackPanel was introduced.
The VirtualizedStackPanel enables much faster refresh in the case of huge lists (as only the visible elements are drawn)

There is a workaround for this (beware, it should not be used on huge lists) : you redefine the ListBox's ItemPanelTemplate, so that it uses StackPanel :

<navigation:Page.Resources>
    <ItemsPanelTemplate x:Key="ItemsPanelTemplate">
        <StackPanel/>
    </ItemsPanelTemplate>
</navigation:Page.Resources>

<StackPanel Orientation="Vertical"  x:Name="LayoutRoot">                       
        <ListBox x:Name="list" ItemsPanel="{StaticResource ItemsPanelTemplate}">
        </ListBox>
</StackPanel>

2) How to programatically change the scrolling position
See the subclass of ListBox below : it provides an accessor to the internal ScrollViewer of the ListBox


3) How to catch the MouseDown / Move / Up events in the listbox :

Create a subclass of ListBox as shown below. The 3 methods :

 internal void MyOnMouseLeftButtonDown(MouseButtonEventArgs e)  
 protected override void OnMouseMove(MouseEventArgs e)  
 protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)  

will be called and you can do whatever you want with them. There is one subtle trick which is that the OnMouseLeftButtonDown method of ListBox is never called : you need to implement a subclass of ListBoxItem where you can handle this event.

using System;
using System.Collections.Generic;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace MyControls
{
  //In order for this class to be usable as a control, you need to create a folder
  //named "generic" in your project, and a "generic.xaml" file in this folder
  //(this is where you can edit the default look of your controls)
  //
  /*
   * Typical content of an "empty" generic.xaml file : 
    <ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:VideoControls">
    </ResourceDictionary>
   */
  public class MyListBox : ListBox
  {
    public MyListBox()
    {
        DefaultStyleKey = typeof(ListBox);
    }

    public override void OnApplyTemplate()
    {
      base.OnApplyTemplate();
    }

    #region ScrollViewer / unlocking access related code
    private ScrollViewer _scrollHost;
    public ScrollViewer ScrollViewer
    {
      get 
      {
        if (_scrollHost == null)
          _scrollHost = FindVisualChildOfType<ScrollViewer>(this);
        return _scrollHost; 
      }
    }

    public static childItemType FindVisualChildOfType<childItemType>(DependencyObject obj)
      where childItemType : DependencyObject
    {
      // Search immediate children first (breadth-first)
      for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
      {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);

        if (child != null && child is childItemType)
          return (childItemType)child;

        else
        {
          childItemType childOfChild = FindVisualChildOfType<childItemType>(child);

          if (childOfChild != null)
            return childOfChild;
        }
      }

      return null;
    }
    #endregion

    //Modify MyListBox so that it uses MyListBoxItem instead of ListBoxItem
    protected override DependencyObject GetContainerForItemOverride()
    {
      MyListBoxItem item = new MyListBoxItem(this);
      if (base.ItemContainerStyle != null)
      {
        item.Style = base.ItemContainerStyle;
      }

      return item;
    }

    //OnMouseLeftButtonUp is never reached, since it is eaten by the Items in the list...
    /*
    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
      base.OnMouseLeftButtonDown(e);
      e.Handled = false;
    }
    */

    internal void MyOnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
      base.OnMouseMove(e);
    }

    protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
    {
      base.OnMouseLeftButtonUp(e);
    }


  }






  public class MyListBoxItem : ListBoxItem
  {
    MyListBox _customListBoxContainer;

    public MyListBoxItem()
    { }

    public MyListBoxItem(MyListBox customListBox)
    {
      this._customListBoxContainer = customListBox;
    }

    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
      base.OnMouseLeftButtonDown(e);

      if (this._customListBoxContainer != null)
      {
        this._customListBoxContainer.MyOnMouseLeftButtonDown(e);
      }

    }
  }
}