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