Форум по Delphi программированию

Delphi Sources



Вернуться   Форум по Delphi программированию > Все о Delphi > Программа и интерфейс
Ник
Пароль
Регистрация <<         Правила форума         >> FAQ Пользователи Календарь Поиск Сообщения за сегодня Все разделы прочитаны

Ответ
 
Опции темы Поиск в этой теме Опции просмотра
  #1  
Старый 14.03.2008, 10:07
vinni vinni вне форума
Начинающий
 
Регистрация: 26.01.2006
Сообщения: 135
Репутация: 10
По умолчанию Как добавить мой пункт меню в стандартное контекстное меню Винды?

Здравствуйте, уважаемые!

Тыкаем правой кнопкой на заголовок любого окна любого приложения - появляется контекстное меню: Восстановить, Переместить, Размер, Свернуть, Развернуть, Закрыть (Alt+F4).
Вопрос: как в это меню добавить свой пункт (вложенное подменю), а затем обрабатывать события по нажатию на него?

Заранее спасибо!

Последний раз редактировалось vinni, 14.03.2008 в 10:16.
Ответить с цитированием
  #2  
Старый 14.03.2008, 11:27
Аватар для alikoder
alikoder alikoder вне форума
Начинающий
 
Регистрация: 05.12.2007
Сообщения: 126
Репутация: 10
По умолчанию

то есть, на форме, или в самой винде, меню тебе нужно? если на форме то скорее всего юзай PopupMenu и обрабатывай нажатие правой кнопки мыши.
Ответить с цитированием
  #3  
Старый 14.03.2008, 12:41
Rosenkrantz Rosenkrantz вне форума
Активный
 
Регистрация: 04.12.2007
Адрес: Москва
Сообщения: 234
Версия Delphi: Delphi 7
Репутация: 40
По умолчанию

В самом простом варианте - вот так.
Код:
unit asmMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

const
  CM_ABOUT = WM_USER + 1;

procedure TForm1.FormCreate(Sender: TObject);
const
  SNewMenuItemCaption = 'About';
var
  Menu: HMENU;
  MenuItemInfo: TMenuItemInfo;
  lpszCapt: PChar;
begin
  // Получаем дескриптор системного меню
  Menu := GetSystemMenu(Handle, False);

  // Добавляем пункт "About"
  GetMem(lpszCapt, Length(SNewMenuItemCaption) + 1);
  StrPCopy(lpszCapt, SNewMenuItemCaption);
  FillChar(MenuItemInfo, SizeOf(MenuItemInfo), 0);
  with MenuItemInfo do begin
    cbSize        := SizeOf(MenuItemInfo);
    fMask         := MIIM_STRING + MIIM_ID;
    fType         := MFT_STRING;
    fState        := 0;
    wID           := CM_ABOUT;
    hSubMenu      := 0;
    hbmpChecked   := 0;
    hbmpUnchecked := 0;
    dwItemData    := 0;
    dwTypeData    := lpszCapt;
    cch           := Length(SNewMenuItemCaption);
  end;
  InsertMenuItem(Menu, 0, True, MenuItemInfo);
  FreeMem(lpszCapt);

  // Добавляем разделитель после нашего пункта
  FillChar(MenuItemInfo, SizeOf(MenuItemInfo), 0);
  with MenuItemInfo do begin
    cbSize        := SizeOf(MenuItemInfo);
    fType         := MFT_MENUBARBREAK;
  end;
  InsertMenuItem(Menu, 1, True, MenuItemInfo);
end;

procedure TForm1.WMSysCommand(var Msg: TWMSysCommand);
begin
  // Обрабатываем команду нашего пункта меню
  if Msg.CmdType = CM_ABOUT then
    ShowMessage('Insert item to system menu demo.');
  inherited;
end;

end.
Здесь в начало меню формы добавляется пункт "About" и отделяется чертой. Есть еще функция AppendMenu, она совсем простая, но она добавляет только в конец меню. С ней выглядело бы так:
Код:
AppendMenu(Menu, MF_STRING, SC_ABOUT, 'About');

