Страницы

Поиск по вопросам

воскресенье, 7 июля 2019 г.

KeyBinding в TreeView для элементов

Пытаюсь разобраться с привязкой "горячих" клавиш к дереву. Не получается передать в качестве параметра к команде саму выбранную ветку дерева.
Хочу, чтобы при нажатии F2 открывалось окно редактирования наименования ветки дерева (или даже лучше не отдельного окна, а напрямую в самом дереве, но с этим как мне показалось еще более сложно. Хотя бы через дополнительное окно)
На текущий момент через контекстное меню работает правильно, так как к каждому элементу дерева привязано свое контекстное меню и в качестве параметра эта ветка и передается.
А вот с привязкой команды по горячей кнопке для меня оказалось сложнее.
Команда объявлена в окне:

Горячая клавиша объявлена в дереве:

В качестве контекста у дерева в инициализации формы присваивается список:
tp_View.DataContext = TPTree;
public ObservableCollection TPTree = new ObservableCollection();
Который отдельно заполняется через другие команды.
Что необходимо передать в CommandParameter, чтобы в команду e.Parameter помещался не весь список дерева, а только выбранный в данный момент? Или нужно делать вообще по другому и я не в ту сторону копаю?
PS: Команда должна привязываться именно к дереву, так как на форме будет несколько деревьев и у каждого клавиша F2 должна вызывать редактирование именно своей ветки.
Спасибо.


Ответ

У вас команда привязана ко всему TreeView, соответственно Binding возвращает TreeView.DataContext, а он может быть каким угодно. Решение очевидное - передавать явно текущий элемент TreeView

Ну и так как при активном TreeView выбранного элемента может не быть - вы должны в коде команды первым делом проверить параметр:
void RenameNode(NodeVm node) { if (node == null) return;
Либо составить правильный CanExecute, но это сложнее, думаю.


Так как же все-таки реализовать редактирование элемента прямо в дереве? Давайте попробуем сделать это!
Я воспользуюсь паттерном MVVM, у меня есть соответствующие заготовки для классов команды:
class DelegateCommand : ICommand { protected readonly Predicate _canExecute; protected readonly Action _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action execute) : this(execute, _ => true) { }
public DelegateCommand(Action execute, Predicate canExecute) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute ?? throw new ArgumentNullException(nameof(canExecute)); }
public bool CanExecute(object parameter) => _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); }
и база для VM:
abstract class Vm : INotifyPropertyChanged { protected bool Set(ref T field, T value, [CallerMemberName]string propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; NotifyPropertyChanged(propertyName); return true; }
protected void NotifyPropertyChanged([CallerMemberName]string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged; }
Теперь, для этого примера я написал такой класс, представляющий один элемент дерева:
class NodeVm : Vm { string name; public string Name { get => name; set => Set(ref name, value, nameof(Name)); }
ObservableCollection children; public ObservableCollection Children { get => children; set => Set(ref children, value, nameof(Children)); }
public NodeVm() { }
public NodeVm(string name, IEnumerable children) { Name = name; Children = new ObservableCollection(children); }
public NodeVm(string name, params NodeVm[] children) : this(name, children.AsEnumerable()) { } }
Теперь главная VM, она должна содержать коллекцию элементов корневого уровня, я вместо этого создал один корневой элемент, но привязку мы сделаем не к нему, а к коллекции его дочерних элементов. Также нам потребуется одно свойство для обозначения текущего редактируемого элемента и одна команда для установки этого элемента, итого:
class MainVm : Vm { public NodeVm RootNode { get; }
NodeVm editableNode; public NodeVm EditableNode { get => editableNode; set => Set(ref editableNode, value, nameof(EditableNode)); }
public DelegateCommand SetEditableNodeCommand { get; }
public MainVm() { RootNode = new NodeVm("RootNode", new NodeVm("Node 1", new NodeVm("Node 7"), new NodeVm("Node 8", new NodeVm("Node 11"), new NodeVm("Node 12", new NodeVm("Node 14"), new NodeVm("Node 15"), new NodeVm("Node 16")), new NodeVm("Node 13")), new NodeVm("Node 9"), new NodeVm("Node 10")), new NodeVm("Node 2"), new NodeVm("Node 3"), new NodeVm("Node 4", new NodeVm("Node 17"), new NodeVm("Node 18", new NodeVm("Node 21"), new NodeVm("Node 22", new NodeVm("Node 24"), new NodeVm("Node 25"), new NodeVm("Node 26")), new NodeVm("Node 23")), new NodeVm("Node 19"), new NodeVm("Node 20")), new NodeVm("Node 5"), new NodeVm("Node 6")); SetEditableNodeCommand = new DelegateCommand(o => EditableNode = (NodeVm)o); } }
Теперь разметка, я в корневой грид окна просто кладу TreeView, у меня больше ничего не будет:
...
Шаблон элементов TreeView будет состоять из TextBlock в обычном режиме и TextBox в режиме редактирования, поэтому просто обернем их в Grid, также я немного подшаманил со стилем TextBox, чтобы он выглядел более "плоско":
... ...
Теперь нам нужно чтобы если текущий элемент в режиме редактирования отображался TextBox, иначе TextBlock, для этого воспользуемся конвертером (я стащил его из этого ответа и немного "докрутил"):
class ObjectsEqualConverter : MarkupExtension, IMultiValueConverter { public T EqualValue { get; set; } public T NotEqualValue { get; set; } public object Convert(object[] values, Type tt, object p, CultureInfo ci) => values[0] == values[1] ? EqualValue : NotEqualValue; public object[] ConvertBack(object value, Type[] tt, object p, CultureInfo ci) => throw new NotImplementedException(); public override object ProvideValue(IServiceProvider serviceProvider) => this; }
class VisibilityEqualConverter : ObjectsEqualConverter { }
Добавляем в TextBlock:

и в TextBox:

Теперь, по клавише F2 нужно выбранный элемент помечать как редактируемый, это у нас уже показано как сделать:

Ну и еще, чтобы при уходе курсора из TextBox редактируемый элемент сбрасывался нужно по событию LostFocus выполнить эту же команду с параметром null, я сделаю это с помощью пакета System.Windows.Interactivity.WPF подключенного из NuGet:

В заголовке окна подключено пространство имен: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Итого:

Комментариев нет:

Отправить комментарий