Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Для новичков > Запуздырить иконку в трей (c)


Автор: MetalFan 1.5.2011, 17:36
Простите за заголовок темы, вроде как все должно быть просто...
Но возникла небольшая сложность, что-то не соображу, как правильно победить.
Цель: Сворачивать приложение в трей (+убирать из панели задач) , по щелчку по иконке - разворачивать (возвращать в панель задач)
Компоненты (Delphi XE): TTrayIcon, TApplicationEvents. Первому назначен OnClick, второму OnMinimize/OnRestore
Код :
Код

procedure TForm1.appevtMinimize(Sender: TObject);
begin
  tricon.Visible := True;
  Hide;
end;

procedure TForm1.appevtRestore(Sender: TObject);
begin
  tricon.Visible := False;
  Show;
  SetForegroundWindow(Handle);
end;

procedure TForm1.triconClick(Sender: TObject);
begin
  if WindowState = wsMinimized then
    Application.Restore
  else
    Application.Minimize;
end;

в аттаче тестовое приложение с указанным кодом.
Проблема: на первый взгляд все хорошо: сворачиваем - получаем иконку в трее, разворачиваем - получаем приложение в панели задач...
Но! При сворачивании по клику по кнопке в панели задач приложение обратно из трея не разворачивается(
Городил собственную обработку сворачивания/разворачивания (вместо Application.Minimize/Restore), но там другие косяки повылазили...
Может кто сталкивался и знает изящное решение?
Всем спасибо за внимание,
wbw, MetalFan.

Автор: Rrader 1.5.2011, 18:05
Как увиделось, проблема в том, что IsIconic работает по-разному в разных случаях сворачивания. А что, если зафорсить единое поведение?
Код

procedure TForm1.appevtMinimize(Sender: TObject);
begin
  tricon.Visible := True;
  SendMessage(Application.Handle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
  Hide;
end;

Автор: MetalFan 1.5.2011, 18:35
Rrader, Да, на IsIconic тоже обратил внимание... почему-то IsIconic "слетает" после того, как Hide  форме делается...
А SC_MINIMIZE приходит до вызова Application.OnMinimize (точнее как раз OnMinimize вызывается вследствие прихода SC_MINIMIZE) судя по коду 
TApplication.WndProc...
Но твой вариант работает верно... спасибо за идею)
Может вообще есть более корректный подход, чем который я изначально привел?
кстати, IsIconic возвращает неверно выставленный внутренний флаг TApplication.FAppIsIconic в моем случае... который выставляется по приходу WM_ACTIVATEAPP, после того, как приложение свернуто... хм. косяк что-ли где-то в VCL.

Автор: MetalFan 11.1.2012, 21:27
Подниму сию тему...
В общем решил исходную задачу следующим рабочим кодом, который прекрасно отрабатывает по клику на иконку в трее (используется стандартный компонент - TTrayIcon):
Код

procedure TForm1.DoRestoreOrMinimize;
begin
  if WindowState = wsMinimized then
  begin
    WindowState := wsNormal;
    Application.Restore;
    Show;
    SetForegroundWindow(Handle);
  end
  else
  begin
    Application.Minimize;
    Hide;
  end;
end;

Данный код сворачивает приложение "в трей", если оно "развернуто", либо разворачивает его из оного, если оно было свернуто.
Иконка в трее теперь видна всегда.

Задача: модифицировать код так, чтобы при вызове этого кода в момент клика мышью на иконке в трее добавился третий вариант реакции - если приложение развернуто, но не на переднем плане, перенести его на передний план...
Казалось бы, что сложного, во вторую ветку добавить проверку вида 
Код

if GetForegroundWindow <> Handle then 
  SetForegroundWindow(Handle)
else
...старый код

Но в момент клика на иконку в трее, при вызове данного кода, GetForegroundWindow всегда <> Self.Handle
Другие, пришедшие в голову проверки (GetTopWindow, Application.Active, Applicaiton.MainForm.Active, etc...) так же не срабатывают, т.к. в момент клика активным окном на переднем плане становится окно тулбара винды...

Собственно уважаемые коллеги, какие будут соображения на эту тему?

p.s.По скольку вопрос напрямую связан с сабжевым вопросом, то создавать новую тему не вижу смысла

Автор: Чучмек 11.1.2012, 23:35
Цитата(MetalFan @  1.5.2011,  17:36 Найти цитируемый пост)
TTrayIcon

А Shell_NotifyIcon недостаточно?


Автор: CodeMonkey 12.1.2012, 07:12
Это баг в Delphi.

Application.Restore первым делом проверяет "свёрнутость" окна. Проблема в том, что делает она это на Handle себя, а не формы (это при MainFormOnTaskbar = True). Application.Handle и Application.MainForm.Handle - это два разных окна и их состояния могут быть несогласованными, что мы и получаем в этом примере.

Решение: сворачивать Application.

Код
procedure TForm1.appevtMinimize(Sender: TObject);
begin
  tricon.Visible := True;
  // Application:
  if (Application.Handle <> 0) and (Application.Handle <> Application.MainFormHandle) then
    ShowWindow(Application.Handle, SW_MINIMIZE);
  // Form:
  Application.MainForm.Hide;
end;

procedure TForm1.appevtRestore(Sender: TObject);
begin
  tricon.Visible := False;
  // Application:
  if (Application.Handle <> 0) and (Application.Handle <> Application.MainFormHandle) then
    ShowWindow(Application.Handle, SW_NORMAL);
  // Form:
  Application.MainForm.Show;
  if Application.MainForm.WindowState = wsMinimized then
    Application.MainForm.WindowState := wsNormal;
  // Activation:
  SetForegroundWindow(Application.MainFormHandle);
end;

procedure TForm1.triconClick(Sender: TObject);
begin
  if Application.MainForm.Visible then
    Application.Minimize
  else
    Application.Restore;
end;


Добавлено через 2 минуты и 34 секунды
P.S. Эх, проморгал исходную тему, а то сохранил бы тебе полгода мучений smile

Автор: CodeMonkey 12.1.2012, 07:28
Цитата
Задача: модифицировать


Чего-то слона-то я и не заметил...

Не очень понял как может быть "если приложение развернуто, но не на переднем плане" одновременно с "при клике мышью на иконке в трее". Ты же скрываешь икноку при показе окна.

В любом случае проверка должна опираться на видимость окна. Видимо окно - на первый план его, нет - восстановить. 

Автор: MetalFan 12.1.2012, 07:59
Цитата(Чучмек @  11.1.2012,  23:35 Найти цитируемый пост)
А Shell_NotifyIcon недостаточно?

по делу есть что сказать?


Цитата(CodeMonkey @  12.1.2012,  07:28 Найти цитируемый пост)
Не очень понял как может быть "если приложение развернуто, но не на переднем плане" одновременно с "при клике мышью на иконке в трее". Ты же скрываешь икноку при показе окна.

В том то и дело, что рассматриваем вариант, когда иконка в трее видна всегда.
Предложенный второй отрезок кода при его вызове работает так: Если приложение свернуто - разворачивает, если развернуто, то сворачивает... 
Необходимо модифицировать: Если приложение НЕ свернуто в трей, но, к примеру, сейчас активно окно другого приложения, то в этом случае наше приложение должно "переехать" на передний план, а не свернуться в трей, при клике по его иконке в этом трее.
Такое поведение наблюдается, к примеру, у The Ваt при соответствующих настройках.
Исходный же алгоритм ВСЕГДА сворачивает приложение, если оно не свернуто.

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

Автор: CodeMonkey 12.1.2012, 11:02
А, всё, понял.

Это просто: возьми Foreground или Active окно (надо проверить, как там с SOT-окнами), найди следующее по Z-порядку (GetNextWindow). Если твоё - то ты сверху, минимизируй. Если не твоё, то ты перекрыт - сворачивайся.

Автор: MetalFan 12.1.2012, 11:19
CodeMonkey, не сработало) пробовал так уже. В момент клика, даже если перед этим было активно наше окно, то GetNextWindow от GetForegroundWindow возвращает вообще другое окно, никак не относящееся к нашему приложению.
з.ы. а что такое SOT-окна?

