ContextMenuを表示する前に、右クリックでTreeViewノードを選択します


回答:


130

ツリーの入力方法に応じて、送信者とe.Sourceの値は異なる場合があります

可能な解決策の1つは、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;
}

TreeViewまたはTreeViewItemのこのイベントですか?
Louis Rhys

1
右クリックが空の場所にある場合、すべてを選択解除する方法はありますか?
Louis Rhys

他の5つから助けた唯一の答え...私はツリービューの人口で本当に何か間違っているのをありがとう。

3
Louis Rhysの質問に答えて:if (treeViewItem == null) treeView.SelectedIndex = -1またはtreeView.SelectedItem = null。どちらでもうまくいくと思います。
James M

24

XAMLのみのソリューションが必要な場合は、ブレンド対話機能を使用できます。

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

2つの興味深い部分があります。

  1. TreeViewItem.IsSelectedプロパティは次のようにバインドされているIsSelectedビューモデルのプロパティ。IsSelectedビューモデルのプロパティをtrueに設定すると、ツリー内の対応するノードが選択されます。

  2. 場合PreviewMouseRightButtonDown(この試料A内のノードの視覚的な部分に火災TextBlockIsSelectedビューモデルのプロパティがtrueに設定されています。1.に戻ると、ツリーでクリックされた対応するノードが選択されたノードになることがわかります。

プロジェクトでBlend Interactivityを取得する1つの方法は、NuGetパッケージUnofficial.Blend.Interactivityを使用することです。


2
すばらしい回答、ありがとうございます!iei名前空間のマッピングが解決する対象とそれらが見つかるアセンブリを示すと便利です。私は、System.Windows.InteractivityアセンブリとMicrosoft.Expression.Interactionsアセンブリにそれぞれあるxmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"とを想定xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"しています。
PRLC 2015

はバインドされたデータオブジェクトのプロパティChangePropertyActionを設定しようとしているため、これは役に立ちIsSelectedませんでした。UIの一部ではないため、IsSelectedプロパティがありません。私は何か間違ったことをしていますか?
アントニン・プロチャズカ

@AntonínProcházka:私の回答では、「データオブジェクト」(またはビューモデル)に、IsSelected私の回答の2番目の段落に記載されているプロパティが必要ですブールプロパティを持つビューモデルの階層コレクションにデータがバインドされていると想定します ...TreeViewIsSelected(私の強調)。
Martin Liversage 2017年

16

「item.Focus();」の使用 「item.IsSelected = true;」を使用すると、100%動作しないようです。します。


このヒントをありがとう。助けて頂きました。
i8abug 2010年

良いヒント。最初にFocus()を呼び出し、次にIsSelected = trueを設定します。
Jim Gomes

12

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

2
期待どおりに動作しません。ルート要素を送信者として常に取得します。social.msdn.microsoft.com/Forums/en-US/wpf/thread/…このような方法で追加されたイベントハンドラーは、期待どおりに機能する同様のソリューションを見つけました。それを受け入れるためにコードを変更しましたか?:-)
alex2k8 2009

どうやらツリービューにどのようにデータを入力するかによります。私が投稿したコードは機能します。これは、私のツールの1つで使用しているコードとまったく同じだからです。
ステファン、

ここでデバッグポイントを設定すると、送信者のタイプを確認できます。もちろん、ツリーの設定方法に基づいて送信者が異なります

これが機能する場合、これは最も単純なソリューションのようです。それは私のために働いた。実際、送信者をTreeViewItemとしてキャストする必要があります。そうでない場合はバグです。
craftworkgames

12

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を返したため、修正しました。


1
これをさらに進めるために、この回答はDependencyProperty拡張機能でこれを実装します。stackoverflow.com
Terrence

7

ほぼ正しいですが、ツリー内の非ビジュアル(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; 
}

このジェネリックメソッドは、奇妙に思えます。それは私に変換エラーを与えます
Rati_Ge

TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem>(e.OriginalSource as DependencyObject)as TreeViewItem;
Anthony Wieser

6

クラスハンドラーを登録することでうまくいくと思います。次のように、ルーティングされたイベントハンドラーを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;
    }
}

私のために働いた!そして、単純すぎる。
dvallejo 2012年

2
こんにちはネイサン。コードはグローバルで、すべてのTreeViewに影響するようです。ローカルのみのソリューションがある方がいいのではないでしょうか。それは副作用を引き起こす可能性がありますか?
Eric Ouellet 2013

このコードは、WPFアプリケーション全体で実際にグローバルです。私の場合、これは必須の動作だったので、アプリケーション内で使用されるすべてのツリービューで一貫していた。ただし、このイベントをツリービューインスタンス自体に登録して、そのツリービューにのみ適用できるようにすることができます。
Nathan Swannet、2016年

2

MVVMを使用してそれを解決する別の方法は、ビューモデルを右クリックするためのbindコマンドです。そこで、他のロジックも指定できますsource.IsSelected = true。これはxmlns:i="http://schemas.microsoft.com/expression/2010/intera‌​ctivity"からのみ使用し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;
        }
    }

1

HierarchicalDataTemplateメソッドで子を選択するときに問題が発生しました。ノードの子を選択すると、どういうわけかその子のルート親が選択されます。私は、MouseRightButtonDownイベントが子のすべてのレベルで呼び出されることを発見しました。たとえば、次のようなツリーがあるとします。

項目1
   -子供1
   -子供2
      - Subitem1
      - Subitem2

Subitem2を選択した場合、イベントは3回発生し、アイテム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に設定すると、親への他の呼び出しが無効になります。
Basit Anwer

0

マウスダウンイベントで選択できます。これにより、コンテキストメニューが起動する前に選択がトリガーされます。


0

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>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.