feat: inline editing with click-to-edit, TextBox overlay, Focus+SelectAll
- Grid overlay: TextBox covers TextBlock when editing (no more dual display) - EditValue property: auto-initialized to DisplayName on edit start - Click on already-selected node enters inline edit (PreviewMouseLeftButtonDown) - Right-click auto-selects node before context menu - TextBox IsVisibleChanged: Focus + SelectAll on Loaded priority - XAML Visibility moved to Style setter (fix DataTrigger precedence) - Light yellow background (#FFFFFDE6)
This commit is contained in:
parent
ad398652a7
commit
d358c31a7d
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Input;
|
||||
using Autodesk.Navisworks.Api;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
@ -56,9 +57,25 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
public bool IsEditing
|
||||
{
|
||||
get => _isEditing;
|
||||
set { _isEditing = value; OnPropertyChanged(nameof(IsEditing)); }
|
||||
set
|
||||
{
|
||||
if (_isEditing == value) return;
|
||||
_isEditing = value;
|
||||
OnPropertyChanged(nameof(IsEditing));
|
||||
// 进入编辑时初始化 EditValue 为当前显示名
|
||||
if (value)
|
||||
EditValue = DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>编辑框的临时文本(进入编辑时初始化为 DisplayName)</summary>
|
||||
public string EditValue
|
||||
{
|
||||
get => _editValue;
|
||||
set { _editValue = value; OnPropertyChanged(nameof(EditValue)); }
|
||||
}
|
||||
private string _editValue;
|
||||
|
||||
/// <summary>是否孤儿节点(模型结构变化后无法定位)</summary>
|
||||
public bool IsOrphan
|
||||
{
|
||||
@ -92,8 +109,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
public AliasNodeViewModel()
|
||||
{
|
||||
StartEditCommand = new RelayCommand(() => IsEditing = true);
|
||||
}
|
||||
|
||||
/// <summary>启动内联编辑的命令</summary>
|
||||
public ICommand StartEditCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 刷新显示文本(别名变更新调用)
|
||||
/// </summary>
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="clr-namespace:NavisworksTransport.UI.WPF.ViewModels"
|
||||
xmlns:converters="clr-namespace:NavisworksTransport.UI.WPF.Converters">
|
||||
xmlns:converters="clr-namespace:NavisworksTransport.UI.WPF.Converters"
|
||||
PreviewKeyDown="UserControl_PreviewKeyDown">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
@ -16,16 +17,16 @@
|
||||
<!-- 别名树 TreeView 的模板 -->
|
||||
<HierarchicalDataTemplate DataType="{x:Type vm:AliasNodeViewModel}"
|
||||
ItemsSource="{Binding Children}">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,1"
|
||||
VerticalAlignment="Center">
|
||||
<!-- 节点名:别名用暗红色,原名用黑色 -->
|
||||
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center"
|
||||
<Grid VerticalAlignment="Center">
|
||||
<!-- 显示模式 -->
|
||||
<TextBlock Text="{Binding DisplayName}"
|
||||
FontSize="12" TextTrimming="CharacterEllipsis" MaxWidth="200"
|
||||
FontWeight="SemiBold">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="#FF1A1A1A"/>
|
||||
<Setter Property="FontStyle" Value="Normal"/>
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding HasAlias}" Value="True">
|
||||
<Setter Property="Foreground" Value="#FFC62828"/>
|
||||
@ -37,21 +38,25 @@
|
||||
<Setter Property="TextDecorations" Value="Strikethrough"/>
|
||||
<Setter Property="Foreground" Value="#FFAAAAAA"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsEditing}" Value="True">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
|
||||
<!-- 编辑文本框(IsEditing=true 时显示) -->
|
||||
<TextBox Text="{Binding Alias, UpdateSourceTrigger=PropertyChanged}"
|
||||
Width="150" VerticalAlignment="Center"
|
||||
Visibility="Collapsed" FontSize="12"
|
||||
<!-- 编辑模式 -->
|
||||
<TextBox Text="{Binding EditValue, UpdateSourceTrigger=PropertyChanged}"
|
||||
FontSize="12"
|
||||
KeyDown="OnAliasTextBoxKeyDown"
|
||||
LostFocus="OnAliasTextBoxLostFocus"
|
||||
Background="#FFFFF8DC"
|
||||
IsVisibleChanged="OnEditTextBoxVisibleChanged"
|
||||
Background="#FFFFFDE6"
|
||||
BorderBrush="#FF1565C0" BorderThickness="1">
|
||||
<TextBox.Style>
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsEditing}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
@ -60,19 +65,7 @@
|
||||
</Style>
|
||||
</TextBox.Style>
|
||||
</TextBox>
|
||||
|
||||
<!-- 铅笔编辑按钮(鼠标悬停显示) -->
|
||||
<Button Style="{StaticResource AliasToolbarButtonStyle}"
|
||||
ToolTip="编辑别名"
|
||||
Tag="{Binding}"
|
||||
Click="OnEditClick"
|
||||
Width="18" Height="18"
|
||||
Margin="2,0,0,0">
|
||||
<Path Data="{StaticResource EditIconGeometry}"
|
||||
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
|
||||
Width="14" Height="14" Stretch="Uniform"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</HierarchicalDataTemplate>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
@ -98,14 +91,6 @@
|
||||
|
||||
<!-- 图标工具栏 -->
|
||||
<StackPanel Orientation="Horizontal" DockPanel.Dock="Right">
|
||||
<Button x:Name="BtnSave" Style="{StaticResource AliasToolbarButtonStyle}"
|
||||
ToolTip="保存别名 (Ctrl+S)"
|
||||
IsEnabled="False"
|
||||
Click="OnSaveCurrentClick">
|
||||
<Path Data="{StaticResource SaveIconGeometry}"
|
||||
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
|
||||
Width="16" Height="16" Stretch="Uniform"/>
|
||||
</Button>
|
||||
<Separator Style="{StaticResource AliasToolbarSeparatorStyle}"/>
|
||||
<Button x:Name="BtnAutoGen" Style="{StaticResource AliasToolbarButtonStyle}"
|
||||
ToolTip="自动生成别名"
|
||||
@ -165,7 +150,9 @@
|
||||
BorderThickness="0"
|
||||
ItemContainerStyle="{StaticResource AliasTreeViewItemStyle}"
|
||||
TreeViewItem.Expanded="OnTreeViewItemExpanded"
|
||||
TreeViewItem.Selected="OnTreeViewItemSelected">
|
||||
TreeViewItem.Selected="OnTreeViewItemSelected"
|
||||
TreeViewItem.PreviewMouseRightButtonDown="OnTreeViewItemRightClick"
|
||||
TreeViewItem.PreviewMouseLeftButtonDown="OnTreeViewItemLeftClick">
|
||||
<TreeView.Resources>
|
||||
<!-- 无焦点时也显示蓝色选中态(浅灰蓝底色 + 白字,匹配选择树) -->
|
||||
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}"
|
||||
|
||||
@ -125,12 +125,8 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
/// <summary>
|
||||
/// 别名树选中 → 同步到 Navisworks 选择
|
||||
/// </summary>
|
||||
private void OnTreeViewSelected(object sender, RoutedEventArgs e)
|
||||
private void OnTreeViewSelected(AliasNodeViewModel node)
|
||||
{
|
||||
if (!(e.OriginalSource is TreeViewItem tvi) || !(tvi.DataContext is AliasNodeViewModel node))
|
||||
return;
|
||||
if (node.IsOrphan) return;
|
||||
|
||||
LogManager.Info($"[别名树] 树点击: NodeKey={node.NodeKey}, OriginalName={node.OriginalName}, ModelItem={node.ModelItem?.DisplayName ?? "null"}");
|
||||
|
||||
_isInternalSelection = true;
|
||||
@ -154,6 +150,18 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
}
|
||||
}
|
||||
|
||||
private void StartInlineEdit(AliasNodeViewModel node, TreeViewItem tvi)
|
||||
{
|
||||
// 直接用,不需要 tvi
|
||||
StartInlineEdit(node);
|
||||
}
|
||||
|
||||
private void StartInlineEdit(AliasNodeViewModel node)
|
||||
{
|
||||
ClearAllEditing();
|
||||
node.IsEditing = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navisworks 选择变化 → 同步到别名树高亮
|
||||
/// </summary>
|
||||
@ -172,7 +180,6 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
var item = selected.First();
|
||||
_currentSelectionItem = item;
|
||||
TbCurrentNode.Text = $"当前: {item.DisplayName}";
|
||||
BtnSave.IsEnabled = true;
|
||||
|
||||
LogManager.Info($"[别名树] NW选中: DisplayName={item.DisplayName}, ClassName={item.ClassName}");
|
||||
|
||||
@ -184,7 +191,6 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
{
|
||||
_currentSelectionItem = null;
|
||||
TbCurrentNode.Text = selected.Count > 1 ? $"当前: ({selected.Count} 个选中)" : "当前: 无";
|
||||
BtnSave.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -510,7 +516,9 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
{
|
||||
if (currentContainer is TreeViewItem t)
|
||||
{
|
||||
_isProgrammaticSelect = true;
|
||||
t.IsSelected = true;
|
||||
_isProgrammaticSelect = false;
|
||||
t.BringIntoView();
|
||||
}
|
||||
return;
|
||||
@ -562,7 +570,9 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
var childTvi = parentTvi.ItemContainerGenerator.ContainerFromItem(current) as TreeViewItem;
|
||||
if (childTvi != null)
|
||||
{
|
||||
_isProgrammaticSelect = true;
|
||||
childTvi.IsSelected = true;
|
||||
_isProgrammaticSelect = false;
|
||||
childTvi.BringIntoView();
|
||||
}
|
||||
else
|
||||
@ -613,25 +623,20 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
// 事件处理:工具栏
|
||||
// ============================================================
|
||||
|
||||
private void OnSaveCurrentClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_currentSelectionItem == null || _dataStore == null) return;
|
||||
string input = TxtAliasInput.Text?.Trim();
|
||||
if (string.IsNullOrEmpty(input)) return;
|
||||
|
||||
var identity = AliasNodeIdentity.FromModelItem(_currentSelectionItem);
|
||||
LogManager.Info($"[别名树] 保存别名: {input}, NodeKey={identity.ToKey()}");
|
||||
_dataStore.SetAlias(identity, input);
|
||||
TxtAliasInput.Clear();
|
||||
|
||||
// 刷新树中对应节点
|
||||
RefreshNodeDisplay(identity.ToKey());
|
||||
}
|
||||
|
||||
private void OnAliasInputKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter && BtnSave.IsEnabled)
|
||||
OnSaveCurrentClick(null, null);
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
if (_currentSelectionItem == null || _dataStore == null) return;
|
||||
string input = TxtAliasInput.Text?.Trim();
|
||||
if (string.IsNullOrEmpty(input)) return;
|
||||
|
||||
var identity = AliasNodeIdentity.FromModelItem(_currentSelectionItem);
|
||||
LogManager.Info($"[别名树] 保存别名: {input}, NodeKey={identity.ToKey()}");
|
||||
_dataStore.SetAlias(identity, input);
|
||||
TxtAliasInput.Clear();
|
||||
RefreshNodeDisplay(identity.ToKey());
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAutoGenClick(object sender, RoutedEventArgs e)
|
||||
@ -715,6 +720,18 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEditTextBoxVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (sender is TextBox tb && tb.IsVisible)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
tb.Focus();
|
||||
tb.SelectAll();
|
||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAliasTextBoxLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CommitEdit(sender as TextBox);
|
||||
@ -768,8 +785,38 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
private void OnCtxEditClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var node = GetSelectedNode();
|
||||
LogManager.Info($"[别名树] OnCtxEditClick: node={node?.OriginalName ?? "null"}");
|
||||
if (node == null) return;
|
||||
OnEditClick(new Button { Tag = node }, null);
|
||||
|
||||
// 从可视化树中查找 TreeViewItem,不依赖 ContainerFromItem
|
||||
var tvi = FindTreeViewItemByDataContext(AliasTreeView, node);
|
||||
LogManager.Info($"[别名树] OnCtxEditClick: tvi found={tvi != null}");
|
||||
if (tvi != null)
|
||||
StartInlineEdit(node, tvi);
|
||||
}
|
||||
|
||||
private static TreeViewItem FindTreeViewItemByDataContext(ItemsControl parent, object dataContext)
|
||||
{
|
||||
// 先检查直接子项
|
||||
for (int i = 0; i < parent.Items.Count; i++)
|
||||
{
|
||||
var item = parent.Items[i];
|
||||
if (item == dataContext)
|
||||
{
|
||||
return parent.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
|
||||
}
|
||||
if (item is AliasNodeViewModel vm)
|
||||
{
|
||||
// 递归搜索子项(先获取容器,再搜它的子项)
|
||||
var tvi = parent.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
|
||||
if (tvi != null && tvi.Items.Count > 0)
|
||||
{
|
||||
var found = FindTreeViewItemByDataContext(tvi, dataContext);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnCtxBatchEditClick(object sender, RoutedEventArgs e)
|
||||
@ -906,9 +953,86 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
LazyLoadChildren(node, aliasMap);
|
||||
}
|
||||
|
||||
private bool _isProgrammaticSelect;
|
||||
|
||||
/// <summary>
|
||||
/// 树节点被选中时:如果 500ms 内再次选中同一节点 → 进入内联编辑
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// 右键前先选中节点,确保右键菜单操作的是正确节点
|
||||
/// </summary>
|
||||
private void OnTreeViewItemRightClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var tvi = FindAncestor(e.OriginalSource as DependencyObject);
|
||||
if (tvi != null)
|
||||
{
|
||||
tvi.IsSelected = true;
|
||||
tvi.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private static TreeViewItem FindAncestor(DependencyObject current)
|
||||
{
|
||||
while (current != null)
|
||||
{
|
||||
if (current is TreeViewItem tvi) return tvi;
|
||||
current = VisualTreeHelper.GetParent(current);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 左键点击已选中节点 → 进入内联编辑
|
||||
/// </summary>
|
||||
private void OnTreeViewItemLeftClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var tvi = FindAncestor(e.OriginalSource as DependencyObject);
|
||||
if (tvi != null && tvi.IsSelected && tvi.DataContext is AliasNodeViewModel node && !node.IsOrphan)
|
||||
{
|
||||
StartInlineEdit(node);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTreeViewItemSelected(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OnTreeViewSelected(sender, e);
|
||||
if (e.OriginalSource is TreeViewItem tvi && tvi.DataContext is AliasNodeViewModel node && !node.IsOrphan)
|
||||
{
|
||||
if (!_isProgrammaticSelect)
|
||||
OnTreeViewSelected(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选中状态下点击 → 进入内联编辑(模仿 DataGrid 行为)
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// F2 键 → 进入内联编辑
|
||||
/// </summary>
|
||||
private void UserControl_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.F2)
|
||||
{
|
||||
EnterEditSelectedNode();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 双击 → 进入内联编辑
|
||||
/// </summary>
|
||||
private void OnTreeViewDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
EnterEditSelectedNode();
|
||||
}
|
||||
|
||||
private void EnterEditSelectedNode()
|
||||
{
|
||||
var node = GetSelectedNode();
|
||||
if (node != null)
|
||||
{
|
||||
var tvi = AliasTreeView.ItemContainerGenerator.ContainerFromItem(node) as TreeViewItem;
|
||||
if (tvi != null) StartInlineEdit(node, tvi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user