3

I'm looking to create a scrolling surfacelistbox which automatically snaps into a position after a drag is finished so that the center item on the screen is centered itself in the viewport.

I've gotten the center item, but now as usual the way that WPF deals with sizes, screen positions, and offsets has me perplexed.

At the moment I've chosen to subscribe to the SurfaceScrollViewer's ManipulationCompleted event, as that seems to consistently fire after I've finished a scroll gesture (whereas the ScrollChanged event tends to fire early).

void ManipCompleted(object sender, ManipulationCompletedEventArgs e)
{
    FocusTaker.Focus(); //reset focus to a dummy element
    List<FrameworkElement> visibleElements = new List<FrameworkElement>();
    for (int i = 0; i < List.Items.Count; i++)
    {
        SurfaceListBoxItem item = List.ItemContainerGenerator.ContainerFromIndex(i) as SurfaceListBoxItem;
        if (ViewportHelper.IsInViewport(item) && (List.Items[i] as string != "Dummy"))
        {
            FrameworkElement el = item as FrameworkElement;
            visibleElements.Add(el);
        }
    }

    int centerItemIdx = visibleElements.Count / 2;
    FrameworkElement centerItem = visibleElements[centerItemIdx];

    double center = ss.ViewportWidth / 2;

    //ss is the SurfaceScrollViewer
    Point itemPosition = centerItem.TransformToAncestor(ss).Transform(new Point(0, 0));

    double desiredOffset = ss.HorizontalOffset + (center - itemPosition.X);

    ss.ScrollToHorizontalOffset(desiredOffset); 

    centerItem.Focus(); //this also doesn't seem to work, but whatever.
}

The list snaps, but where it snaps seems to be somewhat chaotic. I have a line down the center of the screen, and sometimes it looks right down the middle of the item, but other times it's off to the side or even between items. Can't quite nail it down, but it seems that the first and fourth quartile of the list work well, but the second and third are progressively more off toward the center.

Just looking for some help on how to use positioning in WPF. All of the relativity and the difference between percentage-based coordinates and 'screen-unit' coordinates has me somewhat confused at this point.

4

1 回答 1

2

After a lot of trial and error I ended up with this:

void ManipCompleted(object sender, ManipulationCompletedEventArgs e)
{
    FocusTaker.Focus(); //reset focus
    List<FrameworkElement> visibleElements = new List<FrameworkElement>();
    for (int i = 0; i < List.Items.Count; i++)
    {
        SurfaceListBoxItem item = List.ItemContainerGenerator.ContainerFromIndex(i) as SurfaceListBoxItem;
        if (ViewportHelper.IsInViewport(item))
        {
            FrameworkElement el = item as FrameworkElement;
            visibleElements.Add(el);
        }
    }

    Window window = Window.GetWindow(this);

    double center = ss.ViewportWidth / 2;

    double closestCenterOffset = double.MaxValue;
    FrameworkElement centerItem = visibleElements[0];
    foreach (FrameworkElement el in visibleElements)
    {
        double centerOffset = Math.Abs(el.TransformToAncestor(window).Transform(new Point(0, 0)).X + (el.ActualWidth / 2) - center);
        if (centerOffset < closestCenterOffset)
        {
            closestCenterOffset = centerOffset;
            centerItem = el;
        }
    }                        

    Point itemPosition = centerItem.TransformToAncestor(window).Transform(new Point(0, 0));

    double desiredOffset = ss.HorizontalOffset - (center - itemPosition.X) + (centerItem.ActualWidth / 2);

    ss.ScrollToHorizontalOffset(desiredOffset);

    centerItem.Focus();
}

This block of code effectively determines which visible list element is overlapping the center line of the list and snaps that element to the exact center position. The snapping is a little abrupt, so I'll have to look into some kind of animation, but otherwise I'm fairly happy with it! I'll probably use something from here for animations: http://blogs.msdn.com/b/delay/archive/2009/08/04/scrolling-so-smooth-like-the-butter-on-a-muffin-how-to-animate-the-horizontal-verticaloffset-properties-of-a-scrollviewer.aspx

Edit: Well that didn't take long. I expanded the ScrollViewerOffsetMediator to include HorizontalOffset and then simply created the animation as suggested in the above post. Works like a charm. Hope this helps someone eventually.

Edit2: Here's the full code for SnapList:

SnapList.xaml

SnapList.xaml.cs

Note that I got pretty lazy as this project went on an hard-coded some of it. Some discretion will be needed to determine what you do and don't want from this code. Still, I think this should work pretty well as a starting point for anyone who wants this functionality.

The code has also changed from what I pasted above; I found that using Windows.GetWindow gave bad results when the list was housed in a control that could move. I made it so you can assign a control for your movement to be relative to (recommended that be the control just above your list in the hierarchy). I think a few other things changed as well; I've added a lot of customization options including being able to define a custom focal point for the list.

于 2012-08-06T17:45:19.733 回答