从ListBox项中的ContextMenu绑定到ViewModel

时间:2017-11-14 17:22:44

标签: wpf binding

我有一个带有命令的ViewModel' OpenCommand',一个标志' IsConextMenuVisible'和一个可观察的清单'链接'。

public ObservableList<string> Links { get; set; }
public bool IsContextMenuVisible { get; set; }
public ICommand OpenCommand { get; set; }

在XAML中,我想要以下工作。

<ListBox ItemsSource="{Binding Links}">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding}">
        <TextBlock.ContextMenu>
           <ContextMenu Visibility="{Binding IsContextMenuVisible, Converter={StaticResource BoolToVisibiltyHiddenConverter}}">
             <MenuItem Header="Open" Command="{Binding OpenCommand}" CommandParameter="{Binding}"/>
           </ContextMenu>
        </Textblock.ContextMenu>
      </TextBlock>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

我已经为ContextMenu上的内部绑定尝试了一些绑定表达式,但似乎没有任何效果。类似的东西:

Visibility="{Binding Path=DataContext.IsContextMenuVisible,
             Converter={StaticResource BoolToVisibilityCollapsedConverter},
             RelativeSource={RelativeSource AncestorType=ListBox}}"

1 个答案:

答案 0 :(得分:1)

这是有问题的&#34;正如孩子们所说,因为上下文菜单不在视觉树中,所以没有RelativeSource的味道可行。

您经常可以绑定PlacementTarget的属性,但在这种情况下,您需要PlacementTarget的视觉祖先,而RelativeSource不会做其他事情的祖先。

在WPF中,当可视树中存在间隙时,最后一个沟渠选项始终是BindingProxy。这是该类的样子(包括我从中窃取的StackOverflow问题的URL - 该类已被复制并粘贴在本网站上的许多问题和答案中):

//  https://stackoverflow.com/questions/24452264/bindingproxy-binding-to-the-indexed-property
public class BindingProxy : Freezable
{
    #region Overrides of Freezable

    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    #endregion

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

你会像这样使用它。首先将BindingProxy创建为一个资源,在一个可以&#34;参见&#34;所需元素:

<Window.Resources>
    <local:BoolToVisibiltyHiddenConverter x:Key="BoolToVisibiltyHiddenConverter" />

    <!-- {Binding} with no path will be the window's datacontext, the main viewmodel. -->
    <local:BindingProxy Data="{Binding}" x:Key="MainViewModelBindingProxy" />
</Window.Resources>

然后将其用于绑定的Source。所需的DataContext将是代理对象的Data属性,因此提供相对于Data的路径:

<TextBlock.ContextMenu>
    <ContextMenu 
        Visibility="{Binding Data.IsContextMenuVisible, 
            Converter={StaticResource BoolToVisibiltyHiddenConverter}, 
             Source={StaticResource MainViewModelBindingProxy}}"
        >
        <MenuItem 
            Header="Open" 
            Command="{Binding OpenCommand}" 
            CommandParameter="{Binding}"
            />
    </ContextMenu>
</TextBlock.ContextMenu>

现在您还遇到了另一个问题:菜单仍然弹出。它只是不可见。如果用户右键单击,它将无形地弹出,并在IsContextMenuVisible更改为true时突然显示。这不是你想要的。

您可以省略转换器并直接绑定到ContextMenu.IsEnabled:它仍会弹出,但它会变灰。这与常见的Windows UI实践一致。

您还可以使用样式触发器,以便TextBlock只有一个ContextMenu才能拥有它。因为该触发器位于TextBlock上,所以在可视树中我们可以使用传统的RelativeSource进行绑定。

<TextBlock Text="{Binding}">
    <TextBlock.Style>
        <Style TargetType="TextBlock">
            <Style.Triggers>
                <DataTrigger 
                    Binding="{Binding Data.IsContextMenuVisible, 
                        RelativeSource={RelativeSource AncestorType=ListBox}}"
                    Value="True">
                    <Setter Property="ContextMenu">
                        <Setter.Value>
                            <ContextMenu >
                                <MenuItem 
                                    Header="Open" 
                                    Command="{Binding OpenCommand}" 
                                    CommandParameter="{Binding}"
                                    />
                            </ContextMenu>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBlock.Style>
</TextBlock>