【问题标题】:WPF Databinding ContextMenu of Button inside a DataTemplate inside an ItemsControl在 ItemsControl 内的 DataTemplate 内的 Button 的 WPF Databinding ContextMenu
【发布时间】:2016-02-16 16:40:43
【问题描述】:

我想弄清楚如何绑定我拥有的 ItemsControl 中添加的 Button 的 ContextMenu。基本上,我希望能够右键单击一个按钮并将其从位于我的视图模型上的可观察集合中删除。我知道 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>

【问题讨论】:

    标签: c# wpf xaml mvvm data-binding


    【解决方案1】:

    使用WPF: Binding a ContextMenu to an MVVM Command 作为标签可以做什么的介绍,我想出了如何通过使用多个标签来保存我正在寻找的内容的上下文来做我正在寻找的东西。

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

    <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 以获取对象而不是字符串。我这样做的原因是我的 DelegateCommand 中的 CanExecute 方法出现异常

    这是我得到的异常:

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

    我不确定究竟是什么导致该异常抛出,但将其更改为 Object 对我来说可以。

    【讨论】:

      【解决方案2】:

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

      基本上,您可以在 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

      【讨论】:

      • 我应该提到这一点,但我在一个严格控制的环境中,我不能使用第三方依赖项。我需要想办法在没有那个的情况下做到这一点。
      • 在这种情况下你可以使用这个实现stackoverflow.com/a/23909882/3042778
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-18
      • 1970-01-01
      相关资源
      最近更新 更多