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を使用してこれを達成しようとしていますが、何らかの理由でスナップ動作が開始されません。

<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の最も単純な(スタンドアロンの)例です(次のコードのコメントを参照)。

xaml

    <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 に答える