ContextMenuのMenuItemからのElementNameバインディング
-
06-07-2019 - |
質問
ContextMenu
オブジェクト内に含まれる MenuItem
オブジェクトのElementNameとのバインディングが正しく解決されないことに気づいた人はいますか?このサンプルをご覧ください:
<Window x:Class="EmptyWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
x:Name="window">
<Grid x:Name="grid" Background="Wheat">
<Grid.ContextMenu>
<ContextMenu x:Name="menu">
<MenuItem x:Name="menuItem" Header="Window" Tag="{Binding ElementName=window}" Click="MenuItem_Click"/>
<MenuItem Header="Grid" Tag="{Binding ElementName=grid}" Click="MenuItem_Click"/>
<MenuItem Header="Menu" Tag="{Binding ElementName=menu}" Click="MenuItem_Click"/>
<MenuItem Header="Menu Item" Tag="{Binding ElementName=menuItem}" Click="MenuItem_Click"/>
</ContextMenu>
</Grid.ContextMenu>
<Button Content="Menu"
HorizontalAlignment="Center" VerticalAlignment="Center"
Click="MenuItem_Click" Tag="{Binding ElementName=menu}"/>
<Menu HorizontalAlignment="Center" VerticalAlignment="Bottom">
<MenuItem x:Name="anotherMenuItem" Header="Window" Tag="{Binding ElementName=window}" Click="MenuItem_Click"/>
<MenuItem Header="Grid" Tag="{Binding ElementName=grid}" Click="MenuItem_Click"/>
<MenuItem Header="Menu" Tag="{Binding ElementName=menu}" Click="MenuItem_Click"/>
<MenuItem Header="Menu Item" Tag="{Binding ElementName=anotherMenuItem}" Click="MenuItem_Click"/>
</Menu>
</Grid>
</Window>
すべてのバインディングは、ContextMenu内に含まれるバインディングを除き、非常に機能します。実行時にエラーが出力ウィンドウに出力されます。
回避策を知っている人はいますか?ここで何が起こっているのですか?
解決
もっと簡単な解決策を見つけました。
UserControlのコードビハインド:
NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));
他のヒント
他の人が言ったように、「ContextMenu」はビジュアルツリーに含まれておらず、「ElementName」バインディングは機能しません。コンテキストメニューが「DataTemplate」で定義されていない場合にのみ、受け入れられた回答によって提案されたコンテキストメニューの「NameScope」の設定が機能します。 {x:Reference} Markup-Extension を使用してこれを解決しました。 'ElementName'バインディングに似ていますが、ビジュアルツリーをバイパスして、バインディングを異なる方法で解決します。これは、「PlacementTarget」を使用するよりもはるかに読みやすいと思います。次に例を示します。
<Image Source="{Binding Image}">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"
Command="{Binding Source={x:Reference Name=Root}, Path=DataContext.RemoveImage}"
CommandParameter="{Binding}" />
</ContextMenu>
</Image.ContextMenu>
</Image>
MSDNドキュメントによると
x:Referenceは、XAML 2009で定義された構造です。WPFでは、次を使用できます。 XAML 2009機能。ただし、WPFマークアップコンパイルされていないXAMLのみ。 現在、マークアップでコンパイルされたXAMLおよびBAML形式のXAMLは XAML 2009言語のキーワードと機能をサポートします。
それが意味するものは何でも...しかし私のために働く。
これは、xamlのみの別の回避策です。 (これは、 DataContext の中にあるものを必要としていることを前提としています。たとえば、 MVVMing している場合)
オプション1、 ContextMenu の親要素が DataTemplate 内にない場合:
Command="{Binding PlacementTarget.DataContext.MyCommand,
RelativeSource={RelativeSource AncestorType=ContextMenu}}"
これは、OPの質問に対して有効です。 DataTemplate 内にいる場合、これは機能しません。これらの場合、 DataContext は多くの場合コレクション内の多くの1つであり、バインドする ICommand は同じViewModel内のコレクションの兄弟プロパティです(ウィンドウの DataContext など。)
これらの場合、 タグ を使用して、コレクションとICommandの両方を含む親 DataContext を一時的に保持します:
class ViewModel
{
public ObservableCollection<Derp> Derps { get;set;}
public ICommand DeleteDerp {get; set;}
}
およびxaml内
<!-- ItemsSource binds to Derps in the DataContext -->
<StackPanel
Tag="{Binding DataContext, ElementName=root}">
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem
Header="Derp"
Command="{Binding PlacementTarget.Tag.DeleteDerp,
RelativeSource={RelativeSource
AncestorType=ContextMenu}}"
CommandParameter="{Binding PlacementTarget.DataContext,
RelativeSource={RelativeSource AncestorType=ContextMenu}}">
</MenuItem>
コンテキストメニューは、バインドが困難です。コントロールのビジュアルツリーの外側に存在するため、要素名を見つけることができません。
コンテキストメニューのデータコンテキストを配置ターゲットに設定してみてください。 RelativeSourceを使用する必要があります。
<ContextMenu
DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ...
少し実験して、次の回避策を見つけました:
トップレベルの Window
/ UserControl
を INameScope
に実装し、 ContextMenu
NameScope を設定します>トップレベルコントロールに。
public class Window1 : Window, INameScope
{
public Window1()
{
InitializeComponent();
NameScope.SetNameScope(contextMenu, this);
}
// Event handlers and etc...
// Implement INameScope similar to this:
#region INameScope Members
Dictionary<string, object> items = new Dictionary<string, object>();
object INameScope.FindName(string name)
{
return items[name];
}
void INameScope.RegisterName(string name, object scopedElement)
{
items.Add(name, scopedElement);
}
void INameScope.UnregisterName(string name)
{
items.Remove(name);
}
#endregion
}
これにより、コンテキストメニューで Window
内の名前付きアイテムを検索できます。他のオプション?
すでに処理しているマウスクリックのイベントハンドラー内の1行のコードを回避するために、なぜマジックトリックに頼るのかわかりません:
private void MenuItem_Click(object sender, System.Windows.RoutedEventArgs e)
{
// this would be your tag - whatever control can be put as string intot he tag
UIElement elm = Window.GetWindow(sender as MenuItem).FindName("whatever control") as UIElement;
}