Gostaria de selecionar um Nó TreeView WPF com um clique direito, logo antes do ContextMenu ser exibido.
Para WinForms, eu poderia usar um código como este nó Find clicado no menu de contexto , quais são as alternativas do WPF?
Gostaria de selecionar um Nó TreeView WPF com um clique direito, logo antes do ContextMenu ser exibido.
Para WinForms, eu poderia usar um código como este nó Find clicado no menu de contexto , quais são as alternativas do WPF?
Respostas:
Dependendo da forma como a árvore foi preenchida, os valores do remetente e da e.Source podem variar .
Uma das soluções possíveis é usar e.OriginalSource e encontrar TreeViewItem usando VisualTreeHelper:
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
ou treeView.SelectedItem = null
. Eu acredito que qualquer um deve funcionar.
Se você deseja uma solução apenas XAML, pode usar o Blend Interactivity.
Suponha que os TreeView
dados sejam vinculados a uma coleção hierárquica de modelos de visualização tendo uma Boolean
propriedade IsSelected
e uma String
propriedade Name
, bem como uma coleção de itens filhos nomeados 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>
Existem duas partes interessantes:
A TreeViewItem.IsSelected
propriedade está ligada à IsSelected
propriedade no modelo de exibição. Definir a IsSelected
propriedade no modelo de visualização como true selecionará o nó correspondente na árvore.
Quando PreviewMouseRightButtonDown
dispara na parte visual do nó (neste exemplo a TextBlock
), a IsSelected
propriedade no modelo de visualização é definida como verdadeira. Voltando a 1. você pode ver que o nó correspondente que foi clicado na árvore se torna o nó selecionado.
Uma maneira de obter o Blend Interactivity em seu projeto é usar o pacote NuGet Unofficial.Blend.Interactivity .
i
e ei
namespace resolvem embora e em quais assemblies eles podem ser encontrados. Suponho que: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
e xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
, que são encontrados nos assemblies System.Windows.Interactivity e Microsoft.Expression.Interactions respectivamente.
ChangePropertyAction
está tentando definir uma IsSelected
propriedade do objeto de dados vinculado, que não faz parte da interface do usuário, portanto, não tem IsSelected
propriedade. Estou fazendo algo errado?
IsSelected
propriedade conforme declarado no segundo parágrafo da minha resposta: Suponha que os TreeView
dados sejam vinculados a uma coleção hierárquica de modelos de visualização com uma propriedade booleanaIsSelected
... (ênfase minha).
Em XAML, adicione um manipulador PreviewMouseRightButtonDown em 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>
Em seguida, trate o evento assim:
private void TreeViewItem_PreviewMouseRightButtonDown( object sender, MouseEventArgs e )
{
TreeViewItem item = sender as TreeViewItem;
if ( item != null )
{
item.Focus( );
e.Handled = true;
}
}
Usando a ideia original de alex2k8, manipulando corretamente os elementos não visuais da Wieser Software Ltd, o XAML de Stefan, o IsSelected de Erlend e minha contribuição de realmente tornar o método estático Genérico:
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ódigo C # por trás:
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;
}
Edit: O código anterior sempre funcionou bem para este cenário, mas em outro cenário VisualTreeHelper.GetParent retornou null quando LogicalTreeHelper retornou um valor, então corrigiu isso.
Quase certo , mas você precisa estar atento para não visuais na árvore, (como um Run
, por exemplo).
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;
}
Acho que registrar um manipulador de classe deve resolver o problema. Basta registrar um manipulador de eventos roteados no PreviewMouseRightButtonDownEvent do TreeViewItem em seu arquivo de código app.xaml.cs como este:
/// <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;
}
}
Outra maneira de resolver isso usando MVVM é o comando de ligação para clicar com o botão direito em seu modelo de visualização. Lá você também pode especificar outra lógica source.IsSelected = true
. Isso usa apenas xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
de System.Windows.Interactivity
.
XAML para visualização:
<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>
Ver modelo:
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;
}
}
Eu estava tendo problemas ao selecionar filhos com um método HierarchicalDataTemplate. Se eu selecionasse o filho de um nó, de alguma forma, ele selecionaria o pai raiz desse filho. Eu descobri que o evento MouseRightButtonDown seria chamado para todos os níveis em que a criança estava. Por exemplo, se você tiver uma árvore parecida com esta:
Item 1
- Criança 1
- Criança 2
- Subitem1
- Subitem2
Se eu selecionasse o Subitem2, o evento seria disparado três vezes e o item 1 seria selecionado. Resolvi isso com uma chamada booleana e assíncrona.
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; });
}
Parece um pouco confuso, mas basicamente eu defini o booleano como verdadeiro na primeira passagem e o redefini em outro encadeamento em alguns segundos (3 neste caso). Isso significa que a próxima passagem por onde tentaria subir na árvore será ignorada, deixando você com o nó correto selecionado. Parece que funciona até agora :-)
MouseButtonEventArgs.Handled
como true
. Pois a criança é a primeira a ser chamada. Configurar esta propriedade como true irá desativar outras chamadas para o pai.
Você pode selecioná-lo com o evento ao pressionar o mouse. Isso irá acionar o select antes de o menu de contexto entrar em ação.
Se você deseja permanecer dentro do padrão MVVM, você pode fazer o seguinte:
Visão:
<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>
Código por trás:
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);
}
Agora você pode reagir à alteração da propriedade ClickedTreeElement ou pode usar um comando que funcione internamente com ClickedTreeElement.
Visão estendida:
<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>