ツリービューでアイテムを展開してスクロールが必要になると、スクロールバーが表示されます。ただし、新しく展開されたアイテムのブランチについては下にスクロールしません。コントロールの下部でトリミングされます。したがって、ツリーの下部にあるアイテムを展開し続けると、新しい子を表示するために手動で下にスクロールし続ける必要があります。新しく展開されたアイテムを表示するために自動的にスクロールする方法について誰か提案がありますか?
TreeViewで、TreeViewItem.Expandedイベントを処理します(イベントのバブリングのため、TreeViewレベルでこれを行うことができます)。 Expandedハンドラーで、イベントを発生させたTreeViewItemでBringIntoViewを呼び出します。
イベントハンドラーコードでTreeViewItemを取得するには、少し試行錯誤が必要な場合があります。 Expandedイベントハンドラーへの送信者引数は、TreeViewItemではなくTreeView(イベントハンドラーがアタッチされている場所であるため)になると思います(チェックしていません)。また、e.Sourceまたはe.OriginalSourceは、TreeViewItemのデータテンプレートの要素である可能性があります。そのため、VisualTreeHelperを使用してビジュアルツリーを上に移動し、TreeViewItemを見つける必要がある場合があります。ただし、デバッガーを使用して送信者とRoutedEventArgsを検査する場合、これを理解するのは簡単です。
(これを機能させることができ、すべてのTreeViewに同じイベントハンドラーをアタッチする必要がないようにバンドルしたい場合は、 アタッチされた動作 として簡単にカプセル化できるはずです。これにより、スタイルを介してなど、宣言的に適用できるようになります。)
TreeViewItemスタイルの単純なEventSetterを使用して、アイテムが選択されたときにイベントハンドラーを呼び出すことができます。次に、アイテムに対してBringIntoViewを呼び出します。
<TreeView >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="Selected" Handler="TreeViewSelectedItemChanged" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
private void TreeViewSelectedItemChanged(object sender, RoutedEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
if (item != null)
{
item.BringIntoView();
e.Handled = true;
}
}
IsSelectedトリガーの依存関係プロパティを使用します。
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="commands:TreeViewItemBehavior.BringIntoViewWhenSelected" Value="True" />
</Trigger>
</Style.Triggers>
依存関係プロパティのコードは次のとおりです。
public static bool GetBringIntoViewWhenSelected(TreeViewItem treeViewItem)
{
return (bool)treeViewItem.GetValue(BringIntoViewWhenSelectedProperty);
}
public static void SetBringIntoViewWhenSelected(TreeViewItem treeViewItem, bool value)
{
treeViewItem.SetValue(BringIntoViewWhenSelectedProperty, value);
}
public static readonly DependencyProperty BringIntoViewWhenSelectedProperty =
DependencyProperty.RegisterAttached("BringIntoViewWhenSelected", typeof(bool),
typeof(TreeViewItemBehavior), new UIPropertyMetadata(false, OnBringIntoViewWhenSelectedChanged));
static void OnBringIntoViewWhenSelectedChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
TreeViewItem item = depObj as TreeViewItem;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
item.BringIntoView();
}
ここからの戦略と組み合わせてJaredの答えを変更しました: https://stackoverflow.com/a/42238409/2477582
主な利点は、n
の子に対してBringIntoView()のn
呼び出しがないことです。子供の身長のすべてをカバーする領域に対してBringIntoViewを呼び出すのは1回だけです。
さらに、参照されたトピックの目的も実現されます。ただし、不要な場合は、この部分を削除できます。
/// <summary>Prevents automatic horizontal scrolling, while preserving automatic vertical scrolling and other side effects</summary>
/// <remarks>Source: https://stackoverflow.com/a/42238409/2477582 </remarks>
private void TreeViewItem_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
{
// Ignore re-entrant calls
if (m_SuppressRequestBringIntoView)
return;
// Cancel the current scroll attempt
e.Handled = true;
// Call BringIntoView using a rectangle that extends into "negative space" to the left of our
// actual control. This allows the vertical scrolling behaviour to operate without adversely
// affecting the current horizontal scroll position.
m_SuppressRequestBringIntoView = true;
try
{
TreeViewItem tvi = sender as TreeViewItem;
if (tvi != null)
{
// take care of children
int ll_ChildCount = VisualTreeHelper.GetChildrenCount(tvi);
double ll_Height = tvi.ActualHeight;
if (ll_ChildCount > 0)
{
FrameworkElement ll_LastChild = VisualTreeHelper.GetChild(tvi, ll_ChildCount - 1) as FrameworkElement;
ll_Height += ll_ChildCount * ll_LastChild.ActualHeight;
}
Rect newTargetRect = new Rect(-1000, 0, tvi.ActualWidth + 1000, ll_Height);
tvi.BringIntoView(newTargetRect);
}
}
catch (Exception ex)
{
m_Log.Debug("Error in TreeViewItem_RequestBringIntoView: " + ex.ToString());
}
m_SuppressRequestBringIntoView = false;
}
上記の解決策はこれと一緒に機能します:
/// <summary>Correctly handle programmatically selected items (needed due to the custom implementation of TreeViewItem_RequestBringIntoView)</summary>
/// <remarks>Source: https://stackoverflow.com/a/42238409/2477582 </remarks>
private void TreeViewItem_Selected(object sender, RoutedEventArgs e)
{
((TreeViewItem)sender).BringIntoView();
e.Handled = true;
}
この部分では、クリックするたびに要素を切り替えます。
/// <summary>Support for single click toggle</summary>
private void TreeViewItem_MouseUp(object sender, MouseButtonEventArgs e)
{
TreeViewItem tvi = null;
// Source may be TreeViewItem directly, or be a ContentPresenter
if (e.Source is TreeViewItem)
{
tvi = e.Source as TreeViewItem;
}
else if (e.Source is ContentPresenter)
{
tvi = (e.Source as ContentPresenter).TemplatedParent as TreeViewItem;
}
if (tvi == null || e.Handled) return;
tvi.IsExpanded = !tvi.IsExpanded;
e.Handled = true;
}
最後に、XAMLの部分:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<EventSetter Event="RequestBringIntoView" Handler="TreeViewItem_RequestBringIntoView" />
<EventSetter Event="Selected" Handler="TreeViewItem_Selected" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Itowlsonの回答のおかげで、これが私の両方のツリーで機能する拡張イベントハンドラーコードです。
private static void Tree_Expanded(object sender, RoutedEventArgs e)
{
// ignore checking, assume original source is treeviewitem
var treeViewItem = (TreeViewItem)e.OriginalSource;
var count = VisualTreeHelper.GetChildrenCount(treeViewItem);
for (int i = count - 1; i >= 0; --i)
{
var childItem = VisualTreeHelper.GetChild(treeViewItem, i);
((FrameworkElement)childItem).BringIntoView();
}
// do NOT call BringIntoView on the actual treeviewitem - this negates everything
//treeViewItem.BringIntoView();
}
ツリー上の単純なイベントリスナーが私のために働いた:
<TreeView Margin="10,40,10,10" Grid.Column="0" x:Name="treeView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" SelectedItemChanged="TreeView_SelectedItemChanged" />
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) {
if (e.NewValue == null)
return;
((TreeViewItem)e.NewValue).BringIntoView();
}