WPF SurfaceListbox 循环事件
截图:
(1)生成项目
- 打开vs2010,创建一个surface Application工程。
- 进入ContinuousList作为项目名称
- 在SurfaceWindow1.xaml文件,添加一个数据绑定SurfaceListBox控制网格控件模板的一部分。
- 为SurfaceListBox创建一个模板(Template)。当您完成操作之后,SurfaceWindow1.xaml文件的应用程序应该包含以下代码。
<s:SurfaceWindow x:Class="ContinuousList.SurfaceWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="http://schemas.microsoft.com/surface/2008"
Title="ContinuousList"
xmlns:l="clr-namespace:ContinuousList"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="322"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<s:SurfaceListBox Name="MainSurfaceListBox">
<s:SurfaceListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Width="270"/>
</DataTemplate>
</s:SurfaceListBox.ItemTemplate>
<s:SurfaceListBox.Template>
<ControlTemplate>
<s:SurfaceScrollViewer
VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility="Disabled"
CanContentScroll="true">
<l:LoopPanelVertical IsItemsHost="True"/>
</s:SurfaceScrollViewer>
</ControlTemplate>
</s:SurfaceListBox.Template>
</s:SurfaceListBox>
</Grid>
</s:SurfaceWindow>
在控制模板的SurfaceListBox控制,一个SurfaceScrollViewer控制被定义。这个HorizontalScrollBarVisibility属性设置为“禁用”禁用水平滚动的控制。这个VerticalScrollBarVisibility属性设置为“隐藏”,使垂直运动不显示滚动条本身。因此,垂直平移是唯一的方法操纵内容。
这个SurfaceScrollViewer定义使用一个自定义类,叫做LoopPanelVertical。这类源于面板并实现了ISurfaceScrollInfo接口提供定制的循环功能。这个类的进一步讨论在“创建LoopPanelVertical类”一节。
(2)填充列表
SurfaceListBox填充的控制通过使用数据绑定。早期的XAML代码建立绑定。指定的源绑定、修改SurfaceWindow1.xaml。cs文件如下:
#region OnInitialized
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
// Query the registry to find out where the sample photos are stored.
const string shellKey =
@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\Shell Folders";
string imagesPath =
(string)Microsoft.Win32.Registry.GetValue(shellKey, "CommonPictures", null) + @"\Sample Pictures";
try
{
// Set the ItemsSource property.
MainSurfaceListBox.ItemsSource =
System.IO.Directory.GetFiles(imagesPath, "*.jpg");
}
catch (System.IO.DirectoryNotFoundException)
{
// Handle exceptions as needed.
}
}
#endregion
(3)创建LoopPanelVertical类
LoopPanelVertical的类实现了ISurfaceScrollInfo接口启用连续循环的内容SurfaceScrollViewer控制。LoopPanelVertical的实现分为以下几部分。
#region AllCode
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Microsoft.Surface.Presentation;
using Microsoft.Surface.Presentation.Controls;
using Microsoft.Surface.Presentation.Controls.Primitives;
namespace ContinuousList
{
public partial class LoopPanelVertical : Panel, ISurfaceScrollInfo
{
private ScrollViewer owner;
private Size extent = new Size(0, 0);
private Size viewport = new Size(0, 0);
private Point viewportOffset = new Point();
private bool viewportPositionDirty;
private bool verticalScrollAllowed;
private int firstItem;
private double firstItemOffset;
private double previousOffset = double.NaN;
private double totalContentHeight;
// Constructor
public LoopPanelVertical()
{
this.RenderTransform = new TranslateTransform();
}
#region IScrollInfo Properties
// Gets or sets a value that determines if the content can be scrolled horizontally.
public bool CanHorizontallyScroll
{
get
{
return false;
}
set
{
if (value)
{
throw new NotSupportedException();
}
}
}
// Gets or sets a value that determines if the content can be scrolled vertically.
public bool CanVerticallyScroll
{
get
{
return verticalScrollAllowed;
}
set
{
verticalScrollAllowed = value;
}
}
// Gets a value that describes the height of the area.
public double ExtentHeight
{
get { return extent.Height; }
}
// Gets a value that describes the width of the area.
public double ExtentWidth
{
get { return extent.Width; }
}
/// Gets a value that describes the vertical offset of the view box.
public double VerticalOffset
{
get { return viewportOffset.Y; }
}
// Gets a value that describes the horizontal offset of the view box.
public double HorizontalOffset
{
get { return viewportOffset.X; }
}
// Gets a value that describes the height of the viewport.
public double ViewportHeight
{
get { return viewport.Height; }
}
// Gets a value that describes the width of the viewport.
public double ViewportWidth
{
get { return viewport.Width; }
}
// Gets or sets the owner.
public ScrollViewer ScrollOwner
{
get
{
return this.owner;
}
set
{
this.owner = value;
}
}
#endregion
#region IScrollInfo Positioning Methods
// Scroll content up.
public void LineUp()
{
ScrollContent(-1);
}
// Scroll content down.
public void LineDown()
{
ScrollContent(1);
}
public void LineLeft()
{
// Not implemented. Cannot scroll horizontally.
}
public void LineRight()
{
// Not implemented. Cannot scroll horizontally.
}
// Scrolls the content up by ten lines.
public void MouseWheelUp()
{
ScrollContent(10);
}
// Scrolls the content down by ten lines.
public void MouseWheelDown()
{
ScrollContent(-10);
}
public void MouseWheelLeft()
{
// Not implemented. Cannot scroll horizontally.
}
public void MouseWheelRight()
{
// Not implemented. Cannot scroll horizontally.
}
public void PageUp()
{
ScrollContent(-viewport.Height);
}
public void PageDown()
{
ScrollContent(viewport.Height);
}
public void PageLeft()
{
// Not implemented. Cannot scroll horizontally.
}
public void PageRight()
{
// Not implemented. Cannot scroll horizontally.
}
#endregion
#region IScrollInfo Offset Methods
public void SetVerticalOffset(double newOffset)
{
if (CanVerticallyScroll)
{
// Make sure the offset is set for the current inputs.
if (double.IsNaN(previousOffset))
{
previousOffset = newOffset;
}
// Calculate the movement delta.
double difference = previousOffset - newOffset;
// Scroll the content.
ScrollContent(difference);
// Update the offset for next time.
previousOffset = newOffset;
}
}
public void SetHorizontalOffset(double newOffset)
{
// Not implemented. Cannot scroll horizontally.
}
#endregion
#region IScrollInfo MakeVisible Method
public Rect MakeVisible(Visual visual, Rect rectangle)
{
int itemIndex = Children.IndexOf((UIElement)visual);
int index = firstItem;
double itemOffset = firstItemOffset;
// Get the offset for the item that should be visible.
while (index != itemIndex)
{
itemOffset += Children[index].DesiredSize.Height;
index++;
if (index >= Children.Count)
{
index = 0;
}
}
// If the item is not fully in view on the top,
// adjust the offset to bring it into view.
if (itemOffset < viewportOffset.Y)
{
ScrollContent(viewportOffset.Y - itemOffset);
}
// If the item is not fully in view on the bottom,
// adjust the offset to bring it into view.
if (itemOffset + rectangle.Height > viewportOffset.Y + ViewportHeight)
{
ScrollContent((viewportOffset.Y + ViewportHeight) -
(itemOffset + rectangle.Height));
}
return rectangle;
}
#endregion
#region ISurfaceScrollInfo Members
public Vector ConvertFromViewportUnits(Point origin, Vector offset)
{
return offset;
}
public Vector ConvertToViewportUnits(Point origin, Vector offset)
{
return offset;
}
public Vector ConvertToViewportUnitsForFlick(Point origin, Vector offset)
{
return offset;
}
#endregion
#region Overridden Layout Methods
protected override Size MeasureOverride(Size availableSize)
{
// If there's no children, take all the space.
if (this.InternalChildren.Count == 0)
{
return availableSize;
}
// Measure each of the child items. Keep a running sum of heights.
// Used later to measure the viewport and the extent.
this.totalContentHeight = 0;
foreach (UIElement child in this.InternalChildren)
{
child.Measure(availableSize);
this.totalContentHeight += child.DesiredSize.Height;
}
// The viewport should be as large as it can be.
this.viewport = availableSize;
// Remeasuring could invalidate the current viewport position. Mark it as dirty.
this.viewportPositionDirty = true;
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
// If there's no children, take all the space.
if (this.InternalChildren.Count == 0)
{
return finalSize;
}
// Arrange the viewport relative to the extent.
if (this.viewportPositionDirty)
{
PositionViewportAndExtent();
}
double nextDrawingPosition = firstItemOffset;
Rect arrangeInMe;
// From first item to end
for (int i = firstItem; i < InternalChildren.Count; i++)
{
UIElement item = InternalChildren[i];
arrangeInMe =
new Rect(0, nextDrawingPosition,
item.DesiredSize.Width,
item.DesiredSize.Height);
item.Arrange(arrangeInMe);
nextDrawingPosition += item.DesiredSize.Height;
}
// From child zero to firstItem - 1
for (int i = 0; i < firstItem; i++)
{
UIElement item = InternalChildren[i];
arrangeInMe =
new Rect(0, nextDrawingPosition,
item.DesiredSize.Width,
item.DesiredSize.Height);
item.Arrange(arrangeInMe);
nextDrawingPosition += item.DesiredSize.Height;
}
return finalSize;
}
#endregion
#region Viewport and Extent Methods
private void PositionViewportAndExtent()
{
// If the items all fit in the viewport at the same time,
// disable scrolling and center the items.
if (totalContentHeight < viewport.Height)
{
extent = new Size(viewport.Width, totalContentHeight);
SetViewport(0);
firstItemOffset = (viewport.Height - totalContentHeight) / 2;
CanVerticallyScroll = false;
}
// Otherwise, extend the extent past both ends of the viewport
// so there will be plenty of space in which to scroll the items.
else
{
// Make sure the extent is plenty large.
extent = new Size(viewport.Width, Math.Max(1000000, totalContentHeight * 15));
SetViewport((extent.Height - viewport.Height) / 2);
firstItemOffset = (extent.Height - totalContentHeight) / 2;
CanVerticallyScroll = true;
}
viewportPositionDirty = false;
// Because the offset was changed, the current scroll info isn't valid anymore.
if (owner != null)
{
owner.InvalidateScrollInfo();
}
}
private void SetViewport(double newOffset)
{
// Validate the input.
if (newOffset < 0 || viewport.Height >= extent.Height)
{
newOffset = 0;
}
if (newOffset + viewport.Height >= extent.Height)
{
newOffset = extent.Height - viewport.Height;
}
// Value is validated, so use it.
viewportOffset.Y = newOffset;
// Adjust the transform to display based on the new offset.
((TranslateTransform)this.RenderTransform).Y = -newOffset;
// Balance the content around the viewport.
double firstItemBottom =
firstItemOffset + InternalChildren[firstItem].DesiredSize.Height;
int lastItemIndex =
firstItem <= 0 ? InternalChildren.Count - 1 : firstItem - 1;
double lastItemTop =
firstItemOffset + totalContentHeight - InternalChildren[lastItemIndex].DesiredSize.Height;
// Move items as needed.
if (viewportOffset.Y < firstItemBottom)
{
MoveItemsFromBottomToTop();
}
if (viewportOffset.Y + ViewportHeight > lastItemTop)
{
MoveItemsFromTopToBottom();
}
}
// Called by various other methods.
private void ScrollContent(double adjustment)
{
SetViewport(viewportOffset.Y - adjustment);
}
#endregion
#region Move Item Methods
private void MoveItemsFromBottomToTop()
{
// Move from the left to the right
// until the first item that is not in the viewport.
int index = firstItem;
double itemTopOffset = firstItemOffset;
int itemsInViewport = 0;
// Step through the items from the top, and
// count how many items are in the viewport.
while (itemTopOffset < viewportOffset.Y + ViewportHeight)
{
itemsInViewport++;
itemTopOffset += InternalChildren[index].DesiredSize.Height;
index = index >= InternalChildren.Count - 1 ? 0 : index + 1;
}
// The items that are not in the viewport should be distributed
// so there are some on either side of the viewport.
// It's likely that the viewport will continue to be moved in the
// direction it is currently being moved.
// Move 2/3 of the items, and leave 1/3 where they are.
int itemsNotInViewport = InternalChildren.Count - itemsInViewport;
int itemsToRemainInPlace = itemsNotInViewport / 3;
// Skip past the items that will remain in place.
for (int i = 0; i < itemsToRemainInPlace; i++)
{
itemTopOffset += InternalChildren[index].DesiredSize.Height;
index = index >= InternalChildren.Count - 1 ? 0 : index + 1;
}
// Find the amount by which firstItemOffset needs to be adjusted.
double movedHeight = firstItemOffset + totalContentHeight - itemTopOffset;
// Change the first item index, and adjust the offset to match.
firstItemOffset -= movedHeight;
firstItem = index;
// Need to redraw the items.
InvalidateVisual();
}
private void MoveItemsFromTopToBottom()
{
// Move from the top to bottom
// until the first item that is not in the viewport.
int index = firstItem <= 0 ? InternalChildren.Count - 1 : firstItem - 1;
double itemBottomOffset = firstItemOffset + totalContentHeight;
int itemsInViewport = 0;
// Step through the items from the bottom, and
// count how many items are in the viewport.
while (itemBottomOffset > viewportOffset.Y)
{
itemsInViewport++;
itemBottomOffset -= InternalChildren[index].DesiredSize.Height;
index = index <= 0 ? InternalChildren.Count - 1 : index - 1;
}
// The items that are not in the viewport should be distributed
// so there are some on either side of the viewport.
// It's likely that the viewport will continue to be moved in the
// direction it is currently being moved.
// Move 2/3 of the items, and leave 1/3 where they are.
int itemsNotInViewport = InternalChildren.Count - itemsInViewport;
int itemsToRemain = itemsNotInViewport / 3;
// Skip past the items that will remain in place.
for (int i = 0; i < itemsToRemain; i++)
{
itemBottomOffset -= InternalChildren[index].DesiredSize.Height;
index = index <= 0 ? InternalChildren.Count - 1 : index - 1;
}
// Find the amount by which firstItemOffset needs to be adjusted.
double movedHeight = itemBottomOffset - firstItemOffset;
// Change the first item index, and adjust the offset to match.
firstItemOffset += movedHeight;
firstItem = index >= InternalChildren.Count - 1 ? 0 : index + 1;
// Need to redraw the items.
InvalidateVisual();
}
#endregion
} // end Class LoopPanelVertical
}
#endregion