Click here to Skip to main content
15,437,139 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more:
hello,
i used treeview in my windows application..
when i select/change item from it each time,should fire some event..
I used wpf mvvm technique...
But when i select/change any item from treeview it's not raising event,since i binded parent grid in static resource..but i changed it to dynamic way also..
but still not working...
please what mistake i done in code..
plz..do reply...

What I have tried:

in xaml:
XML
 <Grid DataContext="{StaticResource vsRole}">
            <Grid.RowDefinitions>
                <RowDefinition Height="42" />
                <RowDefinition Height="50*" />
                <RowDefinition Height="200" />
            </Grid.RowDefinitions>
            <Label Content="Role" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.ColumnSpan="3" Height="40" HorizontalAlignment="Center" Margin="5" Name="label1" VerticalAlignment="Center" Width="200" FontSize="20" Grid.Row="0" />
            <DockPanel Grid.RowSpan="2" Grid.Row="1" Height="Auto" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Name="dockPanel1" Background="#FFF0F0F0">
                <DataGrid Grid.Row="2" Height="Auto" DockPanel.Dock="Left" AutoGenerateColumns="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding}" Name="dgeRole" RowDetailsVisibilityMode="VisibleWhenSelected">
                    <DataGrid.Columns>
                        <DataGridTextColumn x:Name="RoleColumn" Binding="{Binding Path=Role}" Width="100" Header="Role" />
                        <DataGridComboBoxColumn ItemsSource="{Binding Source={StaticResource vsDepartment1}}" SelectedValueBinding="{Binding Path=DepartmentId}" SelectedValuePath="DepartmentId" DisplayMemberPath="Department" Width="100" Header="DepartmentId" />
                    </DataGrid.Columns>
                </DataGrid>
                <swc:DockSplitter DockPanel.Dock="Left" Width="12" Thickness="5" Background="#FF1C1779" />
                <GroupBox DockPanel.Dock="Left" Width="Auto" VerticalAlignment="Stretch" Height="Auto" HorizontalAlignment="Stretch" Name="grpBoxContent">
                    <Grid DockPanel.Dock="Right" Height="Auto" Name="gridContent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <swc:SearchTextBox Grid.Column="0" Grid.Row="0" Width="120" Margin="3,10,3,3" Name="txtSearch" HorizontalAlignment="Right" VerticalAlignment="Top"  />
                        <GroupBox DockPanel.Dock="Left" Grid.Row="1" Grid.Column="0" Width="Auto" VerticalAlignment="Top" Height="Auto" HorizontalAlignment="Stretch" Name="Group1">
                            <Grid DockPanel.Dock="Left" Height="Auto" Width="Auto" Name="Group1Grid">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="Auto" />
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="Auto" />
                                </Grid.RowDefinitions>
                                <Label Grid.Column="0" Grid.Row="0" Margin="3,3,3,3" Content="Department" VerticalAlignment="Center" />
                                <StackPanel Grid.Column="1" Grid.Row="0" Grid.RowSpan="3" Orientation="Horizontal">
                                    <ComboBox Margin="0,0,0,0" HorizontalAlignment="Left" IsReadOnly="True"  VerticalAlignment="Top" 
                                          Name="ctrlDepartmentId" SelectedValuePath="DepartmentId" 
                                          SelectedValue="{Binding Path=DepartmentId, Mode=TwoWay, ValidatesOnExceptions=True,NotifyOnValidationError=True}" 
                                          DisplayMemberPath="Department" ItemsSource="{Binding Source={StaticResource vsDepartmentsAll}}" 
                                          Width="200" Height="Auto" />
                                    <Expander>
                                        <TreeView Height="107" Margin="-200,0,0,0" Grid.RowSpan="3" HorizontalAlignment="Left" Name="treeView1" 
                                      VerticalAlignment="Top" Width="225" SelectedValuePath="DepartmentId"
                                      ItemsSource="{Binding Source={StaticResource vsDepartment1} }"    >
                                            <TreeView.ItemTemplate>
                                                <HierarchicalDataTemplate ItemsSource="{Binding Path=eDepartment1}" >
                                                    <TextBlock Text="{Binding  Path=Department}"/>
                                                </HierarchicalDataTemplate>
                                            </TreeView.ItemTemplate>
                                            <TreeView.ItemContainerStyle>
                                                <Style TargetType="{x:Type TreeViewItem}">
                                                    <Setter Property="IsSelected" Value="{Binding IsSelected}" />
                                                </Style>
                                            </TreeView.ItemContainerStyle>
                                            <!--<i:Interaction.Triggers>
                                                <i:EventTrigger EventName="SelectedItemChanged">
                                                    <i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}" CommandParameter="argument"/>
                                                </i:EventTrigger>
                                            </i:Interaction.Triggers>-->
                                        </TreeView>
                                    </Expander>
                                </StackPanel>
                                <!--<ComboBox Grid.Column="1" Grid.Row="0" HorizontalAlignment="Left" Margin="3,3,3,3" Name="ctrlDepartmentId" SelectedValuePath="DepartmentId" SelectedValue="{Binding Path=DepartmentId, Mode=TwoWay, ValidatesOnExceptions=True,NotifyOnValidationError=True}" DisplayMemberPath="Department" ItemsSource="{Binding Source={StaticResource vsDepartment1}}" Width="120" Height="Auto" />-->
                                <Label Grid.Column="0" Grid.Row="3" Margin="3,3,3,3" Content="Role" VerticalAlignment="Center" />
                                <TextBox Grid.Column="1" Grid.Row="3" HorizontalAlignment="Left" Margin="3,3,3,3" Name="ctrlRole" Text="{Binding Path=Role, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" Width="120" Height="Auto" VerticalContentAlignment="Center" />
                                <Label Grid.Column="0" Grid.Row="4" Margin="3,3,3,3" Content="Description" VerticalAlignment="Center" />
                                <TextBox Grid.Column="1" Grid.Row="4" HorizontalAlignment="Left" Margin="3,3,3,3" Name="ctrlDescription" Text="{Binding Path=Description, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" Width="200" Height="55" TextWrapping="WrapWithOverflow" VerticalContentAlignment="Top" Grid.RowSpan="2" />
                            </Grid>
                        </GroupBox>
                    </Grid>
                </GroupBox>
            
