Системное меню: как скрыть/переместить стандартные пункты меню

В системном меню (вверху слева от заголовка) я могу добавить свои собственные пункты меню. Я также могу удалить, например, DeleteMenu(SysMenu, SC_MINIMIZE, MF_BYCOMMAND) ;

Однако, если я удалю стандартные [восстановить, свернуть, развернуть, размер, закрыть], их функциональность будет потеряна (т.е. кнопка увеличения больше не работает)

Есть ли способ скрыть эти пункты меню или переместить их с первого ряда системного меню? а) сделать их невидимыми б) перейти в подменю в) удалить, но все равно получать сообщения кнопок


person Henry Crun    schedule 24.06.2017    source источник
comment
Итак, как мне отключить его? (Цель состоит в том, чтобы сделать его невидимым, а не серым и т. д.)   -  person Henry Crun    schedule 25.06.2017
comment
Странно, что этот EnableMenuItem(SysMenu, 3, MF_BYPOSITION+MF_DISABLED) ; ничего не делает   -  person Henry Crun    schedule 25.06.2017
comment
Не используйте MF_BYPOSITION, это не сработает, если у кого-то установлено стороннее приложение, перехватывающее меню. Вы уже знаете идентификатор...   -  person Anders    schedule 25.06.2017


Ответы (3)


а) сделать их невидимыми

В API нет понятия скрытого/невидимого пункта меню.

б) перейти в подменю

Вы можете перемещать (или, скорее, удалять и добавлять) элементы в подменю, не влияя на функциональность.

Например. переместите «свернуть» в подменю:

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) удалить, но все еще получать сообщения кнопок

Если вы удаляете, например, элемент "свернуть", система не отправляет WM_SYSCOMMAND сообщений для команды сворачивания в окно. Так что не будет никакой команды, на которую можно было бы ответить.

Вы по-прежнему можете прослушивать сообщения кнопок, т.е. левая кнопка вниз. Но сообщение о нажатой/нажатой кнопке на самом деле не то же самое, что и нажатие кнопки. Нажатие кнопки состоит из трех действий: опускание мыши, захват и снова нажатие на кнопку. Если вы все равно хотите это сделать, пример может быть:

procedure TForm1.WMNCLButtonDown(var Message: TWMNCLButtonDown);
begin
  inherited;
  if (Message.HitTest = HTMINBUTTON) and not IsIconic(Handle) then
    ShowWindow(Handle, SW_MINIMIZE);
end;
person Sertac Akyuz    schedule 25.06.2017
comment
Мне не совсем понятно, когда вы имеете в виду Уничтожить подменю перед восстановлением системного меню:. То, что я делаю, переходит в полноэкранный режим, и это разрушает меню, поэтому перед этим я отключаю элементы меню Delphi. Должен ли я также делать что-то в form.onclose? - person Henry Crun; 25.06.2017
comment
@Henry - я так не думаю, используйте ReportMemoryLeaksOnShutdown для работы с VCL. Я имел в виду, что не забудьте уничтожить подменю, когда закончите с ним. Если вы отсоедините его до завершения работы программы, система не будет автоматически освобождать связанные с ним ресурсы. - person Sertac Akyuz; 25.06.2017

История:

Первоначально заголовок окна имел только две кнопки в правом верхнем углу, кнопки свертывания и развертывания, и они управлялись стилем окна. В Windows 95 была добавлена ​​кнопка «Закрыть», но тогда возник вопрос, когда ее включать и отключать. Но подождите, мы уже знаем, когда включать и отключать его: приложение сообщало нам, когда оно включало и отключало пункт меню SC_CLOSE. Бинго, достаточно прицепить кнопку «Закрыть» к существующему пункту меню (который приложения уже повадились поддерживать), и волшебство, оно просто работает. Приложениям не нужно писать специальный код для поддержки кнопки «Закрыть».

Теперь вы знаете, почему SC_CLOSE привязан к кнопке. Таким образом, правильный способ предотвратить закрытие пользователем во время какой-либо операции — отключить этот пункт меню.

Если вы настаиваете на его удалении по какой-либо причине, но по-прежнему разрешаете закрытие окна, вам необходимо удалить этот элемент перед отображением системного меню (WM_INITMENU) и вернуть системное меню после его закрытия (WM_UNINITMENUPOPUP).

person Anders    schedule 24.06.2017

Сертак ответил на вопрос: их можно только перемещать.

Вот окончательное решение, позволяющее сделать Sysmenu полезным, которое: а) Перемещает все стандартные элементы, кроме близких к подменю, которое скрыто за разделителем. б) Добавляет элементы меню из Form1.PopupMenu1 в SysMenu c) удаляет их (перед тем, как сделать окно полноэкранным/без полей, так как это разрушает системное меню) 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)
    // 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;
person Henry Crun    schedule 25.06.2017