0

I try to convert winform to wpf with treeview control but I don't know which method in wpf equivalent to treeview.node.find

In winform, I can easily to find treenode by

 Dim MyNode() As TreeNode
 MyNode = TreeView1.Nodes.Find("10.8", True)

But in wpf c# how can i find treenode (maybe in wpf call treeviewitem)

SOLVED

I am finally find the solution for my own by use recursive loop. I know maybe it's not a best way to find node but currently it's work fine

However I think and try to approach another way like aepot's post :)

private TreeViewItem SearchTreeView(string p_sSearchTerm, ItemCollection p_Nodes)
{
    TreeViewItem returnValue = null;
    foreach (TreeViewItem node in p_Nodes)
    {
        if (string.Equals(node.Name.ToString(), p_sSearchTerm) ==true)
        {
            returnValue = node;
            return returnValue;
        }

        if (node.Items.Count > 0) returnValue = SearchTreeView(p_sSearchTerm, node.Items);
    }
    return returnValue;
}

And then, we can use it

var MyNode = SearchTreeView("A10_8", treeView.Items);
if (node != null)
{
    Console.Write(MyNode.Name);
}
Bruce
  • 519
  • 6
  • 23
  • Looking for data in the controls is not the best practice. Do it in your underlying collection instead, and use the controls only for presentation/user input by data binding. – György Kőszeg May 11 '20 at 08:52
  • Hi @GyörgyKőszeg I am not able to understand what you mean. Do you have any example or reference – Bruce May 11 '20 at 09:17
  • [Here](https://www.wpf-tutorial.com/treeview-control/treeview-data-binding-multiple-templates/) is an example for hierarchical data binding that is required to bind a tree. Or, if your problem is searching in your underlying tree model, [here](https://stackoverflow.com/a/7063002/5114784) is a simple solution. – György Kőszeg May 11 '20 at 09:33
  • I try with the second but it's not working :(. I was modified my question. Could you take a look it is there any issing? – Bruce May 11 '20 at 10:55

1 Answers1

1

That is a truly hard thing to interact directly with controls in WPF. And I don't know the answer keeping your development approach. But know how it can be done in other way.

I suggest using MVVM and Binding instead. I've created a demo project showing how it can be done.

It's not a Silver Bullet but a demo to start from.

Due to MVVM pattern approach we need a couple of helper classes.

// INPC Interface implementation for deriving in ViewModels
public class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

// ICommand interface implementation for easy commands use
public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
    public void Execute(object parameter) => _execute(parameter);
}

App features

  • Find and select node by name or part of name
  • Remove selected node
  • Add child node to selected node

You may improve it:

  • Add and store Parent property to avoid upper-level search things

The data hierarchical class (means that it contains a collection of itself)

public class MyTreeNode : NotifyPropertyChanged
{
    private ObservableCollection<MyTreeNode> _items = new ObservableCollection<MyTreeNode>();
    private string _nodeName;
    private bool _isSelected;

    public ObservableCollection<MyTreeNode> Items
    {
        get => _items;
        set
        {
            _items = value;
            OnPropertyChanged();
        }
    }

    public string NodeName
    {
        get => _nodeName;
        set
        {
            _nodeName = value;
            OnPropertyChanged();
        }
    }

    public bool IsSelected
    {
        get => _isSelected;
        set
        {
            _isSelected = value;
            OnPropertyChanged();
        }
    }
}

Then the very important main class MainViewModel it will provide properties to MainWindow.

public class MainViewModel : NotifyPropertyChanged
{
    private ObservableCollection<MyTreeNode> _treeItems;
    private ICommand _searchCommand;
    private ICommand _addCommand;
    private ICommand _removeCommand;

    private string _text;

    public string Text
    {
        get => _text;
        set
        {
            _text = value;
            OnPropertyChanged();
        }
    }

    public MainViewModel()
    {
        TreeItems = new ObservableCollection<MyTreeNode>();

        // demo values for initial test
        TreeItems.Add(new MyTreeNode { NodeName = "Node1" });
        MyTreeNode node = new MyTreeNode { NodeName = "Node2", IsSelected = true };
        TreeItems.Add(node);
        node.Items.Add(new MyTreeNode { NodeName = "SubNode1.1" });
        node.Items.Add(new MyTreeNode { NodeName = "SubNode1.2" });
        node.Items.Add(new MyTreeNode { NodeName = "SubNode1.3" });
        TreeItems.Add(new MyTreeNode { NodeName = "Node3" });
        TreeItems.Add(new MyTreeNode { NodeName = "Node4" });
    }

