1 (изменено: DD, 2011-12-30 03:14:20)

Тема: AHK: Свёртка приложений в трей

Источник и описание.

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

#SingleInstance Force

;установка иконки скрипта в трее с изображением окошек
Menu, Tray, Icon, %A_WinDir%\system32\Shell32.dll, 99
;максимальное количество окон, которое может быть скрыто
mwt_MaxWindows = 30
;горячая клавиша, скрывающая текущее окно
mwt_Hotkey = #h ;Win+H
;горячая клавиша, восстанавливающая все скрытые окна
mwt_HotkeyRestoreAll = ^!r ;Ctrl+Alt+R
;эта установка убирает "системные" пункты меню иконки скрипта в трее;
;если эти пункты нужны, задайте "Y" вместо "N"
mwt_StandardMenu = N
;следующие настройки позволяют избежать потребности отпускать и
;нажимать снова модификатор горячей клавиши всякий раз,
;когда вы хотите быстро скрыть более, чем одно окно
#HotkeyModifierTimeout 100
SetWinDelay 10
SetKeyDelay 0
;может быть запущено не более одного экземпляра сценария
#SingleInstance
;работать со скрытыми окнами
DetectHiddenWindows, On
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;назначение горячей клавиши, скрывающей текущее окно
Hotkey, %mwt_Hotkey%, mwt_Minimize
;назначение горячей клавиши, восстанавливающей все скрытые окна
Hotkey, %mwt_HotkeyRestoreAll%, mwt_RestoreAll
;при завершении сценария необходимо отобразить все скрытые окна, если таковые есть
OnExit, mwt_RestoreAllThenExit
;настройка меню иконки скрипта в трее
if mwt_StandardMenu = Y
	Menu, Tray, Add
else
{
	Menu, Tray, NoStandard
	Menu, Tray, Add, Выход и восстановление всех скрытых окон, mwt_RestoreAllThenExit
}
Menu, Tray, Add, Восстановление всех скрытых окон, mwt_RestoreAll
Menu, Tray, Add
;ограничение ширины меню
if a_AhkVersion = ;пусто (для версий ранее 1.0.22)
	mwt_MaxLength = 100
else
	mwt_MaxLength = 260
return ;конец секции авто-выполнения скрипта
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;отсюда начинается исполнение, когда нажата горячая клавиша, скрывающая текущее окно
mwt_Minimize:
if mwt_WindowCount >= %mwt_MaxWindows%
{
	MsgBox Не может быть скрыто одновременно больше, чем %mwt_MaxWindows% окон.
	return
}
;получаем активное окно и используем его как "последнее найденное окно",
;т.е. окно по умолчанию для всех дальнейших вызовов функций, связанных с окнами
WinWait, A,, 2
if ErrorLevel <> 0
	return
;получаем информацию об активном окне
WinGet, mwt_ActiveID, ID ;хэндл (HWND) окна
WinGetTitle, mwt_ActiveTitle ;заголовок окна
WinGetClass, mwt_ActiveClass ;имя класса окна
WinGet, mwt_ActivePID, PID ;идентификатор процесса, которому принадлежит окно
WinGet, mwt_List, List, ahk_pid %mwt_ActivePID% ;список всех окон этого процесса
WinGet, mwt_ActiveProcess, ProcessName ;имя процесса, которому принадлежит окно
;рабочий стол и панель задач скрывать нельзя
if mwt_ActiveClass in Shell_TrayWnd,Progman
	return
;поскольку скрытие окна не будет деактивировать его, переключимся
;в другое окно (как будто пользователь нажал Alt+Escape)
Send, !{esc}
;собственно скрываем нужные окна процесса (делаем их невидимыми),
;скрытию подлежат все окна текущего процесса со стилем WS_VISIBLE,
;mwt_WindowsIDs - список хэндлов скрываемых окон,
;разделённых символом | (вертикальная черта)
mwt_WindowsIDs = ""
;проводник - особый случай, скрывать можно только текущее окно процесса
If mwt_ActiveProcess = explorer.exe
{
	WinHide, ahk_id %mwt_ActiveID%
	mwt_WindowsIDs = %mwt_WindowsIDs%|%mwt_ActiveID%
}
;скрываем все видимые окна процесса
Else
{
	Loop, %mwt_List%
	{
		mwt_id := mwt_List%A_Index%
		WinGet, style, Style, ahk_id %mwt_id%
		if ( style & 0x10000000 ) ;WS_VISIBLE
		{
			WinHide, ahk_id %mwt_id%
			;Result := DllCall("ShowWindow", "UInt", mwt_id, "Int", "0")
			mwt_WindowsIDs = %mwt_WindowsIDs%|%mwt_id%
		}
	}
}
;если заголовок окна пуст, используем имя класса,
;т.к. нам нужен непустой заголовок пункта меню
if mwt_ActiveTitle =
	mwt_ActiveTitle = ahk_class %mwt_ActiveClass%
