Silverlight 3 - ListBox: как добиться плавной прокрутки и ловить события MouseDown / MouseUp - PullRequest
6 голосов
/ 30 августа 2009

Я пытаюсь адаптировать поведение ListBox для своих нужд и столкнулся с несколькими проблемами

1) Как программно установить положение прокрутки ListBox
ListBox не предоставляет средства доступа к своему внутреннему ScrollViewer, поэтому вы не можете прокрутить его туда, куда хотите.

2) Как точно установить вертикальную прокрутку (т.е. как сделать плавную прокрутку)?
По умолчанию невозможно прокрутить другой список, прокручивая по одному полному элементу за раз (список всегда будет следить за тем, чтобы первый элемент отображался полностью)

В большинстве случаев это нормально, но не мое: я хочу плавного движения ...),

Существует решение этой проблемы с WPF, но не с Silverlight (см. Вопрос «возможно ли реализовать плавную прокрутку в wpf-listview» ) .

3) Как перехватить события MouseDown и MouseUp:
Если вы создадите подкласс ListBox, вы сможете перехватить события MouseUp и MouseMove. Однако событие MouseUp никогда не вызывается (я подозреваю, что оно используется подэлементами ListBox)

1 Ответ

8 голосов
/ 31 августа 2009

Я нашел ответ, поэтому отвечу сам.


1) Как сделать плавную прокрутку ListBox:
Эта проблема не возникала в SilverLight 2, и это происходит только с SilverLight 3, в котором была представлена ​​VirtualizedStackPanel.
VirtualizedStackPanel позволяет значительно быстрее обновлять в случае огромных списков (поскольку рисуются только видимые элементы)

Для этого есть обходной путь (будьте осторожны, его не следует использовать в огромных списках): вы переопределяете ItemPanelTemplate ListBox, чтобы он использовал 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) Как программно изменить положение прокрутки
Смотрите подкласс ListBox ниже: он обеспечивает доступ к внутреннему ScrollViewer ListBox


3) Как перехватить события MouseDown / Move / Up в списке:

Создайте подкласс ListBox, как показано ниже. 3 метода:

 internal void MyOnMouseLeftButtonDown(MouseButtonEventArgs e)  
 protected override void OnMouseMove(MouseEventArgs e)  
 protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)  
Вам позвонят

, и вы можете делать с ними все, что захотите. Есть одна тонкая хитрость, заключающаяся в том, что метод OnMouseLeftButtonDown ListBox никогда не вызывается: вам нужно реализовать подкласс ListBoxItem, где вы можете обработать это событие.

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);
      }

    }
  }
}
...