Если вы хотите добавить подменю, то нужно его предварительно создать и указать его дескриптор в поле hSubMenu структуры TMenuItemInfo. Ну и флаги другие, конечно, выставить в fMask (MIIM_SUBMENU). Меню создается функцией CreateMenu

Последний раз редактировалось Rosenkrantz, 14.03.2008 в 12:44.
Ответить с цитированием
  #4  
Старый 14.03.2008, 12:52
vinni vinni вне форума
Начинающий
 
Регистрация: 26.01.2006
Сообщения: 135
Репутация: 10
По умолчанию

Rosenkrantz, спасибо большущее! :-)
Буду пробовать :-)
Ответить с цитированием
  #5  
Старый 14.03.2008, 14:29
vinni vinni вне форума
Начинающий
 
Регистрация: 26.01.2006
Сообщения: 135
Репутация: 10
По умолчанию

Rosenkrantz, все норм, но нужно чтобы этот пункт меню добавлялся при правом клике не на мое приложение, а на заголовке окна ЛЮБОГО приложения :-)
Т.е., например, я запустил свою прогу (она свернута в трее), тыкаю правой на заголовке окна, например, MS Word или MS Excel, и там должен появиться (кроме стандартных) мой пункт меню (раскрывающееся подменю).
Ответить с цитированием
  #6  
Старый 15.03.2008, 11:48
Rosenkrantz Rosenkrantz вне форума
Активный
 
Регистрация: 04.12.2007
Адрес: Москва
Сообщения: 234
Версия Delphi: Delphi 7
Репутация: 40
По умолчанию

Вот в этом вызове
Код:
Menu := GetSystemMenu(Handle, False);
Handle - дескриптор окна, системное меню которого, вы хотите поменять. В моем примере это дескриптор формы нашего приложения. Чтобы добраться до меню чужого окна, очевидно сначала нужно получить его дескриптор. Т.е. запустившись, ваша программа должна найти окна всех открытых приложений и добавить в их системное меню ваш пункт.

Для этого можете использовать EnumWindows. Эта функция находит все окна приложений (но не находит их дочерние окна! для этого есть EnumChildWindows). Вот тут пример использования.

Чтобы обрабатывать окна, которые будут открываться после запуска вашего приложения, можно поступить двояко.

Первый вариант - повесить глобальный хук. Посмотрите описание функции SetWindowsHookEx. Обратите внимание - глобальный хук обязательно должен находиться в DLL.

Либо можно просто периодически повторять перебор всех открытых окон и проверять - есть у них в меню ваш пункт или нет. Если нет, добавлять. В конце концов, мало кто, запустив приложение, сразу лезет в его системное меню. Запуская раз в 2-3 секунды перебор окон, вы свою задачу решите. Нужно только подобрать интервал, чтобы сильно не грузило систему.

P.S. И, кстати, не забудьте удалить добавленные пункты меню, когда ваша программа будет завершаться.

Последний раз редактировалось Rosenkrantz, 15.03.2008 в 15:46.
Ответить с цитированием
  #7  
Старый 18.03.2008, 15:06
vinni vinni вне форума
Начинающий
 
Регистрация: 26.01.2006
Сообщения: 135
Репутация: 10
По умолчанию

Уважаемый Rosenkrantz! Есть 2 вопроса:
1. Поясни, плиз, поподробнее как создать подменю, а не пункт меню (не пойму как работать с CreateMenu).
2. Как удалить мои созданные подменю после завершения программы? DeleteMenu()? Если да - то что означают ее параметры (кромер первого, он понятен) .

Заранее спасибо!
Ответить с цитированием
  #8  
Старый 20.03.2008, 17:36
Rosenkrantz Rosenkrantz вне форума
Активный
 
Регистрация: 04.12.2007
Адрес: Москва
Сообщения: 234
Версия Delphi: Delphi 7
Репутация: 40
По умолчанию

