というネタを見つけたのでやってみます。久しぶりの WPF ネタ!因みにせっかくなので .NET Core 3.0 Preview 7 で VS2019 Preview 使ってやってみます。
表示用データはこれ!
using System; using System.Collections; using System.Collections.Generic; using System.Text; namespace TreeViewScrollSample { public class Person { public string Name { get; set; } public IEnumerable<Person> Children { get; set; } } }
画面が側はこんな感じでいきましょう。
<Window x:Class="TreeViewScrollSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:TreeViewScrollSample" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="MainWindow" Width="800" Height="450" mc:Ignorable="d"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <Button Click="ScrollButton_Click" Content="Scroll" /> <TreeView x:Name="treeView" Grid.Row="1"> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Children}"> <TextBlock Text="{Binding Name}" /> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Grid> </Window>
これで、こんな感じでコードを書けばスクロールします。
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; namespace TreeViewScrollSample { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { private IEnumerable<Person> People { get; } = Enumerable.Range(1, 100) .Select(x => new Person { Name = $"Tanaka {x}", Children = Enumerable.Range(1, 10) .Select(y => new Person { Name = $"Kimura {x}-{y}", }) .ToArray(), }) .ToArray(); public MainWindow() { InitializeComponent(); treeView.ItemsSource = People; } private async void ScrollButton_Click(object sender, RoutedEventArgs e) { // ContainersGenerated になるまで待つ Task waitUntilContainersGenerated(TreeViewItem container) { var tcs = new TaskCompletionSource<object>(container); void statusChanged(object _, EventArgs __) { if (container.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { tcs.SetResult(null); container.ItemContainerGenerator.StatusChanged -= statusChanged; return; } } container.ItemContainerGenerator.StatusChanged += statusChanged; return tcs.Task; } // 一番最後の最後の要素にスクロールする予定 var parent = People.Last(); var target = parent.Children.Last(); // ツリービュー直下の最後の要素のTreeViewItemを取得 var container = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(parent); // 開く container.IsExpanded = true; // 子の ItemContainerGenerator のステータスが Generated になるまで待つ if (container.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) { await waitUntilContainersGenerated(container); } // スクロール先を取得してスクロール var targetContainer = (TreeViewItem)container.ItemContainerGenerator.ContainerFromItem(target); targetContainer.BringIntoView(); } } }
実行すると…
動いた!!
ソースコードは GitHub に上げておきました。