如何使用父按钮中的 属性 让 CommandParameter 为 ContextMenu 中的 MenuItem 工作?
How do you get CommandParameter working for a MenuItem in a ContextMenu using a property from the parent Button?
结果
我弄清楚自己发生了什么事。我有一种奇怪的组合,所以这对其他人可能有用也可能没用。
我将继续在答案条目中记录解决方案。我的解决方案涉及少量代码隐藏。我一般不喜欢代码隐藏。它仅与视图相关,因此不会破坏 MVVM。如果有人给我一个仅 XAML 的解决方案,而且还不算太远,我会很乐意将其用作可接受的答案。
问题
我有一个 WPF 应用程序 (.NET6),它有一个代表谨慎对象的图像网格。我希望每个人都有一个相同的上下文菜单。每个都需要发送一个唯一的 CommandParameter
以便视图模型可以识别要操作的对象。
为了给自己添加标识符的方法,我将 Button 子类化如下:
public class PartButton : Button
{
public int PartPosition { get; set; }
}
我知道有关上下文菜单不在可视化树中的问题,因此不会从顶级控件(在本例中为 UserControl
)继承 DataContext
。诀窍通常只是使用 PlacementTarget
获取父级,并使用其 DataContext
.
让左键单击和右键单击一样有效也是一个目标。为此,我对 Click
使用 RoutedEvent
,并使用 Storyboard
将按钮上的 ContextMenu.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>
右键单击效果很好。但是,当使用上面的 RoutedEvent
技巧时,左键单击会导致 PlacementTarget
始终为 null
.
<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="Bootloader Test"
CommandParameter="{Binding Path=PlacementTarget.PartPosition, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Command="{Binding Path=PlacementTarget.DataContext.BootloaderTestCmd, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
/>
要工作,它需要 code-behind:
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" />
在实际项目中,我还在对象处声明了按钮的图像,因为我一直在努力通过样式来完成它。该图像取决于必须在 view-model 中解析的索引 (PartPosition
)。很难使用样式从 view-model 使用必须从对象查询的值来获取 属性。不过,这是不同的问题,我以后可能会担心也可能不会担心。
结果 我弄清楚自己发生了什么事。我有一种奇怪的组合,所以这对其他人可能有用也可能没用。
我将继续在答案条目中记录解决方案。我的解决方案涉及少量代码隐藏。我一般不喜欢代码隐藏。它仅与视图相关,因此不会破坏 MVVM。如果有人给我一个仅 XAML 的解决方案,而且还不算太远,我会很乐意将其用作可接受的答案。
问题
我有一个 WPF 应用程序 (.NET6),它有一个代表谨慎对象的图像网格。我希望每个人都有一个相同的上下文菜单。每个都需要发送一个唯一的 CommandParameter
以便视图模型可以识别要操作的对象。
为了给自己添加标识符的方法,我将 Button 子类化如下:
public class PartButton : Button
{
public int PartPosition { get; set; }
}
我知道有关上下文菜单不在可视化树中的问题,因此不会从顶级控件(在本例中为 UserControl
)继承 DataContext
。诀窍通常只是使用 PlacementTarget
获取父级,并使用其 DataContext
.
让左键单击和右键单击一样有效也是一个目标。为此,我对 Click
使用 RoutedEvent
,并使用 Storyboard
将按钮上的 ContextMenu.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>
右键单击效果很好。但是,当使用上面的 RoutedEvent
技巧时,左键单击会导致 PlacementTarget
始终为 null
.
<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="Bootloader Test"
CommandParameter="{Binding Path=PlacementTarget.PartPosition, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Command="{Binding Path=PlacementTarget.DataContext.BootloaderTestCmd, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
/>
要工作,它需要 code-behind:
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" />
在实际项目中,我还在对象处声明了按钮的图像,因为我一直在努力通过样式来完成它。该图像取决于必须在 view-model 中解析的索引 (PartPosition
)。很难使用样式从 view-model 使用必须从对象查询的值来获取 属性。不过,这是不同的问题,我以后可能会担心也可能不会担心。