Вот код, который добавляет пункты в меню всех открытых при старте программы окон, и удаляет эти пункты при выходе из нее.
Код:
unit asmMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

const
  SNewMenuItemCaption = 'About';

var
  CM_about: Cardinal;

function  MenuAlreadyProcessed(Menu: HMENU): Boolean;
// Проверяет, возможно меню уже было обработано
var
  i, Count   : Integer;
  Data       : TMenuItemInfo;
begin
  Result := False;
  Count  := GetMenuItemCount(Menu);
  for i := 0 to Count - 1 do begin
    FillChar(Data, SizeOf(Data), 0);

    Data.cbSize := SizeOf(Data);
    Data.fMask  := MIIM_DATA + MIIM_ID;
    if GetMenuItemInfo(Menu, i, True, Data) then
      // Проверяем, если "наш" пункт меню уже присутствует, значит
      // повторная обработка меню не требуется
      if (Data.wID = CM_ABOUT) and (Data.dwItemData = Application.Handle) then begin
        Result := True;
        Exit;
      end;
  end;
end;

procedure DeleteItem(Menu: HMENU);
// Удалить добавленные пункты из меню
var
  i, Count   : Integer;
  Data       : TMenuItemInfo;
  S          : String;
begin
  Count := GetMenuItemCount(Menu);
  for i := Count - 1 downto 0 do begin
    FillChar(Data, SizeOf(Data), 0);

    Data.cbSize := SizeOf(Data);
    Data.fMask  := MIIM_TYPE + MIIM_DATA + MIIM_ID;
    // Опознаем "свой" пункт меню по дескриптору приложения в dwItemData
    if GetMenuItemInfo(Menu, i, True, Data) and (Data.dwItemData = Application.Handle) then
      DeleteMenu(Menu, i, MF_BYPOSITION);
  end;
end;

procedure AddItem(Menu: HMENU);
// Добавить пунк в меню
var
  MenuItemInfo: TMenuItemInfo;
  CaptStr: PChar;
begin
  // При добавлении своих пунктов меню в dwItemData прописываем дескриптор
  // приложения. При завершении приложения нам нужно
  // будет удалить эти пункты меню; мы будем опознавать их по этому признаку.

  // Добавляем пункт "About"
  GetMem(CaptStr, Length(SNewMenuItemCaption) + 1);
  StrPCopy(CaptStr, SNewMenuItemCaption);
  FillChar(MenuItemInfo, SizeOf(MenuItemInfo), 0);
  with MenuItemInfo do begin
    cbSize        := SizeOf(MenuItemInfo);
    fMask         := MIIM_TYPE + MIIM_DATA + MIIM_ID;
    fType         := MFT_STRING;
    wID           := CM_ABOUT;
    dwItemData    := Application.Handle; // Признак для опознания "своих" пунктов меню
    dwTypeData    := CaptStr;
    cch           := Length(SNewMenuItemCaption);
  end;
  InsertMenuItem(Menu, 0, True, MenuItemInfo);
  FreeMem(CaptStr);

  // Добавляем разделитель после нашего пункта
  FillChar(MenuItemInfo, SizeOf(MenuItemInfo), 0);
  with MenuItemInfo do begin
    cbSize        := SizeOf(MenuItemInfo);
    fMask         := MIIM_TYPE + MIIM_DATA;
    fType         := MFT_SEPARATOR;
    dwItemData    := Application.Handle;
  end;
  InsertMenuItem(Menu, 1, True, MenuItemInfo);
end;

function EnumWinToAddItem(hWindow: HWND): BOOL; stdcall;
// Функция добавляет пункт меню
// Вызывается из EnumWindows
var
  Menu: HMENU;
  WCaption: Array [0..255] Of Char;
begin
  Menu := GetSystemMenu(hWindow, False);
  if (Menu <> 0) and (not MenuAlreadyProcessed(Menu)) then
    AddItem(Menu);

  Result := True;
end;

function EnumWinToDelItem(hWindow: HWND): BOOL; stdcall;
// Функция удаляет пункт меню
// Вызывается из EnumWindows
var
  Menu: HMENU;
