WPF 数据绑定上下文 ItemsControl 内 DataTemplate 中的按钮菜单

本文关键字:按钮 菜单 DataTemplate 数据绑定 上下文 ItemsControl WPF | 更新日期: 2023-09-27 18:34:54

我正在尝试弄清楚如何绑定正在添加到我拥有的 ItemsControl 中的按钮的上下文菜单。基本上,我希望能够右键单击一个按钮并将其从位于我的视图模型上的可观察集合中删除。我知道 ContextMenu 不是 VisualTree 的一部分,所以使用 RelativeSource 沿着树向上走去查找我的 DataContext 对我没有用。

我想做的最终目标是将菜单项上的命令绑定到我的 ViewModel 上的删除命令,然后传入您右键单击的按钮的 Content 属性,以便我可以将其从可观察集合中删除。

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

型:

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>

WPF 数据绑定上下文 ItemsControl 内 DataTemplate 中的按钮菜单

使用 WPF:将上下文菜单绑定到 MVVM 命令作为对标签可以做什么的介绍,我想出了如何通过使用多个标签来保存我正在寻找的上下文来执行我正在寻找的事情。

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

<Window x:Name="MainSettingsWindow"

接下来,在我的 ItemsControl 的数据模板内的按钮上,我设置了一个标签并将其设置为我的窗口

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

接下来,在上下文菜单中,我将上下文菜单的 DataContext 设置为我在按钮上设置的标签,我还需要在上下文菜单上创建一个标签并将其指向按钮的内容属性,以便我可以将其传递到 CommandParameter 中

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

此时,我可以使用 ViewModel 中的命令和按钮中的内容属性正确绑定菜单项。

这是我的 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 以采用对象而不是字符串。我这样做的原因是因为我在我的委托命令中收到 CanExecute 方法的异常

这是我得到的例外:

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

我不确定究竟是什么导致该异常引发,但将其更改为 Object 对我来说效果很好。

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

基本上,您可以在视图模型中注册以侦听传入的消息。类本身,至少在 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