code

ContextMenu를 표시하기 전에 오른쪽 클릭에서 TreeView 노드를 선택

codestyles 2020. 8. 28. 07:30
반응형

ContextMenu를 표시하기 전에 오른쪽 클릭에서 TreeView 노드를 선택


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;
}

XAML 전용 솔루션을 원하는 경우 Blend Interactivity를 사용할 수 있습니다.

가정 TreeView가진 뷰 모델의 계층 컬렉션에 데이터 바인딩이다 Boolean재산 IsSelectedString재산 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>

두 가지 흥미로운 부분이 있습니다.

  1. TreeViewItem.IsSelected속성은 바인딩 된 IsSelected뷰 모델에 대한 속성입니다. IsSelected뷰 모델 속성을 true로 설정하면 트리에서 해당 노드가 선택됩니다.

  2. 경우 PreviewMouseRightButtonDown(이 샘플 A의 노드의 시각적 부분 화재 TextBlock)를 IsSelected뷰 모델에 속성이 참으로 설정된다. 1로 돌아 가면 트리에서 클릭 한 해당 노드가 선택된 노드가되는 것을 볼 수 있습니다.

프로젝트에서 Blend Interactivity를 얻는 한 가지 방법은 NuGet 패키지 Unofficial.Blend.Interactivity 를 사용하는 것 입니다.


"item.Focus ();"사용 "item.IsSelected = true;"를 사용하여 100 % 작동하지 않는 것 같습니다. 않습니다.


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을 반환하므로이를 수정했습니다.


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;
        }
    }

거의 맞지만, 트리에서 비 시각적 요소 (예 :)를주의해야합니다 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;
    }
}

I was having a problem with selecting children with a HierarchicalDataTemplate method. If I selected the child of a node it would somehow select the root parent of that child. I found out that the MouseRightButtonDown event would get called for every level the child was. For example if you have a tree something like this:

Item 1
   - Child 1
   - Child 2
      - Subitem1
      - Subitem2

If I selected Subitem2 the event would fire three times and item 1 would be selected. I solved this with a boolean and an asynchronous call.

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; });
    }

It feels a little cludgy but basically I set the boolean to true on the first pass through and have it reset on another thread in a few seconds (3 in this case). This means that the next passes through where it would try to move up the tree will get skipped leaving you with the correct node selected. It seems to work so far :-)


Another way to solve it using MVVM is bind command for right click to your view model. There you can specify other logic as well as source.IsSelected = true. This uses only xmlns:i="http://schemas.microsoft.com/expression/2010/intera‌​ctivity" from System.Windows.Interactivity.

XAML for view:

<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>

View model:

    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;
        }
    }

You can select it with the on mouse down event. That will trigger the select before the context menu kicks in.

참고URL : https://stackoverflow.com/questions/592373/select-treeview-node-on-right-click-before-displaying-contextmenu

반응형