Chciałbym wybrać węzeł WPF TreeView po kliknięciu prawym przyciskiem myszy, tuż przed wyświetleniem menu ContextMenu.
W przypadku WinForms mógłbym użyć kodu takiego jak ten węzeł Znajdź kliknięty w menu kontekstowym , jakie są alternatywy WPF?
Chciałbym wybrać węzeł WPF TreeView po kliknięciu prawym przyciskiem myszy, tuż przed wyświetleniem menu ContextMenu.
W przypadku WinForms mógłbym użyć kodu takiego jak ten węzeł Znajdź kliknięty w menu kontekstowym , jakie są alternatywy WPF?
Odpowiedzi:
W zależności od sposobu wypełnienia drzewa wartości nadawcy i e.Source mogą się różnić .
Jednym z możliwych rozwiązań jest użycie e.OriginalSource i znalezienie TreeViewItem przy użyciu 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
lub treeView.SelectedItem = null
. Uważam, że jedno i drugie powinno działać.
Jeśli potrzebujesz rozwiązania obsługującego tylko XAML, możesz użyć funkcji Blend Interactivity.
Załóżmy, że TreeView
dane is są powiązane z hierarchiczną kolekcją modeli widoków posiadających Boolean
właściwość IsSelected
i String
właściwość, Name
a także kolekcję elementów podrzędnych o nazwie 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>
Istnieją dwie interesujące części:
TreeViewItem.IsSelected
Nieruchomość jest związana z IsSelected
nieruchomości na widoku model. Ustawienie IsSelected
właściwości w modelu widoku na true spowoduje wybranie odpowiedniego węzła w drzewie.
Po PreviewMouseRightButtonDown
uruchomieniu na wizualnej części węzła (w tym przykładzie a TextBlock
) IsSelected
właściwość modelu widoku jest ustawiona na wartość true. Wracając do 1. widać, że odpowiedni węzeł, który został kliknięty w drzewie, staje się węzłem wybranym.
Jednym ze sposobów uzyskania interaktywności mieszania w projekcie jest użycie pakietu NuGet Unofficial.Blend.Interactivity .
i
i ei
przestrzeni nazw i w jakich zestawach można je znaleźć. Zakładam: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
i xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
, które znajdują się odpowiednio w zestawach System.Windows.Interactivity i Microsoft.Expression.Interactions.
ChangePropertyAction
próbuje ustawić IsSelected
właściwość powiązanego obiektu danych, który nie jest częścią interfejsu użytkownika, więc nie ma IsSelected
właściwości. czy robię coś źle?
IsSelected
właściwość określoną w drugim akapicie mojej odpowiedzi: Załóżmy, że TreeView
dane są powiązane z hierarchicznym zbiorem modeli widoku posiadających właściwość logicznąIsSelected
... (moje podkreślenie).
W języku XAML dodaj program obsługi PreviewMouseRightButtonDown w języku XAML: In XAML, add a PreviewMouseRightButtonDown handler in 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>
Następnie obsłuż zdarzenie w następujący sposób:
private void TreeViewItem_PreviewMouseRightButtonDown( object sender, MouseEventArgs e )
{
TreeViewItem item = sender as TreeViewItem;
if ( item != null )
{
item.Focus( );
e.Handled = true;
}
}
Korzystając z oryginalnego pomysłu z alex2k8, poprawnie obsługując elementy niewizualne z Wieser Software Ltd, XAML od Stefana, IsSelected z Erlend i mój wkład w stworzenie statycznej metody 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>
Kod C # za:
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;
}
Edycja: poprzedni kod zawsze działał dobrze w tym scenariuszu, ale w innym scenariuszu VisualTreeHelper.GetParent zwrócił wartość null, gdy LogicalTreeHelper zwrócił wartość, więc naprawiliśmy to.
Prawie dobrze , ale musisz uważać na elementy niewidoczne w drzewie (jak Run
na przykład a).
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;
}
Myślę, że zarejestrowanie programu obsługi klas powinno załatwić sprawę. Po prostu zarejestruj procedurę obsługi zdarzeń kierowanych w obiekcie PreviewMouseRightButtonDownEvent TreeViewItem w pliku kodu app.xaml.cs w następujący sposób:
/// <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;
}
}
Innym sposobem rozwiązania tego problemu za pomocą MVVM jest polecenie wiązania prawym przyciskiem myszy z modelem widoku. Możesz tam również określić inną logikę source.IsSelected = true
. To używa tylko xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
z System.Windows.Interactivity
.
XAML do wyświetlenia:
<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>
Zobacz 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;
}
}
Miałem problem z wyborem dzieci za pomocą metody HierarchicalDataTemplate. Gdybym wybrał dziecko węzła, w jakiś sposób wybrałoby głównego rodzica tego dziecka. Dowiedziałem się, że zdarzenie MouseRightButtonDown będzie wywoływane na każdym poziomie, na jakim było dziecko. Na przykład, jeśli masz takie drzewo:
Pozycja 1
- Dziecko 1
- Dziecko 2
- Podelement1
- Podelement2
Gdybym wybrał Subitem2, zdarzenie uruchomiłoby się trzy razy i zostałby wybrany element 1. Rozwiązałem to za pomocą wywołania logicznego i asynchronicznego.
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; });
}
Wydaje się trochę lepki, ale zasadniczo ustawiam wartość logiczną na true przy pierwszym przejściu i resetuję ją w innym wątku w ciągu kilku sekund (w tym przypadku 3). Oznacza to, że następne przejście przez miejsce, w którym spróbuje przejść w górę, zostanie pominięte, pozostawiając wybrany właściwy węzeł. Jak dotąd wydaje się, że działa :-)
MouseButtonEventArgs.Handled
do true
. Ponieważ dziecko jest pierwszym wezwaniem. Ustawienie tej właściwości na true spowoduje wyłączenie innych wywołań do rodzica.
Możesz go wybrać za pomocą zdarzenia po naciśnięciu myszy. Spowoduje to wybranie przed uruchomieniem menu kontekstowego.
Jeśli chcesz pozostać w ramach wzorca MVVM, możesz wykonać następujące czynności:
Widok:
<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>
Kod za:
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);
}
Teraz możesz albo zareagować na zmianę właściwości ClickedTreeElement, albo możesz użyć polecenia, które wewnętrznie działa z ClickedTreeElement.
Rozszerzony widok:
<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>