今天,要用WPF实现一个可以通过Windows触屏左右滑动的ListBox控件,并且,同时也可以通过点击两个按钮,进行左右滑动。
实现这个控件,有几个难点:
两种方式,都需要有一个共同的值或方式来记录滑动的距离和方向。否则通过一种方式滑动以后,再用另外一种方式,就会出现错误的距离滑动。
滑动的距离不容易获取,因为ListBox没有类似于OffSet的属性。
当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);
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。