System Menu: How to hide/move standard menuitems

2.3k Views Asked by At

In the system menu (topleft of titlebar), I can add my own menu items. I can also delete e.g.DeleteMenu(SysMenu, SC_MINIMIZE, MF_BYCOMMAND) ;

However if I delete the standard ones [restore,minimize,maximize,size,close] their functionality is lost (i.e. maximize button no longer works)

Is there a way to hide these menuitems or move them off the first rank of the system menu? a) make them not visible b) move to a submenu c) delete but still get button messages

3

There are 3 best solutions below

2
On BEST ANSWER

a) make them not visible

The API has no concept of a hidden/invisible menu item.

b) move to a submenu

You can move (or rather delete and add) items to a submenu without effecting functionality.

E.g. move "minimize" to a submenu:

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;

Destroy the submenu before restoring the system menu:

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

If you delete, f.i., the "minimize" item, the system does not send WM_SYSCOMMAND messages for the minimize command to the window. So there won't be any command to respond to.

You can still listen for button messages, f.i. a left button down. But a button down/up message is not actually the same thing with a button click. A button click consists of three actions, mouse down, capture and up again on the button. If you want to do it anyway an example can be:

procedure TForm1.WMNCLButtonDown(var Message: TWMNCLButtonDown);
begin
  inherited;
  if (Message.HitTest = HTMINBUTTON) and not IsIconic(Handle) then
    ShowWindow(Handle, SW_MINIMIZE);
end;
0
On

History:

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.

Now you know why SC_CLOSE is tied to the button. The correct way prevent the user from closing during some operation is therefore to disable the menu item.

If you insist on deleting it for whatever reason but still allowing the window to be closed then you have to delete the item when the system menu is about to be displayed (WM_INITMENU) and revert the system menu after the menu is closed (WM_UNINITMENUPOPUP).

0
On

Sertac has answered the question: You can only move them.

Here is the complete final solution to making the Sysmenu useful, which does: a) Moves all standard items except close to a submenu, which is hidden behind a separator. b) Adds menu items from Form1.PopupMenu1 to the SysMenu c) removes them (before making the window fullscreen/borderless as this destroys the sysmenu) d) shows the sysmenu

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)
    // https://stackoverflow.com/questions/44735708/system-menu-how-to-hide-move-standard-menuitems/44743027#44743027
    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;