如何用两种方式同时实现ListBox的滚动功能

今天,要用WPF实现一个能够经过Windows触屏左右滑动的ListBox控件,而且,同时也能够经过点击两个按钮,进行左右滑动。spa

实现这个控件,有几个难点:code

  1. 两种方式,都须要有一个共同的值或方式来记录滑动的距离和方向。不然经过一种方式滑动之后,再用另一种方式,就会出现错误的距离滑动。事件

  2. 滑动的距离不容易获取,由于ListBox没有相似于OffSet的属性。it

  3. 当ListBox的内容的元素比较多的时候,也就是能够滑动的时候,不容易得知那些元素是露在外面的。io

Xaml的相关代码:原理

<ListBox 
                x:Name="navItemsListBox"
                ScrollViewer.VerticalScrollBarVisibility="Disabled"
                ScrollViewer.HorizontalScrollBarVisibility="Hidden"
                ScrollViewer.CanContentScroll="False"
                ScrollViewer.IsDeferredScrollingEnabled="True"
                VirtualizingStackPanel.IsVirtualizing="True"
                VirtualizingPanel.ScrollUnit="Item"
                Background="Transparent"
                BorderThickness="0"
                Width="840">
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Horizontal">
                        </StackPanel>
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                    
                            <Label Content="{Binding CategoryTitle}" 
                                   Width="70"
                                   Height="35"/>
                    
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
            <Button Width="30" Height="30" Click="CategoryToLeft"/>
            <Button Width="30" Height="30" Click="CategoryToRight"/>

有种方法,能够实现第二种方式滚动:object

navItemsListBox.ScrollIntoView(navItemsListBox.Items[++CurrentRightIndex]);

这种方法,可让ListBox滚动到其包含的某个元素。可是由于不能记录滚动的具体位置,因此对第一种划屏的方式无能为力,因此这种方法不能采用。List

有种方法,能够完美实现。scroll

首先经过VisualTree的方法得到内嵌在ListBox中的ScrollViewer:方法

Decorator border = VisualTreeHelper.GetChild(navItemsListBox, 0) as Decorator;
        if (border != null)
        {
            scrollViewer = border.Child as ScrollViewer;
            if (scrollViewer != null)
            {
                scrollViewer.ScrollToHorizontalOffset(theOffset);
            }
        }

由于ScrollViewer是能够记录滚动的误差的,好比HorizontalOffset属性就是记录水平的滚动误差的。获取了内嵌在ListBox中的ScrollViewer,也就间接的获取了ListBox的滚动误差。

而后,获取navItemsListBox中的ListBoxItem,用来计算每一个元素的宽度和ListBox左右能滑动的最大宽度:

ListBoxItem theItem = (ListBoxItem)(navItemsListBox.ItemContainerGenerator.ContainerFromIndex(0));
var itemWidth = theItem.ActualWidth;
var itemsTotalWidth = (navItemsListBox.Items.Count - MAXSHOWNINDEX) * itemWidth;

其中,MAXSHOWNINDEX是ListBox能同时展示在外面的最多元素的个数。

那么这个方法的原理就是:由于不管是经过触屏滑屏仍是点击按钮滚动,都会改变ListBox中ScrollViewer的HorizontalOffset的值(固然,也会触发ListBox的ScrollViewer.ScrollChanged事件,我就是经过这个事件来了解这个方法的),那么每次在点击按钮进行滚动的时候,首先要获取当前的HorizontalOffset的值,而后再用

scrollViewer.ScrollToHorizontalOffset(theOffset);

这个方法再次改变HorizontalOffset。这样,就经过HorizontalOffset这个值来统一管理两种滚动方法的偏移量了。

固然,以上功能能够直接采用ScrollViewer实现,可是因为历史代码遗留的缘由,不得不采用ListBox。

最终的code-behind代码以下:

private const int MAXSHOWNINDEX = 9;
    private ScrollViewer scrollViewer = new ScrollViewer();
    private double theOffset = 0;
    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        Decorator border = VisualTreeHelper.GetChild(navItemsListBox, 0) as Decorator;
        if (border != null)
        {
            
            scrollViewer = border.Child as ScrollViewer;
            if (scrollViewer != null)
            {
                scrollViewer.ScrollToHorizontalOffset(theOffset);
            }
        }
    }

    private void CategoryToRight(object sender, RoutedEventArgs e)
    {
        ListBoxItem theItem = (ListBoxItem)(navItemsListBox.ItemContainerGenerator.ContainerFromIndex(0));

        var itemWidth = theItem.ActualWidth;
        var itemsTotalWidth = (navItemsListBox.Items.Count - MAXSHOWNINDEX) * itemWidth;

        theOffset = scrollViewer.HorizontalOffset;

        if (theOffset + itemWidth > itemsTotalWidth)
        {
            theOffset = itemsTotalWidth;
        }
        else
            theOffset += itemWidth;
        
        scrollViewer.ScrollToHorizontalOffset(theOffset);
    }

    private void CategoryToLeft(object sender, RoutedEventArgs e)
    {
        ListBoxItem theItem = (ListBoxItem)(navItemsListBox.ItemContainerGenerator.ContainerFromIndex(0));

        var itemWidth = theItem.ActualWidth;

        theOffset = scrollViewer.HorizontalOffset;

        if (theOffset - itemWidth < 0)
        {
            theOffset = 0;
        }
        else
            theOffset -= itemWidth;

        scrollViewer.ScrollToHorizontalOffset(theOffset);
    }
相关文章
相关标签/搜索