Автор: CodeMonkey 12.1.2012, 11:41
SOT = Stay On Top

Через Spy++ проверь, что там GetNextWindow возвращает. По нему же можно цепочку Z order проверить (вроде).

Автор: MetalFan 12.1.2012, 11:48
CodeMonkey, вот моя пока нерабочая модификация кода:
Код

  if WindowState = wsMinimized then
  begin
    WindowState := wsNormal;
    Application.Restore;
    Show;
    SetForegroundWindow(Handle);
  end
  else
  begin
    if (GetForegroundWindow = Handle) or (GetNextWindow(GetForegroundWindow, GW_HWNDNEXT) = Handle) then
    begin
      Application.Minimize;
      Hide;
    end
    else
      SetForegroundWindow(Handle);
  end;

Смотрел в S++, в момент нажатия по трею GetNextWindow(GetForegroundWindow, GW_HWNDNEXT) и GetNextWindow(GetForegroundWindow, GW_HWNDPREV) возвращает вообще левые окна... что и видно в S++.

тестовое приложение в аттаче.

Автор: CodeMonkey 12.1.2012, 12:30
Цитата
вообще левые окна...


А что это за окна? Чьи они? И какие у них атрибуты?

Добавлено через 1 минуту и 2 секунды
Кстати, а ты пробовал GetLastActivePopup?

Автор: MetalFan 12.1.2012, 13:46
Цитата(CodeMonkey @  12.1.2012,  12:30 Найти цитируемый пост)
А что это за окна? Чьи они? И какие у них атрибуты?

просто окна других приложений. среди них нет моего.

