如何用兩種方式同時實現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);
    }
相關文章
相關標籤/搜索