Windows UWP – 如何以编程方式在ContentTemplate中滚动ListView

我有一个左侧聊天列表和右侧给定聊天消息。

我想让MessageList在出现或者更新数据时滚动到底部。 我怎样才能做到这一点?

我的代码是基于微软的主/明细视图的例子: 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> 

Solutions Collecting From Web of "Windows UWP – 如何以编程方式在ContentTemplate中滚动ListView"

我认为这是您的ListViewContentPresenterContentTemplate中的关键点。

通常我们可以使用ListViewBase.ScrollIntoView(Object)方法将ListView滚动到特定的项目,但是当ListViewDataTemplate ,它是未曝光的。 这里是一个方法,我们可以使用VisualTreeHelper来获得这个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类是这样的:

 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选择一个项目时,为正确的ListView手动刷新ObservableCollectionMessageList )。 但是,您也可以在其他时间将数据添加到MessageList ,并在有新消息时向其添加数据。 ObservableCollection可以自动刷新。 这是我的代码:

 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方法滚动到最后一个项目。