Пропадает keyboard focus с TreeViewItem после изменения свойства content ContentControl с TextBox на TextBlock
Есть TreeView с редактируемыми TreeViewItem. При значении IsEditing = true срабатывает триггер, который меняет контент ContentControl с TextBlock на TextBox. Вызов изменения IsEditing происходит либо через контекстное меню, либо посредством сочетания клавиш. Чтобы переименовать TreeViewItem, надо нажать на F2, после чего появится TextBox для ввода. Чтобы принять изменения, нужно нажать клавиши Esc или Enter.
Изменение контента и сохранение работает исправно, однако при повторном нажатии на F2 (сразу после сохранения) выясняется, что пропадает Keyboard Focus: сочетание клавиш и перемещение по древу становится невозможным до повторного клика на TreeViewItem. Если изменить TextBox и IsEditing = true прописывать ему IsEnabled или Focusable = false, то фокус возвращается TreeViewItem, но только если не менять контент ContentControl (если проверять только с одним TextBox). Есть предположение, что ContentControl просто раньше перехватывает значение IsEditing, меняет контент и TextBox уже не может вернуть фокус выше.
Оставлять только TextBox, стилизованный под TextBlock, я не хочу из соображений производительности.
Как стоит изменить код, чтобы возвращать keyboard фокус обратно на TreeViewItem?
View:
<UserControl x:Class="View.FileMenuView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:ViewModel;assembly=ViewModel"
mc:Ignorable="d"
SnapsToDevicePixels="True"
FontFamily="{DynamicResource Raleway}"
FontSize="14">
<UserControl.InputBindings>
<KeyBinding Key="F2" Command="{Binding EnableEditingModeCommand}" />
<KeyBinding Key="Enter" Command="{Binding DisableEditingModeCommand}" />
<KeyBinding Key="Esc" Command="{Binding DisableEditingModeCommand}" />
</UserControl.InputBindings>
<UserControl.DataContext>
<vm:FileMenuViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<Style x:Key="MenuItemStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Header" Value="{Binding Header}" />
</Style>
<Style x:Key="TreeViewItem" TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
<DataTemplate x:Key="NormalTemplate" DataType="{x:Type vm:Node}">
<TextBlock VerticalAlignment="Center"
Text="{Binding Name}"
Margin="1" />
</DataTemplate>
<DataTemplate x:Key="EditTemplate" DataType="{x:Type vm:Node}">
<TextBox x:Name="EditTextBox"
Style="{StaticResource TextBoxRectangle}"
Text="{Binding Name, UpdateSourceTrigger=LostFocus}"
VerticalAlignment="Center" Cursor="IBeam" KeyDown="EditTextBox_KeyDown"
IsEnabled="{Binding IsEditing}"/>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:NodeWithChilds}" ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContextMenuOpening">
<i:InvokeCommandAction Command="{Binding SelectCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Border BorderBrush="{x:Null}"
Width="21">
<TextBlock Margin="0, 0, 4, 0"
FontFamily="{DynamicResource FontAwesome}"
Text="{Binding Icon}"
Foreground="{DynamicResource BlackOliveBrush}"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</Border>
<ContentControl Content="{Binding}"
FocusVisualStyle="{x:Null}"
Focusable="False"
Foreground="Black">
<ContentControl.ContextMenu>
<ContextMenu Style="{StaticResource ContextMenu}"
ItemsSource="{Binding Menu}"
ItemContainerStyle="{StaticResource MenuItemStyle}">
</ContextMenu>
</ContentControl.ContextMenu>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource NormalTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsEditing}" Value="true">
<Setter Property="ContentTemplate" Value="{StaticResource EditTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</StackPanel>
</HierarchicalDataTemplate>
</UserControl.Resources>
<Grid>
<TreeView x:Name="MyTreeView"
ItemsSource="{Binding MainFolder}"
Style="{StaticResource TreeView}"
ItemContainerStyle="{StaticResource TreeViewItem}" />
</Grid>
</UserControl>
ViewModel:
using Microsoft.Toolkit.Mvvm.Input;
using Model;
using Service;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace ViewModel
{
public class FileMenuViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(name));
}
#endregion INotifyPropertyChanged
public FileMenuViewModel()
{
AddProjectFolder();
EnableEditingModeCommand = new RelayCommand(EnableEditingMode);
DisableEditingModeCommand = new RelayCommand(DisableEditingMode);
}
private readonly ApplicationContextFactory contextFactory = new();
private int _id;
private ObservableCollection<Node> Nodes { get; } = new();
public IRelayCommand EnableEditingModeCommand { get; }
public IRelayCommand DisableEditingModeCommand { get; }
public ObservableCollection<Node> MainFolder { get; set; } = new();
public Node SelectedNode => Nodes.FirstOrDefault(i => i.IsSelected);
private void EnableEditingMode()
{
SelectedNode.IsFocused = true;
SelectedNode.IsEditing = true;
}
private void DisableEditingMode()
{
SelectedNode.IsEditing = false;
}
public void AddEventFolder(ProjectNode projectNode)
{
ObservableCollection<MenuItem> menu = new()
{
};
EventFolder eventFolder = new(menu);
projectNode.Nodes.Add(eventFolder);
Nodes.Add(eventFolder);
}
public void AddPersonFolder(ProjectNode projectNode)
{
ObservableCollection<MenuItem> menu = new()
{
};
PersonFolder personFolder = new(menu);
projectNode.Nodes.Add(personFolder);
Nodes.Add(personFolder);
}
public void AddProjectFolder()
{
ObservableCollection<MenuItem> menu = new()
{
new MenuItem("Добавить новый проект", CreateProject),
new MenuItem("Переименовать", RenameNode),
};
ProjectFolder projectFolder = new(menu);
MainFolder.Add(projectFolder);
Nodes.Add(projectFolder);
ProjectFolder projectFolder1 = new(menu);
ProjectFolder projectFolder2 = new(menu);
EventFolder projectFolder3 = new(menu);
Nodes.Add(projectFolder1);
Nodes.Add(projectFolder2);
Nodes.Add(projectFolder3);
MainFolder.Add(projectFolder1);
MainFolder.Add(projectFolder2);
projectFolder2.Nodes.Add(projectFolder3);
}
public void AddProjectNode(string name)
{
ObservableCollection<MenuItem> menu = new()
{
new MenuItem("Переименовать", RenameNode),
};
ProjectNode childNode = new(name, menu);
Nodes.Add(childNode);
ProjectFolder parentNode = (ProjectFolder)SelectedNode;
parentNode.Nodes.Add(childNode);
parentNode.IsExpanded = true;
childNode.IsSelected = true;
childNode.IsExpanded = true;
AddPersonFolder(childNode);
AddEventFolder(childNode);
childNode.ProjectId = _id;
}
public async Task CreateProject()
{
NewProjectViewModel newProjectViewModel = new();
await DisplayRootRegistry.ShowModalPresentation(newProjectViewModel);
if (newProjectViewModel.IsConfirm)
{
_id = newProjectViewModel.Id;
AddProjectNode(newProjectViewModel.ProjectName);
newProjectViewModel.IsConfirm = false;
}
}
public async Task RenameNode()
{
SelectedNode.IsEditing = true;
}
}
public class MenuItem : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(name));
}
#endregion INotifyPropertyChanged
public MenuItem(string header, Func<Task> command)
{
Command = new AsyncRelayCommand(command);
Header = header;
}
private string _header;
public IAsyncRelayCommand Command { get; }
public string Header
{
get => _header;
set
{
if (_header == value)
{
return;
}
_header = value;
OnPropertyChanged();
}
}
}
public abstract class Node : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(name));
}
#endregion INotifyPropertyChanged
private Node()
{
DisableEditCommand = new RelayCommand(DisableEdit);
SelectCommand = new RelayCommand(Select);
EnableFocusCommand = new RelayCommand(EnableFocus);
}
public Node(string name, ObservableCollection<MenuItem> menu) : this()
{
Name = name;
Menu = menu;
}
public void EnableFocus()
{
IsFocused = true;
}
private string _icon;
private string _name;
private bool _isEditing;
private bool _isExpanded;
private bool _isFocused;
private bool _isSelected;
public IRelayCommand EnableFocusCommand { get; }
public IRelayCommand DisableEditCommand { get; }
public IRelayCommand SelectCommand { get; }
public ObservableCollection<MenuItem> Menu { get; set; }
public string Icon
{
get => _icon;
set
{
if (_icon == value)
{
return;
}
_icon = value;
OnPropertyChanged();
}
}
public bool IsEditing
{
get => _isEditing;
set
{
if (_isEditing == value)
{
return;
}
_isEditing = value;
OnPropertyChanged();
}
}
public bool IsExpanded
{
get => _isExpanded;
set
{
if (_isExpanded == value)
{
return;
}
_isExpanded = value;
OnPropertyChanged();
}
}
public bool IsFocused
{
get => _isFocused;
set
{
if (_isFocused == value)
{
return;
}
_isFocused = value;
OnPropertyChanged();
}
}
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected == value)
{
return;
}
_isSelected = value;
OnPropertyChanged();
}
}
public string Name
{
get => _name;
set
{
if (_name == value)
{
return;
}
_name = value;
OnPropertyChanged();
}
}
private void DisableEdit()
{
IsEditing = false;
}
private void Select()
{
IsSelected = true;
}
}
public abstract class NodeWithChilds : Node
{
public NodeWithChilds(string name, ObservableCollection<MenuItem> menu) : base(name, menu)
{
}
public ObservableCollection<Node> Nodes { get; set; } = new();
}
public class PersonNode : Node
{
private const string IconCode = "\uf007";
public PersonNode(string name, ObservableCollection<MenuItem> menu) : base(name, menu)
{
Icon = IconCode;
}
public PersonNode(int projectId, int personId, string personName, ObservableCollection<MenuItem> menu) : this(personName, menu)
{
PersonId = personId;
ProjectId = projectId;
}
private int _personId;
private int _projectId;
public int PersonId
{
get => _personId;
set
{
if (_personId == value)
{
return;
}
_personId = value;
OnPropertyChanged();
}
}
public int ProjectId
{
get => _projectId;
set
{
if (_projectId == value)
{
return;
}
_projectId = value;
OnPropertyChanged();
}
}
}
public class EventNode : Node
{
private const string IconCode = "\uf783";
public EventNode(string eventName, ObservableCollection<MenuItem> menu) : base(eventName, menu)
{
Icon = IconCode;
}
public EventNode(int projectId, int eventId, string eventName, ObservableCollection<MenuItem> menu) : this(eventName, menu)
{
EventId = eventId;
ProjectId = projectId;
}
private int _eventId;
private int _projectId;
public int EventId
{
get => _eventId;
set
{
if (_eventId == value)
{
return;
}
_eventId = value;
OnPropertyChanged();
}
}
public int ProjectId
{
get => _projectId;
set
{
if (_projectId == value)
{
return;
}
_projectId = value;
OnPropertyChanged();
}
}
}
public class ProjectNode : NodeWithChilds
{
private const string IconCode = "\uf5ad";
public ProjectNode(string projectName, ObservableCollection<MenuItem> menu) : base(projectName, menu)
{
Icon = IconCode;
}
public ProjectNode(int projectId, string projectName, ObservableCollection<MenuItem> menu) : this(projectName, menu)
{
ProjectId = projectId;
}
private int _projectId;
public int ProjectId
{
get => _projectId;
set
{
if (_projectId == value)
{
return;
}
_projectId = value;
OnPropertyChanged();
}
}
}
public class ProjectFolder : NodeWithChilds
{
private const string IconCode = "\uf02d";
public ProjectFolder(ObservableCollection<MenuItem> menu) : base("Проекты", menu)
{
Icon = IconCode;
}
}
public class PersonFolder : NodeWithChilds
{
private const string IconCode = "\uf0c0";
public PersonFolder(ObservableCollection<MenuItem> menu) : base("Персонажи", menu)
{
Icon = IconCode;
}
}
public class EventFolder : NodeWithChilds
{
private const string IconCode = "\uf073";
public EventFolder(ObservableCollection<MenuItem> menu) : base("События", menu)
{
Icon = IconCode;
}
}
}
Пробовала ещё так, но всё равно не работает:
private void EditTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter || e.Key == Key.Escape)
{
TextBox textBox = (TextBox)sender;
textBox.IsEnabled = false;
}
}