如何使用父 Button 的属性让 CommandParameter 对 ContextMenu 中的 MenuItem 起作用?

发布于 2025-01-11 04:16:33 字数 1696 浏览 0 评论 0原文

结果 我弄清楚自己发生了什么事。我有一些奇怪的组合,所以这对其他人可能有用也可能没用。

我将继续在答案条目中记录解决方案。我的解决方案涉及少量的隐藏代码。我一般不喜欢代码隐藏。它仅与视图相关,因此不会破坏 MVVM。如果有人给我一个不太遥远的纯 XAML 解决方案,我会很乐意将其用作可接受的答案。

问题 我有一个 WPF 应用程序 (.NET6),它有一个表示离散对象的图像网格。我希望每个都有相同的上下文菜单。每个对象都需要发送一个唯一的CommandParameter,以便视图模型可以识别要操作的对象。

为了给自己提供一种添加标识符的方法,我对 Button 进行了子类化,如下所示:

public class PartButton : Button
{
    public int PartPosition { get; set; }
}

我知道有关上下文菜单不在可视化树中的问题,因此没有从以下位置继承 DataContext顶级控件(在本例中为UserControl)。诀窍通常是使用 PlacementTarget 获取父级,并使用其 DataContext

左键单击和右键单击同样有效也是一个目标。为此,我使用 RoatedEvent 进行 Click 并使用 StoryboardContextMenu.IsOpen 设置为true。如果您对 XAML 更有经验,您可能已经发现了该问题。

因为有 30 个按钮,所以最终目标是通过样式添加菜单,以便可以使用尽可能少的代码来声明按钮。为了让一个按钮正常工作,我尝试了这样的操作

<jb:PartButton
    Grid.Row="2" Grid.Column="2"
    IsEnabled="{ Binding Path=PartStatuses[1] }"
    PartPosition="1">
    <Image Source="../Resources/Part.png" />
    <jb:PartButton.ContextMenu>
        <ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
            <MenuItem Header="Test Part"
                CommandParameter="{Binding Path=PartPosition}"
                Command="{Binding Source={StaticResource Proxy}, Path=Data.TestPartCmd}"
                />
        </ContextMenu>
    </jb:PartButton.ContextMenu>
</jb:PartButton>

: -点击。但是,当使用上面的 RoatedEvent 技巧时,左键单击会导致 PlacementTarget 始终为 null

Result
I figured out what was going on for myself. I had kind of a weird combination of things, so this may or may not be useful to someone else.

I'll go ahead and document the solution in an answer entry. My solution involves a small amount of code-behind. I'm not generally a fan of code-behind. It's only view-related, so doesn't break MVVM, though. If someone gives me a XAML-only solution that isn't too far out, I'll gladly use that as the accepted answer.

Problem
I have a WPF app (.NET6) that has a grid of images that represent discreet objects. I want each to have an identical context menu. Each one needs to send a unique CommandParameter so that the view-model can identify which object to operate on.

In order to give myself a way to add the identifier, I subclassed Button like this:

public class PartButton : Button
{
    public int PartPosition { get; set; }
}

I'm aware of the issue regarding a context menu not being in the visual tree, and thus not inheriting the DataContext from the top level control (UserControl in this case). The trick is usually just to use PlacementTarget to get the parent, and use its DataContext.

It was also a goal to have a left-click work as well as a right-click. To do that, I used a RoutedEvent for Click and used a Storyboard to set ContextMenu.IsOpen to true on the button. If you're more experienced with XAML, you might already see the problem.

Because there are 30 buttons, the ultimate goal was to add the menu through a style so that the button could be declared using as little code as possible.. Just to get one working, I tried it like this:

