系统菜单:如何 hide/move 标准菜单项
System Menu: How to hide/move standard menuitems
在系统菜单中(标题栏左上角),我可以添加自己的菜单项。
我也可以删除例如DeleteMenu(SysMenu, SC_MINIMIZE, MF_BYCOMMAND) ;
但是,如果我删除标准按钮 [restore、minimize、maximize、size、close],它们的功能就会丢失(即最大化按钮不再有效)
有没有办法隐藏这些菜单项或将它们从系统菜单的第一级移开?
a) 使它们不可见
b) 移动到子菜单
c) 删除但仍然收到按钮消息
Originally, the window caption had only two buttons in the upper right corner, the minimize and maximize buttons, and they were controlled with a window style. Windows 95 added the Close button, but then there was the question of knowing when to enable and disable it. But wait, we already know when to enable and disable it: The application told us when it enabled and disabled the SC_CLOSE menu item. Bingo, just hook up the Close button to the existing menu item (which applications were already in the habit of maintaining), and magic, it just works. No need for applications to write special code to support the Close button.
现在你知道为什么 SC_CLOSE 绑定到按钮了。因此,防止用户在某些操作期间关闭的正确方法是禁用菜单项。
如果您出于某种原因坚持删除它但仍然允许关闭window那么您必须在系统菜单即将显示时删除该项目(WM_INITMENU
)并且revert菜单关闭后的系统菜单(WM_UNINITMENUPOPUP
)。
a) make them not visible
API 没有 hidden/invisible 菜单项的概念。
b) move to a submenu
您可以将项目移动(或者删除和添加)到子菜单而不影响功能。
例如将 "minimize" 移动到子菜单:
var
SysMenu, SubMenu: HMENU;
StrMin: string;
StrMinLen: Integer;
begin
SysMenu := GetSystemMenu(Handle, False);
StrMinLen := GetMenuString(SysMenu, SC_MINIMIZE, nil, 0, MF_BYCOMMAND);
if StrMinLen > 0 then begin
Inc(StrMinLen);
SetLength(StrMin, StrMinLen);
GetMenuString(SysMenu, SC_MINIMIZE, PChar(StrMin), StrMinLen, MF_BYCOMMAND);
SubMenu := CreateMenu;
if SubMenu <> 0 then begin
DeleteMenu(SysMenu, SC_MINIMIZE, MF_BYCOMMAND);
AppendMenu(SubMenu, MF_STRING, SC_MINIMIZE, PChar(StrMin));
InsertMenu(SysMenu, 0, MF_BYPOSITION or MF_POPUP, SubMenu, 'Minimize->');
InsertMenu(SysMenu, 1, MF_BYPOSITION or MF_SEPARATOR, 0, nil);
end;
end;
恢复系统菜单前销毁子菜单:
var
Info: TMenuItemInfo;
begin
Info.cbSize := SizeOf(Info);
Info.fMask := MIIM_SUBMENU;
if GetMenuItemInfo(GetSystemMenu(Handle, False), 0, True, Info) then
DestroyMenu(Info.hSubMenu);
GetSystemMenu(Handle, True);
c) delete but still get button messages
如果您删除 f.i.,"minimize" 项,系统不会将最小化命令的 WM_SYSCOMMAND
消息发送到 window。所以不会有任何命令来响应。
您仍然可以收听按钮消息,f.i。左键按下。但是按钮 down/up 消息实际上与单击按钮并不是一回事。单击按钮包括三个动作,鼠标按下、捕获和再次在按钮上弹起。如果你无论如何都想这样做,一个例子可以是:
procedure TForm1.WMNCLButtonDown(var Message: TWMNCLButtonDown);
begin
inherited;
if (Message.HitTest = HTMINBUTTON) and not IsIconic(Handle) then
ShowWindow(Handle, SW_MINIMIZE);
end;
Sertac 已回答问题:您只能移动它们。
这是使 Sysmenu 有用的完整最终解决方案,它可以:
a) 将所有标准项目移到靠近子菜单的地方,子菜单隐藏在分隔符后面。
b) 将菜单项从 Form1.PopupMenu1 添加到 SysMenu
c) 删除它们(在制作 window fullscreen/borderless 之前,因为这会破坏系统菜单)
d) 显示系统菜单
procedure TForm1.SysMenuAddRemoveExtraItems(Add:boolean=true);
//
var
SysMenu, SubMenu : HMenu;
const NumItems:integer=0;
procedure InsertM(Position,I:integer;J:integer=-1;S:string='');
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647987(v=vs.85).aspx
var M:TMenuItem; H:thandle; Flags:cardinal;
begin
M:=PopupMenu1.Items;
if I>=0 then M:=M.Items[I];
Flags:=MF_BYPOSITION+MF_STRING;
if M.Count>1 then Flags:=Flags+MF_POPUP;
if J>=0 then M:=M.Items[J];
H:=M.Handle;
if S='' then S:=M.Caption;
InsertMenu(SysMenu,Position,Flags,H,pwidechar(S));
inc(NumItems);
end;
procedure InsertSeparator(Position:integer);
begin
InsertMenu(SysMenu,Position,MF_BYPOSITION,MF_SEPARATOR,0); {Add a seperator bar to main form-form1}
inc(NumItems);
end;
procedure RemoveItems;
var i:integer;
begin
for i := 1 to NumItems do //remove items from top
RemoveMenu(SysMenu,0,MF_BYPOSITION);
NumItems:=0;
end;
procedure DeleteAppend(ID:cardinal); //to move standard menuitems to submenu
var
Caption: string;
CaptionLen: Integer;
begin
CaptionLen := GetMenuString(SysMenu, ID, nil, 0, MF_BYCOMMAND);
if CaptionLen > 0 then begin
Inc(CaptionLen);
SetLength(Caption, CaptionLen);
GetMenuString(SysMenu, ID, PChar(Caption), CaptionLen, MF_BYCOMMAND);
DeleteMenu(SysMenu, ID, MF_BYCOMMAND);
AppendMenu(SubMenu, MF_STRING, ID, PChar(Caption));
end;
end;
procedure MoveDefaultSysMenuItemsToSubmenu(Caption:string='';JustSeparator:boolean=false);
//Can either have a a caption or JustSeparator (submenu will be inaccessible)
//
begin
SubMenu := CreateMenu; //make submenu to move them into
if SubMenu <> 0 then begin
DeleteAppend(SC_RESTORE);
DeleteAppend(SC_MOVE);
DeleteAppend(SC_SIZE);
DeleteAppend(SC_MINIMIZE);
DeleteAppend(SC_MAXIMIZE);
if JustSeparator then begin
DeleteMenu(SysMenu, 0, MF_BYPOSITION); //remove separator above CLOSE
InsertMenu(SysMenu, 0, MF_BYPOSITION or MF_POPUP or MF_SEPARATOR, SubMenu, '');
end else begin
DeleteMenu(SysMenu, 0, MF_BYPOSITION); //remove separator above CLOSE
InsertMenu(SysMenu, 0, MF_BYPOSITION or MF_POPUP, SubMenu, PChar(Caption));
InsertMenu(SysMenu, 1, MF_BYPOSITION or MF_SEPARATOR, 0, nil);
end;
end;
end;
procedure DestroySubmenu;
var Info: TMenuItemInfo;
begin
Info.cbSize := SizeOf(Info);
Info.fMask := MIIM_SUBMENU;
if GetMenuItemInfo(GetSystemMenu(Handle, False), 0, True, Info) then
DestroyMenu(Info.hSubMenu);
//GetSystemMenu(Handle, True);
end;
begin
SysMenu := GetSystemMenu(Handle, FALSE) ; {Get system menu}
//InsertMenu(SysMenu,1,MF_BYPOSITION+MF_STRING,SC_MyMenuItem2,'pqr');
if Add then begin
MoveDefaultSysMenuItemsToSubmenu('',true);
// InsertSeparator(0);
InsertM(0,PopupMenu1.Items.Count-2);
InsertM(0,-1,-1,'Menu'); //help
InsertM(0,7);
end
else begin //remove items
RemoveItems;
DestroySubmenu;
end;
end;
//------------------------------------
procedure TForm1.ShowSysMenu;
var P:TPoint;
begin
P:=ClientToScreen(Point(0,0));
TrackPopupMenu(GetSystemMenu(Handle, FALSE), TPM_LEFTALIGN+TPM_TOPALIGN+TPM_RETURNCMD+TPM_NONOTIFY,
P.X,P.Y,0,self.Handle,nil);
end;
//--------------------------------------------------------------
procedure TForm1.WMSysCommand(var Msg: TWMSysCommand);
// https://www.delphipower.xyz/guide_7/customizing_the_system_menu.html
var Item: TMenuItem;
begin
Item := PopupMenu1.FindItem (Msg.CmdType, fkCommand);
if assigned(Item) then Item.Click
else
// case Msg.CmdType of
// //put any other specials in here
// else
inherited;
// end;
end;
在系统菜单中(标题栏左上角),我可以添加自己的菜单项。
我也可以删除例如DeleteMenu(SysMenu, SC_MINIMIZE, MF_BYCOMMAND) ;
但是,如果我删除标准按钮 [restore、minimize、maximize、size、close],它们的功能就会丢失(即最大化按钮不再有效)
有没有办法隐藏这些菜单项或将它们从系统菜单的第一级移开? a) 使它们不可见 b) 移动到子菜单 c) 删除但仍然收到按钮消息
Originally, the window caption had only two buttons in the upper right corner, the minimize and maximize buttons, and they were controlled with a window style. Windows 95 added the Close button, but then there was the question of knowing when to enable and disable it. But wait, we already know when to enable and disable it: The application told us when it enabled and disabled the SC_CLOSE menu item. Bingo, just hook up the Close button to the existing menu item (which applications were already in the habit of maintaining), and magic, it just works. No need for applications to write special code to support the Close button.
现在你知道为什么 SC_CLOSE 绑定到按钮了。因此,防止用户在某些操作期间关闭的正确方法是禁用菜单项。
如果您出于某种原因坚持删除它但仍然允许关闭window那么您必须在系统菜单即将显示时删除该项目(WM_INITMENU
)并且revert菜单关闭后的系统菜单(WM_UNINITMENUPOPUP
)。
a) make them not visible
API 没有 hidden/invisible 菜单项的概念。
b) move to a submenu
您可以将项目移动(或者删除和添加)到子菜单而不影响功能。
例如将 "minimize" 移动到子菜单:
var
SysMenu, SubMenu: HMENU;
StrMin: string;
StrMinLen: Integer;
begin
SysMenu := GetSystemMenu(Handle, False);
StrMinLen := GetMenuString(SysMenu, SC_MINIMIZE, nil, 0, MF_BYCOMMAND);
if StrMinLen > 0 then begin
Inc(StrMinLen);
SetLength(StrMin, StrMinLen);
GetMenuString(SysMenu, SC_MINIMIZE, PChar(StrMin), StrMinLen, MF_BYCOMMAND);
SubMenu := CreateMenu;
if SubMenu <> 0 then begin
DeleteMenu(SysMenu, SC_MINIMIZE, MF_BYCOMMAND);
AppendMenu(SubMenu, MF_STRING, SC_MINIMIZE, PChar(StrMin));
InsertMenu(SysMenu, 0, MF_BYPOSITION or MF_POPUP, SubMenu, 'Minimize->');
InsertMenu(SysMenu, 1, MF_BYPOSITION or MF_SEPARATOR, 0, nil);
end;
end;
恢复系统菜单前销毁子菜单:
var
Info: TMenuItemInfo;
begin
Info.cbSize := SizeOf(Info);
Info.fMask := MIIM_SUBMENU;
if GetMenuItemInfo(GetSystemMenu(Handle, False), 0, True, Info) then
DestroyMenu(Info.hSubMenu);
GetSystemMenu(Handle, True);
c) delete but still get button messages
如果您删除 f.i.,"minimize" 项,系统不会将最小化命令的 WM_SYSCOMMAND
消息发送到 window。所以不会有任何命令来响应。
您仍然可以收听按钮消息,f.i。左键按下。但是按钮 down/up 消息实际上与单击按钮并不是一回事。单击按钮包括三个动作,鼠标按下、捕获和再次在按钮上弹起。如果你无论如何都想这样做,一个例子可以是:
procedure TForm1.WMNCLButtonDown(var Message: TWMNCLButtonDown);
begin
inherited;
if (Message.HitTest = HTMINBUTTON) and not IsIconic(Handle) then
ShowWindow(Handle, SW_MINIMIZE);
end;
Sertac 已回答问题:您只能移动它们。
这是使 Sysmenu 有用的完整最终解决方案,它可以: a) 将所有标准项目移到靠近子菜单的地方,子菜单隐藏在分隔符后面。 b) 将菜单项从 Form1.PopupMenu1 添加到 SysMenu c) 删除它们(在制作 window fullscreen/borderless 之前,因为这会破坏系统菜单) d) 显示系统菜单
procedure TForm1.SysMenuAddRemoveExtraItems(Add:boolean=true);
//
var
SysMenu, SubMenu : HMenu;
const NumItems:integer=0;
procedure InsertM(Position,I:integer;J:integer=-1;S:string='');
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647987(v=vs.85).aspx
var M:TMenuItem; H:thandle; Flags:cardinal;
begin
M:=PopupMenu1.Items;
if I>=0 then M:=M.Items[I];
Flags:=MF_BYPOSITION+MF_STRING;
if M.Count>1 then Flags:=Flags+MF_POPUP;
if J>=0 then M:=M.Items[J];
H:=M.Handle;
if S='' then S:=M.Caption;
InsertMenu(SysMenu,Position,Flags,H,pwidechar(S));
inc(NumItems);
end;
procedure InsertSeparator(Position:integer);
begin
InsertMenu(SysMenu,Position,MF_BYPOSITION,MF_SEPARATOR,0); {Add a seperator bar to main form-form1}
inc(NumItems);
end;
procedure RemoveItems;
var i:integer;
begin
for i := 1 to NumItems do //remove items from top
RemoveMenu(SysMenu,0,MF_BYPOSITION);
NumItems:=0;
end;
procedure DeleteAppend(ID:cardinal); //to move standard menuitems to submenu
var
Caption: string;
CaptionLen: Integer;
begin
CaptionLen := GetMenuString(SysMenu, ID, nil, 0, MF_BYCOMMAND);
if CaptionLen > 0 then begin
Inc(CaptionLen);
SetLength(Caption, CaptionLen);
GetMenuString(SysMenu, ID, PChar(Caption), CaptionLen, MF_BYCOMMAND);
DeleteMenu(SysMenu, ID, MF_BYCOMMAND);
AppendMenu(SubMenu, MF_STRING, ID, PChar(Caption));
end;
end;
procedure MoveDefaultSysMenuItemsToSubmenu(Caption:string='';JustSeparator:boolean=false);
//Can either have a a caption or JustSeparator (submenu will be inaccessible)
//
begin
SubMenu := CreateMenu; //make submenu to move them into
if SubMenu <> 0 then begin
DeleteAppend(SC_RESTORE);
DeleteAppend(SC_MOVE);
DeleteAppend(SC_SIZE);
DeleteAppend(SC_MINIMIZE);
DeleteAppend(SC_MAXIMIZE);
if JustSeparator then begin
DeleteMenu(SysMenu, 0, MF_BYPOSITION); //remove separator above CLOSE
InsertMenu(SysMenu, 0, MF_BYPOSITION or MF_POPUP or MF_SEPARATOR, SubMenu, '');
end else begin
DeleteMenu(SysMenu, 0, MF_BYPOSITION); //remove separator above CLOSE
InsertMenu(SysMenu, 0, MF_BYPOSITION or MF_POPUP, SubMenu, PChar(Caption));
InsertMenu(SysMenu, 1, MF_BYPOSITION or MF_SEPARATOR, 0, nil);
end;
end;
end;
procedure DestroySubmenu;
var Info: TMenuItemInfo;
begin
Info.cbSize := SizeOf(Info);
Info.fMask := MIIM_SUBMENU;
if GetMenuItemInfo(GetSystemMenu(Handle, False), 0, True, Info) then
DestroyMenu(Info.hSubMenu);
//GetSystemMenu(Handle, True);
end;
begin
SysMenu := GetSystemMenu(Handle, FALSE) ; {Get system menu}
//InsertMenu(SysMenu,1,MF_BYPOSITION+MF_STRING,SC_MyMenuItem2,'pqr');
if Add then begin
MoveDefaultSysMenuItemsToSubmenu('',true);
// InsertSeparator(0);
InsertM(0,PopupMenu1.Items.Count-2);
InsertM(0,-1,-1,'Menu'); //help
InsertM(0,7);
end
else begin //remove items
RemoveItems;
DestroySubmenu;
end;
end;
//------------------------------------
procedure TForm1.ShowSysMenu;
var P:TPoint;
begin
P:=ClientToScreen(Point(0,0));
TrackPopupMenu(GetSystemMenu(Handle, FALSE), TPM_LEFTALIGN+TPM_TOPALIGN+TPM_RETURNCMD+TPM_NONOTIFY,
P.X,P.Y,0,self.Handle,nil);
end;
//--------------------------------------------------------------
procedure TForm1.WMSysCommand(var Msg: TWMSysCommand);
// https://www.delphipower.xyz/guide_7/customizing_the_system_menu.html
var Item: TMenuItem;
begin
Item := PopupMenu1.FindItem (Msg.CmdType, fkCommand);
if assigned(Item) then Item.Click
else
// case Msg.CmdType of
// //put any other specials in here
// else
inherited;
// end;
end;