Цитата(CodeMonkey @  12.1.2012,  12:30 Найти цитируемый пост)
Кстати, а ты пробовал GetLastActivePopup? 

пробовал. Если в него отдаешь 0, то возвращается 0, если отдаешь хэндл формы, то получаешь тот же хэндл формы.

Автор: Чучмек 12.1.2012, 21:23
OnPaint

Автор: MetalFan 12.1.2012, 21:30
Чучмек, выскажись по-подробнее....

Автор: Чучмек 12.1.2012, 21:55
Если окно приложения перекрыто другими окнами и становится активным, то для окна вызывается OnPaint
А если окно не перекрыто - то  OnPaint не вызывается.
Отсюда решение: По клику на иконку в трее сделать окно активным и запустить ожидание события  OnPaint (0,5 сек)
Если событие не произошло - скрыть окно.

Автор: MetalFan 12.1.2012, 22:58
Чучмек, хм, как самый крайний вариант может и сойдет, но, имхо, не очень красивое решение...

Автор: CodeMonkey 13.1.2012, 01:23
Выяснил, что все функции работают только в контексте цепочки окна. Я не нашёл ни одной функции, которая давала бы доступ к глобальному Z-порядку.

Для теста использовал такой код:
Код
procedure TForm1.appevtMinimize(Sender: TObject);
begin
//  tricon.Visible := True;
  // Application:
  if (Application.Handle <> 0) and (Application.Handle <> Application.MainFormHandle) then
    ShowWindow(Application.Handle, SW_MINIMIZE);
  // Form:
  Application.MainForm.Hide;
end;

procedure TForm1.appevtRestore(Sender: TObject);
begin
//  tricon.Visible := False;
  // Application:
  if (Application.Handle <> 0) and (Application.Handle <> Application.MainFormHandle) then
    ShowWindow(Application.Handle, SW_NORMAL);
  // Form:
  Application.MainForm.Show;
  if Application.MainForm.WindowState = wsMinimized then
    Application.MainForm.WindowState := wsNormal;
  // Activation:
  SetForegroundWindow(Application.MainFormHandle);
end;

function GetProcessImageFileName(hProcess: THandle; lpImageFileName: PWideChar; nSize: DWORD): DWORD; stdcall; external 'PsAPI.dll' name 'GetProcessImageFileNameW';

procedure TForm1.triconClick(Sender: TObject);

  function GetPrevWnd(const AWnd: HWND): HWND;
  // тут - всякий бред, я экспериментировал
  type
    TParams = record
      PID: DWord;
      Src: HWND;
      Dst: HWND;
    end;
    PParams = ^TParams;

    function Enumer(AWnd: HWND; Params: PParams): BOOL; stdcall;
    var
      PID: DWORD;
    begin
      Result := True;
      if GetWindowThreadProcessId(AWnd, PID) <> 0 then
      begin
        if PID <> Params.PID then
        begin
          Params.Dst := AWnd;
          Result := False;
        end;
      end;
    end;

  var
    PID: DWORD;
    Params: TParams;
  begin
    if GetWindowThreadProcessId(AWnd, PID) = 0 then
    begin
      Result := 0;
      Exit;
    end;

    Params.PID := PID;
    Params.Src := AWnd;
    Params.Dst := 0;
    EnumWindows(@Enumer, Integer(@Params));
    Result := Params.Dst;
  end;

  function GetWindowText(const AWnd: HWND): String;
  var
    Buf1, Buf2: String;
    PID: DWORD;
    Handle: THandle;
  begin
    SetLength(Buf1, 10240);
    SetLength(Buf1, GetClassName(AWnd, PChar(Buf1), Length(Buf1)));

    SetLength(Buf2, 10240);
    SetLength(Buf2, Windows.GetWindowText(AWnd, PChar(Buf2), Length(Buf2)));

    if GetWindowThreadProcessId(AWnd, PID) <> 0 then
    begin
      Handle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, PID);
      try
        SetLength(Result, 10240);
        SetLength(Result, GetProcessImageFileName(Handle, PChar(Result), Length(Result)));
        if Result = '' then
          Result := SysErrorMessage(GetLastError);
      finally
        CloseHandle(Handle);
      end;
      Result := ExtractFileName(Result);
    end
    else
      Result := '?';

    Result := Format('[%s] "%s" (%s)', [Buf1, Buf2, Result]);
  end;

