Windows UWP - How to programmatically scroll ListView in ContentTemplate

烈酒焚心 提交于 2019-12-24 06:18:21

问题


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

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

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

The xaml page:

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


回答1:


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

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;
}

My sample is like this:

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

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; }
}

and my ChatDataTemplateSelector is like this:

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;
    }
}

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());
}

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.



来源:https://stackoverflow.com/questions/37711185/windows-uwp-how-to-programmatically-scroll-listview-in-contenttemplate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!