Member 12938297 Ответов: 1

Как сделать событие selecteditemchanged работать в TreeView с помощью WPF с использованием MVVM


привет,
я использовал treeview в своем приложении для windows..
когда я выбираю/меняю элемент из него каждый раз,должно срабатывать какое-то событие..
Я использовал технику wpf mvvm...
Но когда я выбираю/изменяю любой элемент из treeview,это не вызывает события, так как я привязал родительскую сетку в статическом ресурсе..но я также изменил ее на динамический способ..
но все равно не работает...
пожалуйста, какую ошибку я сделал в коде..
плз..не ответить...

Что я уже пробовал:

в XAML:
 <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>

в файле xaml.cs:
this.DataContext = new frmRoleVwmdl();

в классе ViewModel:
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));
}

Graeme_Grant

Я очистил ваш пост. Пожалуйста, найдите время, чтобы правильно отформатировать свой код.

1 Ответов

Рейтинг:
0

Graeme_Grant

Ниже приведено обновление с рабочим примером получения выбранного элемента для TreeView.

Я отделил иерархическую коллекцию от коллекции данных с помощью CollectionViewSource Любые изменения в данных коллекции будут отражены в иерархической коллекции, используемой представлением .

Я также включил пример того, как сортировать ветви с помощью класса конвертера. Это также можно сделать в CollectionViewSource. Если вы хотите использовать последнее, я оставлю это упражнение на ваше усмотрение. ;)

Вот базовый класс для обертывания INotifyPropertyChanged:

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;
}

Модель данных:
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);
    }
}

Обертка ViewModel для каждой модели данных. Это обрабатывает иерархическую коллекцию и выбор TreeViewItem :
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);
    }
}

Теперь для основной модели представления для представления/окна - связывает коллекцию с пользовательским интерфейсом и обрабатывает SelectedItem:
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;
        }
    }
}

Примечание: Мы слушаем все модели представления данных (EmployeeViewModel) чтобы определить, какой элемент выбирается.

Теперь, когда данные готовы, мы можем построить и привязать пользовательский интерфейс:
<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>

ЗАПИСКА: Как мы связываемся с а CollectionViewSource- нам нужно привязаться к нему. View собственность.

Наконец, вот конвертер для пользовательской сортировки узлов TreeView:
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;
    }
}


Graeme_Grant

И что это за линия? - Обеспечьте ценность в системе.Windows.Data.Binding' выдал исключение.' номер строки '68' и позиция строки '102'.'"

Member 12938297

пока я связываю,
SelectedItemChanged="{Обязательный Элемент }"

Graeme_Grant

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

Member 12938297

можем ли мы привязать selecteditemchnged событие?
я указал режим привязки как oneway..но все равно получаю ту же ошибку

Member 12938297

привет..
это не позволяя использовать для selecteditem={обязательный элемент} внутри дерева..
отображение ошибки как свойства SelectedItem не имеет доступного сеттера..

Graeme_Grant

Ладно, я немного ошибся. SelectedItem работает только с привязкой Xaml к Xaml. Я не тестировал MVVM. Для MVVM это немного по-другому, поэтому я построил пример для вас. Я обновлю свое решение.

Member 12938297

К.: Спасибо.

Graeme_Grant

Решение теперь обновлено.

Graeme_Grant

- Как ты туда попала?

Graeme_Grant

Если вы хотите узнать, что я имел в виду под "SelectedItem работает только с привязкой Xaml к Xaml", посетите эту статью: Работа с JSON в C# и VB[^], загрузите последнюю версию (1.3) исходного кода и посмотрите пример приложения GoogleDrive "WpfFileExplorer". Вот я ссылку на список элементов в TreeView элемент:

<ListBox Grid.Row="1" Grid.Column="1" Margin="4 0 4 4"
            ScrollViewer.HorizontalScrollBarVisibility="Disabled"
            ScrollViewer.VerticalScrollBarVisibility="Auto"
            ItemsPanel="{StaticResource ItemsPanelTemplate}"
            ItemsSource="{Binding ElementName=Folders, Path=SelectedItem.Files}"/>

Запустите его, чтобы увидеть, как он работает.

Есть также несколько других маленьких трюков, которые я делаю с TreeView, которые вам могут понравиться. ;)

Member 2861897

Привет,

Я использовал этот пример, чтобы иметь возможность использовать выбранный элемент в решении MVVM. Он отлично работает, но когда я выбираю второй элемент, я понимаю, что первый все еще выбран, и код выполняется дважды. Поэтому, чтобы быть уверенным, что я не работаю над тем же самым элементом, что и в моем первом выборе, я должен проверить, изменился ли этот элемент. Разве это нормальное поведение?

Чтобы проверить это, просто добавьте MessageBox в следующий код, и вы увидите результат:

частная ServerConnectionPropertyChanged недействительным(объект отправителя, PropertyChangedEventArgs е)
{
if (e.PropertyName == nameof(ServerConnectionViewModel.Изменили реализацию))
{
SelectedServer = sender as ServerConnectionViewModel;

Ящик для сообщений.Показать(SelectedServer.Подключение к серверу.имя хоста);
}
}


В этом примере MessageBox запускается только один раз при первом выборе, но два раза при втором выборе. Показываю текущий, а Райт после него-новый.

Спасибо.

Graeme_Grant

Проверьте свойство SelectionMode, не устанавливайте значение multiple или extended, тогда у вас будет один выбор по умолчанию.

Member 2861897

В TreeView нет свойства SelectionMode.