;обрезаем заголовок окна до максимальной длины mwt_MaxLength
StringLeft, mwt_ActiveTitle, mwt_ActiveTitle, %mwt_MaxLength%
;обеспечение уникальности заголовка пункта меню:
Loop, %mwt_MaxWindows%
{
	if mwt_WindowTitle%a_index% = %mwt_ActiveTitle%
	{
		;Нашли такой же заголовок, так что заголовок не уникален.
		StringTrimLeft, mwt_ActiveIDShort, mwt_ActiveID, 2
		StringLen, mwt_ActiveIDShortLength, mwt_ActiveIDShort
		StringLen, mwt_ActiveTitleLength, mwt_ActiveTitle
		mwt_ActiveTitleLength += %mwt_ActiveIDShortLength%
		mwt_ActiveTitleLength += 1
		if mwt_ActiveTitleLength > %mwt_MaxLength%
		{
			TrimCount = %mwt_ActiveTitleLength%
			TrimCount -= %mwt_MaxLength%
			StringTrimRight, mwt_ActiveTitle, mwt_ActiveTitle, %TrimCount%
		}
		;создаём уникальный заголовок:
		mwt_ActiveTitle = %mwt_ActiveTitle% %mwt_ActiveIDShort%
		break
	}
}
;на всякий случай проверим, нет ли этого окна уже в списке ранее скрытых
mwt_AlreadyExists = n
Loop, %mwt_MaxWindows%
{
	if mwt_WindowID%a_index% = %mwt_ActiveID%
	{
		mwt_AlreadyExists = y
		break
	}
}
;добавим окно в массив и в меню
if mwt_AlreadyExists = n
{
	Menu, Tray, add, %mwt_ActiveTitle%, RestoreFromTrayMenu
	mwt_WindowCount += 1
	Loop, %mwt_MaxWindows% ;поиск свободного элемента в массиве
	{
		if mwt_WindowID%a_index% = ;элемент свободен
		{
			mwt_WindowID%a_index% = %mwt_ActiveID%
			mwt_WindowTitle%a_index% = %mwt_ActiveTitle%
			mwt_WindowPID%a_index% = %mwt_ActivePID%
			mwt_WinIDs%a_index% = %mwt_WindowsIDs%
			break
		}
	}
}
return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;отсюда начинается исполнение, когда вызвана команда меню иконки скрипта в трее,
;соответствующая конкретному скрытому окну
RestoreFromTrayMenu:
Menu, Tray, delete, %A_ThisMenuItem%
;поиск окна по его уникальному заголовку, сохраненному как название пункта меню
Loop, %mwt_MaxWindows%
{
	if mwt_WindowTitle%a_index% = %A_ThisMenuItem%
	{
		StringTrimRight, IDToRestore, mwt_WindowID%a_index%, 0
		StringTrimRight, PIDToRestore, mwt_WindowPID%a_index%, 0
		mwt_WindowsIDs := mwt_WinIDs%a_index%
		Loop, parse, mwt_WindowsIDs, |
			WinShow, ahk_id %A_LoopField%
		WinActivate ahk_id %IDToRestore%
		mwt_WindowID%a_index% = ;очищаем элементы массивов
		mwt_WindowTitle%a_index% =
		mwt_WindowPID%a_index% =
		mwt_WinIDs%a_index% =
		mwt_WindowCount -= 1
		break
	}
}
return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;отсюда начинается исполнение, когда вызвана команда меню иконки скрипта в трее
;"Выход и восстановление всех скрытых окон"
mwt_RestoreAllThenExit:
Gosub, mwt_RestoreAll
ExitApp ;завершение работы скрипта
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;отсюда начинается исполнение, когда вызвана команда меню иконки скрипта в трее
;"Восстановление всех скрытых окон"
mwt_RestoreAll:
Loop, %mwt_MaxWindows%
{
	if mwt_WindowID%a_index% <>
	{
		StringTrimRight, IDToRestore, mwt_WindowID%a_index%, 0
		StringTrimRight, PIDToRestore, mwt_WindowPID%a_index%, 0
		mwt_WindowsIDs := mwt_WinIDs%a_index%
		Loop, parse, mwt_WindowsIDs, |
			WinShow, ahk_id %A_LoopField%
		WinActivate ahk_id %IDToRestore%
		StringTrimRight, MenuToRemove, mwt_WindowTitle%a_index%, 0
		Menu, Tray, delete, %MenuToRemove%
		mwt_WindowID%a_index% =
		mwt_WindowTitle%a_index% =
		mwt_WindowPID%a_index% =
		mwt_WinIDs%a_index% =
		mwt_WindowCount -= 1
	}
	if mwt_WindowCount = 0
		break
}
return