</DockPanel>
        </Grid>

in xaml.cs file:
C#
this.DataContext = new frmRoleVwmdl();

in ViewModel class:
C#
private static object _selectedItem = null;
public static object SelectedItem
{
	get { return _selectedItem; }
	private set
	{
		if (_selectedItem != value)
		{
			_selectedItem = value;
			OnSelectedItemChanged();
		}
	}
}

static  void OnSelectedItemChanged()
{
	// Raise event / do other things
	MessageBox.Show("Record Saved!!");
}

private bool _isSelected;
public bool IsSelected
{
	get { return _isSelected; }
	set
	{
		if (_isSelected != value)
		{
			_isSelected = value;
			OnPropertyChanged("IsSelected");
			if (_isSelected)
			{
				SelectedItem = this;
			}
		}
	}
}

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
	var handler = this.PropertyChanged;
	if (handler != null)
		handler(this, new PropertyChangedEventArgs(propertyName));
}
Posted
Updated 4-Jun-18 4:52am
v3
Comments
Graeme_Grant 12-Sep-17 4:42am    
I've cleaned up your post. Please take the time to correctly format your code.

1 solution

Below is an update with a working example for getting the selected item for a TreeView.

I've separated the hierarchical collection from the data collection by using CollectionViewSource. Any changes to the collection data will be reflected in the hierarchical collection used by the view.

I have also included an example of how to sort the branches using a Converter class. It could also be done in the CollectionViewSource. If you want to use the latter, I'll leave that exercise up to you. ;)

