Skip to main content
Email Password   helpLost your password?

Introduction

This article demonstrates yet another effective use of the WindowFinder utility that I presented in my CodeProject article early in January this year

It also incorporates the screen capture routine presented by Joseph M. Newcomer in his CodeProject article

The demo app is a Sys Tray utility that activates a context menu when doubled-clicked. Using the demo app, you are able to capture any window or control anywhere on the screen and save the screen-captured bitmap into the clipboard. After that, you'll be able to paste the bitmap from the clipboard onto any bitmap editing tool of your choice.

Although my main intention is to give a demonstration of one possible use of the Window Finder utility, such a screen capture utility is pretty useful all on its own. Although you can always use the "PrintScreen" button or the "Alt+PrintScreen" buttons, this utility allows you to precisely select windows and controls. Thus saving you the trouble of having to perform further bitmap area selection and cutting and pasting.

Usage

Summary Of How It Works

Please read through my "MS Spy++ Window Finder" article to understand how the window finder part of this demo app works. I have made extensive re-use of the source codes of the WindowFinder app. The following is a summary of the enhancements and transformations that were made to the original WindowFinder source codes :

Detailed Explanation Of How It Works

In order to make a System Tray app, I have removed the WS_VISIBLE style from the main window of the application and explicitly hidden it after creation :

  // Enhancement to WindowFinder - main window will not have 

  // WS_VISIBLE style.

  dwStyle = WS_OVERLAPPEDWINDOW;  

  // Create the main window.

  g_hwndMainWnd = CreateWindow
  (
    szMainWindowClassName,     // name of window class 

    WINDOW_SNAPSHOT_MAIN_WINDOW_TITLE,  // title 

    dwStyle, // window style - normal 

    CW_USEDEFAULT, // X coordinate - let Windows decide 

    CW_USEDEFAULT, // Y coordinate - let Windows decide 

    CW_USEDEFAULT, // width - let Windows decide 

    CW_USEDEFAULT, // height - let Windows decide

    NULL,          // no parent window 

    NULL,          // no override of class menu 

    hInstance,     // handle for this instance

    NULL           // no additional arguments 

  );

  ...
  ...
  ...

  // Display the window. 

  ShowWindow(g_hwndMainWnd, SW_HIDE);  
    // Enhancement to WindowFinder - hide the main window.


  UpdateWindow(g_hwndMainWnd);

The main window will still be required to process messages for the Sys Tray icon.

I have also defined a new function InitialiseShellModules() which is called within WinMain(). This function will perform initializations for the Sys Tray icon. This function is listed below :

BOOL InitialiseShellModules()
{
  NOTIFYICONDATA nid;
  BOOL bRetTemp = FALSE;
  BOOL bRet = TRUE;

  g_hIconSysTray = (HICON)LoadImage
  (
    (HINSTANCE)g_hInst, 
      // handle of the instance that contains the image 

    (LPCTSTR)MAKEINTRESOURCE(IDI_ICON_SYS_TRAY), 
      // name or identifier of image 

    (UINT)IMAGE_ICON, // type of image 

    (int)16, // desired width 

    (int)16, // desired height 

    (UINT)0 // load flags 

  ); 

  memset (&nid, 0, sizeof(NOTIFYICONDATA));
  nid.cbSize = sizeof(NOTIFYICONDATA);
  nid.hWnd = g_hwndMainWnd;
  nid.uID = ICON_ID;
  nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
  nid.hIcon = g_hIconSysTray;
  nid.uCallbackMessage = WM_SYS_TRAY_MESSAGE;
  strcpy (nid.szTip, WINDOW_SNAPSHOT_TOOL_TIP);
  
  bRetTemp = Shell_NotifyIcon
  (
    (DWORD)NIM_ADD, // message identifier 

    (PNOTIFYICONDATA)&nid // pointer to structure 

  ); 

  g_dwLastError = GetLastError ();

  return bRet;
}

The function contains basic sys tray initialization and startup code. The counterpart of InitialiseShellModules() is UninitialiseShellModules() which will basically unregister our icon from the System Tray.

As can be seen in InitialiseShellModules(), we have indicated that the main application window g_hwndMainWnd is to receive the WM_SYS_TRAY_MESSAGE message meant for our icon. Hence, we provide a WM_SYS_TRAY_MESSAGE message handler in MainWndProc() :

  case WM_SYS_TRAY_MESSAGE:
  {
    lRet = 0;
    bCallDefWndProc = TRUE;

    if (wParam == ICON_ID)
    {
      // Enhancement - Bio/Andrew Peace. Invoke context menu 

      // only when RIGHT-mouse click.

      if (lParam == WM_RBUTTONDOWN)
      {
        // Enhancement to Window Finder - display context menu only 

        // if we currently allow it. 

        if (g_bAllowContextMenu)
        {
          DisplayContextMenu (hwnd);
        }
      }

      // Enhancement - Bio/Andrew Peace. Invoke Search Window Dialog 

      // directly when icon is double-clicked.

      if (lParam == WM_LBUTTONDBLCLK)
      {
        // Enhancement to Window Finder - display the Search Window 

        // Dialog box immediately. If the Search Window Dialog is 

        // already alive, put it in the foreground. 

        if (g_hwndSearchDialog)
        {
          SetForegroundWindow (g_hwndSearchDialog);
        }
        else
        {
          PostMessage (hwnd, WM_START_SEARCH_WINDOW, 0, 0);
        }
      }
    }
    break;
  }