2

Re: AHK: Свёртка приложений в трей

#HotkeyModifierTimeout, 100
DetectHiddenWindows, On
SetWinDelay, 10
SetKeyDelay, 0
mwt_MaxWindows:=30, mwt_MaxLength:=36
HiddenIDs:=[], MenuItems:=[]
Hotkey, #vk48, mwt_Minimize
Hotkey, ^!vk52, mwt_RestoreAll
Menu, Tray, Icon, % A_WinDir "\system32\Shell32.dll", 99
Menu, Tray, NoStandard
Menu, Tray, Tip, % "Win+H - hide current window"
               . "`nCtrl+Alt+R - restore all windows"
Menu, Tray, Add, &Terminate, mwt_RestoreAllThenExit
Menu, Tray, Add, &Restore all, mwt_RestoreAll
Menu, Tray, Add
OnExit, mwt_RestoreAllThenExit
Return

mwt_Minimize:
   If GetCount()>=mwt_MaxWindows
   {
      MsgBox, 4160,, % "Не может быть скрыто одновременно"
                    . " больше, чем "mwt_MaxWindows " окон.", 1
      Return
   }
   WinWait, A,, 2
   If ErrorLevel
      Return
   WinGet, mwt_ActiveID, ID
   WinGetTitle, mwt_ActiveTitle
   WinGetClass, mwt_ActiveClass
   WinGet, mwt_ActivePID, PID
   WinGet, mwt_List, List, % "ahk_pid"mwt_ActivePID
   WinGet, mwt_ActiveProcess, ProcessName
   If !mwt_ActiveTitle
      mwt_ActiveTitle:=mwt_ActiveProcess
   If mwt_ActiveClass~="WorkerW|Shell_TrayWnd|Progman"
      Return
   Send, !{Esc}
   If StrLen(mwt_ActiveTitle)>mwt_MaxLength
      mwt_ActiveTitle:=SubStr(mwt_ActiveTitle, 1, mwt_MaxLength)"..."
   For Key, Value In MenuItems
   {
      If % Value=mwt_ActiveTitle
         mwt_ActiveTitle:=mwt_ActiveTitle
                        . " ("GetCountWithTitle(mwt_ActiveTitle)+1 ")"
   }
   If % mwt_ActiveProcess="explorer.exe"
   {
      WinHide, % "ahk_id"mwt_ActiveID
      HiddenIDs.Insert(mwt_ActiveTitle, mwt_ActiveID)
   }
   Else
   {
      Loop, % mwt_List
      {
         mwt_id:=mwt_List%A_Index%
         WinGet, WStyle, Style, % "ahk_id"mwt_id
         If (WStyle & 0x10000000) ; WS_VISIBLE
         {
            WinHide, % "ahk_id"mwt_id
            HiddenIDs.Insert(mwt_ActiveTitle
                            . (A_Index=1 ? "":A_Index), mwt_id)
         }
      }
   }
   Menu, Tray, Add, % mwt_ActiveTitle, RestoreFromTrayMenu
   MenuItems.Insert(mwt_ActiveTitle, mwt_ActiveTitle)
   Return

RestoreFromTrayMenu:
   Menu, Tray, Delete, % MenuItem:=A_ThisMenuItem
   For Key, Value In HiddenIDs
   {
      If % Key=MenuItem
      {
         WinShow, % "ahk_id"Value
         WinActivate, % "ahk_id"Value
         HiddenIDs[Key]:="", MenuItems[Key]:=""
      }
      Else If InStr(Key, MenuItem)=1
      {
         WinShow, % "ahk_id"Value
         HiddenIDs[Key]:=""
      }
   }
   Return

mwt_RestoreAll:
   For Key, Value In HiddenIDs
      If % Value!=""
         WinShow, % "ahk_id"Value
   HiddenIDs:=[]
   For Key, Value In MenuItems
      If % Value!=""
         Menu, Tray, Delete, % Value
   MenuItems:=[]
   Return

mwt_RestoreAllThenExit:
   Gosub, mwt_RestoreAll
   ExitApp

GetCountWithTitle(Title)
{
   global MenuItems
   For Key, Value In MenuItems
      If InStr(Value, Title)=1
         Counter++
   Return, Counter
}

GetCount()
{
   global MenuItems
   For Key, Value In MenuItems
      Counter++
   Return, Counter
}

3

Re: AHK: Свёртка приложений в трей

Это славно, Grey!