Windows UWP - 如何在 ContentTemplate 中以编程方式滚动 ListView [英] Windows UWP - How to programmatically scroll ListView in ContentTemplate

查看:23
本文介绍了Windows UWP - 如何在 ContentTemplate 中以编程方式滚动 ListView的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

左侧有聊天列表,右侧有给定聊天的消息.

I have a list of chats on the left and messages for a given chat on the right.

我想让 MessageList 在出现或更新其数据时滚动到底部.我该怎么做?

I want to have the MessageList to scroll to the bottom whenever it appears or gets its data updated. How can I do this?

我的代码基于 Microsoft 的主/详细视图示例:https://github.com/Microsoft/Windows-universal-samples/blob/master/Samples/XamlMasterDetail/cs/MasterDetailPage.xaml

My code is based off of Microsoft's Master/Detail view example: https://github.com/Microsoft/Windows-universal-samples/blob/master/Samples/XamlMasterDetail/cs/MasterDetailPage.xaml

xaml 页面:

<Page
x:Class="MyApp.Pages.ChatsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp.Pages"
xmlns:data="using:MyApp.Model.Profile"
xmlns:vm="using:MyApp.ViewModel"
xmlns:util="using:MyApp.Util"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<Page.Transitions>
    <TransitionCollection>
        <NavigationThemeTransition />
    </TransitionCollection>
</Page.Transitions>


<Page.Resources>

   <util:BoolToVisibilityConverter x:Key="BoolToVisConverter" />

    <!--CollectionViewSource x:Name="Chats"
        Source="{x:Bind ViewModel}"/>
    <CollectionViewSource x:Name="Chat"
        Source="{Binding ChatViewModel, Source={StaticResource Chats}}"/>
    <CollectionViewSource x:Name="Messages"
        Source="{Binding MessageViewModel, Source={StaticResource Chat}}"/-->

    <DataTemplate x:Key="MasterListViewItemTemplate" >
        <Grid Margin="0,11,0,13" BorderBrush="Gray" BorderThickness="2">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

            <TextBlock Text="{Binding ChatName}" Style="{ThemeResource ChatListTitleStyle}" />

            <TextBlock
                Text="{Binding LastMessage}"
                Grid.Row="1"
                MaxLines="1"
                Style="{ThemeResource ChatListTextStyle}" />
            <TextBlock
                Text="{Binding LastSender}"
                Grid.Column="1"
                Margin="12,1,0,0"
                Style="{ThemeResource ChatListLastSenderStyle}" />
        </Grid>
    </DataTemplate>

    <DataTemplate x:Key="DetailContentTemplate">

        <ListView x:Name="MessageList" ItemsSource="{Binding Messages}" ScrollViewer.VerticalScrollMode="Auto">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel BorderBrush="Black" BorderThickness="1" Padding="1">
                        <TextBlock Text="{Binding Message}" Style="{StaticResource NewsfeedTextStyle}"/>
                        <Image Visibility="{Binding Path=IsPhoto, Converter={StaticResource BoolToVisConverter} }" Source="{Binding Photo}" />
                        <Image Visibility="{Binding Path=IsReaction, Converter={StaticResource BoolToVisConverter} }" Width="200" Height="200" Source="{Binding Reaction}" />
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Sender}" Style="{StaticResource NewsfeedTimestampStyle}" Margin="1"/>
                            <TextBlock Text="{Binding SentTime}" Style="{StaticResource NewsfeedTimestampStyle}" Margin="1"/>
                        </StackPanel>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </DataTemplate>

</Page.Resources>