<jb:PartButton
    Grid.Row="2" Grid.Column="2"
    IsEnabled="{ Binding Path=PartStatuses[1] }"
    PartPosition="1">
    <Image Source="../Resources/Part.png" />
    <jb:PartButton.ContextMenu>
        <ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
            <MenuItem Header="Test Part"
                CommandParameter="{Binding Path=PartPosition}"
                Command="{Binding Source={StaticResource Proxy}, Path=Data.TestPartCmd}"
                />
        </ContextMenu>
    </jb:PartButton.ContextMenu>
</jb:PartButton>

That works fine for a right-click. However, when using the RoutedEvent trick above, left-click results in PlacementTarget always being null.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我要还你自由 2025-01-18 04:16:33
            <ContextMenu>
                <MenuItem Header="Test Part" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}" Command="{Binding TestPartCmd}"/>
            </ContextMenu>

而ViewModel这样的

    public RelayCommand<System.Windows.Controls.MenuItem> TestPartCmd
    {
        get
        {
            return new RelayCommand<System.Windows.Controls.MenuItem>((menuItem) =>
            {
                int partPosition = ((PartButton)((System.Windows.Controls.Primitives.Popup)((System.Windows.FrameworkElement)menuItem.Parent).Parent).PlacementTarget).PartPosition;
            });
        }
    }

可能还有更好的办法,不过我还没想到。

            <ContextMenu>
                <MenuItem Header="Test Part" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}" Command="{Binding TestPartCmd}"/>
            </ContextMenu>

and ViewModel like this

    public RelayCommand<System.Windows.Controls.MenuItem> TestPartCmd
    {
        get
        {
            return new RelayCommand<System.Windows.Controls.MenuItem>((menuItem) =>
            {
                int partPosition = ((PartButton)((System.Windows.Controls.Primitives.Popup)((System.Windows.FrameworkElement)menuItem.Parent).Parent).PlacementTarget).PartPosition;
            });
        }
    }

There may be a better way, but I haven't thought of it yet。

我只土不豪 2025-01-18 04:16:33

正如所承诺的,这是我当前的解决方案:

<ContextMenu>
    <MenuItem Header="Bootloader Test"
        CommandParameter="{Binding Path=PlacementTarget.PartPosition, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
        Command="{Binding Path=PlacementTarget.DataContext.BootloaderTestCmd, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
        />

要工作,它需要隐藏代码:

public void OnPartButtonPressed(object sender, RoutedEventArgs e)
{
    if (sender is Button btn)
    {
        btn.ContextMenu.PlacementTarget = btn;
        btn.ContextMenu.IsOpen = true;
    }
}

就目前而言,这现在已完全实现为一种样式,因此每个按钮真正需要的一切都是这样的:

<jb:PartButton
    PartPosition="1"
    Grid.Row="2" Grid.Column="2" />

在实际项目中,我还声明了对象上按钮的图像,因为我正在努力通过样式来完成该操作。图像取决于必须在视图模型中解析的索引 (PartPosition)。使用必须从对象查询的值来使用样式从视图模型获取属性是很困难的。不过,不同的问题,以及我以后可能会或可能不会担心的事情。

As promised, here is my current solution:

<ContextMenu>
    <MenuItem Header="Bootloader Test"
        CommandParameter="{Binding Path=PlacementTarget.PartPosition, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
        Command="{Binding Path=PlacementTarget.DataContext.BootloaderTestCmd, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
        />

To work, it needs this code-behind:

public void OnPartButtonPressed(object sender, RoutedEventArgs e)
{
    if (sender is Button btn)
    {
        btn.ContextMenu.PlacementTarget = btn;
        btn.ContextMenu.IsOpen = true;
    }
}

As it sits, this is now fully implemented as a style so that all that's really needed for each button is something like this:

<jb:PartButton
    PartPosition="1"
    Grid.Row="2" Grid.Column="2" />

In the real project, I also declare the image for the button at the object because I was struggling with getting that done via a style. The image depends on an index (PartPosition) that has to be resolved in the view-model. It's hard to use a style to get a property from the view-model using a value that has to be queried from the object. Different problem, though, and something I may or may not worry about later.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文