0

Why does my TreeViewItem does not wrap in my sample code?

enter image description here

Xaml:

<Window x:Class="WpfAppTestScrollViewBehavior.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfAppTestScrollViewBehavior"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowModel></local:MainWindowModel>
    </Window.DataContext>

    <TabControl>
        <TabItem Header="Test">
            <TreeView ItemsSource="{Binding Level1s, Mode=OneWay}" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                      Background="LightGoldenrodYellow">
                <TreeView.Resources>
                    <HierarchicalDataTemplate DataType="{x:Type local:Level1}"
                                            ItemsSource="{Binding Path=InstalledFontCollection}">
                        <Grid HorizontalAlignment="Stretch" Background="LightGreen">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"></ColumnDefinition>
                                <ColumnDefinition></ColumnDefinition>
                            </Grid.ColumnDefinitions>
                            <CheckBox Grid.Column="0" IsChecked="{Binding Path=IsSelected}"></CheckBox>
                            <TextBlock Grid.Column="1" Text="{Binding Path=Name}" 
                                        TextWrapping="Wrap" Margin="5,0,0,0"></TextBlock>
                        </Grid>
                    </HierarchicalDataTemplate>
                </TreeView.Resources>
            </TreeView>
        </TabItem>
    </TabControl>
</Window>

Code:

using System;
using System.Collections.Generic;

namespace WpfAppTestScrollViewBehavior
{
    public class MainWindowModel
    {
        public List<Level1> Level1s { get; } = new List<Level1>();

        public MainWindowModel()
        {
            Level1s.Add(new Level1());
        }
    }
}


using System;
using System.Drawing.Text;

namespace WpfAppTestScrollViewBehavior
{
    public class Level1
    {
        public bool IsSelected { get; set; }
        public string Name { get; set; } = "a very long name in order to test text wrapping functionnality";

        public InstalledFontCollection InstalledFontCollection { get; } = new InstalledFontCollection();
    }
}

Just to prevent quick wrong answers:

You can add this code and it works fine:

<TabItem Header="Test 2">
    <ScrollViewer HorizontalScrollBarVisibility="Disabled">
        <Grid HorizontalAlignment="Stretch" Background="LightPink">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"></ColumnDefinition>
                <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <CheckBox Grid.Column="0"></CheckBox>
            <TextBlock Grid.Column="1" Text="long name just for testing" 
                        TextWrapping="Wrap" Margin="5,0,0,0"></TextBlock>
        </Grid>
    </ScrollViewer>
</TabItem>

Results of previous working code (just as example of what i expect):

enter image description here

Eric Ouellet
  • 10,996
  • 11
  • 84
  • 119
  • take a look at the similar questions: [Q1](https://stackoverflow.com/questions/45776693/making-a-portion-of-a-treeviewitem-wrap), [Q2](https://stackoverflow.com/questions/42868421/treeviewitem-with-stretch) – ASh May 10 '18 at 07:27
  • @ASh, not exactly what I'm looking for. I almost got it. I should come with what I want today... if everything goes well. It will be generics and should apply to any cases of hierarchical template. – Eric Ouellet May 10 '18 at 13:13

3 Answers3

0

ColumnDefinition defaults to * so the TextBlock in column 1 will take all available space.

Try experimenting with MaxWidth like this: <ColumnDefinition Width="Auto" MaxWidth="100"/>

EDIT

If you don't want to use predefined Width or MaxWidth, bind the Width of the Grid to ActualWidth of TabControl like this:

<Grid Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=ActualWidth}" />

I just tested this approach and it wrapped the TextBlock fine and re-wrapped it as I resized the window... If the Text of the TextBlock has no margin relative to the right window border when Width is set this way you can fix that by setting the right margin of the TextBlock to 10 or more depending on the desired padding...

Dean Kuga
  • 11,878
  • 8
  • 54
  • 108
  • The grid width is suppose to be of its container width. If it would be the case, everything should work as expected. Col 1 should take all the available space of what grid.width - grid.col0.width left. The problem seems to come from something between the treeview-scrollview and my hierarchical-template where something tell to my grid (and textblock) that it could takes all the space it want. I do not want to fix any size (like MaxWidth). – Eric Ouellet May 08 '18 at 20:39
  • You can look at additional information at the end. The expected behavior is shown and there is no artifact or width to set. The problem com from the TreeView usage but I don't understand why and how to fix it properly. – Eric Ouellet May 08 '18 at 20:55
0

