ContextMenu가 표시되기 직전에 마우스 오른쪽 버튼을 클릭하여 WPF TreeView 노드를 선택하고 싶습니다.
WinForms의 경우 컨텍스트 메뉴 에서 클릭 한 Find 노드 와 같은 코드를 사용할 수 있습니다 . WPF 대안은 무엇입니까?
ContextMenu가 표시되기 직전에 마우스 오른쪽 버튼을 클릭하여 WPF TreeView 노드를 선택하고 싶습니다.
WinForms의 경우 컨텍스트 메뉴 에서 클릭 한 Find 노드 와 같은 코드를 사용할 수 있습니다 . WPF 대안은 무엇입니까?
답변:
트리가 채워진 방식에 따라 보낸 사람과 e.Source 값이 다를 수 있습니다 .
가능한 솔루션 중 하나는 e.OriginalSource를 사용하고 VisualTreeHelper를 사용하여 TreeViewItem을 찾는 것입니다.
private void OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
TreeViewItem treeViewItem = VisualUpwardSearch(e.OriginalSource as DependencyObject);
if (treeViewItem != null)
{
treeViewItem.Focus();
e.Handled = true;
}
}
static TreeViewItem VisualUpwardSearch(DependencyObject source)
{
while (source != null && !(source is TreeViewItem))
source = VisualTreeHelper.GetParent(source);
return source as TreeViewItem;
}
if (treeViewItem == null) treeView.SelectedIndex = -1
또는 treeView.SelectedItem = null
. 둘 다 작동해야한다고 생각합니다.
XAML 전용 솔루션을 원하는 경우 Blend Interactivity를 사용할 수 있습니다.
가정 TreeView
가진 뷰 모델의 계층 컬렉션에 데이터 바인딩이다 Boolean
재산 IsSelected
과 String
재산 Name
뿐만 아니라라는 이름의 하위 항목의 컬렉션을 Children
.
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseRightButtonDown">
<ei:ChangePropertyAction PropertyName="IsSelected" Value="true" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
두 가지 흥미로운 부분이 있습니다.
TreeViewItem.IsSelected
속성은 바인딩 된 IsSelected
뷰 모델에 대한 속성입니다. IsSelected
뷰 모델 의 속성을 true로 설정하면 트리에서 해당 노드가 선택됩니다.
경우 PreviewMouseRightButtonDown
(이 샘플 A의 노드의 시각적 부분 화재 TextBlock
)를 IsSelected
뷰 모델에 속성이 참으로 설정된다. 1로 돌아 가면 트리에서 클릭 한 해당 노드가 선택된 노드가되는 것을 볼 수 있습니다.
프로젝트에서 Blend Interactivity를 얻는 한 가지 방법은 NuGet 패키지 Unofficial.Blend.Interactivity 를 사용하는 것 입니다.
i
하고 ei
어느 어셈블리 그들은 내가 가정에서 발견 될 수 있지만에 대한 네임 스페이스 매핑 해결 :. xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
그리고 xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
각각 System.Windows.Interactivity 및 Microsoft.Expression.Interactions 어셈블리에서 발견되는.
ChangePropertyAction
의 IsSelected
속성 을 설정하려고했기 때문에이 방법은 도움이되지 않았으므로 속성이 없습니다 IsSelected
. 내가 뭘 잘못하고 있니?
IsSelected
에 내 대답의 두 번째 단락에 명시된 속성 이 있어야합니다. 데이터가 부울 속성을 가진 뷰 모델의 계층 적 컬렉션에 바인딩되어 있다고 가정합니다 .TreeView
IsSelected
(내 강조).
"item.Focus ();"사용 "item.IsSelected = true;"를 사용하여 100 % 작동하지 않는 것 같습니다. 않습니다.
XAML에서 XAML에 PreviewMouseRightButtonDown 처리기를 추가합니다.
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- We have to select the item which is right-clicked on -->
<EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
</Style>
</TreeView.ItemContainerStyle>
그런 다음 다음과 같이 이벤트를 처리합니다.
private void TreeViewItem_PreviewMouseRightButtonDown( object sender, MouseEventArgs e )
{
TreeViewItem item = sender as TreeViewItem;
if ( item != null )
{
item.Focus( );
e.Handled = true;
}
}
alex2k8의 원래 아이디어를 사용하여 Wieser Software Ltd의 비 비주얼, Stefan의 XAML, Erlend의 IsSelected 및 정적 메서드 Generic을 만드는 데 기여한 내용을 올바르게 처리합니다.
XAML :
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- We have to select the item which is right-clicked on -->
<EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown"
Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
</Style>
</TreeView.ItemContainerStyle>
뒤에 C # 코드 :
void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
TreeViewItem treeViewItem =
VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject);
if(treeViewItem != null)
{
treeViewItem.IsSelected = true;
e.Handled = true;
}
}
static T VisualUpwardSearch<T>(DependencyObject source) where T : DependencyObject
{
DependencyObject returnVal = source;
while(returnVal != null && !(returnVal is T))
{
DependencyObject tempReturnVal = null;
if(returnVal is Visual || returnVal is Visual3D)
{
tempReturnVal = VisualTreeHelper.GetParent(returnVal);
}
if(tempReturnVal == null)
{
returnVal = LogicalTreeHelper.GetParent(returnVal);
}
else returnVal = tempReturnVal;
}
return returnVal as T;
}
편집 : 이전 코드는이 시나리오에서 항상 잘 작동했지만 다른 시나리오에서는 LogicalTreeHelper가 값을 반환 할 때 VisualTreeHelper.GetParent가 null을 반환하므로이를 수정했습니다.
거의 맞지만, 트리에서 비 시각적 요소 (예 :)를주의해야합니다 Run
.
static DependencyObject VisualUpwardSearch<T>(DependencyObject source)
{
while (source != null && source.GetType() != typeof(T))
{
if (source is Visual || source is Visual3D)
{
source = VisualTreeHelper.GetParent(source);
}
else
{
source = LogicalTreeHelper.GetParent(source);
}
}
return source;
}
클래스 핸들러를 등록하는 것이 트릭을해야한다고 생각합니다. 다음과 같이 app.xaml.cs 코드 파일에있는 TreeViewItem의 PreviewMouseRightButtonDownEvent에 라우트 된 이벤트 핸들러를 등록하기 만하면됩니다.
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
EventManager.RegisterClassHandler(typeof(TreeViewItem), TreeViewItem.PreviewMouseRightButtonDownEvent, new RoutedEventHandler(TreeViewItem_PreviewMouseRightButtonDownEvent));
base.OnStartup(e);
}
private void TreeViewItem_PreviewMouseRightButtonDownEvent(object sender, RoutedEventArgs e)
{
(sender as TreeViewItem).IsSelected = true;
}
}
MVVM을 사용하여 문제를 해결하는 또 다른 방법은 뷰 모델을 마우스 오른쪽 버튼으로 클릭하는 bind 명령입니다. 거기에서 다른 로직과 source.IsSelected = true
. 이것은 단지 사용 xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
에서 System.Windows.Interactivity
.
보기 용 XAML :
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseRightButtonDown">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.TreeViewItemRigthClickCommand}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
모델보기 :
public ICommand TreeViewItemRigthClickCommand
{
get
{
if (_treeViewItemRigthClickCommand == null)
{
_treeViewItemRigthClickCommand = new RelayCommand<object>(TreeViewItemRigthClick);
}
return _treeViewItemRigthClickCommand;
}
}
private RelayCommand<object> _treeViewItemRigthClickCommand;
private void TreeViewItemRigthClick(object sourceItem)
{
if (sourceItem is Item)
{
(sourceItem as Item).IsSelected = true;
}
}
HierarchicalDataTemplate 메서드로 자식을 선택하는 데 문제가있었습니다. 노드의 자식을 선택하면 어떻게 든 해당 자식의 루트 부모를 선택합니다. 나는 MouseRightButtonDown 이벤트가 아이가있는 모든 레벨에 대해 호출된다는 것을 알았습니다. 예를 들어 다음과 같은 나무가있는 경우 :
항목 1
- 자녀 1
- 자녀 2
-의 SubItem1
- Subitem2
Subitem2를 선택하면 이벤트가 세 번 실행되고 항목 1이 선택됩니다. 부울 및 비동기 호출로이 문제를 해결했습니다.
private bool isFirstTime = false;
protected void TaskTreeView_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var item = sender as TreeViewItem;
if (item != null && isFirstTime == false)
{
item.Focus();
isFirstTime = true;
ResetRightClickAsync();
}
}
private async void ResetRightClickAsync()
{
isFirstTime = await SetFirstTimeToFalse();
}
private async Task<bool> SetFirstTimeToFalse()
{
return await Task.Factory.StartNew(() => { Thread.Sleep(3000); return false; });
}
약간 어설프게 느껴지지만 기본적으로 첫 번째 패스에서 부울을 true로 설정하고 몇 초 안에 다른 스레드에서 재설정합니다 (이 경우 3). 즉, 트리 위로 이동하려는 다음 패스를 건너 뛰고 올바른 노드를 선택한 상태로 둡니다. 지금까지 작동하는 것 같습니다 :-)
MouseButtonEventArgs.Handled
하는 것 true
입니다. 아이가 처음으로 부름을 받기 때문입니다. 이 속성을 true로 설정하면 부모에 대한 다른 호출이 비활성화됩니다.
MVVM 패턴을 유지하려면 다음을 수행 할 수 있습니다.
전망:
<TreeView x:Name="trvName" ItemsSource="{Binding RootElementListView}" Tag="{Binding ClickedTreeElement, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:YourTreeElementClass}" ItemsSource="{Binding Path=Subreports}">
<TextBlock Text="{Binding YourTreeElementDisplayProperty}" PreviewMouseRightButtonDown="TreeView_PreviewMouseRightButtonDown"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
비하인드 코드 :
private void TreeView_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is TextBlock tb && tb.DataContext is YourTreeElementClass te)
{
trvName.Tag = te;
}
}
ViewModel :
private YourTreeElementClass _clickedTreeElement;
public YourTreeElementClass ClickedTreeElement
{
get => _clickedTreeElement;
set => SetProperty(ref _clickedTreeElement, value);
}
이제 ClickedTreeElement 속성 변경에 반응하거나 ClickedTreeElement와 내부적으로 작동하는 명령을 사용할 수 있습니다.
확장보기 :
<UserControl ...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<TreeView x:Name="trvName" ItemsSource="{Binding RootElementListView}" Tag="{Binding ClickedTreeElement, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonUp">
<i:InvokeCommandAction Command="{Binding HandleRightClickCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:YourTreeElementClass}" ItemsSource="{Binding Path=Subreports}">
<TextBlock Text="{Binding YourTreeElementDisplayProperty}" PreviewMouseRightButtonDown="TreeView_PreviewMouseRightButtonDown"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</UserControl>