Here is a base class to wrap INotifyPropertyChanged:
C#
public abstract class ObservableBase : INotifyPropertyChanged
{
    public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<TValue>.Default.Equals(field, default(TValue)) || !field.Equals(newValue))
        {
            field = newValue;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

The data model:
C#
public class EmployeeModel : ObservableBase
{
    private int id;
    public int Id
    {
        get => id;
        set => Set(ref id, value);
    }

    private string name;
    public string Name
    {
        get => name;
        set => Set(ref name, value);
    }

    private string role;
    public string Role
    {
        get => role;
        set => Set(ref role, value);
    }

    private int managerId;
    public int ManagerId
    {
        get => managerId;
        set => Set(ref managerId, value);
    }
}

A wrapper ViewModel for each data model. This handles the hierarchical collection and TreeViewItem selection :
C#
public class EmployeeViewModel : ObservableBase
{
    public EmployeeModel Employee { get; set; }
    public CollectionViewSource Subordinates { get; set; }

    private bool isSelected;
    public bool IsSelected
    {
        get => isSelected;
        set => Set(ref isSelected, value);
    }
}

Now for the main view model for the view/window - binds the collection to the UI and handles the SelectedItem:
C#
public class MainViewModel : ObservableBase
{
    public MainViewModel()
    {
        MockData();
    }

    private EmployeeViewModel selectedEmployee;
    public EmployeeViewModel SelectedEmployee
    {
        get => selectedEmployee;
        set => Set(ref selectedEmployee, value);
    }

    // employee list
    private ObservableCollection<EmployeeViewModel> employeesData
        = new ObservableCollection<EmployeeViewModel>();

    // employee list hierarchical view for UI
    public CollectionViewSource Employees { get; set; }

    private void MockData()
    {
        // Listen for changes to the collection
        employeesData.CollectionChanged += EmployyeeDataCollectionChanged;

        // Now add employees
        employeesData.Add(new EmployeeViewModel
        {
            Employee = new EmployeeModel
            {
                Id = 1,
                Name = "Bob"
            }
        });

        employeesData.Add(new EmployeeViewModel
        {
            Employee = new EmployeeModel
            {
                Id = 2,
                Name = "Paul",
                ManagerId = 3
            }
        });

        employeesData.Add(new EmployeeViewModel
        {
            Employee = new EmployeeModel
            {
                Id = 3,
                Name = "Mary",
                ManagerId = 1
            }
        });

        employeesData.Add(new EmployeeViewModel
        {
            Employee = new EmployeeModel
            {
                Id = 4,
                Name = "Joe",
                ManagerId = 1
            }
        });

        employeesData.Add(new EmployeeViewModel
        {
            Employee = new EmployeeModel
            {
                Id = 5,
                Name = "Jane",
                ManagerId = 2
            }
        });

        // Build hierarchical View for UI
        Employees = new CollectionViewSource { Source = employeesData };
        Employees.View.Filter =  new Predicate<object>((o)
                => (o as EmployeeViewModel)?.Employee.ManagerId == 0);

        foreach (var employee in employeesData)
        {
            employee.Subordinates = new CollectionViewSource
            { Source = employeesData };
            employee.Subordinates.View.Filter = new Predicate<object>((o)
                => (o as EmployeeViewModel)?.Employee.ManagerId
                    == employee.Employee.Id);
        }
    }

    // Listen or unlisten to employees as they're added or removed
    private void EmployyeeDataCollectionChanged(object sender,
                                                NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                for (int i = 0; i < e.NewItems.Count; i++)
                {
                    var employee = e.NewItems[i] as EmployeeViewModel;
                    employee.PropertyChanged += EmployeePropertyChanged;

                }
                break;

            case NotifyCollectionChangedAction.Remove:
                for (int i = 0; i < e.OldItems.Count; i++)
                {
                    var employee = e.NewItems[i] as EmployeeViewModel;
                    employee.PropertyChanged -= EmployeePropertyChanged;
                }
                break;
        }
    }

    // Only listen for the employee being selected
    private void EmployeePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(EmployeeViewModel.IsSelected))
        {
            SelectedEmployee = sender as EmployeeViewModel;
        }
    }
}

Note: We are listening to all the data ViewModels (EmployeeViewModel) to identify which item is being selected.

Now that the Data is ready, we can build & bind the UI:
XML
<Window
    x:Class="TreeViewSelectedItem.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    mc:Ignorable="d"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    xmlns:local="clr-namespace:TreeViewSelectedItem"

    Title="CodeProject  -  TREEVIEW SELECTED ITEM"
    WindowStartupLocation="CenterScreen" Height="500" Width="300">

    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TreeView ItemsSource="{Binding Employees.View}">
            <TreeView.Resources>

                <local:TvBranchSortPropertyConverter x:Key="SortConverter"/>

                <HierarchicalDataTemplate DataType="{x:Type local:EmployeeViewModel}"
                                          ItemsSource="{Binding Subordinates.View, 
                                          Converter={StaticResource SortConverter},
                                          ConverterParameter=Employee.Name}">
                    <TextBlock Text="{Binding Employee.Name}"
                               VerticalAlignment="Center"/>
                </HierarchicalDataTemplate>

            </TreeView.Resources>

            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsSelected" Value="{Binding IsSelected}" />
                </Style>
            </TreeView.ItemContainerStyle>

        </TreeView>

        <TextBlock Text="{Binding SelectedEmployee.Employee.Name, FallbackValue=None}"
                   Grid.Row="1" Margin="10"/>

    </Grid>

</Window>

NOTE: As we are binding with a CollectionViewSource, we need to bind to it's View property.

Lastly, here is the converter for custom sorting of the TreeView nodes:
C#
public class TvBranchSortPropertyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var view = value as ListCollectionView;
        view.SortDescriptions.Add(new SortDescription(parameter.ToString(), ListSortDirection.Ascending));
        return view;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}
 
Share this answer
 
v2
Comments
Graeme_Grant 12-Sep-17 7:07am    
And which line is this? "Provide value on 'System.Windows.Data.Binding' threw an exception.' Line number '68' and line position '102'.'"
Member 12938297 12-Sep-17 7:15am     CRLF
while i binding, SelectedItemChanged="{Binding SelectedItem }"
Graeme_Grant 12-Sep-17 7:17am    
Of course it failed, you can't bind to an event, only a property. Re-read my answer.
Member 12938297 12-Sep-17 7:17am     CRLF
can we bind selecteditemchnged event? i specified binding mode as oneway..but still getting same error
Member 12938297 12-Sep-17 7:43am     CRLF
hello.. it is not allowing to use SelectedItem={Binding SelectedItem} inside treeview.. showing error as property SelectedItem does not have an accessible setter..

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900