WPF TreeView - Как прокрутить, чтобы видна развернутая ветка - PullRequest
23 голосов
/ 11 февраля 2010

Когда я раскрываю элементы в виде дерева так, что необходима прокрутка, появляется полоса прокрутки. Тем не менее, он не прокручивается вниз для вновь развернутой ветви элементов - они обрезаются в нижней части элемента управления. Поэтому, продолжая расширять элементы в нижней части дерева, я вынужден вручную прокручивать страницу вниз, чтобы увидеть новых детей. У кого-нибудь есть предложение, как сделать так, чтобы он автоматически прокручивался, чтобы показать новые расширенные элементы?

Ответы [ 6 ]

17 голосов
/ 29 февраля 2012

Вы можете использовать простой EventSetter в стиле TreeViewItem, чтобы вызывать обработчик событий, когда элемент выбран. Затем вызовите 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;  
    }
}
17 голосов
/ 11 февраля 2010

В TreeView обработайте событие TreeViewItem.Expanded (вы можете сделать это на уровне TreeView из-за всплытия событий).В расширенном обработчике вызовите BringIntoView для TreeViewItem, вызвавшего событие.

Вам может понадобиться немного проб и ошибок, чтобы получить доступ к TreeViewItem в коде обработчика события.Я думаю (не проверял), что аргумент отправителя вашего расширенного обработчика событий будет TreeView (так как именно там подключен обработчик событий), а не TreeViewItem.И e.Source или e.OriginalSource могут быть элементом в шаблоне данных TreeViewItem.Поэтому вам может понадобиться использовать VisualTreeHelper, чтобы пройтись по визуальному дереву и найти TreeViewItem.Но если вы используете отладчик для проверки отправителя и RoutedEventArgs, это будет тривиально, чтобы выяснить.

(Если вы можете заставить это работать и хотите связать его, так что вам не нужноприкрепите один и тот же обработчик событий к каждому TreeView, должно быть легко инкапсулировать его как присоединенное поведение , что позволит вам применять его декларативно, в том числе через стиль.)

15 голосов
/ 30 апреля 2010

Использование свойства зависимости для триггера 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();
}
2 голосов
/ 11 февраля 2010

Благодаря ответу 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();
}
1 голос
/ 10 октября 2018

Я изменил ответ Джареда в сочетании со следующей стратегией: https://stackoverflow.com/a/42238409/2477582

Основным преимуществом является то, что нет n вызовов BringIntoView () для n потомков. Существует только один вызов BringIntoView для области, охватывающей все высоты ребенка.

Кроме того, цель упомянутой темы также реализована. Но эта часть может быть удалена, если нежелательна.

/// <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>
0 голосов
/ 27 февраля 2019

У меня работал простой приемник событий на дереве:

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