3

Я новенькая в программировании, поэтому не судите строго :-) я хочу добавить возможность добавлять в TreeView новые nodes по запросу юзера с помощью right click на мышь В данный момент я научилась добавлять один node, который появляется в UI, но при добавлении других, UI не обновляется и nodes остаются только в IList<TreeItemViewModel> items.

введите сюда описание изображения

последующие nodes не добавляются в папку xxxxxxxxxxx

XAML

<TreeView Name="PackageTreeView" ItemsSource="{Binding Value.RootNodes}" MouseDoubleClick="PackageTreeView_MouseDoubleClick" >
            <TreeView.ItemContainerStyle >
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="ContextMenu" Value="{StaticResource TreeItemContextMenu}" />
                    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                    <Setter Property="FontWeight" Value="Normal" />
                    <EventSetter Event="Selected" Handler="TreeViewSelectedItemChanged" />
                    <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="FontWeight" Value="Bold" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TreeView.ItemContainerStyle>
            <TreeView.Resources >
                <HierarchicalDataTemplate 
                    DataType="{x:Type viewModel:TreeItemViewModel}" 
                    ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal" MouseRightButtonDown="UIElement_OnMouseRightButtonDown">
                        <Image Source="{Binding ImageName}" />
                        <TextBlock ToolTip="{Binding CountImages}"
                            Text="{Binding Name, Mode=TwoWay}" >
                        </TextBlock>
                    </StackPanel>
                </HierarchicalDataTemplate>
            </TreeView.Resources>
    &lt;/TreeView&gt;

Code

public void AddNode(ITreeNode node)
        {
            IList<TreeItemViewModel> items = new List<TreeItemViewModel>();
            for (int idx = 0; idx < 1; idx++)
            {
                TreeItemViewModel item = new TreeItemViewModel(node, this);
            items.Add(item);
        }
        Children.AddRange(items);
        IsExpanded = !IsExpanded;
        IsExpanded = true;
    }


public bool IsExpanded { get => _isExpanded; set { if (value != _isExpanded) { _isExpanded = value; OnPropertyChanged(); }

            // Expand all the way up to the root.
            if (_isExpanded &amp;&amp; Parent != null)
            {
                Parent.IsExpanded = true;
            }

            // Lazy load the child items, if necessary.
            if (HasDummyChild)
            {
                Children.Remove(DummyChild);
                LoadChildren();
            }
        }
    }

private void LoadChildren() { Mouse.OverrideCursor = Cursors.Wait; Children.AddRange((from child in _treeNode.Children select new TreeItemViewModel(child, this))); Mouse.OverrideCursor = null; }

    /// &lt;summary&gt;
    ///     Returns true if this object's Children have not yet been populated.
    /// &lt;/summary&gt;
    public bool HasDummyChild =&gt; Children.Count == 1 &amp;&amp; Children[0] == DummyChild;

Teti
  • 85

1 Answers1

3

Приведу общий пример. Здесь 2 нюанса:

  • Свойство, обновление которого ожидается в интерфейсе, должно вызывать событие PropertyChanged, а класс, в котором оно находится, реализовывать интерфейс INotifyPropertyChanged.

  • Коллекция, обновление которой ожидается в интерфейсе должна реализовывать интрефейс INotifyCollectionChanged, и такая коллекция существует - ObservableCollection.

Привязка дерева с автоматическим обновлением интерфейса

Реализуем интерфейс INotifyPropertyChanged

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

Реализуем ноду

public class TreeNode : NotifyPropertyChanged
{
    private ObservableCollection<TreeNode> _children = new ObservableCollection<TreeNode>();
    private string _name;
public ObservableCollection&lt;TreeNode&gt; Children
{
    get =&gt; _children;
    set
    {
        _children = value;
        OnPropertyChanged();
    }
}

public string Name
{
    get =&gt; _name;
    set
    {
        _name = value;
        OnPropertyChanged();
    }
}

}

Реализуем свойство с корневыми нодами

public class MainViewModel : NotifyPropertyChanged
{
    private ObservableCollection<TreeNode> _items;
    public ObservableCollection<TreeNode> Items
    {
        get => _items;
        set
        {
            _items = value;
            OnPropertyChanged();
        }
    }
public MainViewModel()
{
    Items = new ObservableCollection&lt;TreeNode&gt;
    {
        new TreeNode{ Name = &quot;Root&quot; }
    };
}

}

Привяжем TreeView к этим данным

<TreeView ItemsSource="{Binding Items}">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <TextBlock Text="{Binding Name}" />
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

На этом этапе TreeView полностью динамически связан с данными - любые изменения в данных повлекут за собой немедленные изменения в интерфейсе. То есть можно пересоздавать/заменять или менять местами коллекции, можно то же самое вытворять с элементами этих коллекций, в любых количествах в любое время. (Главное помнить, что изменения в данных, к которым привязан интерфейс, следует делать в UI потоке, в противном случае ObservableCollection вполне оправданно выдаст вам исключение NotSupportedException, или еще что-нибудь поломается).

Контекстное меню "Добавить элемент"

Потребуются команды (вместо мучений с обработчиками событий).

И есть вот такой класс для удобного использования команд.

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
    add =&gt; CommandManager.RequerySuggested += value;
    remove =&gt; CommandManager.RequerySuggested -= value;
}

public RelayCommand(Action&lt;object&gt; execute, Predicate&lt;object&gt; canExecute = null)
    =&gt; (_execute, _canExecute) = (execute, canExecute);

public bool CanExecute(object parameter)
    =&gt; _canExecute == null || _canExecute(parameter);

public void Execute(object parameter)
    =&gt; _execute(parameter);

}

Далее реализуем добавление дочернего элемента в команде ноды, немного доработав класс TreeNode

public class TreeNode : NotifyPropertyChanged
{
    private ObservableCollection<TreeNode> _children = new ObservableCollection<TreeNode>();
    private string _name;
    private ICommand _addChildCommand;
public ObservableCollection&lt;TreeNode&gt; Children
{
    get =&gt; _children;
    set
    {
        _children = value;
        OnPropertyChanged();
    }
}

public string Name
{
    get =&gt; _name;
    set
    {
        _name = value;
        OnPropertyChanged();
    }
}

public ICommand AddChildCommand =&gt; _addChildCommand ??= new RelayCommand(parameter =&gt;
{
    Children.Add(new TreeNode { Name = Name + &quot;-Child&quot; + (Children.Count + 1) });
});

}

И добавим контекстное меню в разметку

<TreeView ItemsSource="{Binding Items}">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <TextBlock Text="{Binding Name}"/>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu>
                        <MenuItem Header="Add child" Command="{Binding AddChildCommand}"/>
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>

Для удаления элемента потребуется ссылка на родительскую коллекцию, как у вас уже реализовано в коде из вопроса. Выглядеть это может так.

<MenuItem Header="Remove" Command="{Binding RemoveCommand}"/>
public ICommand RemoveCommand => _removeCommand ??= new RelayCommand(parameter =>
{
    ParentItems.Remove(this);
});

Если вам принципиально реагировать на правую кнопку мыши вместо контекстного меню, можно привязаться к команде например вот так. У вас в разметке StackPanel, покажу на ее примере.

<StackPanel>
    <StackPanel.InputBindings>
        <MouseBinding MouseAction="RightClick" Command="{Binding AddChildCommand}"/>
    </StackPanel.InputBindings>
    <TextBlock Text="{Binding Name}"/>
</StackPanel>

При реализации этого задания ни одного обработчика событий не пострадало.

введите сюда описание изображения

aepot
  • 49,560