<Grid x:Name="LayoutRoot" Loaded="LayoutRoot_Loaded">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="AdaptiveStates" CurrentStateChanged="AdaptiveStates_CurrentStateChanged">
            <VisualState x:Name="DefaultState">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="720" />
                </VisualState.StateTriggers>
            </VisualState>

            <VisualState x:Name="NarrowState">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="0" />
                </VisualState.StateTriggers>

                <VisualState.Setters>
                    <Setter Target="MasterColumn.Width" Value="*" />
                    <Setter Target="DetailColumn.Width" Value="0" />
                    <Setter Target="MasterListView.SelectionMode" Value="None" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
        <ColumnDefinition x:Name="MasterColumn" Width="320" />
        <ColumnDefinition x:Name="DetailColumn" Width="*" />
    </Grid.ColumnDefinitions>

    <TextBlock
        Text="Chats"
        Margin="12,8,8,8"
        Style="{ThemeResource TitleTextBlockStyle}" />

    <ListView
        x:Name="MasterListView"
        Grid.Row="1"
        ItemContainerTransitions="{x:Null}"
        ItemTemplate="{StaticResource MasterListViewItemTemplate}"
        Background="{StaticResource ApplicationPageBackgroundThemeBrush}"
        IsItemClickEnabled="True"
        ItemClick="MasterListView_ItemClick">
        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
            </Style>
        </ListView.ItemContainerStyle>
    </ListView>

    <ContentPresenter
        x:Name="DetailContentPresenter"
        Grid.Column="1"
        Grid.RowSpan="2"
        BorderThickness="1,0,0,0"
        Padding="24,0"
        BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
        Content="{x:Bind MasterListView.SelectedItem, Mode=OneWay}"            
        ContentTemplate="{StaticResource DetailContentTemplate}">
        <ContentPresenter.ContentTransitions>
            <!-- Empty by default. See MasterListView_ItemClick -->
            <TransitionCollection />
        </ContentPresenter.ContentTransitions>
    </ContentPresenter>
</Grid>

推荐答案

我认为关键是你的 ListView 位于 ContentPresenter 的 ContentTemplate.

I think it's the key point that your ListView is inside of the ContentTemplate of ContentPresenter.

通常我们可以使用ListViewBase.ScrollIntoView(Object) 方法 方法将 ListView 滚动到特定项目,但是当 ListViewDataTemplate 内部时,它是未公开的.这里有一个方法,我们可以使用 VisualTreeHelper 得到这个 ListView:

Usually we can use ListViewBase.ScrollIntoView(Object) method method to scroll ListView to the specific item, but when the ListView is inside of DataTemplate, it is unexposed. Here is a method, we can use VisualTreeHelper to get this ListView:

public static T FindChildOfType<T>(DependencyObject root) where T : class
{
    var queue = new Queue<DependencyObject>();
    queue.Enqueue(root);
    while (queue.Count > 0)
    {
        DependencyObject current = queue.Dequeue();
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++)
        {
            var child = VisualTreeHelper.GetChild(current, i);
            var typedChild = child as T;
            if (typedChild != null)
            {
                return typedChild;
            }
            queue.Enqueue(child);
        }
    }
    return null;
}

我的样本是这样的:

<Grid.ColumnDefinitions>
    <ColumnDefinition x:Name="MasterColumn" Width="320" />
    <ColumnDefinition x:Name="DetailColumn" Width="*" />
</Grid.ColumnDefinitions>

<ListView x:Name="MasterListView" Grid.Column="0" ItemsSource="{x:Bind ChatList}" SelectionChanged="MasterListView_SelectionChanged">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:ChatEntity">
            <TextBlock Text="{x:Bind Member}" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

<ContentPresenter x:Name="DetailContentPresenter" Grid.Column="1"
                  Content="{x:Bind MasterListView.SelectedItem, Mode=OneWay}">
    <ContentPresenter.ContentTemplate>
        <DataTemplate x:DataType="local:ChatEntity">
            <Grid>
                <Grid.Resources>
                    <DataTemplate x:Key="FromMessageDataTemplate">
                        <StackPanel Orientation="Horizontal" FlowDirection="LeftToRight">
                            <TextBlock Text="{Binding Member}" Width="30" Foreground="Blue" FontWeight="Bold" />
                            <TextBlock Text=":" Width="10" Foreground="Blue" FontWeight="Bold" />
                            <TextBlock Text="{Binding Content}" Foreground="Red" />
                        </StackPanel>
                    </DataTemplate>
                    <DataTemplate x:Key="ToMessageDataTemplate">
                        <StackPanel Orientation="Horizontal" FlowDirection="RightToLeft">
                            <TextBlock Text="Me" Width="30" HorizontalAlignment="Right" Foreground="Blue" FontWeight="Bold" />
                            <TextBlock Text=":" Width="10" HorizontalAlignment="Right" Foreground="Blue" FontWeight="Bold" />
                            <TextBlock Text="{Binding Content}" HorizontalAlignment="Right" Foreground="Green" />
                        </StackPanel>
                    </DataTemplate>
                    <local:ChatDataTemplateSelector x:Key="ChatDataTemplateSelector"
                                MessageFromTemplate="{StaticResource FromMessageDataTemplate}"
                                MessageToTemplate="{StaticResource ToMessageDataTemplate}" />
                </Grid.Resources>
                <ListView ItemsSource="{x:Bind MessageList}" ItemTemplateSelector="{StaticResource ChatDataTemplateSelector}">
                    <ListView.ItemContainerStyle>
                        <Style TargetType="ListViewItem">
                            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                        </Style>
                    </ListView.ItemContainerStyle>
                </ListView>
            </Grid>
        </DataTemplate>
    </ContentPresenter.ContentTemplate>
