【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

ListBoxのホイール対応

Silverlight 3でMouseWheelがサポートされたためListBoxをスクロールさせようとしましたが、Silverlight 2とは仕様が変わっていることに気付きました。Silverlight 2のListBoxのScrollViewerはVerticalOffsetがドット単位でしたが、Silverlight 3では行単位になりました。

互換性ですが、Silverlight 2で作成したアプリケーションをSilverlight 3で動かすと、ScrollViewerはドット単位で動きます。Silverlight 2が過去入っていなかったマシンでも挙動は変化したことや、TextBox.IsReadOnlyをfalseにすると背景が灰色になったりキャレットが消えたりしたことから(Silverlight 2にはない挙動)、単なるSideBySideでライブラリが切り替わっているわけでもないようです。Silverlight 3でリビルドすると行単位に切り替わります。

ListBoxをホイール対応にするコードは以下の通りです。ListBox以外への対応はコードの後で補足します。

var listBox1 = new ListBox();
listBox1.MouseWheel += (sender, e) =>
{
    Func<DependencyObject, ScrollViewer> getChildVisual = null;
    getChildVisual = dobj =>
    {
        if (dobj is ScrollViewer) return dobj as ScrollViewer;
        int count = VisualTreeHelper.GetChildrenCount(dobj);
        for (int i = 0; i < count; i++)
        {
            var ret = getChildVisual(VisualTreeHelper.GetChild(dobj, i));
            if (ret != null) return ret;
        }
        return null;
    };
    var sv = getChildVisual(listBox1);
    if (sv == null) return;
    sv.ScrollToVerticalOffset(Math.Min(sv.ScrollableHeight,
        Math.Max(0, sv.VerticalOffset - e.Delta / 40)));
    e.Handled = true;
};

補足

  • デフォルトでSilverlightにフォーカスが当たっていないため、一度クリックしてからでないとホイールが反応しません。
  • getChildVisualをnullで初期化しているのは、無名関数の中から再帰呼び出しする際に参照できるようにするためです。F#のrecのようなものです。
  • ListBox以外のコントロールでは e.Delta / 40 の部分を e.Delta / 3 にします。これは冒頭で述べたように、ListBoxが行単位なのに対し他のコントロールはドット単位のためです。