Create your own Flip Task Bar with live thumbnails using Vista Desktop Window Manager DWM
The sample below uses Desktop Window Manager under Windows Vista with Aero to get dynamic live thumbnails of running applications. If a movie is playing in Windows Media Player, it will play in the thumbnail too!
EnumChildWindows or EnumWindows is used to enumerate all running windows created by the various processes on your machine.
I extracted code from: How does EventHandler work? IConnectionPoint! to make the CAsmLib class to generate assembly code. The sample below uses the class to generate ASM code that gets called by EnumChildWindows and calls _VFP.DoCmd method to insert the Window handles found into a cursor.
On both XP and Vista, the code will generate a cursor with Window Handles and their titles.
A SQL statement executed in a timer routine determines if any new processes were added and if so, to refresh the display.
SELECT hWnd,Title FROM NewTopLevel WHERE hWnd NOT in (SELECT hWnd FROM TopLevel) UNION ;
SELECT hWnd,Title FROM TopLevel WHERE hWnd NOT in (SELECT hWnd FROM NewTopLevel) ;
INTO CURSOR temp
If you’re running on Vista and DwmIsCompositionEnabled, the code will then create a form and an array of live thumbnails on it. The form uses an array of invisible commandbuttons for each thumbnail. Using an Image control without an image displays a cross. All we’re doing is providing an hWnd and a rect for DWM to draw the thumbnail.
(Vista Aero can have DwmIsCompositionEnabled enabled or disabled. See Control Panel->System and Maintenance->System->Advanced System Settings->Advanced->Performance->Settings->Visual Effects->Enable desktop composition)
GetWindowThreadProcessId is called to get the Process Id of the window handle.
Play a movie: make it repeat if it’s short, do things in other windows, and you’ll see the thumbnails update on the form. Hit Alt-Tab (or Alt-Ctrl-Tab to keep the flip display on, or even WindowsKey-Tab), and you’ll see the thumbnail still live. Use the slider to changes the size of the thumbnails. You can even make the thumbs larger than the source application!
Click on a thumbnail to make it the foreground application. Resize the thumbnail form. Modify source windows by editing text, etc. Hit WindowKey->M to minimize all windows.
CLEAR ALL
CLEAR
MODIFY COMMAND PROGRAM() NOWAIT
SET SAFETY OFF
PUBLIC oForm
oForm=CREATEOBJECT("CThumbForm")
#define WS_VISIBLE 0x10000000
#define WS_BORDER 0x00800000
DEFINE CLASS CThumbForm as Form
ShowWindow=2 && Top Level
width=SYSMETRIC(1) && entire width of display
height=100
MinButton=.f. && don't allow minimize for us
nThumbs=0 && number of thumbs currently on form
fDWM = .f. && are we running under Vista Desktop Window Management?
nThumbWidth=400 && size of thumb to draw
nThumbHeight=this.nThumbWidth * SYSMETRIC(2)/SYSMETRIC(1) && same aspect ratio as desktop
ADD OBJECT cmdQuit as CommandButton WITH Caption="\<Quit",cancel=.t.
ADD OBJECT cmdRefresh as CommandButton WITH Caption="\<Refresh",left=120
ADD OBJECT oSlider as cSlider WITH left=250
ADD OBJECT oTimer as Timer WITH interval=2000 && millisecs
PROCEDURE oTimer.Timer
thisform.GetHWnds("NewTopLevel")
SELECT hWnd,Title FROM NewTopLevel WHERE hWnd NOT in (SELECT hWnd FROM TopLevel) UNION ;
SELECT hWnd,Title FROM TopLevel WHERE hWnd NOT in (SELECT hWnd FROM NewTopLevel) ;
INTO CURSOR temp
IF _Tally>0 && a new window was created or destroyed
thisform.GetThumbNails() && ToDo: optimize for only the change
ENDIF
PROCEDURE cmdRefresh.Click
thisform.GetThumbNails()
PROCEDURE cmdQuit.Click
thisform.release
PROCEDURE Init
SET TALK OFF
IF VAL(OS(3))>=6 && runnning under Vista. Check for Desktop Compostion enabled
DECLARE integer DwmIsCompositionEnabled IN dwmapi integer @ dwEnabled
dwEnabled=0
IF DwmIsCompositionEnabled(@dwEnabled) = 0 AND dwEnabled>0
this.fDWM = .t.
ENDIF
ENDIF
IF this.fDWM
DECLARE integer DwmRegisterThumbnail IN dwmapi integer hwndDest, integer hwndSrc, integer @ nThumbnailId
DECLARE integer DwmUnregisterThumbnail IN dwmapi integer nThumbnailId
DECLARE integer DwmQueryThumbnailSourceSize IN dwmapi integer nThumbnailId, string @pSize
DECLARE integer DwmUpdateThumbnailProperties IN dwmapi integer hThumbnailId, string @ ptnProperties
DECLARE integer SetForegroundWindow IN WIN32API integer
DECLARE integer GetWindowPlacement IN WIN32API integer hWnd, string @ pPlacement
DECLARE integer SetWindowPlacement IN WIN32API integer hWnd, string @ pPlacement
this.Visible=1
* this.GetThumbNails() && Resize will call GetThumbNails
ELSE
NEWOBJECT("CEnumWindows") && call the class that creates the cursor of hWnds
LOCATE && go to the top of the cursor
BROWSE LAST NOWAIT
RETURN .f. && don't create form
ENDIF
PROCEDURE Resize
this.GetThumbNails()
PROCEDURE GetHWnds(DestCursor as string)
NEWOBJECT("CEnumWindows") && call the class that creates the cursor of hWnds
SELECT * FROM hwnds WHERE BITAND(style,WS_VISIBLE+WS_BORDER) = WS_VISIBLE+WS_BORDER AND ;
hWnd != thisform.HWnd ;
INTO CURSOR (DestCursor) && Only those hWnds which are visible and have a border
RETURN _tally
PROCEDURE GetThumbNails
LOCAL cStr,oLbl,cName,x,y,oi
thisform.LockScreen= .T.
FOR indx = 1 TO thisform.nThumbs
cName="im"+PADL(indx,3,"0") && im001, im002...
thisform.RemoveObject(cName)
IF TYPE("thisForm."+cName+"lbl")="O"
thisform.RemoveObject(cName+"lbl")
ENDIF
ENDFOR
thisform.nThumbs=this.GetHWnds("TopLevel")
INDEX on hwnd TAG hwnd
nRatio = thisform.oSlider.Value/thisform.oSlider.max
x = 0
y = 20
indx=1
SCAN
cName="im"+PADL(indx,3,"0") && im001, im002...
thisform.AddObject(cName,"CThumb")
cStr="Thisform."+cName
oi = EVALUATE(cStr)
oi.Height = this.nThumbHeight * nRatio
oi.Width = this.nThumbWidth * nRatio
oi.Left = x
oi.Top = y
oi.visible=1
oi.RegThumb(hWnd,ALLTRIM(title))
indx=indx+1
x = x + this.nThumbWidth* nRatio
IF x + this.nThumbWidth* nRatio > thisform.Width
x=0
y = y + this.nThumbHeight* nRatio+20
ENDIF
ENDSCAN
thisform.LockScreen= .f.
ENDDEFINE
DEFINE CLASS cSlider AS Olecontrol
OleClass="mscomctllib.slider.2"
PROCEDURE Init
this.min=1
this.max=100
this.value=INT(this.max/5)
this.SmallChange=INT(this.max/50)
this.LargeChange=INT(this.max/5)
PROCEDURE Change && when the slider value changes
thisform.GetThumbNails()
ENDDEFINE
#define GWL_STYLE 0xfffffff0
#define WS_MINIMIZE 0x20000000
#define SW_RESTORE 9
#define DWM_TNP_RECTDESTINATION 1
#define DWM_TNP_RECTSOURCE 2
#define DWM_TNP_OPACITY 4
#define DWM_TNP_VISIBLE 8
#define DWM_TNP_SOURCECLIENTAREAONLY 0x10
DEFINE CLASS CThumb as CommandButton
nThumbId = 0
style=1 && invisible
enabled=.t.
hWnd=0
PROCEDURE click && user clicked on thumbnail: let's activate it
IF BITAND(GetWindowLong(this.hWnd,GWL_STYLE), WS_MINIMIZE) > 0 && if window is minimized
pPlacement=BINTOC(11*4,"4rs") + SPACE(10*4) && 11 4 byte words
IF GetWindowPlacement(this.hWnd, @pPlacement) > 0
pPlacement = LEFT(pPlacement,2*4) + BINTOC(SW_RESTORE,"4rs")+SUBSTR(pPlacement,13) && Restore it
SetWindowPlacement(this.hWnd, @pPlacement)
ENDIF
ENDIF
SetForegroundWindow(this.hWnd)
PROCEDURE RegThumb(hWnd as Integer,cTitle as String)
LOCAL cStr,oLbl,cName
nResult=0
this.hWnd=hWnd
IF DwmRegisterThumbnail(thisform.hWnd, m.hWnd, @nResult ) = 0 AND nResult > 0
this.nThumbId = nResult
cName=this.Name+"lbl" && iml001002lbl
cStr="thisform."+cName
thisform.AddObject(cName,"Label")
oLbl=EVALUATE(cStr)
WITH oLbl as Label
.Top=this.Top+this.Height
.Left = this.Left
.Width = MAX(this.Width-10,0)
.Height = 20
.Caption=cTitle
.Visible=1
ENDWITH
* @this.Left,this.top+this.Height say cTitle
cStr=SPACE(8)
*!* ?"TSize",DwmQueryThumbnailSourceSize(nResult, @cStr) && gets the size of the Source window
*!* ?CTOBIN(LEFT(cStr,4),"4rs"),CTOBIN(RIGHT(cStr,4),"4rs")
dwFlags= DWM_TNP_RECTDESTINATION + DWM_TNP_OPACITY + DWM_TNP_VISIBLE
nOpacity = 255 && can make the thumbnails glass
fVisible= 1 && make the thumb visible?
fSourceClientAreaOnly = 0 && just client area of source thumbnail?
rDest= ;
BINTOC(this.Left,"4rs") + ;
BINTOC(this.Top,"4rs") + ;
BINTOC(this.Left + this.Width,"4rs") + ;
BINTOC(this.Top+this.Height,"4rs") && where to render the thumbnail
rSrc= ;
BINTOC(0,"4rs") + ;
BINTOC(0,"4rs") + ;
BINTOC(0,"4rs") + ;
BINTOC(0,"4rs") && rSrc: region of thumbnail to render. We'll use the whole src image
cProps = ;
BINTOC(dwFlags,"4rs") + ;
rDest + ;
rSrc + ;
CHR(nOpacity) + ;
BINTOC(fVisible,"4rs") + ;
BINTOC(fSourceClientAreaOnly,"4rs")
hr =DwmUpdateThumbnailProperties(nResult, @ cProps)
ENDIF
PROCEDURE destroy
IF this.nThumbId > 0
DwmUnregisterThumbnail(this.nThumbId)
ENDIF
ENDDEFINE
DEFINE CLASS CEnumWindows AS CAsmLib
PROCEDURE Init
DODEFAULT() && call parent class Init
DECLARE integer GetWindowText IN WIN32API integer, string @, integer
DECLARE integer GetWindowLong IN WIN32API integer, integer
DECLARE integer GetWindowThreadProcessId IN WIN32API integer hWnd, integer @ pid
CREATE cursor HWnds (hWnd i, title c(100), style i,pid n)
this.CreateEnumWindowCode("INSERT INTO HWnds (hWnd) VALUES (%d)") && use this cmd to insert a record into the cursor
SELECT hWnds && Now scan through the cursor and get the window titles and styles
SCAN
cText=SPACE(100)
nLen=GetWindowText(hWnd,@cText,LEN(cText)) && Get the title of the window
mPid=0
GetWindowThreadProcessId(hWnd,@mpid) && Get the window's ProcessID
REPLACE title WITH LEFT(cText,nLen) ,style WITH GetWindowLong(hWnd,GWL_STYLE),pid WITH mPid
ENDSCAN
PROTECTED PROCEDURE CreateEnumWindowCode(cCmd as String)
*This simple code doesn't need jumps, but included anyway for general usefulness for branching code
CREATE CURSOR jumps (cLabel c(20),cFlag c(1),sCodePos i) && cFlag="D" defined, "R", reference
INDEX on cLabel+cFlag TAG cLabel
nLocals=0x60 && enough space for local vars
sCode=""
sCode = sCode + CHR(0x55) && push ebp
sCode = sCode + CHR(0x8b) + CHR(0xec) && mov ebp, esp
sCode = sCode + CHR(0x81) + CHR(0xec)+BINTOC(nLocals * 4, "4rs") && sub esp, nLocals
*!* sCode = sCode + CHR(0x6a) + CHR(0x00) && push 0
*!* sCode = sCode + this.CallDllFunction("MessageBeep", "user32") && MessageBeep(0)
* sCode = sCode + CHR(0xcc) && int 3 DebugBreak() to attach a debugger
*swprintf(ebp-a0h, 0x30,cCmd,hWnd) && replace the "%d" with the hWnd: "INSERT INTO HWnds (hWnd) VALUES (%d)"
sCode = sCode + CHR(0x8b) + CHR(0x45) + CHR(0x08) && mov eax, [ebp+8] && get the hWnd
sCode = sCode + CHR(0x50) && push eax
sCode = sCode + CHR(0xb8) + BINTOC(this.MakeStr(cCmd,.t.),"4rs") && mov eax, str (Unicode)
sCode = sCode + CHR(0x50) && push eax
sCode = sCode + CHR(0x8d) + CHR(0x45)+CHR(0xa0) && lea eax, [ebp-a0h] && addr to put swprintf result
sCode = sCode + CHR(0x50) && push eax
sCode = sCode + this.CallDllFunction("swprintf", "msvcrt") && swprintf(ebp-a0h, 0x30,cCmd,hWnd)
sCode = sCode + CHR(0x83)+ CHR(0xc4)+ CHR(0xc) && add esp, 0ch pop 3*4 _cdecl args
*SysAllocString() This string for each window,so it must be freed with SysFreeString below
sCode = sCode + CHR(0x8d) + CHR(0x45)+CHR(0xa0) && lea eax, [ebp-a0h] && addr of swprintf result
sCode = sCode + CHR(0x50) && push eax
sCode = sCode + this.CallDllFunction("SysAllocString", "oleaut32") && SysAllocString
*_vfp.DoCmd()
sCode = sCode + CHR(0x89) + CHR(0x45) + CHR(0xf0) && mov [ebp-10h], eax ; save the bstr so we can free it
sCode = sCode + CHR(0x50) && push eax
sCode = sCode + CHR(0xb8) + BINTOC(SYS(3095,_vfp),"4rs") && mov eax, the IDispatch for _VFP
sCode = sCode + CHR(0x50) && push eax && the THIS pointer(_vfp)
sCode = sCode + CHR(0x8b) + CHR(0) && mov eax, [eax] && get the vTable
sCode = sCode + CHR(0x05) + BINTOC(0x84,"4rs") && add eax, 84h the function at 84h in the vTable, which is DoCmd
sCode = sCode + CHR(0xff) + CHR(0x10)&& call [eax] && call indirect
sCode = sCode + CHR(0x83) + CHR(0xf8) + CHR(0x00) && cmp eax, 0 && if hr = SUCCESS
* jne FailedDoCmd
sCode = sCode + CHR(0x75)+CHR(0x00) && jne nBytes && nBytes calc'd below. je= 0x74, Jump if Equal, jne = 0x75
INSERT INTO jumps values ("FailedDoCmd","R",LEN(sCode)) && refer to a label to jump to at this pos
*else { //FailedDoCmd
INSERT INTO jumps values ("FailedDoCmd","D",LEN(sCode)) && define a label at this pos
* now free the bstr
sCode = sCode + CHR(0x8b) + CHR(0x45) + CHR(0xf0) && mov eax, [ebp-10h]
sCode = sCode + CHR(0x50) && push eax
sCode = sCode + this.CallDllFunction("SysFreeString", "oleaut32") && vswprintf(ebp-a0h, 0x30,cCmd,hWnd)
sCode = sCode + CHR(0xb8) + BINTOC(1,"4rs") && mov eax, 1 && return 1 so Enum continues
* sCode = sCode + CHR(0x33) + CHR(0xc0) && xor eax,eax && make return value 0 so won't enum any more windows
sCode = sCode + CHR(0x8b) + CHR(0xe5) && mov esp, ebp
sCode = sCode + CHR(0x5d) && pop ebp
sCode = sCode + CHR(0xc2)+CHR(0x08)+CHR(0x00) && ret 8 && EnumChildProc has 2 parms: pop 2 args=8 bytes
USE jumps AGAIN IN 0 ORDER 1 ALIAS jumpdefs
SCAN FOR cFlag="R" && look for all references
=SEEK(jumps.cLabel+"D","jumpdefs")
sCode=LEFT(sCode,jumps.sCodePos-1)+CHR(jumpdefs.sCodePos - jumps.sCodePos) + SUBSTR(sCode,jumps.sCodePos+1) && now fix up the jump location to jump to the definition
ENDSCAN
AdrCode=this.memAlloc(LEN(sCode),sCode) && allocate memory for the code
EnumChildWindows(0,AdrCode,0) && EnumChildWindows needs a callback function. We'll give it our code.Added benefit: Win32 Exception handling of Declare dll
USE IN jumpdefs
USE IN jumps
ENDDEFINE
DEFINE CLASS CAsmLib as Custom && utility to help generate ASM code
hProcHeap =0
PROCEDURE Init
SET ASSERTS ON
DECLARE integer LoadLibrary IN WIN32API string
DECLARE integer FreeLibrary IN WIN32API integer
DECLARE integer GetProcAddress IN WIN32API integer hModule, string procname
DECLARE integer GetProcessHeap IN WIN32API
DECLARE integer HeapAlloc IN WIN32API integer hHeap, integer dwFlags, integer dwBytes
DECLARE integer HeapFree IN WIN32API integer hHeap, integer dwFlags, integer lpMem
DECLARE integer CLSIDFromString IN ole32 string lpszProgID, string @ strClSID
DECLARE integer SysAllocString IN oleaut32 string wstr
DECLARE integer SysFreeString IN oleaut32 integer bstr
DECLARE integer EnumChildWindows IN WIN32API integer hWnd, integer lpEnumProc, integer lParam
CREATE CURSOR memAllocs (memPtr i, AllocType c(1)) && track mem allocs that need to be freed: H=Heap,B=BSTR,L=Library
this.hProcHeap = GetProcessHeap()
PROCEDURE MemAlloc(nSize as Integer, cStr as String) as Integer
LOCAL nAddr
nAddr = HeapAlloc(this.hProcHeap, 0, nSize) && allocate memory
ASSERT nAddr != 0 MESSAGE "Out of memory"
INSERT INTO memAllocs VALUES (nAddr,"H") && track them for freeing later
SYS(2600,nAddr, LEN(cStr),cStr) && copy the string into the mem
RETURN nAddr
PROCEDURE CallDllFunction(strExport as String, strDllName as String) as String
*Create a string of machine code that calls a function in a DLL. Parms should already be pushed
LOCAL nAddr as Integer, hModule as Integer
hModule = LoadLibrary(strDllName)
INSERT INTO memAllocs VALUES (hModule,"L") && track loads for freeing later
nAddr=GetProcAddress(hModule,strExport)
ASSERT nAddr != 0 MESSAGE "Error: Export not found "+ strExport+" "+ strDllName
RETURN CHR(0xb8)+BINTOC(nAddr,"4rs") + CHR(0xff) + CHR(0xd0) && mov eax, addr; call eax
PROCEDURE MakeStr(str as String, fConvertToUnicode as Logical, fMakeBstr as Logical) as Integer
* converts a string into a memory allocation and returns a pointer
LOCAL nRetval as Integer
IF fConvertToUnicode
str=STRCONV(str+CHR(0),5)
ELSE
str = str + CHR(0) && null terminate
ENDIF
IF fMakeBstr
nRetval= SysAllocString(str)
ASSERT nRetval != 0 MESSAGE "Out of memory"
INSERT INTO memAllocs VALUES (nRetval,"B") && track them for freeing later
ELSE
nRetval= this.MemAlloc(LEN(str),str)
ENDIF
RETURN nRetval
PROCEDURE Destroy
LOCAL i,nSel
nSel=SELECT()
SELECT memAllocs
SCAN
DO CASE
CASE AllocType="B" && BSTR
SysFreeString(memPtr)
CASE AllocType="H" && Heap
HeapFree(this.hProcHeap,0,memPtr)
CASE AllocType="L" && LoadLibrary
FreeLibrary(memPtr)
ENDCASE
ENDSCAN
USE
SELECT (nSel)
ENDDEFINE
End Of Code
Comments
Anonymous
May 09, 2007
I received a question regarding some of my recent posts on Vista : Forgive my ignorance, but where isAnonymous
May 22, 2007
In this post: Create your own Flip Task Bar with live thumbnails using Vista Desktop Window Manager DWMAnonymous
August 06, 2007
You can use CreateToolhelp32Snapshot and its family of functions to enumerate the running processes onAnonymous
August 10, 2007
Here are some of the links that I often visit regarding VFP9 SP2 and Sedna. The official Microsoft Visual...