转载

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

今天,要用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); }  
正文到此结束
Loading...