developer tip

ContextMenu의 MenuItem에서 ElementName 바인딩

optionbox 2020. 11. 18. 08:56
반응형

ContextMenu의 MenuItem에서 ElementName 바인딩


다른 사람이 ElementName을 사용하는 바인딩이 MenuItem개체 내에 포함 된 개체에 대해 올바르게 확인되지 않는다는 사실을 알고 ContextMenu있습니까? 이 샘플을 확인하십시오.

<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'바인딩이 작동하지 않습니다. 허용 된 답변에서 제안한대로 컨텍스트 메뉴의 'NameScope'설정은 컨텍스트 메뉴가 'DataTemplate'에 정의되지 않은 경우에만 작동합니다. 'ElementName'바인딩과 비슷하지만 시각적 트리를 우회하여 바인딩을 다르게 해결하는 {x : Reference} Markup-Extension사용하여이 문제를 해결했습니다 . 나는 이것이 '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 는 종종 컬렉션의 많은 것 중 하나이며 바인딩하려는 ICommand 는 동일한 ViewModel ( 예 : Window 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>

컨텍스트 메뉴는 바인딩하기가 까다 롭습니다. 컨트롤의 시각적 트리 외부에 존재하므로 요소 이름을 찾을 수 없습니다.

Try setting the datacontext of your context menu to its placement target. You have to use RelativeSource.

<ContextMenu 
   DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ...

After experimenting a bit, I discovered one work around:

Make top level Window/UserControl implement INameScope and set NameScope of ContextMenu to the top level control.

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
}

This allows the context menu to find named items inside of the Window. Any other options?


I'm not sure why resort to magic tricks just to avoid a one line of code inside the eventhandler for the mouse click you already handle:

    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;
    }

참고URL : https://stackoverflow.com/questions/1013558/elementname-binding-from-menuitem-in-contextmenu

반응형