When the user right-clicks on the icon, we first check the BOOL flag g_bAllowContextMenu to see if we allow the display of the context menu. This is important because we do not want to allow the user the possibility of having two Search Window Dialogs running at the same time. The g_bAllowContextMenu flag is set to TRUE when the Search Window Dialog Box is displayed and will be set to FALSE when it is closed (see the WM_INITDIALOG and the WM_COMMAND (with wID == IDOK) message handlers in SearchWindowDialogProc() for more details).

We may, of course, opt to disable the "Take A Snapshot" menu item. This is a good idea, especially if there are any other options in the context menu not related directly to window selection. This will certainly complicate the code further but it can be done. When the user double-clicks on the sys tray icon, to directly start up the Search Window Dialog Box, we first check the global variable g_hwndSearchDialog to see if it contains any value. This g_hwndSearchDialog global variable is set to the HWND of the Search Window Dialog box when the dialog box is started up (see WM_INITDIALOG handler in SearchWindowDialogProc()) and is set to NULL when the dialog is closed (see WM_COMMAND, wID == IDOK, in SearchWindowDialogProc()).

If the Search Window Dialog Box is not running at the moment (null value in g_hwndSearchDialog), we start up the dialog by posting the WM_START_SEARCH_WINDOW message to the Main Window. If the Search Window dialog box is already running at the moment (non-null value in g_hwndSearchDialog), we bring the Search Window to the foreground immediately. This provides greater convenience for the user especially when the Search Window is hidden by another window. By bringing the Search Window dialog to the foreground when the user double-clicks the Sys Tray icon, the user can avoid minimizing windows in order to get to it.

The various handlers for the context menu items are listed below. They are part of the WM_COMMAND handler of the MainWndProc() :

  case WM_COMMAND:
  {
    WORD wNotifyCode = HIWORD(wParam); // notification code 

    WORD wID = LOWORD(wParam);         
      // item, control, or accelerator identifier 

    HWND hwndCtl = (HWND)lParam;      // handle of control 


    if (wNotifyCode == 0)
    {
      // Message is from a menu.

      // Enhancement to Window Finder - perform 

      // a window snapshot operation.

      if (wID == IDM_CONTEXT_TAKE_SNAPSHOT)
      {
        PostMessage (hwnd, WM_START_SEARCH_WINDOW, 0, 0);
      }

      if (wID == IDM_CONTEXT_ABOUT)
      {
        MessageBox (NULL, ABOUT_WINDOW_FLOATER, 
             WINDOW_SNAPSHOT_MAIN_WINDOW_TITLE, MB_OK);
      }

      if (wID == IDM_CONTEXT_SHUTDOWN)
      {
        PostMessage (hwnd, WM_CLOSE, 0, 0);
      }

      lRet = 0;
      bCallDefWndProc = FALSE;
    }
    else
    {
      bCallDefWndProc = TRUE;
    }
 
    break;
  }

Let us examine the handler for IDM_CONTEXT_TAKE_SNAPSHOT. It will post the WM_START_SEARCH_WINDOW message back to the main window which will eventually call on StartSearchWindowDialog() to start the Search Window dialog box :

  case WM_START_SEARCH_WINDOW :
  {
    lRet = 0;
    bCallDefWndProc = FALSE;

    StartSearchWindowDialog (hwnd);

    break;
  }

The Window Search Dialog Box is managed by the SearchWindowDialogProc() dialog box procedure. This has been covered in my "MS Spy++ Style Window Finder" article. Please refer to the article for more details.

The important modification to the original SearchWindowDialogProc() is the handler for the IDOK button :

  if ((wID == IDOK) && (g_hwndFoundWindow))
  {
    // Action.  

    // Important to hide the dialog and make sure it really disappears 

    // before we take the snapshot.

    ShowWindow (hwndDlg, SW_HIDE);
    Sleep(500);  // That's why we must wait for a while.

    CaptureWindowToClipboard (g_hwndFoundWindow);
  }

Here, when the user clicks on the "OK" button, and a window or control has been selected, we perform 3 important steps to capture the screenshot of the selected window/control :

The reasons for taking the 3 steps are explained next. In general, before one captures the screen shot of a window or control, that window or control must be entirely visible. It must not be obscured partially or entirely by another window. Please refer to the discussion thread in Joseph Newcomer's "Screen Capture to the Clipboard" article in which this is discussed.