begin
  Label1.Caption := 'Application.Handle: ' + IntToHex(Application.Handle, 8);
  Label2.Caption := 'Application.MainForm.Handle: ' + IntToHex(Application.MainForm.Handle, 8);
  Label3.Caption := 'GetForegroundWindow: ' + IntToHex(GetForegroundWindow, 8) + ' ' + GetWindowText(GetForegroundWindow);
  Label4.Caption := 'GetActiveWindow: ' + IntToHex(GetActiveWindow, 8) + ' ' + GetWindowText(GetActiveWindow);
  Label5.Caption := 'Next(GetForegroundWindow): ' + IntToHex(GetNextWindow(GetForegroundWindow, GW_HWNDNEXT), 8) + ' ' + GetWindowText(GetNextWindow(GetForegroundWindow, GW_HWNDNEXT));
  Label6.Caption := 'Next(GetActiveWindow): ' + IntToHex(GetNextWindow(GetActiveWindow, GW_HWNDNEXT), 8) + ' ' + GetWindowText(GetNextWindow(GetActiveWindow, GW_HWNDNEXT));
  Label7.Caption := 'Prev(GetForegroundWindow): ' + IntToHex(GetNextWindow(GetForegroundWindow, GW_HWNDPREV), 8) + ' ' + GetWindowText(GetNextWindow(GetForegroundWindow, GW_HWNDPREV));
  Label8.Caption := 'Prev(GetActiveWindow): ' + IntToHex(GetNextWindow(GetActiveWindow, GW_HWNDPREV), 8) + ' ' + GetWindowText(GetNextWindow(GetActiveWindow, GW_HWNDPREV));
  Label9.Caption := 'LastActive(GetForegroundWindow): ' + IntToHex(GetLastActivePopup(GetForegroundWindow), 8) + ' ' + GetWindowText(GetLastActivePopup(GetForegroundWindow));
  Label10.Caption := 'LastActive(GetActiveWindow): ' + IntToHex(GetLastActivePopup(GetActiveWindow), 8) + ' ' + GetWindowText(GetLastActivePopup(GetActiveWindow));
  Label11.Caption := 'MyPrev(GetForegroundWindow): ' + IntToHex(GetPrevWnd(GetForegroundWindow), 8) + ' ' + GetWindowText(GetPrevWnd(GetForegroundWindow));
  Label12.Caption := 'MyPrev(GetActiveWindow): ' + IntToHex(GetPrevWnd(GetActiveWindow), 8) + ' ' + GetWindowText(GetPrevWnd(GetActiveWindow));

  {
  if Application.MainForm.Visible then
    Application.Minimize
  else
    Application.Restore;
  }
end;


Предлагаю попробовать так: перечислить все окна. Отобрать из них те, которые видимые, не SOT и пересекают твой клиентский прямоугольник. Если такие есть - ты перекрыт, давай наверх. Если таких нет - ты наверху, убирайся в трей.

Добавлено через 3 минуты и 59 секунд
Возможно, вместо перечисления окон можно глянуть в сторону GetClipBox.

Автор: Чучмек 13.1.2012, 02:53
PtVisible

Автор: Qu1nt 13.1.2012, 03:49
http://delphimaster.net/view/2-1169797326
Или вот, что получилось у меня.
Код

function EnumProc(Handle: THandle; var Foreground: THandle): Boolean; stdcall;
var
  ClassName: array [Byte] of Char;
begin
  GetClassName(Handle, ClassName, SizeOf(ClassName));
  Foreground := Handle;
  Result := not IsWindowEnabled(Handle) or not IsWindowVisible(Handle) or
    (GetWindow(Handle, GW_OWNER) <> 0) or
    (GetWindowLongPtr(Handle, GWL_EXSTYLE) and WS_EX_TOPMOST <> 0) or
    (ClassName = 'NotifyIconOverflowWindow') or
    (ClassName = 'Shell_TrayWnd') or
    (ClassName = 'Internet Explorer_Hidden');
end;

function GetForeground: THandle;
begin
  EnumWindows(@EnumProc, NativeInt(@Result));
end;

Автор: MetalFan 13.1.2012, 09:11
Цитата(CodeMonkey @  13.1.2012,  01:23 Найти цитируемый пост)
и пересекают твой клиентский прямоугольник.

не понял, как это определить... если просто сравнить пересечение прямоугольников окон. так если чужое окно будет под или над моим, получается, что прямоугольники пересекуться...


Цитата(CodeMonkey @  13.1.2012,  01:23 Найти цитируемый пост)
глянуть в сторону GetClipBox

а вот это посмотрим

Цитата(Чучмек @  13.1.2012,  02:53 Найти цитируемый пост)
PtVisible 

хм, и что, проверять каждый пиксель формы? ну как еще один крайний вариант - сойдет. хотя оно же не учитывает так называемые SOT окна наверняка.


Qu1nt, да, тема стара, как мир)))
предложенный код исходит из предположения, что EnumWindows перечисляет окна в порядке видимости сверху вниз? А список исключения по ClassName если вдруг поменяется? программа какая-либо сторонняя вдруг так же вылезет "наверх"... спасибо, как вариант, возможно прокатит. Но как-то на мой взгляд не надежно....

Добавлено @ 09:12
з.ы. как же ритлабовцы в своем thebat добились нужного поведения...  и uTorrent тоже как надо работает....

Автор: CodeMonkey 13.1.2012, 09:20
Не видел TheBat, но может они читерят smile