</ContentPresenter>

ChatEntity 类和 MessageEntity 类是这样的:

The ChatEntity class and MessageEntity class are like this:

public class ChatEntity
{
    public string Member { get; set; }
    public ObservableCollection<MessageEntity> MessageList { get; set; }
}

public class MessageEntity
{
    public enum MsgType
    {
        From,
        To
    }

    public string Member { get; set; }
    public string Content { get; set; }
    public MsgType MessageType { get; set; }
}

和我的 ChatDataTemplateSelector 是这样的:

public class ChatDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate MessageFromTemplate { get; set; }
    public DataTemplate MessageToTemplate { get; set; }

    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
    {
        MessageEntity msg = item as MessageEntity;
        if (msg != null)
        {
            if (msg.MessageType == MessageEntity.MsgType.From)
                return MessageFromTemplate;
            else
                return MessageToTemplate;
        }
        return null;
    }
}

首先我在左边的ListView中加载了ChatList,在左边的ListViewSelectionChanged事件中, 我加载了 MessageList,这将确保 MessageList 被更新.就像每次选择左侧 ListView 中的项目时,手动刷新右侧 ListViewObservableCollection(MessageList).但是您也可以在其他时间向 MessageList 添加数据,并在有新消息时添加数据.ObservableCollection 可以自动刷新.这是我的代码:

Firstly I loaded the ChatList in the left ListView, and in the SelectionChanged event of the left ListView, I loaded the MessageList, this will ensure the MessageList be updated. It's like manually refreshing ObservableCollection(MessageList) for the right ListView each time you selected a item in the left ListView. But you can also add data to the MessageList in other time, and add data to it whenever there is a new message. The ObservableCollection can automatically get refresh. Here is my code:

private ObservableCollection<MessageEntity> messageList;
private ObservableCollection<ChatEntity> ChatList;

public MainPage()
{
    this.InitializeComponent();
    messageList = new ObservableCollection<MessageEntity>();
    ChatList = new ObservableCollection<ChatEntity>();
}

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    ChatList.Add(new ChatEntity { Member = "Tom", MessageList = messageList });
    ChatList.Add(new ChatEntity { Member = "Peter" });
    ChatList.Add(new ChatEntity { Member = "Clark" });
}

private void MasterListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    messageList.Clear();
    for (int i = 0; i < 100; i++)
    {
        if (i % 2 == 0)
            messageList.Add(new MessageEntity { Member = "Tom", Content = "Hello!", MessageType = MessageEntity.MsgType.From });
        else
            messageList.Add(new MessageEntity { Content = "World!", MessageType = MessageEntity.MsgType.To });
    }
    var listView = FindChildOfType<ListView>(DetailContentPresenter);
    listView.ScrollIntoView(messageList.Last());
}

我的样本中的数据都是假的.该示例看起来有点复杂,但实际上非常简单,只需使用VisualTreeHelper 找到ListView 并使用其ScrollIntoView 方法滚动到最后一项.

Data in my sample are all fake. The sample looks a little complex, but it's actually very simple, just use VisualTreeHelper to find the ListView and use its ScrollIntoView method to scroll to the last item.

这篇关于Windows UWP - 如何在 ContentTemplate 中以编程方式滚动 ListView的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