今天,要用WPF實現一個能夠經過Windows觸屏左右滑動的ListBox控件,而且,同時也能夠經過點擊兩個按鈕,進行左右滑動。spa
實現這個控件,有幾個難點:code
兩種方式,都須要有一個共同的值或方式來記錄滑動的距離和方向。不然經過一種方式滑動之後,再用另一種方式,就會出現錯誤的距離滑動。事件
滑動的距離不容易獲取,由於ListBox沒有相似於OffSet的屬性。it
當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); }