Since you have not specified Width it is giving available Width to the <Textblock>

Why does my TreeViewItem does not wrap in my sample code?

The text is being wrapped but because of unrestricted width available wrap is not seen

For example:

XAML: Width: 150 units TextWrapping = "NoWrap"

<TextBlock Grid.Column="1" Text="{Binding Path=Name}"   Width="150" Name="textblock1"
                                        TextWrapping="NoWrap" Margin="10,0,0,0"></TextBlock>

OUTPUT: enter image description here

XAML: Width: 150 units TextWrapping = "Wrap"

<TextBlock Grid.Column="1" Text="{Binding Path=Name}"   Width="150" Name="textblock1"
                                        TextWrapping="Wrap" Margin="10,0,0,0"></TextBlock>

OR

<Grid HorizontalAlignment="Stretch" Width="150" Background="LightGreen">
     public string Name { get; set; } = ;

enter image description here

Clint
  • 6,011
  • 1
  • 21
  • 28
  • It should work but not as expected. I should not be forced to set a width anywhere. In worse case, I should be able to bind the grid width to tree view container in some way. But there should be a better way to accomplish it. Please see the additional section at the end for more information. – Eric Ouellet May 08 '18 at 20:49
  • Yes the edit section was not there earlier, is there any other portion of xaml that has a dependency to this one ? – Clint May 08 '18 at 20:53
  • I added the portion at the end of the question only after I saw the 2 answers. There is no other code. The sample has been made in order to make it easy to find the answer (at least that was my intention). The original problem was detected in a huge project where there is a lots of windows. – Eric Ouellet May 08 '18 at 21:02
  • I might have found the information you need on another answer https://leecampbell.com/2009/01/ . https://stackoverflow.com/questions/11209515/how-to-wrap-textblock-content-in-treeview – Clint May 08 '18 at 21:34
  • Seems interesting. But also seems incomplete, at least what I saw. I will take a closer look tomorrow... I have to go. Thanks!!! – Eric Ouellet May 08 '18 at 21:41
  • I did not forget you. Your update is really nice but not perfect. I'm working on it right now and will update question when I will be happy with a solution. Thanks a lots for your great update. It helps a lot but i'm looking for a generic solution. – Eric Ouellet May 09 '18 at 17:45
0

I came up with a solution that is generic and seems to works for any cases whatever content is inside TreeViewItem. It could be applied as a behavior (I favor this in order to make it simpler to use).

One restriction of using this behavior is: Define the behavior as the last element in the TreeView xaml. By doing so it will support any additional ItemContainerStyle previously defined.

Behavior code:

using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using HQ.Wpf.Util;
using HQ.Wpf.Util.Converter;

namespace WpfAppTestScrollViewBehavior
{
    public class BehaviorTreeView : Behavior<TreeView>
    {
        public static readonly DependencyProperty ShowHorizontalScrollBarProperty = DependencyProperty.Register(
            "PropertyType", typeof(bool), typeof(BehaviorTreeView), new PropertyMetadata(true));

        /// <summary>
        /// Settting this poroperty to false will fake a TreeView-ScrollView ViewPort finite width. 
        /// TreeViewItem will be advise to Calculate their width according to the width of the 
        /// ViewPort of the ScrollView of the TreeView.
        /// </summary>
        public bool ShowHorizontalScrollBar
        {
            get { return (bool)GetValue(ShowHorizontalScrollBarProperty); }
            set { SetValue(ShowHorizontalScrollBarProperty, value); }
        }

        // ******************************************************************   
        protected override void OnAttached()
        {
            base.OnAttached();

            if (!ShowHorizontalScrollBar)
            {
                Style style = AssociatedObject.ItemContainerStyle ?? new Style(typeof(TreeViewItem));
                var eventSetter = new EventSetter(FrameworkElement.LoadedEvent, new RoutedEventHandler(this.TreeViewItemLoaded));
                style.Setters.Add(eventSetter);
                AssociatedObject.ItemContainerStyle = style;
            }
        }

