2

今天,要用WPF实现一个可以通过Windows触屏左右滑动的ListBox控件,并且,同时也可以通过点击两个按钮,进行左右滑动。

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

  1. 两种方式,都需要有一个共同的值或方式来记录滑动的距离和方向。否则通过一种方式滑动以后,再用另外一种方式,就会出现错误的距离滑动。

  2. 滑动的距离不容易获取,因为ListBox没有类似于OffSet的属性。

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

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"/>

有种方法,可以实现第二种方式滚动:

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

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

有种方法,可以完美实现。

首先通过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);
    }

    

snowell
365 声望11 粉丝

全栈,跨平台


引用和评论

0 条评论