Включение ScrollViewer HorizontalSnapPoints с привязываемой коллекцией

Я пытаюсь создать такой же опыт, как в ScrollViewerSample из примеров Windows 8 SDK, чтобы иметь возможность привязываться к элементам в ScrollViewer при прокрутке влево и вправо. Реализация из примера (который работает) выглядит следующим образом:

<code><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>
</code>

Единственная разница с моей желаемой реализацией состоит в том, что я не хочу StackPanel с элементами внутри, но что-то, что я могу связать. Я пытаюсь сделать это с помощью ItemsControl, но по какой-то причине поведение Snap не срабатывает:

<code><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>
</code>

Предложения будут с благодарностью!

Благодаря Денису, я использовал следующий стиль в ItemsControl и полностью удалил ScrollViewer и встроенный ItemsPanelTemplate:

<code><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>
</code>

Ответы на вопрос(2)

вот самый простой (и автономный) пример для горизонтального 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;
        }
    }

Об этом примере.

Большинство проверок и обработка ошибок опущены.

Если вы переопределяете стиль / шаблон ListView, части поиска VisualTree должны быть изменены соответствующим образом.

Я предпочел бы создать унаследованный от элемента управления ListView с этой логикой, чем использовать предоставленный пример как есть в реальном коде.Тот же код работает для вертикального случая (или обоих) с небольшими изменениями.Упоминаемая ошибка привязки - ошибка ScrollViewer при обработке случаев SnapPointsType.MandatorySingle и SnapPointsType.Mandatory. Появляется для предметов с нефиксированными размерами

.

 MB.09 мар. 2016 г., 04:40
Спасибо за этот образец, хорошо иметь его по этому важному вопросу. К вашему сведению, код не нужен вообще. Вы можете использовать стиль, чтобы установить все свойства, которые вы устанавливаете в коде. (Я не знаю об ошибках, которые вы упоминаете, хотя)
Решение Вопроса

ь сложно. Чтобы точки привязки работали, непосредственный потомок ScrollViewer должен реализовывать интерфейс IScrollSnapPointsInfo. ItemsControl не реализует IScrollSnapPointsInfo, и, следовательно, вы не увидите поведение привязки.

Чтобы обойти эту проблему, у вас есть несколько вариантов:

Создайте пользовательский класс, производный от ItemsControl, и реализуйте интерфейс IScrollSnapPointsInfo.Создайте пользовательский стиль для элемента управления и установите свойство HorizontalSnapPointsType в ScrollViewer внутри стиля.

Я реализовал прежний подход и могу подтвердить, что он работает, но в вашем случае лучше выбрать пользовательский стиль.

 yalematta25 апр. 2015 г., 09:53
Можно ли привести пример, пожалуйста?

Ваш ответ на вопрос