web-dev-qa-db-ja.com

WPFTreeView-展開されたブランチが表示されるようにスクロールする方法

ツリービューでアイテムを展開してスクロールが必要になると、スクロールバーが表示されます。ただし、新しく展開されたアイテムのブランチについては下にスクロールしません。コントロールの下部でトリミングされます。したがって、ツリーの下部にあるアイテムを展開し続けると、新しい子を表示するために手動で下にスクロールし続ける必要があります。新しく展開されたアイテムを表示するために自動的にスクロールする方法について誰か提案がありますか?

23
Jared

TreeViewで、TreeViewItem.Expandedイベントを処理します(イベントのバブリングのため、TreeViewレベルでこれを行うことができます)。 Expandedハンドラーで、イベントを発生させたTreeViewItemでBringIntoViewを呼び出します。

イベントハンドラーコードでTreeViewItemを取得するには、少し試行錯誤が必要な場合があります。 Expandedイベントハンドラーへの送信者引数は、TreeViewItemではなくTreeView(イベントハンドラーがアタッチされている場所であるため)になると思います(チェックしていません)。また、e.Sourceまたはe.OriginalSourceは、TreeViewItemのデータテンプレートの要素である可能性があります。そのため、VisualTreeHelperを使用してビジュアルツリーを上に移動し、TreeViewItemを見つける必要がある場合があります。ただし、デバッガーを使用して送信者とRoutedEventArgsを検査する場合、これを理解するのは簡単です。

(これを機能させることができ、すべてのTreeViewに同じイベントハンドラーをアタッチする必要がないようにバンドルしたい場合は、 アタッチされた動作 として簡単にカプセル化できるはずです。これにより、スタイルを介してなど、宣言的に適用できるようになります。)

19
itowlson

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;  
    }
}
18
Vinod.M.S

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

ここからの戦略と組み合わせて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>
2
Nicolas

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

ツリー上の単純なイベントリスナーが私のために働いた:

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