Например, первый клик - если окно видимо, то наверх его. Если второй клик (но не дабл-клик) происходит менее чем через 2 сек после первого - то свернуть окно, если позднее - то начать с начала.

Автор: MetalFan 13.1.2012, 09:34
CodeMonkey, неа... даж если надо окном бата/utorrenta поместить, к примеру, калькулятор, то при клике на иконку в трее окно не свернется в трей, а "переедет" на передний план.

Сегодня попробую все предложенные варианты, отпишусь.

Добавлено через 2 минуты и 29 секунд
http://delphimaster.net/view/4-1124275353/3, как раз, как ты писал с проверкой на пересечение окон, что выше по Z-порядку находятся. В общем надо пробовать.

Автор: MetalFan 13.1.2012, 15:46
Небольшой подитог: все способы, определяющие частично перекрытие окна не катят на мой взгляд. т.к. неправильно сработают в след.ситуации: наше окно открыто не на весь экран и видно полностью, рядом АКТИВНО окно другого приложения так же не на весь экран и не перекрывающее наше окно. В этом случае по логике с перекрытиями наше окно считается поверх всех, т.к. видно полностью, что на мой взгляд неправильно.

Вариант Qu1nt вроде как работает правильно, только вот не нравятся мне проверка на классы окон, ибо может появиться вдруг еще какой-нибудь класс, который сломает сию добрую логику...

и одна вот эта часть проверки, кмк, так же написана неверно (скобок не хватает):
Код

...
(GetWindowLongPtr(Handle, GWL_EXSTYLE and WS_EX_TOPMOST) <> 0) or
...

Разве должно быть не так:
Код

((GetWindowLongPtr(Handle, GWL_EXSTYLE) and WS_EX_TOPMOST) <> 0) or


Автор: MetalFan 13.1.2012, 17:14
В общем модифицировал функцию от Qu1nt и все заработало ПОЧТИ как надо:
Код

function EnumProc(Handle: HWND; LResult: LPARAM): Boolean; stdcall;
begin
  PInteger(LResult)^:= Handle;
  Result := not IsWindowEnabled(Handle) or
            not IsWindowVisible(Handle) or
            ( GetWindow(Handle, GW_OWNER) <> 0 ) or
            ( GetWindowLongPtr(Handle, GWL_EXSTYLE) and WS_EX_TOPMOST <> 0 );
end;

function GetForeground: THandle;
begin
  EnumWindows(@EnumProc, NativeInt(@Result));
end;

Код

  if WindowState = wsMinimized then
  begin
    WindowState := wsNormal;
    Application.Restore;
    Show;
    SetForegroundWindow(Handle);
  end
  else
  begin
    if GetForeground = Self.Handle then
    begin
      Application.Minimize;
      Hide;
    end
    else
      SetForegroundWindow(Handle);
  end;


Это Почти заключается в небольшом отличии от поведения ранееупомянутой мыши.
Наше окно всегда считается foreground, даже если сейчас активно другое окно с ExStyle = WS_EX_TOPMOST...
С одной стороны это наверное правильно... Замена проверки ExStyle на WS_EX_TOOLWINDOW привело к тому, что если на экране есть одно SOT-окно, то наше окно вообще перестает сворачиваться (такое же поведение и у uTorrent, кстати).
Как же в бате сделали разработчики, чтобы все работало как надо красиво... загадка.

Автор: Qu1nt 13.1.2012, 17:15
Ой, не там скобку убрал:
Код

(GetWindowLongPtr(Handle, GWL_EXSTYLE) and WS_EX_TOPMOST <> 0) 

Подход основывался на:
http://stackoverflow.com/questions/210504/enumerate-windows-like-alt-tab-does
http://code.google.com/p/k-kovalev-personal/source/browse/trunk/HotKeying/HotKeying/Window/WindowDataProvider.cs

Автор: Qu1nt 13.1.2012, 17:31
MetalFan, уточни какого поведения ты хочешь добиться.

Автор: MetalFan 13.1.2012, 18:41
Qu1nt, поведение я ж вроде описывал.
Необходимо момент нажатия на иконку в трее:
1. Если окно свернуто, то развернуть его.
2. Если окно приложения активно в данный момент, то свернуть его "в трей".
3. Если окно развернуто из трея, но не активно в данный момент, то сделать его активным.

в п.3 как раз и загвоздка. Как корректно определить, активно ли окно в момент нажатия на иконку в трее.
Самый приближенный к "правде" вариант - последний приведенный мною на основании твоего кода.

Автор: Qu1nt 13.1.2012, 20:53
Вот еще вариант:
Код

function EnumProc(Handle: HWND; LParam: LPARAM): BOOL; stdcall;
begin
  PHandle(LParam)^ := Handle;
  Result := (GetParent(Handle) <> 0) or
    (GetWindowLongPtr(Handle, GWL_EXSTYLE) and WS_EX_TOPMOST <> 0);