begin
  // Получаем системное меню окна
  Menu := GetSystemMenu(hWindow, False);
  // Меню есть не у всех окон, поэтому нужна проверка на 0
  if (Menu <> 0) then
    DeleteItem(Menu);

  // Если вернуть False, EnumWindows закончит работу
  Result := True;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  // Удаляем наши пункты меню из всех окон
  EnumWindows(@EnumWinToDelItem, LongInt(Self));
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // Регистрируем уникальное системное сообщение
  CM_ABOUT := RegisterWindowMessage('CM_ABOUT');
  // Добавляем пункт меню во все окна
  EnumWindows(@EnumWinToAddItem, LongInt(Self));
end;

procedure TForm1.WMSysCommand(var Msg: TWMSysCommand);
begin
  // Обрабатываем команду нашего пункта меню
  if Msg.CmdType = CM_ABOUT then
    ShowMessage('Insert item to system menu demo.');
  inherited;
end;

end.
Про создание меню я же написал - CreateMenu. А добавлять в него пункты абсолютно точно так же, как в процедуре AddItem в моем примере. Ну, если уж совсем никак - отпишитесь, я напишу демонстрационный пример. Сегодня уже лениво

Параметры DeleteMenu:
1 - Меню
2 - Смысл его зависит от третьего параметра
3 - MF_BYPOSITION или MF_BYCOMMAND. Если MF_BYPOSITION, то второй параметр означает позицию пункта, который мы удаляем в меню (начиная с нуля). Если MF_BYCOMMAND - второй параметр это команда, которая связана с пунктом меню. В моем примере используется удаление по позиции.

Последний раз редактировалось Rosenkrantz, 20.03.2008 в 17:41.
Ответить с цитированием
  #9  
Старый 21.03.2008, 16:24
vinni vinni вне форума
Начинающий
 
Регистрация: 26.01.2006
Сообщения: 135
Репутация: 10
По умолчанию

Если можно, напиши, плиз, пример с раскрыващимся подменю, в котором, например, 10 пунктов. И обработка нажатия на мой пункт меню (procedure TForm1.WMSysCommand(var Msg: TWMSysCommand)) работает только при клике на меню, появляющееся при нажатии на форму моего приложения, т.е. обработка нажатия на других окнах не работает - ничего не происходит...
Ответить с цитированием
  #10  
Старый 21.03.2008, 16:51
Rosenkrantz Rosenkrantz вне форума
Активный
 
Регистрация: 04.12.2007
Адрес: Москва
Сообщения: 234
Версия Delphi: Delphi 7
Репутация: 40
По умолчанию

Цитата:
Сообщение от vinni
т.е. обработка нажатия на других окнах не работает - ничего не происходит...
Разумеется, не происходит, откуда же им знать, как обрабатывать вашу команду меню. Для этого вам нужно перехватывать сообщения, поступающие в окна других приложений, т.е. ставить хук. Вы посмотрите ссылки в моем сообщении, там много информации-то.

Пример создания меню напишу чуть попозже.
Ответить с цитированием
  #11  
Старый 22.03.2008, 02:03
Rosenkrantz Rosenkrantz вне форума
Активный
 
Регистрация: 04.12.2007
Адрес: Москва
Сообщения: 234
Версия Delphi: Delphi 7
Репутация: 40
По умолчанию

Цитата:
Сообщение от vinni
Если можно, напиши, плиз, пример с раскрыващимся подменю, в котором, например, 10 пунктов.

Во-первых, поправлю сам себя: для создания подменю нужно использовать CreatePopupMenu, а не CreateMenu.

