[Windows Phone 开发] 为 ListBox 增加功能吧 - 加入快速导览 ScrollBar (上)

使用 Windows Phone App 时,经常会遇到的问题

“列表这么长,Item 这么多,为什么我不能直接拉到指定的 Item?”

本文章旨为解决此问题,让各位开发者都能轻松替自己的 ListBox 加值,成为 “ListBox、改”

本文从替内建控件加值的角度出发,希望读者能够从中了解如何自行撰写一个 CustomControl (自订控件)


使用 Windows Phone App 时,经常会遇到的问题

“列表这么长,Item 这么多,为什么我不能直接拉到指定的 Item?”

本文章旨为解决此问题,让各位开发者都能轻松替自己的 ListBox 加值,成为 “ListBox、改”

本文从替内建控件加值的角度出发,希望读者能够从中了解如何自行撰写一个 CustomControl (自订控件)

预计达成的目标

  • 撰写一个新的控件 ListBoxWithScrollBar,提供使用者快速切换到想看的地方
  • 避免现有程序更动幅度过大,新控件必须相容旧有的 ListBox

实践步骤

  1. 将 ListBoxWithScrollBar 继承于 ListBox
  2. 于 ListBoxWithScrollBar 的外观配置中,加入 Slider,并预计将它用来作为 ScrollBar
  3. 调整上述 Slider 的外观配置,让它长得像 ScrollBar 该有的样子
  4. 让 ScrollBar 能做出该有的动作

开始动手吧

于项目中新增一个类 (Class),名为 ListBoxWithScrollBar

为了能让自己撰写用来强化 ListBox 的新控件“ListBoxWithScrollBar”能够相容原有的 ListBox,故直接继承它


public class ListBoxWithScrollBar : ListBox

为了相容于 ListBox,我决定参考 (偷) ListBox 的 Template

随便拉一个 ListBox,对它按右键 → 编辑范本 → 编辑副本,跳出建立 Style 资源窗口后,名称不重要,定义于请选此文档

在该 XAML 档中即可发现如下面的 Style


        <style targettype="ListBox" x:key="ListBoxStyle1">
            
        </style>

很神奇吧,这就是原生控件 ListBox 的 Template,换句话说,这就是 ListBox 的外观呈现

ListBox 可以滚动的原因在于里面有一个名为 "ScrollViewer" 的 ScrollViewer 控件

ListBox 可以产生一个个 Item 的原因在于里面有 ItemsPresenter 控件

这个 Template 需要做一些修改,才可套用在 ListBoxWithScrollBar 上

首先将整个 Template 复制下来,贴到 App.xaml 中,并注意需置于 Application.Resources 标签内,并小心不要动到原有的项目

之所以要贴到 App.xaml 中,是因为 CustomControl 的外观都需要置于一个全域的 Application.Resources 中

接下来要将这个 Template 的 TargetType 改为 ListBoxWithScrollBar




        <style targettype="CustomControl:ListBoxWithScrollBar">
            
        </style>
    

切回到 ListBoxWithScrollBar.cs,在建构子中加入 DefaultStyleKey = typeof(ListBoxWithScrollBar); ,如此便能使自订控件套用刚刚写好的 Template


        public ListBoxWithScrollBar()
        {
            DefaultStyleKey = typeof(ListBoxWithScrollBar);
        }

在 ListBoxWithScrollBar 中加入 Slider,并且将这个 Slider 摆成直的,放在最右边

就像一般常见的 ScrollBar 那样


        <style targettype="CustomControl:ListBoxWithScrollBar">
            
        </style>

到目前为止,ListBoxWithScrollBar 这个控件就只是在 ListBox 上摆了一个毫无反应的 Slider

接下来要让 Slider 变成真正有功能的 ScrollBar

切回 ListBoxWithScrollBar.cs,并在 class name 的上方加入 [TemplatePartAttribute(Name = "ItemNavigateSlider", Type = typeof(Slider))]

TemplatePartAttribute 意在声明 (规定) 这个控件的 Template 中必须要有一个名为 ItemNavigateSlider 的控件,且这个控件是 Slider 类

如此我们便可以在 ListBoxWithScrollBar.cs 中,透过 base.GetTemplateChild("ItemNavigateSlider") as Slider; 来取得这个 Slider 的实例


    [TemplatePartAttribute(Name = "ItemNavigateSlider", Type = typeof(Slider))]
    public class ListBoxWithScrollBar : ListBox
    {
        Slider itemSlider;

        public ListBoxWithScrollBar()
        {
            DefaultStyleKey = typeof(ListBoxWithScrollBar);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            itemSlider = base.GetTemplateChild("ItemNavigateSlider") as Slider;
        }
    }

取得 Slider 实例后就好办事了

因为 Slider 在拉动时会触发事件 ValueChanged,我们只要听此事件,并在使用者拉动时,对 ListBox  做相对应处理

站在巨人的肩膀上开发,真省事 :D

我希望滑动 Slider 时,可同时将 ListBox 滑动到相对应的 Item

所以 Slider 的最大值应与目前 Items 内的数量一致,并且将 Slider 的最小更动幅度设为 1.0,以符合预期的“Slider 动一格,ListBox 就动一格”


        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            itemSlider = base.GetTemplateChild("ItemNavigateSlider") as Slider;
            if (itemSlider != null)
            {
                if (this.Items == null)
                {
                    itemSlider.Visibility = System.Windows.Visibility.Collapsed;
                }
                else
                {
                    itemSlider.Maximum = this.Items.Count - 1;
                    itemSlider.SmallChange = 1.0;
                    itemSlider.LargeChange = 10.0;
                    itemSlider.Value = itemSlider.Maximum;
                    itemSlider.ValueChanged += itemSlider_ValueChanged;
                }
            }
        }

当 Slider.ValueChanged 触发时,我们要透过 ListBox 已经撰写好的 ScrollIntoView 方法,将画面卷动至该去的地方

但是,该卷动到哪呢?

ListBox 的 Item,其 Index 是越下面越大

但摆直的 Slider (Orientation="Vertical") ,其值却是越下面越小

这…似乎有点麻烦

只好以差值 (最大值 - 目前值) 的方式来解决此问题

其实 Slider 有一个属性叫 IsDirectionReversed,可以让值的递增递减方向相反,这就留给读者去测试啰


        private void itemSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
        {
            Slider targetSlider = sender as Slider;
            if (targetSlider != null)
            {
                Int32 scrollItemIndex = (Int32)(targetSlider.Maximum - targetSlider.Value);
                if (this.Items.Count >= scrollItemIndex)
                {
                    Object targetItem = this.Items.ElementAt(scrollItemIndex);
                    this.ScrollIntoView(targetItem);
                }
            }
        }

到目前为止,已经实现了拉动 ListBoxWithScrollBar 中的 ScrollBar,快速切换到想看的地方

但是,仍有一些问题需要解决

1. 这个 ScrollBar 长得很突兀,它明明就是直的 Slider

2. 滑动 ListBox,旁边的 ScrollBar 没有跟着移动

本文的范例程序

[Windows Phone 开发] 为 ListBox 增加功能吧 - 加入快速导览 ScrollBar (下)

原文:大专栏  [Windows Phone 开发] 为 ListBox 增加功能吧 - 加入快速导览 ScrollBar (上)


上一篇:溢出滚动并去掉滚动条兼容火狐各浏览器


下一篇:Tkinter编程应知应会(17)-滚动条Scrollbar(续)