end;

function GetForeground: THandle;
begin
  EnumWindows(@EnumProc, LPARAM(@Result));
end;

Разницы между поведением этого кода и The Bat! 5 не увидел. 

Автор: CodeMonkey 14.1.2012, 05:36
В описании EnumWindows не сказано, в каком порядке она обходит окна.

Автор: Чучмек 14.1.2012, 09:34
Код

keybd_event(VK_MENU,0,0,0);
keybd_event(VK_ESCAPE,0,0,0);
keybd_event(VK_ESCAPE,0,KEYEVENTF_KEYUP,0);
keybd_event(VK_MENU,0,KEYEVENTF_KEYUP,0);
application.ProcessMessages;
if form1.Active then ...

Автор: b8195108 14.1.2012, 14:28
Код

function isWindowFullVisible(AWnd: THandle): Boolean;
var
  R1, R2, R3: TRect;
begin
  GetWindowRect(AWnd, R1);
  GetWindowRect(GetDesktopWindow, R2);
  Result :=
    (IsWindowVisible(AWnd)) and (not IsIconic(AWnd)) and
    (R1.Left >= R2.Left) and (R1.Top >= R2.Top) and
    (R1.Right <= R2.Right) and (R1.Bottom <= R2.Bottom);
  while (AWnd <> 0) and (Result) do
    begin
      AWnd := GetNextWindow(AWnd, GW_HWNDPREV);
      if (IsWindowVisible(AWnd)) and (not IsIconic(AWnd)) then
        begin
          GetWindowRect(AWnd, R2);
          if IntersectRect(R3, R2, R1) then
            Result := False;
        end;
  end;
end;

Автор: MetalFan 14.1.2012, 14:41
CodeMonkey, действительно, не сказано... но вроде бы работает. Может лучше конечно на GetNextWindow переделать...

Чучмек, тоже какой-то колхоз.

b8195108, не катит, т.к.
Цитата(MetalFan @  13.1.2012,  15:46 Найти цитируемый пост)
все способы, определяющие частично перекрытие окна не катят на мой взгляд. т.к. неправильно сработают в след.ситуации: наше окно открыто не на весь экран и видно полностью, рядом АКТИВНО окно другого приложения так же не на весь экран и не перекрывающее наше окно. В этом случае по логике с перекрытиями наше окно считается поверх всех, т.к. видно полностью, что на мой взгляд неправильно.





Автор: b8195108 14.1.2012, 15:19
MetalFan, сорри, пропустил.
зы имхо не совсем правильная логика)

Автор: MetalFan 16.1.2012, 12:17
Qu1nt, отличия есть. если перейти с нашего окна к SOT-окну, то при клике по иконке в трее наша программа сворачивается, хотя хотелось бы, чтобы она активировалась.

Автор: Чучмек 17.1.2012, 00:39
Вот как выглядит функция обратного вызова для  EnumWindows у uTorrent
Код

004b5583 sub_4b5583:                      ; function entry point
004b5583   push    $fffffff0
004b5585   push    dword ptr [esp+8]
004b5589   call    dword ptr [$4f178c]    ; GetWindowLongW (USER32.dll)
004b5589
004b558f   test    eax, $10000000
004b5594   jnz     loc_4b559b
004b5594
004b5596   xor     eax, eax
004b5598   inc     eax
004b5599   jmp     loc_4b55c0
004b5599
004b5599 ; ---------------------------------------------------------
004b5599
004b559b loc_4b559b:
004b559b   mov     eax, [esp+8]
004b559f   inc     dword ptr [eax]
004b55a1   push    esi
004b55a2   push    0
004b55a4   push    dword ptr [esp+$c]
004b55a8   call    dword ptr [$4f1828]    ; GetWindowThreadProcessId (USER32.dll)
004b55a8
004b55ae   mov     esi, eax
004b55b0   call    dword ptr [$4f1198]    ; GetCurrentThreadId (kernel32.dll)
004b55b0
004b55b6   xor     ecx, ecx
004b55b8   cmp     esi, eax
004b55ba   setnz   cl
004b55bd   pop     esi
004b55be   mov     eax, ecx
004b55bd
004b55c0 loc_4b55c0:
004b55c0   ret     8

Код

function cb(hwnd:cardinal;var lparam:cardinal):bool;stdcall;
begin
result:=true;
if WS_VISIBLE=WS_VISIBLE and GetWindowLong(hwnd,GWL_STYLE)then
 begin
 inc(lparam);
 if GetCurrentThreadId=GetWindowThreadProcessId(hwnd,nil) then result:=false;
 end;
end;

А вот сам вызов EnumWindows
 
Код

