This question relates to the post here - the original question was resolved, but then another cropped up.
As you can see the MenuItem is appearing the wrong side
As the prior issue was related to another control library I tested it with a minimal example...
Opened a new MVC project targeting .NET 8. Gave the grid on the MainWindow a name and then added a ToolBar with a button and a DataGrid. Applied the ContextMenu to each in turn and got the same behavior.
Private Sub MainWindow_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized
For i As Integer = 0 To 2
Dim vRow As New RowDefinition
If i = 1 Then
vRow.Height = New GridLength(35, GridUnitType.Star)
Else
vRow.Height = New GridLength(35)
End If
TestGrid.RowDefinitions.Add(vRow)
Next
Dim vToolBar As New ToolBar
With vToolBar
End With
Grid.SetRow(vToolBar, 0)
TestGrid.Children.Add(vToolBar)
Dim vButton As New Button
With vButton
.Content = "Test"
.ContextMenu = ReturnContexMenu()
End With
vToolBar.Items.Add(vButton)
Dim vDG As New DataGrid
With vDG
' .ContextMenu = ReturnContexMenu()
End With
Grid.SetRow(vDG, 1)
TestGrid.Children.Add(vDG)
End Sub
...and this is the ContextMenu
Private Function ReturnContexMenu() As ContextMenu
Try
Dim FlagMI As New MenuItem
With FlagMI
.Header = "Quick Flag"
.Name = "FlagMI"
End With
Dim MarkReadMI As New MenuItem
With MarkReadMI
.Header = "Mark as read"
.Name = "MarkReadMI"
End With
Dim MarkUnreadMI As New MenuItem
With MarkUnreadMI
.Header = "Mark as unread"
.Name = "MarkUnreadMI"
End With
Dim MarkAsMI As New MenuItem
With MarkAsMI
.Header = "Mark as"
.Items.Add(MarkReadMI)
.Items.Add(MarkUnreadMI)
End With
Dim vMainContext As New ContextMenu
With vMainContext
.Name = "MainContextMenu"
.Items.Add(FlagMI)
.Items.Add(MarkAsMI)
End With
Return vMainContext
Catch ex As Exception
Return Nothing
End Try
End Function
I have tried adding a class for MenuItem and adding the DependencyProperty of Primatives.PlacementMode and setting the default value to PlacementMode.Right - in fact I tried every setting, but it has no effect.
Public Class MIx
Inherits System.Windows.Controls.MenuItem
Public Shared ReadOnly HoldingItem As DependencyProperty = DependencyProperty.Register(
name:="IsHoldingItem", propertyType:=GetType(Primitives.PlacementMode), ownerType:=GetType(MIx),
typeMetadata:=New FrameworkPropertyMetadata(defaultValue:=PlacementMode.Right))
Protected Overrides Sub OnInitialized(e As EventArgs)
MyBase.OnInitialized(e)
Style = Nothing
End Sub
Public Property IsHoldingItem As Primitives.PlacementMode
Get
Return GetValue(HoldingItem)
End Get
Set(value As Primitives.PlacementMode)
SetValue(HoldingItem, value)
End Set
End Property
End Class
==========EDIT========
With a big thanks to @BionicCode for all his hard work, it now works
Just in case someone else runs into the same problem onApplyTemplate is...
Public Overrides Sub onApplyTemplate()
MyBase.OnApplyTemplate()
Me.PART_Popup = TryCast(GetTemplateChild("PART_Popup"), Popup)
If Not (Me.PART_Popup) Is Nothing Then
Me.PART_Popup.CustomPopupPlacementCallback = New CustomPopupPlacementCallback(AddressOf OnChildItemHostPlacementChanged)
Me.PART_Popup.Placement = PlacementMode.Custom
End If
End Sub
Your issue is not reproducible. However, I believe it can be fixed for your special scenario when you manually control the placement of the internal Popup
by setting Popup.Placement
to PlacementMode.Custom
:
PositionableMenuItem.vb
We extend MenuItem
to gracefully handle the custom positioning of the subitems.
The custom PositionableMenuItem
retrieves its internal Popup
and sets the Popup.Placement
property to PlacemmentMode.Custom
. This enables the Popup
to invoke the Popup.CustomPopupPlacementCallback
that we define in order to manually align the Popup
(the sub-menu items).
Use the new PositionableMenuItem.ChildPlacement
property to allow the client to select the position (currently only left, top, right and bottom are supported) - see example below.
Public Class PositionableMenuItem
Inherits MenuItem
Public Shared ReadOnly ChildPlacementProperty As DependencyProperty = DependencyProperty.Register(
"ChildPlacement",
GetType(PlacementMode),
GetType(PositionableMenuItem),
New FrameworkPropertyMetadata(PlacementMode.Right))
Public Property ChildPlacement As PlacementMode
Get
Return CType(GetValue(ChildPlacementProperty), PlacementMode)
End Get
Set(ByVal value As PlacementMode)
Return SetValue(ChildPlacementProperty, value)
End Set
End Property
Private Property PART_Popup As Popup
Public Overrides Sub onApplyTemplate()
MyBase.OnApplyTemplate()
Me.PART_Popup = TryCast(GetTemplateChild("PART_Popup"), Popup)
If Not (Me.PART_Popup) Is Nothing Then
Me.PART_Popup.CustomPopupPlacementCallback = New CustomPopupPlacementCallback(AddressOf OnChildItemHostPlacementChanged)
Me.PART_Popup.Placement = PlacementMode.Custom
End If
End Sub
Private Function OnChildItemHostPlacementChanged(ByVal popupSize As Size, ByVal targetSize As Size, ByVal offset As Point) As CustomPopupPlacement()
Dim parent = CType(Me.Parent, Control)
Dim parentPadding As Thickness = parent.Padding
Dim additionalTargetWidth As Double = parentPadding.Left + parentPadding.Right + parent.BorderThickness.Right
Dim additionalTargetHeight As Double = parentPadding.Top + parentPadding.Bottom + parent.BorderThickness.Bottom
Dim horizontalOffset As Double = 0
Dim verticalOffset As Double = 0
Select Case Me.ChildPlacement
Case PlacementMode.Bottom
verticalOffset = targetSize.Height + additionalTargetHeight
horizontalOffset -= parentPadding.Left
Case PlacementMode.Right
horizontalOffset = targetSize.Width + additionalTargetWidth
Case PlacementMode.Left
horizontalOffset -= popupSize.Width + parentPadding.Left
Case PlacementMode.Top
verticalOffset = -popupSize.Height
horizontalOffset = -parentPadding.Left
Case Else
Throw New NotSupportedException()
End Select
Dim location = New Point(horizontalOffset, verticalOffset)
Dim primaryAxis As PopupPrimaryAxis = If(horizontalOffset > 0, PopupPrimaryAxis.Horizontal, PopupPrimaryAxis.Vertical)
Dim placement = New CustomPopupPlacement(location, primaryAxis)
Return {placement}
End Function
Protected Overrides Sub OnInitialized(ByVal e As EventArgs)
MyBase.OnInitialized(e)
Me.Style = Nothing
End Sub
End Class
You can use PositionableMenuItem
to replace all MenuItem
elements or only use it for those MenuItem
elements that have child items.
Private Function ReturnContexMenu() As ContextMenu
Try
Dim FlagMI As MenuItem = New PositionableMenuItem()
If True Then
Dim withBlock = FlagMI
withBlock.Header = "Quick Flag"
withBlock.Name = "FlagMI"
End If
Dim MarkReadMI As MenuItem = New PositionableMenuItem()
If True Then
Dim withBlock = MarkReadMI
withBlock.Header = "Mark as read"
withBlock.Name = "MarkReadMI"
End If
Dim MarkUnreadMI As MenuItem = New PositionableMenuItem()
If True Then
Dim withBlock = MarkUnreadMI
withBlock.Header = "Mark as unread"
withBlock.Name = "MarkUnreadMI"
End If
Dim MarkAsMI As MenuItem = New PositionableMenuItem() With {
.ChildPlacement = PlacementMode.Right
}
If True Then
Dim withBlock = MarkAsMI
withBlock.Header = "Mark as"
withBlock.Items.Add(MarkReadMI)
withBlock.Items.Add(MarkUnreadMI)
End If
Dim vMainContext As ContextMenu = New ContextMenu()
If True Then
Dim withBlock = vMainContext
withBlock.Name = "MainContextMenu"
withBlock.Items.Add(MarkAsMI)
withBlock.Items.Add(FlagMI)
End If
Return vMainContext
Catch ex As Exception
Return Nothing
End Try
End Function