Enabling ScrollViewer HorizontalSnapPoints with bindable collection

后端 未结 2 757
情歌与酒
情歌与酒 2021-02-04 09:13

I\'m trying to create a similar experience as in the ScrollViewerSample from the Windows 8 SDK samples to be able to snap to the items inside a ScrollViewer when scrolling left

2条回答
  •  说谎
    说谎 (楼主)
    2021-02-04 10:08

    Ok, here is the simplest (and standalone) example for horizontal ListView with binded items and correctly working snapping (see comments in following code).

    xaml:

        
            
            
                
                    
                
            
            
            
                
                    
                
            
        
    

    background code:

        private void YourListView_OnLoaded(object sender, RoutedEventArgs e)
        {
            //get ListView
            var yourList = sender as ListView;
    
            //*** yourList style-based changes ***
            //see Style here https://msdn.microsoft.com/en-us/library/windows/apps/mt299137.aspx
    
            //** Change orientation of scrollviewer (name in the Style "ScrollViewer") **
            //1. get scrollviewer (child element of yourList)
            var sv = GetFirstChildDependencyObjectOfType(yourList);
    
            //2. enable ScrollViewer horizontal scrolling
            sv.HorizontalScrollMode =ScrollMode.Auto;
            sv.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
            sv.IsHorizontalRailEnabled = true;
    
            //3. disable ScrollViewer vertical scrolling
            sv.VerticalScrollMode = ScrollMode.Disabled;
            sv.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
            sv.IsVerticalRailEnabled = false;
            // //no we have horizontally scrolling ListView
    
    
            //** Enable snapping **
            sv.HorizontalSnapPointsType = SnapPointsType.MandatorySingle; //or you can use SnapPointsType.Mandatory
            sv.HorizontalSnapPointsAlignment = SnapPointsAlignment.Near; //example works only for Near case, for other there should be some changes
            // //no we have horizontally scrolling ListView with snapping and "scroll last item into view" bug (about bug see here http://stackoverflow.com/questions/11084493/snapping-scrollviewer-in-windows-8-metro-in-wide-screens-not-snapping-to-the-las)
    
            //** fix "scroll last item into view" bug **
            //1. Get items presenter (child element of yourList)
            var ip = GetFirstChildDependencyObjectOfType(yourList);
            //  or   var ip = GetFirstChildDependencyObjectOfType(sv); //also will work here
    
            //2. Subscribe to its SizeChanged event
            ip.SizeChanged += ip_SizeChanged;
    
            //3. see the continuation in: private void ip_SizeChanged(object sender, SizeChangedEventArgs e)
        }
    
    
        public static T GetFirstChildDependencyObjectOfType(DependencyObject depObj) where T : DependencyObject
        {
            if (depObj is T) return depObj as T;
    
            for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                var child = VisualTreeHelper.GetChild(depObj, i);
    
                var result = GetFirstChildDependencyObjectOfType(child);
                if (result != null) return result;
            }
            return null;
        }
    
        private void ip_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            //3.0 if rev size is same as new - do nothing
            //here should be one more condition added by && but it is a little bit complicated and rare, so it is omitted.
            //The condition is: yourList.Items.Last() must be equal to (yourList.Items.Last() used on previous call of ip_SizeChanged)
            if (e.PreviousSize.Equals(e.NewSize)) return;
    
            //3.1 get sender as our ItemsPresenter
            var ip = sender as ItemsPresenter;
    
            //3.2 get the ItemsPresenter parent to get "viewable" width of ItemsPresenter that is ActualWidth of the Scrollviewer (it is scrollviewer actually, but we need just its ActualWidth so - as FrameworkElement is used)
            var sv = ip.Parent as FrameworkElement;
    
            //3.3 get parent ListView to be able to get elements Containers
            var yourList = GetParent(ip);
    
            //3.4 get last item ActualWidth
            var lastItem = yourList.Items.Last();
            var lastItemContainerObject = yourList.ContainerFromItem(lastItem);
            var lastItemContainer = lastItemContainerObject as FrameworkElement;
            if (lastItemContainer == null)
            {
                //NO lastItemContainer YET, wait for next call
                return;
            }
            var lastItemWidth = lastItemContainer.ActualWidth;
    
            //3.5 get margin fix value
            var rightMarginFixValue = sv.ActualWidth - lastItemWidth;
    
            //3.6. fix  "scroll last item into view" bug
            ip.Margin = new Thickness(ip.Margin.Left, 
                ip.Margin.Top, 
                ip.Margin.Right + rightMarginFixValue, //APPLY FIX
                ip.Margin.Bottom);
        }
    
        public static T GetParent(DependencyObject reference) where T : class
        {
            var depObj = VisualTreeHelper.GetParent(reference);
            if (depObj == null) return (T)null;
            while (true)
            {
                var depClass = depObj as T;
                if (depClass != null) return depClass;
                depObj = VisualTreeHelper.GetParent(depObj);
                if (depObj == null) return (T)null;
            }
        }
    

    About this example.

    1. Most of checks and errors handling is omitted.

    2. If you override ListView Style/Template, VisualTree search parts must be changed accordingly

    3. I'd rather create inherited from ListView control with this logic, than use provided example as-is in real code.
    4. Same code works for Vertical case (or both) with small changes.
    5. Mentioned snapping bug - ScrollViewer bug of handling SnapPointsType.MandatorySingle and SnapPointsType.Mandatory cases. It appears for items with not-fixed sizes

    .

提交回复
热议问题