右クリックで、ContextMenuが表示される直前にWPF TreeViewノードを選択したいと思います。
WinFormsの場合、このFindノードのようなコードをコンテキストメニューの下でクリックできます。WPFの代替は何ですか?
右クリックで、ContextMenuが表示される直前にWPF TreeViewノードを選択したいと思います。
WinFormsの場合、このFindノードのようなコードをコンテキストメニューの下でクリックできます。WPFの代替は何ですか?
回答:
ツリーの入力方法に応じて、送信者と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;
}
if (treeViewItem == null) treeView.SelectedIndex = -1
またはtreeView.SelectedItem = null
。どちらでもうまくいくと思います。
XAMLのみのソリューションが必要な場合は、ブレンド対話機能を使用できます。
想定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>
2つの興味深い部分があります。
TreeViewItem.IsSelected
プロパティは次のようにバインドされているIsSelected
ビューモデルのプロパティ。IsSelected
ビューモデルのプロパティをtrueに設定すると、ツリー内の対応するノードが選択されます。
場合PreviewMouseRightButtonDown
(この試料A内のノードの視覚的な部分に火災TextBlock
)IsSelected
ビューモデルのプロパティがtrueに設定されています。1.に戻ると、ツリーでクリックされた対応するノードが選択されたノードになることがわかります。
プロジェクトでBlend Interactivityを取得する1つの方法は、NuGetパッケージUnofficial.Blend.Interactivityを使用することです。
i
とei
名前空間のマッピングが解決する対象とそれらが見つかるアセンブリを示すと便利です。私は、System.Windows.InteractivityアセンブリとMicrosoft.Expression.Interactionsアセンブリにそれぞれあるxmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
とを想定xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
しています。
ChangePropertyAction
を設定しようとしているため、これは役に立ちIsSelected
ませんでした。UIの一部ではないため、IsSelected
プロパティがありません。私は何か間違ったことをしていますか?
IsSelected
私の回答の2番目の段落に記載されているプロパティが必要です:ブールプロパティを持つビューモデルの階層コレクションにデータがバインドされていると想定します ...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を選択した場合、イベントは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に設定すると、親への他の呼び出しが無効になります。
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>