        // ******************************************************************   
        private void TreeViewItemLoaded(object sender, RoutedEventArgs e)
        {
            var tvi = sender as TreeViewItem;
            var contentPresenter = tvi.FindFirstChildWithNameRecursive<ContentPresenter>("PART_Header");

            var treeView = tvi.GetVisualParentRecursive<TreeView>();

            double offsetX = contentPresenter.TransformToAncestor(treeView).Transform(new Point(0, 0)).X;

            var scrollViewer = treeView.FindVisualChild<ScrollViewer>();

            Binding binding = new Binding();
            binding.Source = scrollViewer;
            binding.Path = new PropertyPath(nameof(ScrollViewer.ViewportWidth));
            binding.Mode = BindingMode.OneWay;

            var converter = new NumericFixedNumberAddedConverter();
            binding.Converter = converter;
            binding.ConverterParameter = -offsetX;

            BindingOperations.SetBinding(contentPresenter, FrameworkElement.WidthProperty, binding);
        }

        // ******************************************************************   

    }
}

Behavior Dependencies:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;

namespace WpfAppTestScrollViewBehavior
{
    class NumericFixedNumberAddedConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            try
            {
                return (double) value + double.Parse(parameter.ToString());
            }
            catch (Exception ex)
            {
                Debug.Print(ex.ToString());
                return value;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace WpfAppTestScrollViewBehavior
{
    public static class UiUtility
    {
        // ******************************************************************
        public static Window GetTopLevelOwnerWindowOrMainAppWindow(Control usercontrol)
        {
            return GetTopLevelOwnerWindow(usercontrol) ?? Application.Current.MainWindow;
        }

        // ******************************************************************
        public static Window GetTopLevelOwnerWindowOrMainAppWindow(DependencyObject dependencyObject)
        {
            return GetTopLevelOwnerWindow(dependencyObject) ?? Application.Current.MainWindow;
        }

        // ******************************************************************
        public static Window GetTopLevelOwnerWindow(Control usercontrol)
        {
            return GetTopLevelOwnerWindow((DependencyObject)usercontrol);
        }

        // ******************************************************************
        public static Window GetTopLevelOwnerWindow(DependencyObject dependencyObject)
        {
            while (dependencyObject != null && !(dependencyObject is Window))
            {
                var dependencyObjectCopy = dependencyObject;
                dependencyObject = VisualTreeHelper.GetParent(dependencyObject);

                if (dependencyObject == null)
                {
                    dependencyObject = dependencyObjectCopy;
                    String propName = "DockSite";
                    PropertyInfo pi = dependencyObject.GetType().GetProperty(propName);
                    if (pi != null)
                    {
                        DependencyObject dependencyObjectTemp = null;
                        try
                        {
                            dependencyObjectTemp = dependencyObject.GetType().InvokeMember(propName,
                                BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public, null, dependencyObject, null) as DependencyObject;
                        }
                        catch (Exception)
                        {

                        }

                        if (dependencyObjectTemp != null)
                        {
                            dependencyObject = LogicalTreeHelper.GetParent(dependencyObjectTemp);
                        }
                        else
                        {
                            dependencyObject = LogicalTreeHelper.GetParent(dependencyObject);
                        }
                    }
                    else
                    {
                        dependencyObject = LogicalTreeHelper.GetParent(dependencyObject);
                    }
                }
            }

            return dependencyObject as Window;
        }

        // ******************************************************************
        public static T FindVisualParent<T>(DependencyObject element) where T : DependencyObject
        {
            var parent = element;
            while (parent != null)
            {
                var correctlyTyped = parent as T;
                if (correctlyTyped != null)
                {
                    return correctlyTyped;
                }

                parent = VisualTreeHelper.GetParent(parent) as DependencyObject;

            }
            return null;
        }

        // ******************************************************************
        public static bool IsParentOf(DependencyObject parent, DependencyObject child)
        {
            if (parent == null || child == null)
            {
                return false;
            }

            DependencyObject childParent = child;
            do
            {
                if (childParent == parent)
                {
                    return true;
                }

                childParent = VisualTreeHelper.GetParent(childParent);
            } while (childParent != null);

            return false;
        }

        // ******************************************************************
        public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
        {
            if (depObj != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                    if (child != null && child is T)
                    {
                        yield return (T)child;
                    }

                    foreach (T childOfChild in FindVisualChildren<T>(child))
                    {
                        yield return childOfChild;
                    }
                }
            }
        }

        // ******************************************************************
        public static DataGridColumnHeader GetColumnHeaderFromColumn(DataGrid dataGrid, DataGridColumn column)
        {
            // dataGrid is the name of your DataGrid. In this case Name="dataGrid"
            foreach (var columnHeader in FindVisualChildren<DataGridColumnHeader>(dataGrid))
            {
                if (columnHeader.Column == column)
                {
                    return columnHeader;
                }
            }
            return null;
        }

        // ******************************************************************
        public static void SafeInvoke(Action action)
        {
            if (Application.Current.Dispatcher.CheckAccess())
            {
                action();
            }
            else
            {
                Application.Current.Dispatcher.Invoke(action);
            }
        }

        // ******************************************************************
        public static void SafeBeginInvoke(Action action)
        {
            if (Application.Current.Dispatcher.CheckAccess())
            {
                action();
            }
            else
            {
                Application.Current.Dispatcher.BeginInvoke(action);
            }
        }

        // ******************************************************************
        public static void BindingRefresh(DependencyObject dependencyObject, DependencyProperty dependencyProperty)
        {
            BindingExpressionBase b = BindingOperations.GetBindingExpressionBase(dependencyObject, dependencyProperty);
            if (b != null)
            {
                b.UpdateTarget();
            }
        }

        // ******************************************************************
        /// <summary>
        /// Finds a Child of a given item in the visual tree. 
        /// </summary>
        /// <param name="parent">A direct parent of the queried item.</param>
        /// <typeparam name="T">The type of the queried item.</typeparam>
        /// <param name="childName">x:Name or Name of child. </param>
        /// <returns>The first parent item that matches the submitted type parameter. 
        /// If not matching item can be found, 
        /// a null parent is being returned.</returns>
        public static T FindChild<T>(DependencyObject depObj, string childName)
            where T : DependencyObject
        {
            // Confirm obj is valid. 
            if (depObj == null) return null;

            // success case
            if (depObj is T && ((FrameworkElement)depObj).Name == childName)
                return depObj as T;

            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(depObj, i);

                //DFS
                T obj = FindChild<T>(child, childName);

                if (obj != null)
                    return obj;
            }

            return null;
        }

        // ******************************************************************
        public static void DebugPrintControlParentHierarchy(object frameworkElement)
        {
            StringBuilder hierarchy = new StringBuilder();
            var fe = frameworkElement as FrameworkElement;
            while (fe != null)
            {
                hierarchy.Append(String.Format("{0} [{1}] ==> ", fe.GetType(), fe.Name));
                fe = VisualTreeHelper.GetParent(fe) as FrameworkElement;
            }

            hierarchy.Append("!TOP!");

            Debug.Print(hierarchy.ToString());
        }

        // ******************************************************************
        /// <summary>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static T FindVisualChild<T>(this DependencyObject obj) where T : DependencyObject
        {
            if (obj != null && obj is Visual)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                    var visualChild = child as T;
                    if (visualChild != null)
                        return visualChild;
                    else
                    {
                        T childOfChild = FindVisualChild<T>(child);
                        if (childOfChild != null)
                            return childOfChild;
                    }
                }
            }
            return null;
        }

        // ******************************************************************
        public static FrameworkElement GetVisualParent(this UIElement element)
        {
            DependencyObject parent = VisualTreeHelper.GetParent(element);
            do
            {
                var fe = parent as FrameworkElement;
                if (fe != null)
                {
                    return fe;
                }
                parent = VisualTreeHelper.GetParent(parent);
            } while (parent != null);

            return null;
        }

        // ******************************************************************
        public static void BringToFront(this Panel panel, UIElement element)
        {
            int maxIndex = 0;
            foreach (UIElement e in panel.Children)
            {
                maxIndex = Math.Max(maxIndex, Panel.GetZIndex(e));
            }

            Panel.SetZIndex(element, maxIndex + 1);
        }

        //// ******************************************************************
        ///// <summary>
        ///// Return the center point of an direct child of a Canvas (not yet tested)
        ///// </summary>
        ///// <param name=""></param>
        ///// <param name="elementRelativeTo">If elementRelativeTo == null, will use direct parent</param>
        ///// <returns></returns>
        //public static Point GetCanvasElementCenterPoint(this FrameworkElement element)
        //{
        //  return new Point(
        //      Canvas.GetLeft(element) + (element.ActualWidth / 2),
        //      Canvas.GetTop(element) + (element.ActualHeight / 2));
        //}

        // ******************************************************************
        public enum PointPositionVertical
        {
            Top,
            Center,
            Bottom
        }

        // ******************************************************************
        public enum PointPositionHorizontal
        {
            Left,
            Center,
            Right
        }

        // ******************************************************************
        public static Point GetChildCoordinate(this UIElement elementContainer, FrameworkElement childElement,
            PointPositionHorizontal pointPositionHorizontal = PointPositionHorizontal.Left,
            PointPositionVertical pointPositionVertical = PointPositionVertical.Top)
        {
            double x;
            switch (pointPositionHorizontal)
            {
                case PointPositionHorizontal.Center:
                    x = childElement.ActualWidth / 2;
                    break;
                case PointPositionHorizontal.Right:
                    x = childElement.ActualWidth;
                    break;
                default:
                    x = 0;
                    break;
            }

            double y;
            switch (pointPositionVertical)
            {
                case PointPositionVertical.Center:
                    y = childElement.ActualHeight / 2;
                    break;
                case PointPositionVertical.Bottom:
                    y = childElement.ActualHeight;
                    break;
                default:
                    y = 0;
                    break;
            }

            return childElement.TranslatePoint(new Point(x, y), elementContainer);
        }

        // ******************************************************************
        public static void ApplyToEachVisualChildRecursively(this DependencyObject obj, Action<DependencyObject> action)
        {
            if (obj != null && obj is Visual)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                    if (child != null)
                    {
                        action(child);
                        ApplyToEachVisualChildRecursively(child, action);
                    }
                }
            }
        }

        // ******************************************************************
        public static T GetVisualParentRecursive<T>(this DependencyObject obj) where T : class
        {
            var element = obj as FrameworkElement;
            if (element != null)
            {
                var frameWorkElement = VisualTreeHelper.GetParent(element) as FrameworkElement;
                if (frameWorkElement != null)
                {
                    var t = frameWorkElement as T;
                    if (t != null)
                    {
                        return t;
                    }
                    return frameWorkElement.GetVisualParentRecursive<T>();
                }
            }

            return null;
        }

        // ******************************************************************
        public static T HitTest<T>(this Visual visual, Point pt) where T : class
        {
            T hitResult = null;

            VisualTreeHelper.HitTest(visual, null, result =>
            {
                if (result.VisualHit is T)
                {
                    hitResult = result.VisualHit as T;
                    return HitTestResultBehavior.Stop;
                }

                hitResult = result.VisualHit?.GetVisualParentRecursive<T>();
                if (hitResult != null)
                {
                    return HitTestResultBehavior.Stop;
                }

                return HitTestResultBehavior.Continue;

            }, new PointHitTestParameters(pt));

            return hitResult;
        }

        // ******************************************************************
        public static IEnumerable<T> GetChildrenRecursive<T>(this DependencyObject depObj) where T : class
        {
            int count = VisualTreeHelper.GetChildrenCount(depObj);
            for (int n = 0; n < count; n++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(depObj, n);
                if (child is T)
                {
                    yield return child as T;
                }

                foreach (T depObjChild in child.GetChildrenRecursive<T>())
                {
                    yield return depObjChild;
                }
            }
        }

        // ******************************************************************
        /// <summary>
        /// EO, 2017-05-11: New code
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static T GetVisualParentRecursive<T>(this DependencyObject obj, Predicate<T> predicate = null) where T : class
        {
            var element = obj as FrameworkElement;
            if (element != null)
            {
                var frameWorkElement = VisualTreeHelper.GetParent(element) as FrameworkElement;
                if (frameWorkElement != null)
                {
                    var t = frameWorkElement as T;
                    if (t != null)
                    {
                        if (predicate == null || predicate(t))
                        {
                            return t;
                        }
                    }
                    return frameWorkElement.GetVisualParentRecursive<T>(predicate);
                }
            }

            return null;
        }

        // ******************************************************************   
        /// <summary>
        /// EO, 2017-05-11: New code
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parent"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static T FindFirstChildWithNameRecursive<T>(this DependencyObject parent, string name) where T : FrameworkElement
        {
            return FindFirstChildRecursive(parent, (T child) => child.Name == name);
        }

        // ******************************************************************   
        /// <summary>
        /// Find all controls (visual or not)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parent"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static T FindFirstChildRecursive<T>(this DependencyObject parent, Predicate<T> predicate = null) where T : DependencyObject
        {
            if (parent == null)
            {
                return null;
            }

            //use the visual tree for Visual / Visual3D elements
            if (parent is Visual || parent is Visual3D)
            {
                int count = VisualTreeHelper.GetChildrenCount(parent);
                for (int i = 0; i < count; i++)
                {
                    var child = VisualTreeHelper.GetChild(parent, i);
                    var childAsT = child as T;
                    if (childAsT != null)
                    {
                        if (predicate == null || predicate(childAsT))
                        {
                            return childAsT;
                        }
                    }

                    var result = FindFirstChildRecursive(child, predicate);
                    if (result != null)
                    {
                        return result;
                    }
                }
            }
            else //use the logical tree for content / framework elements
            {
                foreach (DependencyObject child in LogicalTreeHelper.GetChildren(parent))
                {
                    var childAsT = child as T;
                    if (childAsT != null)
                    {
                        if (predicate == null || predicate(childAsT))
                        {
                            return childAsT;
                        }
                    }

                    var result = FindFirstChildRecursive(child, predicate);
                    if (result != null)
                    {
                        return result;
                    }
                }
            }

            return null;
        }

        // ******************************************************************   
        /// <summary>
        /// Non recursive
        /// Based on stackoverflow: http://stackoverflow.com/questions/13248013/visualtreehelper-not-finding-children-of-dependencyobject-how-can-i-reliably-fi
        /// Find all controls (visual or not)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parent"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static IEnumerable<T> FindChilds<T>(this DependencyObject parent, Predicate<T> predicate) where T : DependencyObject
        {
            if (parent == null) yield break;

            //use the visual tree for Visual / Visual3D elements
            if (parent is Visual || parent is Visual3D)
            {
                int count = VisualTreeHelper.GetChildrenCount(parent);
                for (int i = 0; i < count; i++)
                {
                    var childAsT = VisualTreeHelper.GetChild(parent, i) as T;
                    if (childAsT != null)
                    {
                        if (predicate(childAsT))
                        {
                            yield return childAsT;
                        }
                    }
                }
            }
            else //use the logical tree for content / framework elements
            {
                foreach (DependencyObject obj in LogicalTreeHelper.GetChildren(parent))
                {
                    var childAsT = obj as T;
                    if (childAsT != null)
                    {
                        if (predicate(childAsT))
                        {
                            yield return childAsT;
                        }
                    }
                }
            }
        }

        // ******************************************************************   
        /// <summary>
        /// EO, 2017-05-11: New code
        /// Find all controls (visual or not)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parent"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static List<T> FindChildsWithNameRecursive<T>(this DependencyObject parent, string name) where T : FrameworkElement
        {
            return FindChildsRecursive(parent, (T child) => child.Name == name);
        }

        // ******************************************************************   
        /// <summary>
        /// Find all controls (visual or not)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parent"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static List<T> FindChildsRecursive<T>(this DependencyObject parent, Predicate<T> predicate = null) where T : DependencyObject
        {
            List<T> childs = new List<T>();
            return FindChildsRecursiveInternal(parent, predicate, childs);
        }

        // ******************************************************************   
        private static List<T> FindChildsRecursiveInternal<T>(this DependencyObject parent, Predicate<T> predicate, List<T> childs) where T : DependencyObject
        {
            if (parent != null)
            {
                //use the visual tree for Visual / Visual3D elements
                if (parent is Visual || parent is Visual3D)
                {
                    int count = VisualTreeHelper.GetChildrenCount(parent);
                    for (int i = 0; i < count; i++)
                    {
                        var child = VisualTreeHelper.GetChild(parent, i);
                        var childAsT = child as T;
                        if (childAsT != null)
                        {
                            if (predicate == null || predicate(childAsT))
                            {
                                childs.Add(childAsT);
                            }
                        }

                        FindChildsRecursiveInternal(child, predicate, childs);
                    }
                }
                else //use the logical tree for content / framework elements
                {
                    foreach (DependencyObject child in LogicalTreeHelper.GetChildren(parent))
                    {
                        var childAsT = child as T;
                        if (childAsT != null)
                        {
                            if (predicate == null || predicate(childAsT))
                            {
                                childs.Add(childAsT);
                            }
                        }

                        FindChildsRecursiveInternal(child, predicate, childs);
                    }
                }
            }

            return childs;
        }

        // ******************************************************************   }
    }
}

Usage:

        <!-- ScrollViewer.HorizontalScrollBarVisibility="Disabled" -->
        <TreeView ItemsSource="{Binding Level1s, Mode=OneWay}">

            ...

            <i:Interaction.Behaviors>
                <local:BehaviorTreeView ShowHorizontalScrollBar="False"></local:BehaviorTreeView>
            </i:Interaction.Behaviors>

Thanks to Dean Kuga. Its solution give me the start to what I came up with.

Results:

enter image description here

Eric Ouellet
  • 10,996
  • 11
  • 84
  • 119