在ItemsControl内的DataTemplate中,Button的WPF数据绑定ContextMenu

时间:2016-02-16 16:40:44

标签: c# wpf xaml mvvm data-binding

我试图找出如何绑定我在ItemsControl中添加的Button的ContextMenu。基本上,我希望能够右键单击按钮并将其从位于我的viewmodel上的可观察集合中删除。我知道ContextMenu不是VisualTree的一部分,所以使用RelativeSource来查找我的DataContext并不是对我有用。

我想要做的最终目标是将MenuItem上的命令绑定到我的ViewModel上的RemoveCommand,然后传入右键单击的Button的Content属性,以便我可以从observable集合中删除它

对此的任何帮助将不胜感激。

型号:

public class Preset
{
    public string Name { get; set; }
}

视图模型:

public class SettingsWindowViewModel
{
    public ObservableCollection<Preset> MyPresets { get; } = new ObservableCollection<Preset>();

    private ICommand _plusCommand;
    public ICommand PlusCommand => _plusCommand ?? (_plusCommand = new DelegateCommand(AddPreset));

    private ICommand _removeCommand;
    public ICommand RemoveCommand => _removeCommand ?? (_removeCommand = new DelegateCommand<string>(RemovePreset));

    private void AddPreset()
    {
        var count = MyPresets.Count;
        MyPresets.Add(new Preset {Name = $"Preset{count+1}"});
    }

    private void RemovePreset(string name)
    {
        var preset = MyPresets.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.CurrentCultureIgnoreCase));
        if (preset!= null)
        {
            MyPresets.Remove(preset);
        }
    }
}

XAML:

<Window x:Class="WpfTesting.Esper.Views.SettingsWindow"
        x:Name="MainSettingsWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:viewModels="clr-namespace:WpfTesting.Esper.ViewModels"
        mc:Ignorable="d"
        Title="SettingsWindow" Height="470" Width="612">
    <Window.DataContext>
        <viewModels:SettingsWindowViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <Style BasedOn="{StaticResource {x:Type MenuItem}}" TargetType="{x:Type MenuItem}" x:Key="PopupMenuItem">
            <Setter Property="OverridesDefaultStyle" Value="True"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type MenuItem}">
                        <Border>
                            <ContentPresenter ContentSource="Header"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="35"/>
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="2" Orientation="Horizontal">
            <Button Width="70" Content="Load"/>
            <Button Width="70"  Content="Save As"/>
            <ItemsControl ItemsSource="{Binding MyPresets}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Button Width="70" Content="{Binding Name}">
                            <Button.ContextMenu>
                                <ContextMenu>
                                    <MenuItem Style="{StaticResource PopupMenuItem}" Header="Remove">
                                        <!--
                                        I need to set up binding a Command to a method on the DataContext of the Window, and I need to pass in the Content of the Button that is the parent of the ContextMenu
                                        -->
                                    </MenuItem>
                                </ContextMenu>
                            </Button.ContextMenu>
                        </Button>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
            <Button Width="20" Background="Transparent" BorderBrush="Transparent" Content="+" FontSize="21.333" HorizontalAlignment="Center" VerticalAlignment="Center" Command="{Binding PlusCommand}"/>
        </StackPanel>
    </Grid>
</Window>

2 个答案:

答案 0 :(得分:0)

我基本上遇到了类似的问题,我找到的解决方案是使用一些MVVM框架,如Devexpress或Mvvm Light。

基本上,您可以在viewModel中注册以侦听传入的消息。类本身,至少在Devexpress实现中使用弱引用,因此您甚至可能不注销消息处理程序,也不会导致内存泄漏。

我曾使用此方法从ObservableCollection中删除右键单击选项卡,因此它与您的场景类似。

你可以看一下:

https://community.devexpress.com/blogs/wpf/archive/2013/12/13/devexpress-mvvm-framework-interaction-of-viewmodels-messenger.aspx

在这里:

https://msdn.microsoft.com/en-us/magazine/jj694937.aspx

答案 1 :(得分:0)

使用WPF: Binding a ContextMenu to an MVVM Command作为标签可以做的介绍,我通过使用多个标签来保存我正在寻找的上下文,找到了如何做我想要的事情。

我首先确保给我的窗口一个x:名称

<Window x:Name="MainSettingsWindow"

接下来,在我的ItemsControl的DataTemplate里面的Button上,我设置了一个Tag并将其设置为我的Window

<ItemsControl ItemsSource="{Binding MyPresets}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Width="70" Content="{Binding Name}" Tag="{Binding ElementName=MainSettingsWindow}">

接下来,在ContextMenu中,我将ContextMenu的DataContext设置为Button上设置的Tag,我还需要在ContextMenu上创建一个Tag并将其指回Button的Content属性以便我可以将其传递给CommandParameter

<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Mode=Self}}">

此时,我现在可以使用ViewModel中的Command和Button中的Content属性正确绑定我的MenuItem

这是我的ItemsControl的最终XAML:

<ItemsControl ItemsSource="{Binding MyPresets}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Width="70" Content="{Binding Name}" Tag="{Binding ElementName=MainSettingsWindow}">
                <Button.ContextMenu>
                    <ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Mode=Self}}">
                        <MenuItem Header="Remove" 
                                  Style="{StaticResource PopupMenuItem}"
                                  Command="{Binding Path=DataContext.RemoveCommand}"
                                  CommandParameter="{Binding Path=Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
                    </ContextMenu>
                </Button.ContextMenu>
            </Button>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

需要注意的一点是,我必须更改ViewModel上的CommandParameter以获取Object而不是String。我这样做的原因是因为我在DelegateCommand

中的CanExecute方法上遇到了异常

这是我得到的例外:

Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.String'.

我不确定是什么导致该异常抛出,但将其更改为Object对我来说没问题。