004b55c3 sub_4b55c3:                      ; function entry point
004b55c3   push    ebp
004b55c4   mov     ebp, esp
004b55c6   push    ecx
004b55c7   and     dword ptr [ebp-4], 0
004b55cb   lea     eax, [ebp-4]
004b55ce   push    eax
004b55cf   push    $4b5583
004b55d4   call    dword ptr [$4f16d8]    ; EnumWindows (USER32.dll)
004b55d4
004b55da   xor     eax, eax
004b55dc   cmp     dword ptr [ebp-4], 2
004b55e0   setle   al
004b55e3   leave
004b55e4   ret


Код

....
var1:=0;
result:=false;
EnumWindows(@cb,integer(@var1));
if var1<=2 then result:=true;

Автор: MetalFan 17.1.2012, 09:26
Чучмек, ну во первых, uTorrent работает не совсем корректно в случае, когда на экране есть SOT-окно - он перестает сворачиваться в трей вообще. ну и во вторых, спасибо конечно за реверс-инжиниринг, но ты хоть проверил, что у тебя получилось то?)

Автор: Чучмек 17.1.2012, 13:11
Цитата(MetalFan @  17.1.2012,  09:26 Найти цитируемый пост)
но ты хоть проверил, что у тебя получилось то

Вот теперь проверил. Работает.
Код

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure ni(var msg:tmessage);message wm_user+1;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
function cb(hwnd:cardinal;var lparam:cardinal):bool;stdcall;
begin
result:=true;
if WS_VISIBLE=WS_VISIBLE and GetWindowLong(hwnd,GWL_STYLE)then
 begin
 inc(lparam);
 if GetCurrentThreadId=GetWindowThreadProcessId(hwnd,nil) then result:=false;
 end;
end;

procedure TForm1.ni(var msg:tmessage);
var var1 :cardinal;
begin
if msg.LParam=messages.WM_LBUTTONDBLCLK then
 begin
 var1:=0;
 EnumWindows(@cb,integer(@var1));
 if var1<=2 then form1.Visible:=false else
   begin
   form1.Visible:=true;
   setForegroundWindow(form1.Handle);
   end;
 end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var nd:TNotifyIconDataA ;
begin
nd.cbSize:=sizeof(nd);
nd.Wnd:=form1.Handle;
nd.uCallbackMessage:=wm_user+1;
nd.hIcon:=application.Icon.Handle;
nd.szTip:='Project1';
nd.uFlags:=NIF_TIP or NIF_ICON or NIF_MESSAGE;
Shell_NotifyIcon(NIM_ADD,@nd);
end;

end.


Автор: MetalFan 17.1.2012, 13:34
Чучмек, опять же работает без учета текущего активного topmost окна... т.е. так же, как и мой последний вариант.
Но как вариант сойдет.
Я, кстати, остановился на варианте с GetNextWindow... т.к. действительно не стоит полагаться, что enumwindows обходит и будет обходить окна именно в порядке от верхнего к нижнему...

Автор: Чучмек 17.1.2012, 13:47
Это уже не ко мне. Это к создателям uTorrent.
Там, где ты про колхоз писал... Суть в том, чтобы после клика в трее "попросить" систему восстановить порядок окон.

Автор: MetalFan 17.1.2012, 13:51
Чучмек, да это понятно) просто uTorrent, как оказалось, не очень удачным примером оказался.
А про колхоз - ну посылать сочетания клавиш для того, чтобы "попросить" систему о чем-то, имхо, не совсем правильно...

Но все равно спасибо за активную помощь в поиске решения)  smile 

Автор: Чучмек 17.1.2012, 13:54
Цитата(MetalFan @  17.1.2012,  13:51 Найти цитируемый пост)
не очень удачным

А какой удачный?

Автор: MetalFan 17.1.2012, 14:10
Чучмек, идеальным, на мой взгляд, считаю поведение TheBat.

Автор: Qu1nt 17.1.2012, 15:29
MetalFan, очередная итерация.
Код

procedure TForm1.ApplicationEvents1Deactivate(Sender: TObject);
begin
  FToTray := not GetWindowLongPtr(GetForegroundWindow, GWL_EXSTYLE) and
    (WS_EX_TOPMOST or WS_EX_TOOLWINDOW) = 0;
end;

procedure TForm1.TrayIcon1Click(Sender: TObject);
begin
  if WindowState = wsMinimized then
  begin
    WindowState := wsNormal;
    Application.Restore;
    Show;
    Application.BringToFront;
  end
  else
    if FToTray then
    begin
      Application.Minimize;
      Hide;
    end
    else
      Application.BringToFront;
end;

Всё короче и короче smile

Автор: Чучмек 18.1.2012, 14:10
Цитата(MetalFan @  17.1.2012,  14:10 Найти цитируемый пост)
TheBat

The Bat! написан на delphi или на чем-то подобном, а delphi компилит ###код smile 
Но из того что удалось понять следует следующее:
Сообщение трея $0401
Сообщения трея принимает не основная форма. 
В случае когда  основное окно The Bat есть foregroun:
   По сообщению основного окна WM_ACTIVATE/WA_INACTIVE запоминается текущее время.
   Если клик в трее происходит через "менее чем ...", окно сворачивается в трей. 


