質問
コンテキストメニュー項目からのコマンドの起動に関連する興味深い問題...
InsertRowCmdというコントロールに行を挿入するコマンドを起動したい。このコマンドは、行を挿入する場所を知る必要があります。
Mouse.GetPosition()を使用できますが、現在のマウスの位置を取得できます。これはメニュー項目の上にあります。代わりに、コンテキストメニューの原点を取得したいです。
コマンドのパラメーターとしてコンテキストメニューの起点を渡す方法に関する提案はありますか?
サンプルコード:
<UserControl x:Name="MyControl">
<!--...-->
<ContextMenu x:Name="menu">
<MenuItem Header="Insert Row" Command="{x:Static customCommands:MyCommands.InsertRowCmd}" CommandParameter="?"/>
</ContextMenu>
</UserControl>
現在のアイデアは次のとおりです。
-代わりにクリックハンドラを使用して、コード内でオリジンを見つけることができます。問題は、有効化/無効化を処理する必要があることです。
-クリックイベントを処理し、コンテキストメニューの原点を保存します。この保存された情報をコマンドに渡します。コマンドが実行される前にクリックイベントが発生することを確認しました。
アイデアはありますか
編集:
Josh Smithの CommandSinkBinding を使用して、コマンド処理をViewModelクラスにルーティングします。そのため、コマンドの実行を処理するコードはビューについて何も知りません。
解決
を使用する必要があります。 TranslatePoint
を使用して、 ContextMenu
の左上(0、0)をグリッド内の座標に変換します。これを行うには、 CommandParameter
を ContextMenu
にバインドし、コンバーターを使用します。
CommandParameter="{Binding IsOpen, ElementName=_menu, Converter={StaticResource PointConverter}}"
もう1つのアプローチは、 ContextMenu
が開かれるたびに、タイプ Point
の添付読み取り専用プロパティを自動的に更新する添付動作です。使用方法は次のようになります。
<ContextMenu x:Name="_menu" local:TrackBehavior.TrackOpenLocation="True">
<MenuItem Command="..." CommandParameter="{Binding Path=(local:TrackBehavior.OpenLocation), ElementName=_menu}"/>
</ContextMenu>
したがって、 TrackOpenLocation
添付プロパティは、 ContextMenu
に添付し、 > ContextMenu
が開きます。次に、 MenuItem
は OpenLocation
にバインドするだけで、 ContextMenu
が最後に開かれた場所を取得できます。
他のヒント
ケントの答えに続いて、私は彼の添付されたプロパティの提案を使用し、これで終わりました(ジョシュ・スミスの添付ビヘイビアの例):
public static class TrackBehavior
{
public static readonly DependencyProperty TrackOpenLocationProperty = DependencyProperty.RegisterAttached("TrackOpenLocation", typeof(bool), typeof(TrackBehavior), new UIPropertyMetadata(false, OnTrackOpenLocationChanged));
public static bool GetTrackOpenLocation(ContextMenu item)
{
return (bool)item.GetValue(TrackOpenLocationProperty);
}
public static void SetTrackOpenLocation(ContextMenu item, bool value)
{
item.SetValue(TrackOpenLocationProperty, value);
}
public static readonly DependencyProperty OpenLocationProperty = DependencyProperty.RegisterAttached("OpenLocation", typeof(Point), typeof(TrackBehavior), new UIPropertyMetadata(new Point()));
public static Point GetOpenLocation(ContextMenu item)
{
return (Point)item.GetValue(OpenLocationProperty);
}
public static void SetOpenLocation(ContextMenu item, Point value)
{
item.SetValue(OpenLocationProperty, value);
}
static void OnTrackOpenLocationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var menu = dependencyObject as ContextMenu;
if (menu == null)
{
return;
}
if (!(e.NewValue is bool))
{
return;
}
if ((bool)e.NewValue)
{
menu.Opened += menu_Opened;
}
else
{
menu.Opened -= menu_Opened;
}
}
static void menu_Opened(object sender, RoutedEventArgs e)
{
if (!ReferenceEquals(sender, e.OriginalSource))
{
return;
}
var menu = e.OriginalSource as ContextMenu;
if (menu != null)
{
SetOpenLocation(menu, Mouse.GetPosition(menu.PlacementTarget));
}
}
}
そしてXamlで使用するには、次のものが必要です:
<ContextMenu x:Name="menu" Common:TrackBehavior.TrackOpenLocation="True">
<MenuItem Command="{Binding SomeCommand}" CommandParameter="{Binding Path=(Common:TrackBehavior.OpenLocation), ElementName=menu}" Header="Menu Text"/>
</ContextMenu>
ただし、追加する必要がありました:
NameScope.SetNameScope(menu, NameScope.GetNameScope(this));
私のビューのコンストラクターに、そうでなければ CommandParameter
のバインディングは ElementName = menu
を検索できませんでした。
ケントの答えに加えて、「標準的な方法」について考えてください。 F.e. ListBoxにContextMenuがある場合、選択された項目はメニューがポップアップする前に設定されるため、メニューの位置は必要ありません。そのため、コントロールに「選択」されるものがある場合、右クリック...