今天,要用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); }