Во-вторых, вот функция, которая создаст нам это самое подменю:
Код:
function  CreateSubMenu: HMENU;
// Создаем подменю
begin
  Result := CreatePopupMenu;

  AppendMenu(Result, MF_STRING,     CM_ABOUT, 'Item 0');
  AppendMenu(Result, MF_STRING,     CM_ABOUT, 'Item 2');
  AppendMenu(Result, MF_STRING,     CM_ABOUT, 'Item 3');
  AppendMenu(Result, MF_STRING,     CM_ABOUT, 'Item 4');
  AppendMenu(Result, MF_SEPARATOR,  WM_NULL,  nil);
  AppendMenu(Result, MF_STRING,     CM_ABOUT, 'Item 5');
end;
Как видите, ничего сложного.

Теперь нужно только подправить немножко процедуру AddItem:
Код:
...
  with MenuItemInfo do begin
    cbSize        := SizeOf(MenuItemInfo);
    fMask         := MIIM_TYPE + MIIM_DATA + MIIM_SUBMENU;
    fType         := MFT_STRING;
    wID           := 0;
    hSubMenu      := CreateSubMenu; // Назначаем подменю
    dwItemData    := Application.Handle; // Признак для опознания "своих" пунктов меню
    dwTypeData    := CaptStr;
    cch           := Length(SNewMenuItemCaption);
  end;
...
Ответить с цитированием
  #12  
Старый 28.03.2008, 21:11
vinni vinni вне форума
Начинающий
 
Регистрация: 26.01.2006
Сообщения: 135
Репутация: 10
По умолчанию

Rosenkrantz, я понимаю, достал уже наверное тебя... :-)
Можеш плиз написать пример или объяснить поподробнее как обработать нажатие на определенный пункт меню?
Ответить с цитированием
  #13  
Старый 29.03.2008, 15:20
Rosenkrantz Rosenkrantz вне форума
Активный
 
Регистрация: 04.12.2007
Адрес: Москва
Сообщения: 234
Версия Delphi: Delphi 7
Репутация: 40
По умолчанию

Цитата:
Сообщение от vinni
Rosenkrantz, я понимаю, достал уже наверное тебя... :-)
Ну, как бы на то и форум.
Цитата:
Сообщение от vinni
Можеш плиз написать пример или объяснить поподробнее как обработать нажатие на определенный пункт меню?
Но я ничего нового не скажу - хук вам надо вешать и обрабатывать перехваченные события. Я тут было накатал вам много букв про хуки, но потом нашел хорошую статью на русском и решил, что там понятней написано.

Почитайте, поэкспериментируйте, может и пример никакой не понадобится. Для начала напишите локальный хук, который будет ловить нужное вам событие внутри вашей программы, а дальше уже будет проще.
Ответить с цитированием
  #14  
Старый 03.04.2008, 10:14
vinni vinni вне форума
Начинающий
 
Регистрация: 26.01.2006
Сообщения: 135
Репутация: 10
По умолчанию

Rosenkrantz, спасибо большое за инфу! В теории все ясно, но на практике пока никак... Я так понимаю, чтобы добавить мой пункт меню - нужно это делать при создании окна вновь запускаемой программы. Хук должен быть WH_SHELL? И насчет обработки нажатия - тоже хз. Если можеш, напиши, плиз, пример... Спасибо!
Ответить с цитированием
  #15  
Старый 03.04.2008, 12:43
Rosenkrantz Rosenkrantz вне форума
Активный
 
Регистрация: 04.12.2007
Адрес: Москва
Сообщения: 234
Версия Delphi: Delphi 7
Репутация: 40
По умолчанию

Нет, для добавления в меню своего пункта хук, в общем-то, не нужен. Можно, как я писал уже, сделать по таймеру обход всех открытых окон раз в 30-40 секунд и добавлять. А чтобы вот перехватить выбор пользователем вашего пункта в этом меню, нужен хук.

Насчет написать - м.б. в выходные чего-нибудь напишу. Не обещаю, но постараюсь.
Ответить с цитированием
Ответ


Delphi Sources

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения

BB-коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход


Часовой пояс GMT +3, время: 22:47.


 

Сайт

Форум

FAQ

RSS лента

Прочее

 

Copyright © Форум "Delphi Sources" by BrokenByte Software, 2004-2023

ВКонтакте   Facebook   Twitter