如何单独修改TPopupMenu中单个TMenuItem子菜单的BiDiMode?
实现单个菜单项及其子菜单的RTL行为
完全可以实现你的需求,无需修改整个弹出菜单的BiDiMode,以下是三种可行方案:
方案一:利用VCL的RightToLeft属性(Delphi 2009+)
Delphi 2009及后续版本的TMenuItem提供了RightToLeft属性,可单独控制菜单项的文本方向、箭头位置和子菜单展开方向。只需给目标菜单项及其所有子项设置该属性为rtlYes即可:
实现步骤:
- 编写递归函数遍历目标菜单项的所有子项,统一设置RTL属性:
procedure SetMenuItemRTLRecursive(AMenuItem: TMenuItem); var I: Integer; begin AMenuItem.RightToLeft := rtlYes; for I := 0 to AMenuItem.Count - 1 do SetMenuItemRTLRecursive(AMenuItem.Items[I]); end;
- 在窗体初始化(如
OnCreate事件)或弹出菜单显示前(OnPopup事件)调用该函数:
procedure TMainForm.FormCreate(Sender: TObject); begin // YourTargetItem为需要设置的带自菜单的目标菜单项 SetMenuItemRTLRecursive(YourTargetItem); end;
设置完成后,目标菜单项的箭头会移到左侧,子菜单向左展开,图标自动显示在菜单项右侧,所有子项都会继承该RTL行为,主菜单其余部分保持LTR不变。
方案二:自绘菜单项(兼容旧版Delphi)
若使用Delphi 2009之前的版本,无RightToLeft属性,可通过自绘菜单项实现自定义布局:
实现步骤:
- 给目标菜单项及其所有子项设置
OwnerDraw := True - 处理弹出菜单的
OnMeasureItem事件,计算菜单项尺寸:
procedure TMainForm.PopupMenu1MeasureItem(Sender: TObject; ACanvas: TCanvas; var Width, Height: Integer; Item: TMenuItem); const PADDING = 4; ICON_SIZE = 16; ARROW_SIZE = 16; begin Height := ACanvas.TextHeight('Ag') + PADDING * 2; Width := ACanvas.TextWidth(Item.Caption) + ICON_SIZE + ARROW_SIZE + PADDING * 4; end;
- 处理
OnDrawItem事件,手动绘制箭头、文本和图标(反转默认顺序):
procedure TMainForm.PopupMenu1DrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect; Selected: Boolean); var Item: TMenuItem; ArrowRect, TextRect, IconRect: TRect; TextOffset: Integer; const PADDING = 4; ELEM_SIZE = 16; begin Item := Sender as TMenuItem; // 绘制选中状态背景 if Selected then begin ACanvas.Brush.Color := clHighlight; ACanvas.Font.Color := clHighlightText; end else begin ACanvas.Brush.Color := clMenu; ACanvas.Font.Color := clMenuText; end; ACanvas.FillRect(ARect); // 绘制向左的箭头(左侧位置) if Assigned(Item.SubMenu) then begin ArrowRect := Rect(ARect.Left + PADDING, ARect.Top + (ARect.Bottom - ARect.Top - ELEM_SIZE) div 2, ARect.Left + PADDING + ELEM_SIZE, ARect.Top + (ARect.Bottom - ARect.Top + ELEM_SIZE) div 2); DrawLeftArrow(ACanvas, ArrowRect); end; // 绘制文本(中间位置) TextOffset := PADDING + ELEM_SIZE; TextRect := Rect(ARect.Left + TextOffset, ARect.Top, ARect.Right - TextOffset, ARect.Bottom); ACanvas.TextOut(TextRect.Left, TextRect.Top + (TextRect.Bottom - TextRect.Top - ACanvas.TextHeight(Item.Caption)) div 2, Item.Caption); // 绘制图标(右侧位置) if (Item.ImageIndex <> -1) and Assigned(PopupMenu1.Images) then begin IconRect := Rect(ARect.Right - PADDING - ELEM_SIZE, ARect.Top + (ARect.Bottom - ARect.Top - ELEM_SIZE) div 2, ARect.Right - PADDING, ARect.Top + (ARect.Bottom - ARect.Top + ELEM_SIZE) div 2); PopupMenu1.Images.Draw(ACanvas, IconRect.Left, IconRect.Top, Item.ImageIndex); end; end; // 自定义绘制向左的箭头 procedure TMainForm.DrawLeftArrow(ACanvas: TCanvas; ARect: TRect); var Points: array[0..2] of TPoint; begin ACanvas.Pen.Color := ACanvas.Font.Color; ACanvas.Brush.Color := ACanvas.Font.Color; Points[0] := Point(ARect.Left + 3, ARect.Top + 3); Points[1] := Point(ARect.Right - 3, ARect.Top + (ARect.Bottom - ARect.Top) div 2); Points[2] := Point(ARect.Left + 3, ARect.Bottom - 3); ACanvas.Polygon(Points); end;
- 处理子菜单的
OnPopup事件,调整子菜单展开位置为向左:
procedure TMainForm.TargetSubMenuPopup(Sender: TObject); var Item: TMenuItem; ScreenPos: TPoint; begin Item := Sender as TMenuItem; if Assigned(Item.Parent) then begin // 计算子菜单左上角位置:父菜单项左侧减去子菜单宽度 ScreenPos := Item.Parent.ClientToScreen(Point(Item.Left - Item.SubMenu.Width, Item.Top)); SetWindowPos(Item.SubMenu.Handle, 0, ScreenPos.X, ScreenPos.Y, 0, 0, SWP_NOZORDER or SWP_NOSIZE); end; end;
方案三:Windows API直接修改菜单样式
若不想依赖VCL属性或自绘,可通过SetMenuInfo API直接修改子菜单的RTL样式:
procedure TMainForm.PopupMenu1Popup(Sender: TObject); var TargetItem: TMenuItem; SubMenuHandle: HMENU; MenuInfo: TMenuInfo; begin TargetItem := PopupMenu1.Items.Find('你的目标菜单项标题'); // 或直接引用菜单项变量 if Assigned(TargetItem) and Assigned(TargetItem.SubMenu) then begin SubMenuHandle := TargetItem.SubMenu.Handle; FillChar(MenuInfo, SizeOf(MenuInfo), 0); MenuInfo.cbSize := SizeOf(MenuInfo); MenuInfo.fMask := MIM_STYLE; MenuInfo.dwStyle := MNS_RIGHTTOLEFT or MNS_RIGHTORDER; SetMenuInfo(SubMenuHandle, MenuInfo); // 同时设置目标菜单项的RTL属性,使箭头左移 TargetItem.RightToLeft := rtlYes; end; end;
该方法通过API给子菜单设置RTL样式使其向左展开,配合菜单项的RightToLeft属性实现箭头左移,效果与方案一类似。
内容的提问来源于stack exchange,提问作者Leonard
相关产品推荐
相关产品推荐