Добавлю:
TheBat время получает через GetTickCount
менее чем - это 500 тиков.

Автор: MetalFan 18.1.2012, 14:29
Чучмек, я так и предполагал... одна из мыслей была как раз по определению некой временной дельты между потерей активности приложения и кликом по трею...
Теперь за дело говорю - спасибо!

Автор: Qu1nt 18.1.2012, 15:00
MetalFan, чем мой последний вариант не устроил?

Автор: CodeMonkey 18.1.2012, 20:20
Цитата
В случае когда  основное окно The Bat есть foregroun:
   По сообщению основного окна WM_ACTIVATE/WA_INACTIVE запоминается текущее время.
   Если клик в трее происходит через "менее чем ...", окно сворачивается в трей. 


Эге, я же http://forum.vingrad.ru/index.php?showtopic=328395&view=findpost&p=2448208.

Автор: kuzduk 13.9.2012, 12:22
Эгегей! Я знаю как просто и красиво решить данную проблему, когда при нажатии на трей-иконку надо определить активна ли была наша программа или нет!


Моё решение такое:
Код


type
  TForm1 = class(TForm)
   
   public
   procedure TrayClick(var msg: TMessage); message WM_MYICONNOTIFY; //перехватываем события трей-иконки

  end;


var
  _WindowActiv: Boolean = False;








implementation



procedure TForm1.TrayClick(var msg: TMessage);
begin

case msg.LParam
of    

  
  WM_MOUSEMOVE://событие когда навели курсор на трей-иконку нашей программы 
  begin
    //Запоминаем во внешнюю переменную активно ли окно нашей программы
    if (Application.Handle = GetForegroundWindow) or (Self.Handle = GetForegroundWindow)
    then _WindowActiv := True
    else _WindowActiv := False;
  end;


  WM_LBUTTONDOWN://событие когда кликнули ПКМ по трей-иконке нашей программы
  begin
          if IsIconic(Application.Handle) //свёрнута ли форма
//
          then//свёрнуто:
          begin
              Application.Restore;
              SetForegroundWindow(Application.Handle); //Вывести окно на передний план
              ShowNeedIcon(IconOnRestore.ItemIndex);
          end

          else//НЕ свёрнуто:
          begin
//              ВНИМАНИЕ!!! Self.Handle <> Application.Handle
//              if GetWindow(Self.Handle, GW_HWNDFIRST) = Self.Handle ...
//              if GetForegroundWindow = Self.Handle ...
//              if GetActiveWindow = Self.Handle ...
//              if GetLastActivePopup(GetDesktopWindow) = Self.Handle ...



              //Гемморой в том, что после клика по трей-иконке активной становится панель задач,
              //следовательно надо найти предыдущее активное окно,
              //если было активно окно нашей программы => свернуть программу
              //если было активно окно НЕ нашей программы => вывести окно нашей программы на передний план
              
              if _WindowActiv = true

              then//окно активно => сворачиваем
              begin
//                ShowMessage('активно');

                Application.Minimize;
                ShowNeedIcon(2);
                _WindowActiv := False;
              end

              else//окно пассивно => на передний план
              begin
//                ShowMessage('пассивно');

                SetForegroundWindow(Application.Handle);
                _WindowActiv := True;
              end;
          end;
  end;

end;

end;





--------------------


через Z-последовательности определить ничего не получилось - куча посторонних окон...

также можно попробовать поиграть с GetLastActivePopup(GetDesktopWindow)


--------------------


всем рекомендую качать мой модуль KuTray для работы с иконкой в трее и панели задач: http://kuzduk.narod.ru/_tray.html  smile 


также смотрите другие мои полезные модули и программы: http://kuzduk.narod.ru/_tvor.html  smile 

Автор: Akella 3.2.2013, 00:29
Кто-нибудь сталкивался с тем, что в Windows 7 x64 значок TTrayIcon не исчезает из области уведомления, хотя программа закрывается корректно?
Используется D2007 и стандартная компонента TTrayIcon, но немного переделанная:

Код

//уже не помню для чего это нужно было
Type
  TCrackTrayIcon = class(TTrayIcon)
  protected
    procedure WindowProc(var Message: TMessage); override;
  end;

  TfmMain = class(TForm)
...
...


implementation
....
...

procedure TfmMain.FormCreate(Sender: TObject);
begin
//так создаем
  TrayIcon2 := TCrackTrayIcon.Create(Application);
  TrayIcon2.Hint := constAppName + '(' + constSiteName + ')';
  TrayIcon2.Visible := True;
  TrayIcon2.PopupMenu := pmTray;
  TrayIcon2.Icon := Self.Icon;
  TrayIcon2.OnClick := TrayIconClick;


Автор: gesper 3.2.2013, 03:52
А не переделаный так же все делает?

Автор: Akella 3.2.2013, 10:47
Всё равно остаётся

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)