As such, please ensure that the selected window/control is totally visible when you click on the "OK" button of the Search Window dialog box. Note that this is most evident when you are performing debugging because you will be brought to the Visual Studio IDE and the selected window may be obscured. However, anticipating that the Search Window Dialog Box itself could be obscuring the selected window/control, I have made sure that I first hide the dialog box before performing the screen capture.

Furthermore, while I was doing testing, I noticed that sometimes, even after I have commanded the dialog to hide itself, I still get an image of the dialog box on top of the selected window in the screenshot. The speed of the hiding of any window really depends on the OS itself. To get round this problem, I have added in the Sleep() API to make our application halt for just half-a-second (enough time to make sure the dialog box is really hidden) before taking the screenshot. If you find that you need more time for this, simply increase the parameter for Sleep() to a greater value.

I would also like to touch a little on the CaptureWindowToClipboard() function and the modifications that I made to Joseph's toClipboard() function.

BOOL CaptureWindowToClipboard (HWND hwndToCapture)
{
  BOOL bRet = FALSE;

  // Enhancement - Bio/Ahmed. Check first that "hwndToCapture" 

  // is a valid window.

  if((hwndToCapture) && (::IsWindow (hwndToCapture)))
  {
    bRet = TRUE;
    toClipboard_Bio((CWnd *)CWnd::FromHandle (hwndToCapture), TRUE);
  }

  return bRet;
}

I basically used the CWnd::FromHandle() method to dynamically convert the selected window to a CWnd pointer.

void toClipboard_Bio(CWnd * wnd, BOOL FullWnd)
{
  CDC *dc;
  if(FullWnd)
  { /* full window */
    dc = new CWindowDC(wnd);
    //HDC hdc = ::GetWindowDC(wnd->m_hWnd);

    //dc -> Attach(hdc);

  } /* full window */
  else
  { /* client area only */
    dc = new CClientDC(wnd);
    //HDC hdc = ::GetDC(wnd->m_hWnd);

    //dc -> Attach(hdc);

  } /* client area only */

I have created a new function toClipboard_Bio() which is essentially a copy of Joseph's toClipboard() routine albeit I changed the "dc" variable from a CDC object to a pointer to a CDC. Via polymorphism, I have later created a pointer to a CWindowDC or a CClientDC based on the FullWnd BOOL parameter. All references to "dc" have been modified in the light of the fact that "dc" is now a pointer. I have also made sure that "dc" is deleted at the end of the routine. The problem discovered with Joseph's code lies in the way that CDC objects are destroyed. Please refer to my discussion thread in Joseph's article for more details.

In Conclusion

Updates, Enhancements And Bug Fixes

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralCapture Text Pin
cat_12a
8:18 19 May '07  
QuestionExcellent Tool Pin
.Suchit
18:41 4 Feb '06  
AnswerRe: Excellent Tool Pin
Lim Bio Liong
5:48 5 Feb '06  
GeneralHow capturing is working Pin
sajjadakhter
16:55 15 Jul '05  
Generalhow can i get desktop window`s GUI in memory???? Pin
jags_vc
2:09 7 Mar '05  
GeneralHow can i get the mouse pointer ? Pin
Rodrigo Vaz
20:00 7 Apr '04  
GeneralRe: How can i get the mouse pointer ? Pin
eligetiv
12:32 9 Jan '05  
GeneralHow can i detect a global screen capture? Pin
Miguel Lopes
14:36 27 Oct '03  
GeneralRe: How can i detect a global screen capture? Pin
Lim Bio Liong
21:36 1 Nov '03  
GeneralA small improvement I hope... Pin
jyodak1
8:26 30 Dec '02  
GeneralHow to capture the active window? Pin
Anonymous
0:42 14 Oct '02  
GeneralRe: How to capture the active window? Pin
Anthony_Yio
1:22 15 Sep '03  
GeneralErrors from Source version. Pin
WREY
13:23 11 Aug '02  
GeneralRe: Errors from Source version. Pin
WREY
16:23 11 Aug '02  
GeneralRe: Errors from Source version. Pin
WREY
17:06 11 Aug '02  
GeneralHow to capture CMediaplayer image? Pin
Jesper S
10:15 10 Apr '02  
Generalgood utility- how 2 capture menus? Pin
Joe1
2:47 15 Jan '02  
GeneralRe: good utility- how 2 capture menus? Pin
Lim Bio Liong
0:16 16 Jan '02  
GeneralAn error ?? Pin
Ahmed Ismaiel Zakaria
8:01 13 Jan '02  
GeneralRe: An error ?? Pin
Lim Bio Liong
17:28 13 Jan '02  
GeneralThank you Pin
Ahmed Ismaiel Zakaria
11:48 21 Jan '02  
GeneralRe: Thank you Pin
Lim Bio Liong
3:06 22 Jan '02  
GeneralUseful! Pin
Andrew Peace
6:51 12 Jan '02  
GeneralRe: Useful! Pin
Nish [BusterBoy]
9:19 12 Jan '02  
GeneralRe: Useful! Pin
Andrew Peace
13:00 12 Jan '02  


Last Updated 13 Jan 2002 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009