Rapid
 Interactive
  Programming
   Environment

Tips and Tricks

 

Avoiding a second thread

Sometimes I want an application that does some work but the work takes a long time, and the user might want to stop the application before the work is completed. A good example is the duplication finder application. If I run the duplication finder on a 2 terra byte disk, then the application is “busy” for about 14 hours.

I wanted a way to provide a cancel button. If I simply add a cancel button to the GUI, then I need to cheack its status every once in a while. In principle, no problem but it means that I must hold the running application and let the operating system take control and let it pass any user GUI interaction to the application.

Now it is a probem. I do not want to structure the work in to segments just to fit to the GUI. What I want is a separate window with a cancel button, and when that button is pressed it must change a value in my application. Then, every once in a while  my application can check to see if it should stop by testing the variable.

The classic computing solution is to have two threads, one for the GUI and one for the work. But this is a bit complicated.

The easy solution is to have a single thread, but 2 windows. One window is the main GUI window. The other window is a non-modal dialogue window that has only one user input control, a cancel button, it can have numerous output controls for user generated messages.

Because the dialogue window is modeless it does not stop the main GUI from running, so the work gets done. But the trick is to make the GUI window temporarily invisible, so only the dialogue with the cancel button is visible.

The cancel button is linked to a minimal piece of code that sets an internal variable.The GUI window with its work is coded to check this variable every once in a while, but before checking the variable the cancel code has a small loop that processes any incoming messages for the application. But since the application window is invisible then the incoming messages can not come from user actions, except for actions from the cancel button!

Since the two windows belong to the same thread then they share access to the variable.

If the cancel button is pressed the variable is set, this is seen by the application which can then take appropriate action.

It is a matter of taste whether the cancel button terminates the dialogue and makes the main GUI visible or whether the main window code does it.

The following is a screenshot of the cancel dialogue used in the RIPE version of the duplicate file finder.

This is the code that the work loop calls every once in a whilre, for the duplicate finder application this was called once every 100 handled files. The code lets the user interact with the cancel dialogue (the main window was made invisible when the cancel dialogue was made visible). The code handles any outstanding windows messages which includes the user pressing the cancel button.

Sorry (okay so I am not sorry really but you know what I mean) I prefer to write code in good old fashioned “C”, or assembler or “Forth”. Further I write code at the Win32 API level, and even worse I use unmanaged code.

So, once more, sorry (George the Dinosaur).

void fth_Force_Screen_Update ()
{
   MSG msg;
   BOOL res ;

   while (1)
   { 
       res = PeekMessage(&msg,0,0,0,PM_REMOVE);
       if (!res || msg.message==WM_QUIT) return;
       TranslateMessage(&msg);
       DispatchMessage(&msg);
   }
    
}

After calling this code the work loop checks a variable that is initialised to indicate “not cancelled”, to see if the cancel button was pressed by the user.

This is the callback code for the cancel dialogue.

LRESULT CALLBACK dialogue_cb(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)

   switch (message)
   {
       case WM_INITDIALOG:
           return TRUE;
           break;

       case WM_COMMAND:
           switch ( LOWORD(wParam) )
           {
               case 3 :
               accepted_length = GetWindowText
               ( (HWND) lParam                              // handle of widgit
               , (LPTSTR ) pad_area                       // address of buffer for text
               , (WPARAM) sizeof(pad_area) -1         // maximum number of characters to copy
               ) ;
               return TRUE;
               break;

           case IDOK:
               CancelState = -1 ;                           // sets the CANCEL variable to imply CANCELLED
               dialogHwnd = 0 ;
               EndDialog(hDlg, LOWORD(wParam)) ;    // terminate the dialogue
               return TRUE;
               break;
          
           default:
               break ; 
       } ;
       break;
   }
   return FALSE;
}

This is the code that creates the cancel dialogue. Yes, yes. I know that it is not necessary to program at this level.

I found the basis of this code somewhere on the Internet about 12 years ago. I have reworked it to suit my needs, my thanks to whoever claims the original.


