13

我正在尝试创建与 Windows 8 SDK 示例中的 ScrollViewerSample 类似的体验,以便能够在左右滚动时捕捉到 ScrollViewer 内的项目。示例中的实现(有效)如下所示:

<ScrollViewer x:Name="scrollViewer" Width="480" Height="270"
              HorizontalAlignment="Left" VerticalAlignment="Top"
              VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto" 
              ZoomMode="Disabled" HorizontalSnapPointsType="Mandatory">
    <StackPanel Orientation="Horizontal">
        <Image Width="480" Height="270" AutomationProperties.Name="Image of a cliff" Source="images/cliff.jpg" Stretch="None"  HorizontalAlignment="Left" VerticalAlignment="Top"/>
        <Image Width="480" Height="270" AutomationProperties.Name="Image of Grapes" Source="images/grapes.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
        <Image Width="480" Height="270" AutomationProperties.Name="Image of Mount Rainier" Source="images/Rainier.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
        <Image Width="480" Height="270" AutomationProperties.Name="Image of a sunset" Source="images/sunset.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
        <Image Width="480" Height="270" AutomationProperties.Name="Image of a valley" Source="images/valley.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
    </StackPanel>
</ScrollViewer>

与我想要的实现的唯一区别是我不想要一个里面有项目的 StackPanel,但我可以绑定到一些东西。我正在尝试使用 ItemsControl 来完成此操作,但由于某种原因,Snap 行为没有生效:

<ScrollViewer x:Name="scrollViewer" Width="480" Height="270"
              HorizontalAlignment="Left" VerticalAlignment="Top"
              VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto" 
              ZoomMode="Disabled" HorizontalSnapPointsType="Mandatory">
    <ItemsControl>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <Image Width="480" Height="270" AutomationProperties.Name="Image of a cliff" Source="images/cliff.jpg" Stretch="None"  HorizontalAlignment="Left" VerticalAlignment="Top"/>
        <Image Width="480" Height="270" AutomationProperties.Name="Image of Grapes" Source="images/grapes.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
        <Image Width="480" Height="270" AutomationProperties.Name="Image of Mount Rainier" Source="images/Rainier.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
        <Image Width="480" Height="270" AutomationProperties.Name="Image of a sunset" Source="images/sunset.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
        <Image Width="480" Height="270" AutomationProperties.Name="Image of a valley" Source="images/valley.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
    </ItemsControl>
</ScrollViewer>

建议将不胜感激!


感谢 Denis,我最终在 ItemsControl 上使用了以下样式,并完全删除了 ScrollViewer 和内联 ItemsPanelTemplate:

<Style x:Key="ItemsControlStyle" TargetType="ItemsControl">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ItemsControl">
                <ScrollViewer Style="{StaticResource HorizontalScrollViewerStyle}" HorizontalSnapPointsType="Mandatory">
                    <ItemsPresenter />
                </ScrollViewer>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
4

2 回答 2

14

让快照点为绑定集合工作可能很棘手。要使 ScrollViewer 的直接子节点工作的快照点应实现 IScrollSnapPointsInfo 接口。ItemsControl 没有实现 IScrollSnapPointsInfo,因此您不会看到捕捉行为。

要解决此问题,您有几个选择:

  • 创建从 ItemsControl 派生的自定义类并实现 IScrollSnapPointsInfo 接口。
  • 为项目控件创建自定义样式并在样式内的 ScrollViewer 上设置 Horizo​​ntalSnapPointsType 属性。

我已经实现了以前的方法,并且可以确认它有效,但在您的情况下,自定义样式可能是更好的选择。

于 2012-04-11T20:05:43.767 回答
1

好的,这是水平 ListView 的最简单(和独立)示例,其中包含绑定项目和正确工作的捕捉(请参阅以下代码中的注释)。

xml

    <ListView x:Name="YourListView"
              ItemsSource="{x:Bind Path=Items}"
              Loaded="YourListView_OnLoaded">
        <!--Set items panel to horizontal-->
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <ItemsStackPanel Orientation="Horizontal" />
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
        <!--Some item template-->
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}"/>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

背景代码

    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<ScrollViewer>(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<ItemsPresenter>(yourList);
        //  or   var ip = GetFirstChildDependencyObjectOfType<ItemsPresenter>(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<T>(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<T>(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<ListView>(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<T>(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;
        }
    }

关于这个例子。

  1. 大多数检查和错误处理都被省略了。

  2. 如果您覆盖 ListView 样式/模板,则必须相应更改 VisualTree 搜索部分

  3. 我宁愿用这个逻辑创建继承自 ListView 控件,也不愿在实际代码中按原样使用提供的示例。
  4. 相同的代码适用于垂直大小写(或两者),只需稍作改动。
  5. 提到的捕捉错误 - 处理 SnapPointsType.MandatorySingle 和 SnapPointsType.Mandatory 情况的 ScrollViewer 错误。它出现在尺寸不固定的物品上

.

于 2016-01-25T14:42:10.353 回答