    public ObservableCollection<MyTreeNode> TreeItems
    {
        get => _treeItems;
        set
        {
            _treeItems = value;
            OnPropertyChanged();
        }
    }

    // search by node name implementation
    private MyTreeNode SearchItemByName(ObservableCollection<MyTreeNode> nodes, string searchText)
    {
        if (searchText?.Length > 0)
        {
            foreach (MyTreeNode node in nodes)
            {
                if (node.NodeName.Contains(searchText, StringComparison.InvariantCultureIgnoreCase))
                {
                    return node;
                }
                if (node.Items.Count > 0)
                {
                    MyTreeNode result = SearchItemByName(node.Items, searchText);
                    if (result != null) return result;
                }
            }
        }
        return null;
    }

    // need for remove action to find the collection that contains the required item
    private ObservableCollection<MyTreeNode> FindParentCollection(ObservableCollection<MyTreeNode> nodes, MyTreeNode searchNode)
    {
        if (searchNode != null)
        {
            foreach (MyTreeNode node in nodes)
            {
                if (node.Equals(searchNode))
                {
                    return nodes;
                }
                if (node.Items.Count > 0)
                {
                    ObservableCollection<MyTreeNode> result = FindParentCollection(node.Items, searchNode);
                    if (result != null) return result;
                }
            }
        }
        return null;
    }

    // Commands where buttons are attached to.
    public ICommand SearchCommand => _searchCommand ?? (_searchCommand = new RelayCommand(parameter =>
    {
        MyTreeNode result = SearchItemByName(TreeItems, Text);
        if (result != null)
            result.IsSelected = true;
    }));

    public ICommand AddCommand => _addCommand ?? (_addCommand = new RelayCommand(parameter =>
    {
        MyTreeNode newNode = new MyTreeNode { NodeName = Text };
        if (parameter is MyTreeNode node)
            node.Items.Add(newNode);
        else
            TreeItems.Add(newNode);
    }));

    public ICommand RemoveCommand => _removeCommand ?? (_removeCommand = new RelayCommand(parameter =>
    {
        MyTreeNode node = parameter as MyTreeNode;
        ObservableCollection<MyTreeNode> nodes = FindParentCollection(TreeItems, node);
        nodes.Remove(node);
    }, parameter => parameter is MyTreeNode));
}

And full markup that will help to reproduce the entire app

<Window x:Class="WpfApp2.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:WpfApp2"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainViewModel/><!-- MainViewModel instantiated here -->
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBox Text="{Binding Text}" Margin="5" Width="300"/>
            <Button Margin="5" Content="Search" Command="{Binding SearchCommand}"/>
            <Button Margin="5" Content="Add" Command="{Binding AddCommand}" CommandParameter="{Binding SelectedItem, ElementName=MyTreeView}"/>
            <Button Margin="5" Content="Remove" Command="{Binding RemoveCommand}" CommandParameter="{Binding SelectedItem, ElementName=MyTreeView}"/>
        </StackPanel>
        <TextBlock Grid.Row="1" Margin="5" Text="{Binding SelectedItem.NodeName, ElementName=MyTreeView}"/>
        <TreeView x:Name="MyTreeView" Grid.Row="2" Margin="5" ItemsSource="{Binding TreeItems}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Items}">
                    <TextBlock Text="{Binding NodeName}"/>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
            <TreeView.Resources>
                <Style TargetType="TreeViewItem">
                    <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
                    <Setter Property="IsExpanded" Value="True"/>
                </Style>
            </TreeView.Resources>
        </TreeView>
    </Grid>
</Window>

enter image description here

And the traditionally for MVVM newcomers: code-behind class

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}
aepot
  • 4,558
  • 2
  • 12
  • 24
  • 1
    Thank you so much for your very detail and clearly answer. it's seem to be new and quite difficult but let me try to learn and use this way. once again thanks you – Bruce May 11 '20 at 14:45
  • @Bruce You're welcome. The main benefit is conrtols updating dynamically when you change the data, by `OnPropertyChanged()` call. The second key for dynamic UI updates is `ObservableCollection` (similar to `List`) which fires `CollectionChanged` event inside which natively supported by all items controls. There's a lot of information about WPF+MVVM on StackOverflow. For example in my profile you can find the other detailed answers related to the same approach. It worth learning. – aepot May 11 '20 at 15:05
  • 1
    opp so sorry about it, just a missing due to douple click. I check accept button again. Thansk you aepot :) – Bruce May 13 '20 at 03:24