ContextMenu内のMenuItemにCommandTargetを設定する方法は?
-
03-07-2019 - |
質問
(この質問は、もう1つですが、ここに配置する必要があると思うほど十分に異なります。)
これは(大きく切り取られた) Window
:
<Window x:Class="Gmd.TimeTracker2.TimeTrackerMainForm"
xmlns:local="clr-namespace:Gmd.TimeTracker2"
xmlns:localcommands="clr-namespace:Gmd.TimeTracker2.Commands"
x:Name="This"
DataContext="{Binding ElementName=This}">
<Window.CommandBindings>
<CommandBinding Command="localcommands:TaskCommands.ViewTaskProperties"
Executed="HandleViewTaskProperties"
CanExecute="CanViewTaskPropertiesExecute" />
</Window.CommandBindings>
<DockPanel>
<!-- snip stuff -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- snip more stuff -->
<Button Content="_Create a new task" Grid.Row="1" x:Name="btnAddTask" Click="HandleNewTaskClick" />
</Grid>
</DockPanel>
</Window>
そして、これは(大きく切り取られた) UserControl
:
<UserControl x:Class="Gmd.TimeTracker2.TaskStopwatchControl"
xmlns:local="clr-namespace:Gmd.TimeTracker2"
xmlns:localcommands="clr-namespace:Gmd.TimeTracker2.Commands"
x:Name="This"
DataContext="{Binding ElementName=This}">
<UserControl.ContextMenu>
<ContextMenu>
<MenuItem x:Name="mnuProperties" Header="_Properties" Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="What goes here?" />
</ContextMenu>
</UserControl.ContextMenu>
<StackPanel>
<TextBlock MaxWidth="100" Text="{Binding Task.TaskName, Mode=TwoWay}" TextWrapping="WrapWithOverflow" TextAlignment="Center" />
<TextBlock Text="{Binding Path=ElapsedTime}" TextAlignment="Center" />
<Button Content="{Binding Path=IsRunning, Converter={StaticResource boolToString}, ConverterParameter='Stop Start'}" Click="HandleStartStopClicked" />
</StackPanel>
</UserControl>
さまざまな手法により、 UserControl
を Window
に動的に追加できます。おそらくウィンドウのボタンを介して。おそらく、もっと問題なのは、アプリケーションの起動時に永続的なバッキングストアから。
xamlからわかるように、ユーザーが Task
で実行できるさまざまな操作を処理する方法としてコマンドを使用するのが理にかなっていると判断しました。すべてのコマンドロジックをより正式に定義されたコントローラーレイヤーにファクタリングするという最終的な目標でこれを行っていますが、一度に1ステップずつリファクタリングしようとしています。
私が遭遇している問題は、 UserControl
の ContextMenu
のコマンドとコマンドの CanExecute
の相互作用に関連しています。ウィンドウで定義されます。アプリケーションが最初に起動し、保存されたタスクがウィンドウのTaskStopwatchesに復元されるとき、実際のUI要素は選択されません。次に、 ViewTaskProperties
コマンドを実行しようとして Window
の UserControl
をすぐにrクリックすると、 CanExecute
ハンドラーは実行されず、メニュー項目は無効のままです。次に、何かフォーカスを与えるためにUI要素(ボタンなど)をクリックすると、 CanExecute
ハンドラーが実行され、 CanExecuteRoutedEventArgs
のSourceプロパティがUI要素に設定されます。フォーカスがあります。
いくつかの点で、この動作は既知のようです-メニューは常にメニュー項目からイベントを送信することを避けるために、最後にフォーカスされた要素を通してイベントをルーティングすることを学びました。ただし、イベントのソースはコントロール自体であるか、コントロールがそれ自体をラップしているタスクであると思います(ただし、 Task
は要素ではないため、ソースになるとは思わないでください。)
UserControl
の MenuItem
の CommandTarget
プロパティが欠落していると思われたので、最初に考えたのはUserControlから来るコマンドなので、当然私は最初に試しました:
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding ElementName=This}" />
これは無効なバインディングとして失敗しました。理由はわかりません。そして、「うーん、ツリーを検索しているので、必要なのはRelativeSourceでしょう」と思いました。そして私はこれを試しました:
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TaskStopwatchControl}}}" />
それも失敗しましたが、xamlをもう一度見ると、 ContextMenu
はUserControlのプロパティにあり、子要素ではないことに気付きました。だから私は推測した(そしてこの時点では推測だった):
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding RelativeSource={x:Static RelativeSource.Self}}" />
そしてそれも失敗しました。
このような推測とチェックに失敗したことは、私を立ち退かせ、ここで何らかの基本的な概念が欠けていることに気付くのに十分です。だから私は何をしますか?
- コマンドソースを変更するメカニズムを提供するという点で、
CommandTarget
の役割は正しいですか? -
UserControl.ContextMenu
のMenuItem
を所有するUserControl
にバインドするにはどうすればよいですか?それとも、単に必要だと思っているからといって、何か間違ったことをしているのでしょうか? -
コンテキストメニューの前にフォーカスがあった要素ではなく、クリックしてコンテキストメニューを生成した要素によって設定されたコマンドのコンテキストを取得したいのは間違っていますか?
を使用する代わりに、独自のコマンドを書く必要があるかもしれません。RoutedUICommand
:private static RoutedUICommand viewTaskPropertiesCommand = new RoutedUICommand("View a task's details.", "ViewTaskProperties", typeof(TaskCommands)); public static RoutedUICommand ViewTaskProperties { get { return viewTaskPropertiesCommand; } }
-
私の設計に根本的な欠陥はありますか?これは私の最初の重要なWPFプロジェクトです。
解決
1:はい、 CommandTarget RoutedCommandがルーティングを開始する場所を制御します。
2: ContextMenu にはがありますUserControlへのアクセスを許可するPlacementTarget プロパティ:
<MenuItem x:Name="mnuProperties" Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding PlacementTarget,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}}"/>
すべてのMenuItemでこれを繰り返さないようにするには、スタイルを使用できます。
3&amp; 4:私はあなたの願望は合理的だと思います。 ExecuteハンドラーはWindow上にあるため、現時点では重要ではありませんが、アプリケーションの異なる領域があり、それぞれに同じコマンドの独自のExecuteハンドラーがある場合、フォーカスがどこにあるかが問題になります。
他のヒント
私が見つけた同様の解決策は、親のTagプロパティを使用してデータコンテキストを取得することでした:
<Grid Tag="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Grid.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem
Header="{Binding Path=ToolbarDelete, Mode=Default, Source={StaticResource Resx}}"
Command="{Binding RemoveCommand}"
CommandParameter="{Binding DataContext.Id, RelativeSource={RelativeSource TemplatedParent}}"/>
</ContextMenu>
</Grid.ContextMenu>
<TextBlock Text="{Binding Name}" Padding="2" />
</Grid>