HWND my_dialogue ()
{
   WORD     *p, *pdlgtemplate ;
   int          nchar;
   DWORD  lStyle;
   HWND     hwnd ;
   char       * prompt;
   HWND     myhwnd ;
 
   // ========================================================
   // This code gives access to some time functions
   // The DIALOGUE automatically shows the time at which the dialogue was "popped-up"
   // Why, because I wanted it this way.

   char buf[40] ;

   SYSTEMTIME stUTC, stLocal;

   GetSystemTime(& stUTC);   
   SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal);

   // ========================================================

   hwnd = cli_hwnd ;  // this is the handle of my main WINDOW

   CancelState = 0 ;  // this is the IMPORTANT variable
                   // It must be global, i.e. visible to the main code
  
   // ========================================================
   // allocate some memory to hold the DIALOGUE template 
  
   pdlgtemplate = p = (PWORD) LocalAlloc (LPTR, 1000) ;

   lStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE ;
  
   *p++ = LOWORD (lStyle) ;
   *p++ = HIWORD (lStyle) ;
   *p++ = 0 ; // LOWORD (lExtendedStyle)
   *p++ = 0 ; // HIWORD (lExtendedStyle)
   *p++ = 4 ; // NumberOfItems
   *p++ = 10 ; // x
   *p++ = 10 ; // y
   *p++ = 240 ; // Width
   *p++ = 050 ; // Height
   *p++ = 0 ; // Menu
   *p++ = 0 ; // Class
    
   // ========================================================
   // Create the default caption for the dialogue window
   // The user can overwrite this if the want

   nchar = nCopyAnsiToWideChar (p, TEXT( "RipeTech: Cancel Dialogue" )) ;
   p += nchar;

    
   p = lpwAlign (p) ; // make sure the first item starts on a DWORD boundary

   lStyle = BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD;
  
   *p++ = LOWORD (lStyle) ;
   *p++ = HIWORD (lStyle) ;
   *p++ = 0 ;         // LOWORD (lExtendedStyle)
   *p++ = 0 ;         // HIWORD (lExtendedStyle)
   *p++ = 05 ;        // x
   *p++ = 10 ;        // y
   *p++ = 40 ;        // Width
   *p++ = 25 ;        // Height
   *p++ = IDOK ;      // ID

   *p++ = (WORD)0xffff; //  class i.d. Button in this case
   *p++ = (WORD)0x0080;

   // text of this item
   nchar = nCopyAnsiToWideChar (p, TEXT("Cancel")) ;
   p += nchar ;
  
   *p++ = 0 ; // advance pointer over nExtraStuff WORD

   // ========================================================
    
   p = lpwAlign (p) ; // start on a DWORD boundary 

  
   lStyle =   WS_VISIBLE | WS_CHILD | ES_MULTILINE | 0 ;

   *p++ = LOWORD (lStyle) ;
   *p++ = HIWORD (lStyle) ;
   *p++ = 0 ;         // LOWORD (lExtendedStyle)
   *p++ = 0 ;         // HIWORD (lExtendedStyle)
   *p++ = 60 ;        // x
   *p++ = 10 ;        // y
   *p++ = 170 ;       // Width
   *p++ = 10 ;        // Height
   *p++ = 02 ;        // ID

    
   *p++ = (WORD)0xffff ; // class i.d. Static in this case
   *p++ = (WORD)0x0081 ;
  
 
   nchar = nCopyAnsiToWideChar (p, TEXT("")) ; // text of the item
   p += nchar;

   *p++ = 0;  // advance pointer over nExtraStuff WORD

   // ========================================================
  
   p = lpwAlign (p) ; // start on a DWORD boundary 

  
   lStyle =   WS_VISIBLE | WS_CHILD | ES_MULTILINE | 0 ;

   *p++ = LOWORD (lStyle) ;
   *p++ = HIWORD (lStyle) ;
   *p++ = 0 ;         // LOWORD (lExtendedStyle)
   *p++ = 0 ;         // HIWORD (lExtendedStyle)
   *p++ = 60 ;        // x
   *p++ = 25 ;        // y
   *p++ = 80 ;        // Width
   *p++ = 10 ;        // Height
   *p++ = 03 ;        // ID

    
   *p++ = (WORD)0xffff ; // class i.d. Static in this case
   *p++ = (WORD)0x0081 ;
  

   // set up a text field to show the time at which this Dialogue was popped up
   sprintf (buf , " Started at %02d:%02d:%02d" , stLocal.wHour , stLocal.wMinute , stLocal.wSecond  ) ; 
   nchar = nCopyAnsiToWideChar (p, TEXT(buf)) ; // text of the item 
   p += nchar;

   *p++ = 0;  // advance pointer over nExtraStuff WORD

   // ========================================================
  
   p = lpwAlign (p) ; // start on a DWORD boundary 

  
   lStyle =   WS_VISIBLE | WS_CHILD | ES_MULTILINE  ;

   *p++ = LOWORD (lStyle) ;
   *p++ = HIWORD (lStyle) ;
   *p++ = 0 ;         // LOWORD (lExtendedStyle)
   *p++ = 0 ;         // HIWORD (lExtendedStyle)
   *p++ = 150 ;       // x
   *p++ = 25 ;        // y
   *p++ = 80 ;        // Width
   *p++ = 10 ;        // Height
   *p++ = 04 ;        // ID

   *p++ = (WORD) 0xffff ;   // class i.d. Static in this case 
   *p++ = (WORD) 0x0081 ;
  
   nchar = nCopyAnsiToWideChar (p, TEXT("")) ; // text of the item
   p += nchar ;

   *p++ = 0 ; // advance pointer over nExtraStuff WORD

   // ========================================================

   myhwnd = CreateDialogIndirect  (hInst  , (LPDLGTEMPLATE) pdlgtemplate, hwnd, (DLGPROC) dialogue_cb) ;

   LocalFree (LocalHandle (pdlgtemplate)) ;
  
   return myhwnd ;
}
 

 

 

[DNS Query] [Dup Finder] [EAN DVD Manager] [File Hash Checker] [Icon Ripper] [ISBN Book Manager] [SQLite Utility] [Tone & Morse] [Tips and Tricks] [VBA]