7.0. The Scope Program - Part B

 
    7.1. Data Flow Diagram
        7.1.1. Data Flow Paths
    7.2 The Grid
    7.3 The Traces
    7.4. Storing and Restoring the Pervious Position of the Main Frame
    7.5. Setting-Up RS232 Communications
    7.6. Solving the Flickering Problem
    7.7. Creating the Split Window View
    7.8. Saving / Opening Scope Data
    7.9. Copying the Scope Display to the Windows Clipboard
    7.10. Exporting the Scope Display as a Bitmap (BMP) File
    7.11. Owner Drawn Menus with Bitmaps
        7.11.1. Integrating Brent Corkum's BCMenu Class with the Scope Program.
    7.12. Windows 95/98 Compatibility Problem (scope v1/008a, 20/12/2001)
    7.13. Windows XP Compatibility Problem (Scope v1.017a, 05/02/2002)
    7.14. Additional Video Card Features That May Explain Low CPU Usage.

 
7.8. Saving / Opening Scope Data
 
The scope program has the capability of saving and opening waveform data along with the current settings (e.g. selected time-base, sample rate, enabled channels, scope resolution, etc…). This is one of the key advantages that the PC based scope has over traditional analogue scopes. Traditionally users of analogue scopes had to draw on graph paper the scope waveforms if a permanent record was required.
 
This saving of scope data feature is far more useful than taking a screen dump of the screen or copying the scope waveform to the windows clipboard. Obviously taking a screen dump of the screen, using the windows clipboard, and exporting the scope display to a bitmap are useful for importing an image of the scope display into applications like Microsoft Word, but these images cannot be manipulated (e.g. channel time-base). The saving and opening of the scope data is far more useful for keeping a record of an event for analysis at a later time, because the scope controls are fully functional, for example time-base, center points of waveforms, channel offset, grid resolution and enable/disable channels are adjustable. 
 
Figure 7.8a. Scope data saved to File1.sdf (Scope V1.040a, 07/04/2002)
 
*.sdf is the file extension used, it is short for Scope Data File.
 
If the user clicks on menu item [File] [Save] the following function is called: -
void CScopeDoc::OnFileSave()
{
        // 07/04/2002 by CKM
 
        if((_access(m_Filename, 0)) == -1)
        {
               OnFileSaveAs();
        }
        else
        {
               Save();
        }      
}
 
Basically the OnFileSave() function calls the OnFileSaveAs() function if there is no current file (untitled) or the Save() function to save changes to the current file.
 
If user clicks on menu item [File] [Save As] the following function is called: -
void CScopeDoc::OnFileSaveAs()
{
        // 07/04/2002 by CKM
        CFileDialog dlg (FALSE, _T("*.sdf"), m_Filename, OFN_HIDEREADONLY,_T("Scope Data         
File(*.sdf)|*.sdf||"));
 
        if (IDOK == dlg.DoModal())
        {
               /* Check for existence */
               if( (_access(dlg.GetPathName(), 0 )) != -1 )
               {
                       if(AfxMessageBox("File '" + dlg.GetPathName() + "' Already exists. Do you
               want to replace the existing file ?", MB_YESNO | MB_ICONINFORMATION) !=
               IDYES)
                              return;
               }
       
               m_Filename = dlg.GetPathName();
               Save();
        }      
}
 
Basically the OnFileSaveAs() function using a windows standard CFileDialog box to ask the user to specify a filename and directory. The function also checks if the selected filename already exists, if it does the user gets the option to override the file or to cancel save operation. Function Save() is called, if a valid filename has been entered and user has not cancelled the save operation.
 
Figure 7.8b. Windows standard CFileDialog box
 
This function creates and writes the scope data file: -
void CScopeDoc::Save()
{
        // 07/04/2002 by CKM
        CView* pView = GetActiveView();
        if (pView == NULL)     return;
       
        CChildFrame* pChild = (CChildFrame*)pView->GetParentFrame();       
        CScopeView* SView =  (CScopeView*) pChild->m_wndSplitter.GetPane(0,0);
       
 
        CScopeApp* pApp = (CScopeApp*)AfxGetApp();
        FILE *infile;
 
        int i = 0;
 
        if((infile=fopen(m_Filename,"wr"))==NULL)
        {
               AfxMessageBox("Unable to create file");
               return;
        }
 
        rewind(infile);
 
 
        /* Save Channel 1,2,3 & 4 */
        for(i=0;i<10000;i++) fprintf(infile,"%d\n",pApp->m_nCH1Volt[i]);
        for(i=0;i<10000;i++) fprintf(infile,"%d\n",pApp->m_nCH2Volt[i]);
        for(i=0;i<10000;i++) fprintf(infile,"%d\n",pApp->m_nCH3Volt[i]);
        for(i=0;i<10000;i++) fprintf(infile,"%d\n",pApp->m_nCH4Volt[i]);
 
 
        /* Save Variables */
        fprintf(infile,"%d\n", SView->x);
        fprintf(infile,"%d\n", SView->y);
        fprintf(infile,"%d\n",SView->m_nCH1POS);
        fprintf(infile,"%d\n",SView->m_nCH2POS);
        fprintf(infile,"%d\n",SView->m_nCH3POS);
        fprintf(infile,"%d\n",SView->m_nCH4POS);
        fprintf(infile,"%d\n",SView->m_nCH1_Offset);
        fprintf(infile,"%d\n",SView->m_nCH2_Offset);
        fprintf(infile,"%d\n",SView->m_nCH3_Offset);
        fprintf(infile,"%d\n",SView->m_nCH4_Offset);
        fprintf(infile,"%d\n",pApp->m_nInputRange);
        fprintf(infile,"%d\n",pApp->m_nSampleMode);
        fprintf(infile,"%d\n",pApp->m_SampleRateSelection);
        fprintf(infile,"%d\n",m_bCH1_ENABLE);
        fprintf(infile,"%d\n",m_bCH2_ENABLE);
        fprintf(infile,"%d\n",m_bCH3_ENABLE);
        fprintf(infile,"%d\n",m_bCH4_ENABLE);
        fprintf(infile,"%d\n",m_TimeBaseSelection);
        fprintf(infile,"%d\n",m_nCH1_VOLTS);
        fprintf(infile,"%d\n",m_nCH2_VOLTS);
        fprintf(infile,"%d\n",m_nCH3_VOLTS);
        fprintf(infile,"%d\n",m_nCH4_VOLTS);
        fprintf(infile,"%d\n",m_nTrigger);
        fprintf(infile,"%d\n",m_nTriggerType);
        fprintf(infile,"%d\n",SView->m_ColorGridBackGround);
        fprintf(infile,"%d\n",SView->m_nRefreshRate);
 
        fclose(infile);
 
        SetTitle(m_Filename);
}
 
Notice that function Save() uses the old ASIC C method for saving files, this method is 100% windows compatible (including long filenames) and is preferred by most C++ programmers. Basically a pointer to the file is setup (*infile) and the ASIC C function fprintf() is used to save scope data to the file using the ASCII format. Writing the file in binary and not ASCII would produce a more efficient file, but this file would not be readable in notepad for example. First the four data arrays (one for each channel) are saved, then all the key variables are saved (e.g. channel center point position m_nCH1POS, channel offset m_nCH1_Offset etc...).
 
If user clicks on menu item [File] [Open] the following function is called: -
void CScopeDoc::OnFileOpen()
{
        // 07/04/2002 by CKM
        CView* pView = GetActiveView();
        if (pView == NULL) return;
       
        CChildFrame* pChild = (CChildFrame*)pView->GetParentFrame();       
        CScopeView* SView =  (CScopeView*) pChild->m_wndSplitter.GetPane(0,0);
       
        CScopeApp* pApp = (CScopeApp*)AfxGetApp();
        FILE *infile;
        int i;
        int temp;
 
        CFileDialog dlg (TRUE, _T("*.sdf"), m_Filename, OFN_HIDEREADONLY,_T("Scope Data
File(*.sdf)|*.sdf||"));
 
        if (IDOK == dlg.DoModal())
        {
               m_Filename = dlg.GetPathName();
 
               if((infile=fopen(m_Filename,"rw"))==NULL)
               {
                       AfxMessageBox("Unable to open file");
                       return;
               }
 
               rewind(infile);
 
               /* Open Channel 1 */
               for(i=0;i<10000;i++)
               {
                       fscanf(infile,"%d\n",&temp);
                       pApp->m_nCH1Volt[i] = temp;
               }
 
               /* Open Channel 2 */
               for(i=0;i<10000;i++)
               {
                       fscanf(infile,"%d\n",&temp);
                       pApp->m_nCH2Volt[i] = temp;
               }
 
               /* Open Channel 3 */
               for(i=0;i<10000;i++)
               {
                       fscanf(infile,"%d\n",&temp);
                       pApp->m_nCH3Volt[i] = temp;
               }
       
               /* Open Channel 4 */
               for(i=0;i<10000;i++)
               {
                       fscanf(infile,"%d\n",&temp);
                       pApp->m_nCH4Volt[i] = temp;
               }
 
               fscanf(infile,"%d\n",&SView->x);
               fscanf(infile,"%d\n",&SView->y);
               fscanf(infile,"%d\n",&SView->m_nCH1POS);
               fscanf(infile,"%d\n",&SView->m_nCH2POS);
               fscanf(infile,"%d\n",&SView->m_nCH3POS);
               fscanf(infile,"%d\n",&SView->m_nCH4POS);
               fscanf(infile,"%d\n",&SView->m_nCH1_Offset);
               fscanf(infile,"%d\n",&SView->m_nCH2_Offset);
               fscanf(infile,"%d\n",&SView->m_nCH3_Offset);
               fscanf(infile,"%d\n",&SView->m_nCH4_Offset);
               fscanf(infile,"%d\n",&pApp->m_nInputRange);
               fscanf(infile,"%d\n",&pApp->m_nSampleMode);
               fscanf(infile,"%d\n",&pApp->m_SampleRateSelection);
               fscanf(infile,"%d\n",&m_bCH1_ENABLE);
               fscanf(infile,"%d\n",&m_bCH2_ENABLE);
               fscanf(infile,"%d\n",&m_bCH3_ENABLE);
               fscanf(infile,"%d\n",&m_bCH4_ENABLE);
               fscanf(infile,"%d\n",&m_TimeBaseSelection);
               fscanf(infile,"%d\n",&m_nCH1_VOLTS);
               fscanf(infile,"%d\n",&m_nCH2_VOLTS);
               fscanf(infile,"%d\n",&m_nCH3_VOLTS);
               fscanf(infile,"%d\n",&m_nCH4_VOLTS);
               fscanf(infile,"%d\n",&m_nTrigger);
               fscanf(infile,"%d\n",&m_nTriggerType);
               fscanf(infile,"%d\n",&SView->m_ColorGridBackGround);
               fscanf(infile,"%d\n",&SView->m_nRefreshRate);
       
               fclose(infile);
              
               UpdateAllViews(NULL,HINT_OPEN,NULL);
               SView->PrepareGrid();
               SView->PrepareTraces();
               SView->OnDrawGrid();
               SView->OnDrawCH1();
               SView->OnDrawCH2();
               SView->OnDrawCH3();
               SView->OnDrawCH4();
        }      
        SetTitle(m_Filename);
}
 
A standard Windows CFileDialog is used to allow the user to select the file to open, then a pointer to the file is setup. Once again the old ASIC C method of accessing files is used, this time using the function fscanf() to read the file line-by-line. The data is retrieved from the data file in exactly the same order as it was saved; once all fields have been acquired the scope display is refreshed.
Figure 7.8c. Example: File1.sdf has been opened, then CH1/CH2 was disabled with voltage / time-base scales modified
 
Figure 7.8c. demonstrates the potential of this feature, File1.sdf has been loaded. Channel 1 and Channel 2 has been disabled (Hidden), voltage and time-base scales have been modified. Zero point of channels 3 and 4 has been moved and a large offset has been applied to channel 3.
 
Figure 7.8d. Demonstrating aliasing by selecting a out of range time-base
 
It is important to remember that the saved waveforms are with respect to the original sample rate. The scope program increases the time-base by skipping readings and decreases the time-base by duplicating readings. As shown in figure 7.8d. it is possible for aliasing to occur if the selected time-base requires for less than 2 samples per cycle to be displayed, hence it is important to make sure the sample-rate is optimal for the waveform being monitored before saving. 
 
File1.sdf (235kB): -
0
298
589
867
1126
1360
1563
1732
1861
1949
1994
1994
1949
1861
1732
1563
1360
1126
867
589
298
0
-298
-589
-867
-1126
-1360
-1563
-1732
-1861
-1949
-1994
-1994
-1949
-1861
-1732
-1563
-1360
-1126
-867
-589
-298
0
298
589
867
1126
1360
1563
1732
1861
1949
1994
1994
1949
1861
1732
1563
1360
1126
 
The file format is simple the data is listed in one large column. The data shown above is part of the data for channel 1; clearly it is a sine-wave. The values are actual voltage readings that have been multiplied by 1000 to improve accuracy, dividing by 1000 will convert back to the voltage, for example 298 is 0.298 V, 589 is 5.89V and the peak 1994 is 1.994 Volts.

 
 
7.9. Copying the Scope Display to the Windows Clipboard
 
The scope program has the ability to copy the scope display to the windows clipboard in the form of a bitmap; the paste command in many Windows applications (e.g. Word) will import the scope image. The user can use the right-click menu (as shown in figure 7.9a), the edit menu, the shortcut key [Ctrl+C], or the copy toolbar icon.
 
Figure 7.9a. Screen dump demonstrating how to copy the scope display to the clipboard (Scope V1.038a, 04/04/2002)
 
Figure 7.9b. Scope display was pasted into this document from the clipboard
Figure 7.9b is an image of the scope display that was copied into the Windows clipboard and pasted into Microsoft Word (This document).
 
Only the scope waveforms are copied and not the entire scope display as was the case for the screen dump shown in figure 7.9a.
 
It maybe useful for other information like time-base and channel voltage scales to be included. This change is recommended and should be adopted sometime in the future, because the scope waveforms are useless without knowledge of the time-base and voltages scales used.
 
 
 
 
 
 
 
 This function copies the scope display to the clipboard: -
void CScopeView::OnEditCopy()
{
 
    /* -----------------------------------------------------------
    | Function: void CScopeView::OnEditCopy()                     |
    | Description: Copies the scope display to the Windows        |
    | clipboard.                                                  |
    | Return: Void.                                               |
    | Date: 15/02/2002                                            |
    | Version: 1.0                                                |
    | By: Colin McCord                                            |
    ----------------------------------------------------------- */
 
 
    CRect rect;
    CClientDC dc(this);
    CDC memDC;
    CBitmap bitmap;

 
    GetClientRect(&rect);
 
 
    // Create memDC
    memDC.CreateCompatibleDC(&dc);
    bitmap.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height());
    CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);
 
    // Fill in memDC
    memDC.FillSolidRect(rect, dc.GetBkColor());
 
 
    // Redraw grid to memory DC
    if (memDC.GetSafeHdc() != NULL)
    {
        // first drop the grid on the memory dc
    memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
    &m_dcGrid, 0, 0, SRCCOPY);

    // now add the plot on top as a "pattern" via SRCPAINT.
    // works well with dark background and a light plot
    memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
    &m_dcCH1, 0, 0, SRCPAINT); //SRCPAINT
 
    memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
    &m_dcCH2, 0, 0, SRCPAINT); //SRCPAINT
 
    memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
    &m_dcCH3, 0, 0, SRCPAINT); //SRCPAINT

    memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
    &m_dcCH4, 0, 0, SRCPAINT); //SRCPAINT
    }
 
 
    // Copy contents of memDC to clipboard
    OpenClipboard();
    EmptyClipboard();
    SetClipboardData(CF_BITMAP, bitmap.GetSafeHandle());
    CloseClipboard();
 
 
    // Clean up
    memDC.SelectObject(pOldBitmap);
    bitmap.Detach();
}
 

 
7.10. Exporting the Scope Display as a Bitmap (BMP) File
 
The scope program has the ability to save the scope display as a bitmap file. The user clicks on [Export] in the [File] menu (see figure 7.10a), then a standard windows save dialogue box appears (see figure 7.10b) to ask the user were to save the file.
Figure 7.10a. User clicks [Export]
Figure 7.10b. Save dialogue box appears
 
Figure 7.10c. Screen dump of exported file scope.bmp
Figure 7.10c shows the contains of the exported file scope.bmp.
 
Saving the scope display to a BMP file would have been fairly simple if a handle to a device-independent bitmap existed. Simply write BITMAPINFOHEADER information followed by the contents of the bitmap. The three fields that have to be set in the BITMAPINFOHEADER are the bfType which should always be "BM", the bfSize which is the size of the bitmap including the infoheader and the bfOffBits which is the offset to the bitmap bits from the start of the file.
 
But unfortunately the scope display is a device-dependent bitmap. Therefore a DIB (Device Independent Bitmap) must first be created from it.
 
 
 
 
 
 
 
 
The following function converts a DDB (Device Dependent Bitmap) to a DIB (Device Independent Bitmap): -
HANDLE CScopeView::DDBToDIB(CBitmap &bitmap, DWORD dwCompression, CPalette *pPal)
{
/* -----------------------------------------------------------
   | Fuction:     HANDLE CScopeView::DDBToDIB(...)           |
   | Description: Convert Device-Dependent Bitmap (DDB) to   |
   |              Device-independent Bitmap (DIB).           |
   | Return:      Void.                                      |
   | Date:        16/02/2002                                 |
   | Verison:     1.3                                        |
   | By:          Colin K McCord                             |
   ----------------------------------------------------------- */
 
        // DDBToDIB            - Creates a DIB from a DDB
        // bitmap              - Device dependent bitmap
        // dwCompression- Type of compression - see BITMAPINFOHEADER
        // pPal                - Logical palette
 
 
        BITMAP                        bm;
        BITMAPINFOHEADER       bi;
        LPBITMAPINFOHEADER     lpbi;
        DWORD                         dwLen;
        HANDLE                        hDIB;
        HANDLE                        handle;
        HDC                           hDC;
        HPALETTE                      hPal;
 
 
        ASSERT( bitmap.GetSafeHandle() );
 
        // The function has no arg for bitfields
        if( dwCompression == BI_BITFIELDS )
               return NULL;
 
        // If a palette has not been supplied use defaul palette
        hPal = (HPALETTE) pPal->GetSafeHandle();
        if (hPal==NULL)
               hPal = (HPALETTE) GetStockObject(DEFAULT_PALETTE);
 
        // Get bitmap information
        bitmap.GetObject(sizeof(bm),(LPSTR)&bm);
 
        // Initialize the bitmapinfoheader
        bi.biSize                     = sizeof(BITMAPINFOHEADER);
        bi.biWidth                    = bm.bmWidth;
        bi.biHeight            = bm.bmHeight;
        bi.biPlanes            = 1;
        bi.biBitCount          = bm.bmPlanes * bm.bmBitsPixel;
        bi.biCompression       = dwCompression;
        bi.biSizeImage         = 0;
        bi.biXPelsPerMeter     = 0;
        bi.biYPelsPerMeter     = 0;
        bi.biClrUsed           = 0;
        bi.biClrImportant      = 0;
 
        // Compute the size of the  infoheader and the color table
        int nColors = (1 << bi.biBitCount);
 
        dwLen  = bi.biSize + nColors * sizeof(RGBQUAD);
 
        // We need a device context to get the DIB from
        hDC = ::GetDC(NULL);
 
        hPal = SelectPalette(hDC,hPal,FALSE);
        RealizePalette(hDC);
 
        // Allocate enough memory to hold bitmapinfoheader and color table
        hDIB = GlobalAlloc(GMEM_FIXED,dwLen);
 
        if (!hDIB)
        {
               SelectPalette(hDC,hPal,FALSE);
               ::ReleaseDC(NULL,hDC);
               return NULL;
        }
 
        lpbi = (LPBITMAPINFOHEADER)hDIB;
 
        *lpbi = bi;
 
        // Call GetDIBits with a NULL lpBits param, so the device driver 
        // will calculate the biSizeImage field 
        GetDIBits(hDC, (HBITMAP)bitmap.GetSafeHandle(), 0L, (DWORD)bi.biHeight,
                       (LPBYTE)NULL, (LPBITMAPINFO)lpbi, (DWORD)DIB_RGB_COLORS);
 
        bi = *lpbi;
 
        // If the driver did not fill in the biSizeImage field, then compute it
        // Each scan line of the image is aligned on a DWORD (32bit) boundary
        if (bi.biSizeImage == 0){
               bi.biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8) 
                                             * bi.biHeight;
 
               // If a compression scheme is used the result may infact be larger
               // Increase the size to account for this.
               if (dwCompression != BI_RGB)
                       bi.biSizeImage = (bi.biSizeImage * 3) / 2;
        }
 
        // Realloc the buffer so that it can hold all the bits
        dwLen += bi.biSizeImage;
        if (handle = GlobalReAlloc(hDIB, dwLen, GMEM_MOVEABLE))
               hDIB = handle;
        else{
               GlobalFree(hDIB);
 
               // Reselect the original palette
               SelectPalette(hDC,hPal,FALSE);
                       ::ReleaseDC(NULL,hDC);
               return NULL;
        }
 
        // Get the bitmap bits
        lpbi = (LPBITMAPINFOHEADER)hDIB;
 
        // FINALLY get the DIB
        BOOL bGotBits = GetDIBits( hDC, (HBITMAP)bitmap.GetSafeHandle(),
                              0L,                           // Start scan line
                              (DWORD)bi.biHeight,           // # of scan lines
                              (LPBYTE)lpbi                  // address for bitmap bits
                              + (bi.biSize + nColors * sizeof(RGBQUAD)),
                              (LPBITMAPINFO)lpbi,           // address of bitmapinfo
                              (DWORD)DIB_RGB_COLORS);       // Use RGB for colour table
 
        if( !bGotBits )
        {
               GlobalFree(hDIB);
               SelectPalette(hDC,hPal,FALSE);
               ::ReleaseDC(NULL,hDC);
               return NULL;
        }
 
        SelectPalette(hDC,hPal,FALSE);
        ::ReleaseDC(NULL,hDC);
        return hDIB;
}

 

 
 
Convert the scope display to a bitmap (DDB) and ask the user for a filename: -
void CScopeView::OnFileExport()
{
/* -----------------------------------------------------------
   | Fuction:     void CScopeView::OnFileExport()            |
   | Description: User specifies filename using CFileDialog, |
   |              then the scope display is save to that     |
   |               filename using the standard bitmap         |
   |             graphic format.                            |
   | Return:      Void.                                      |
   | Date:        16/02/2002                                 |
   | Verison:     1.1                                        |
   | By:          Colin K McCord                             |
   ----------------------------------------------------------- */
 
 
        HANDLE         hDIB;   
        CRect          rect;
        CClientDC      dc(this);
        CDC            memDC;
        CBitmap        bitmap;
        
        GetClientRect(&rect); 
 
        // Create memDC
        memDC.CreateCompatibleDC(&dc);
        bitmap.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height());    
        CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);
 
        // Fill in memDC
        memDC.FillSolidRect(rect, dc.GetBkColor()); 
        
 
        // Redraw grid to memory DC
        if (memDC.GetSafeHdc() != NULL)
        {
               // first drop the grid on the memory dc
               memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight, 
                                      &m_dcGrid, 0, 0, SRCCOPY);
    
               // now add the plot on top as a "pattern" via SRCPAINT.
               // works well with dark background and a light plot
               memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight, 
                              &m_dcCH1, 0, 0, SRCPAINT);  //SRCPAINT
        
               memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight, 
                              &m_dcCH2, 0, 0, SRCPAINT);  //SRCPAINT
 
               memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight, 
                              &m_dcCH3, 0, 0, SRCPAINT);  //SRCPAINT
        
               memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight, 
                              &m_dcCH4, 0, 0, SRCPAINT);  //SRCPAINT
 
 
        }
 
        
        /* Convert Device-Dependent Bitmap (DDB) to Device-independent Bitmap (DIB) */
        hDIB = DDBToDIB(bitmap, BI_RGB, NULL);
 
        CFileDialog dlg (FALSE, _T("*.bmp"), _T("Scope"), OFN_HIDEREADONLY,_T("Windows or OS/2 
        Bitmap (*.bmp)|*.bmp||"));
        dlg.m_ofn.lpstrTitle = "Export Scope Display";
        if (IDOK == dlg.DoModal())
        {
        
               /* Check for existence */
               if( (_access(dlg.GetPathName(), 0 )) != -1 )
               {
                       if(MessageBox("File '" + dlg.GetPathName() + "' Already exists. Do you want
                       to replace the existing file ?","Export Scope Display", MB_YESNO |                     
                       MB_ICONINFORMATION) != IDYES)
                              return;
               }
        
               WriteDIB(dlg.GetPathName(),hDIB);     // Save as Bitmap
        
        }       
 
}
 
 
Write bitmap to specified file: -
BOOL CScopeView::WriteDIB(CString szFile, HANDLE hDIB)
{
/* -----------------------------------------------------------
   | Fuction:     BOOL CScopeView::WriteDIB(........)        |
   | Description: Writes bitmap to specified file.           |
   | Return:      Void.                                      |
   | Date:        16/02/2002                                 |
   | Verison:     1.1                                        |
   | By:          Colin K McCord                             |
   ----------------------------------------------------------- */
 
        // WriteDIB            - Writes a DIB to file
        // Returns             - TRUE on success
        // szFile              - Name of file to write to
        // hDIB                - Handle of the DIB
 
        BITMAPFILEHEADER       hdr;
        LPBITMAPINFOHEADER     lpbi;
 
        if (!hDIB)
               return FALSE;
 
        CFile file;
        if( !file.Open( szFile, CFile::modeWrite|CFile::modeCreate) )
               return FALSE;
 
        lpbi = (LPBITMAPINFOHEADER)hDIB;
 
        int nColors = 1 << lpbi->biBitCount;
 
        // Fill in the fields of the file header 
        hdr.bfType             = ((WORD) ('M' << 8) | 'B');  // is always "BM"
        hdr.bfSize             = GlobalSize (hDIB) + sizeof( hdr );
        hdr.bfReserved1        = 0;
        hdr.bfReserved2        = 0;
        hdr.bfOffBits          = (DWORD) (sizeof( hdr ) + lpbi->biSize +
                                             nColors * sizeof(RGBQUAD));
 
        // Write the file header 
        file.Write( &hdr, sizeof(hdr) );
 
        // Write the DIB header and the bits 
        file.Write( lpbi, GlobalSize(hDIB) );
 
        return TRUE;
 
}
 
 


7.11. Owner Drawn Menus with Bitmaps
 
The scope program uses owner drawn menus with bitmaps. To save time a freeware class (BCMenu.h) written by Brent Corkum was used (download from [W4]). This class can mimic the new MS Office XP style of menus (Used in scope program), or the old MS Office 97 style of menus. Figures 7.11a shows a screen dump of Microsoft Word XP with its file menu open, while figure 7.11b shows the file menu of the Scope program. Notice that the menu style of the Scope program is similar, but not perfect, to that of Word XP. It is clear that Brent Corkum has done a good job developing this class and it was decided after testing on Windows 98/2000/XP for the scope program to make use of his class as it made the program more professional looking and more user-friendly (Icons in the menus).
 
Figure 7.11a. Microsoft Word XP file menu
Figure 7.11b. Scope Ver1.036a file Menu
 
“This class, BCMenu, implements owner drawn menus derived from the CMenu class. The purpose of which is to mimic the menu style used in Visual C++ 5.0 and MS Word. I can't take credit for all the code; some portions of it were taken from code supplied by Ben Ashley and Girish Bharadwaj. The difference between their codes and this one is quite simple; this one makes it very easy to build these cool menus with bitmaps into your application. I've removed the Icon loading stuff and replaced it with Bitmaps. The bitmaps allow you to use the 16X15 toolbar bitmaps directly from your toolbars in the resource editor. As well, there is no scaling of the bitmaps so they always look good. You can also load Bitmap resources and define bitmaps for your check marks. I've also added the default checkmark drawing stuff, separators, proper alignment of keyboard accelerator text, keyboard shortcuts, proper alignment of popup menu items, proper system colour changes when the Display Appearance changes, plus bug fixes to the Ben Ashley's LoadMenu function for complex submenu systems. I made quite a few other modifications as well, too many to list or remember. I also use the disabled bitmap dithering function of Jean-Edouard Lachand-Robert to create the disabled state bitmaps. I must admit, it does a much better job then the DrawState function. If you find any bugs, memory leaks, or just better ways of doing things, please let me know. I used Visual C++ 5.0 and I have not tested compatibility with earlier VC versions. I've tested it on Win 95/NT at various resolutions and colour palette sizes.” Brent Corkum [W4]
 
Title block from BCMenu.h: -
//*************************************************************************
// BCMenu.h : header file
// Version : 3.0
// Date : January 2002
// Author : Brent Corkum
// Email :  corkum@rocscience.com
// Latest Version : http://www.rocscience.com/~corkum/BCMenu.html
//
// Bug Fixes and portions of code supplied by:
//
// Ben Ashley,Girish Bharadwaj,Jean-Edouard Lachand-Robert,
// Robert Edward Caldecott,Kenny Goers,Leonardo Zide,
// Stefan Kuhr,Reiner Jung,Martin Vladic,Kim Yoo Chul,
// Oz Solomonovich,Tongzhe Cui,Stephane Clog,Warren Stevens,
// Damir Valiulin
//
// You are free to use/modify this code but leave this header intact.
// This class is public domain so you are free to use it any of
// your applications (Freeware,Shareware,Commercial). All I ask is
// that you let me know so that if you have a real winner I can
// brag to my buddies that some of my code is in your app. I also
// wouldn't mind if you sent me a copy of your application since I
// like to play with new stuff.
//*************************************************************************
 
 
7.11.1. Integrating Brent Corkum’s BCMenu Class with the Scope Program
 
Step 1: Add #include “BCMenu.cpp” to stdafx.cpp: -
// stdafx.cpp : source file that includes just the standard includes
// Scope.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
#include "UsefulSplitterWnd.cpp"
#include "XTabCtrl.cpp"
#include "StatLink.cpp"
#include "BCMenu.cpp"
#include "ColorStatic.cpp"
 
Step 2: Add #include “BCMenu.h”, m_default and m_menu to MainFrm.h: -
// MainFrm.h : interface of the CMainFrame class
//
/////////////////////////////////////////////////////////////////////////////
 
#if !defined(AFX_MAINFRM_H__8C66EB30_6DEB_4044_863D_D90EA5FE5723__INCLUDED_)
#define AFX_MAINFRM_H__8C66EB30_6DEB_4044_863D_D90EA5FE5723__INCLUDED_
 
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
 
#include "BCMenu.h" // Bitmap Menus
 
class CMainFrame : public CMDIFrameWnd
...
public:
        void ComRealTimeBuffered();
        void ComRealTimeScroll();
        BCMenu m_default;
        BCMenu m_menu;
...
 
Step 3: Add function NewMenu to class CMainFrame (Notice menu bitmaps are from toolbars): -
HMENU CMainFrame::NewMenu()
{
        static UINT toolbars[]=
        {
               IDR_MAINFRAME,
               IDR_TOOLBAR
        };
 
        // Load the menu from the resources
        m_menu.LoadMenu(IDR_SCOPETYPE); 
 
        // One method for adding bitmaps to menu options is
        // through the LoadToolbars member function. This method
        // allows you to add all the bitmaps in a toolbar object
        // to menu options (if they exist). The first function
        // parameter is an array of toolbar id's. The second is
        // the number of toolbar id's. There is also a function
        // called LoadToolbar that just takes an id.
        m_menu.LoadToolbars(toolbars,2);
 
        return(m_menu.Detach());
}
 
Step 4: Add function NewDefaultMenu() to class CMainFrame: -
HMENU CMainFrame::NewDefaultMenu()
{
        m_default.LoadMenu(IDR_MAINFRAME);
        m_default.LoadToolbar(IDR_MAINFRAME);
        return(m_default.Detach());
}
 
Step 5: Edit InitInstance() function in class CScopeApp: -
BOOL CScopeApp::InitInstance()
{
        ...
 
        // create main MDI Frame window
        CMainFrame* pMainFrame = new CMainFrame;
        if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
               return FALSE;
        m_pMainWnd = pMainFrame;
 
        // This code replaces the MFC created menus with
        // Ownerdrawn versions
        pDocTemplate->m_hMenuShared=pMainFrame->NewMenu();
        pMainFrame->m_hMenuDefault=pMainFrame->NewDefaultMenu();
 
        // This simulates a window being opened if you don't have
        // a default window displayed at start-up
        pMainFrame->OnUpdateFrameMenu(pMainFrame->m_hMenuDefault);
 
        ...
 
}
 
Step 6: Add the message handler for the WM_MEASUREITEM message: -
void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
        //This handler ensure that the popup menu items are
        // drawn correctly
 
        BOOL setflag=FALSE;
 
        if(lpMeasureItemStruct->CtlType==ODT_MENU)
        {
               if(IsMenu((HMENU)lpMeasureItemStruct->itemID))
               {
                       CMenu* cmenu =
                       CMenu::FromHandle((HMENU)lpMeasureItemStruct->itemID);
 
                       if(m_menu.IsMenu(cmenu)||m_default.IsMenu(cmenu))
                       {
                                      m_menu.MeasureItem(lpMeasureItemStruct);
                                      setflag=TRUE;
                       }
               }
        }
 
        if(!setflag)CMDIFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
 
Step 7: Add the message handler for the WM_MENUCHAR message: -
LRESULT CMainFrame::OnMenuChar(UINT nChar, UINT nFlags, CMenu* pMenu)
{
        //This handler ensures that keyboard shortcuts work
        LRESULT lresult;
        if(m_menu.IsMenu(pMenu)||m_default.IsMenu(pMenu))
               lresult=BCMenu::FindKeyboardShortcut(nChar, nFlags, pMenu);
        else
               lresult=CMDIFrameWnd::OnMenuChar(nChar, nFlags, pMenu);
        return(lresult);
}
 
Step 8: Add the message handler for the WM_ INITMENUPOPUP message: -
void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
        //This handler updates the menus from time to time
        CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
       
        CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
        if(!bSysMenu)
        {
               if(m_menu.IsMenu(pPopupMenu)||m_default.IsMenu(pPopupMenu))
               BCMenu::UpdateMenu(pPopupMenu);
        }
       
}
 
Step 9: Specify XP menu style for all versions of Microsoft windows and not just XP (Edit BCMenu.cpp): -
#include "stdafx.h"        // Standard windows header file
#include "BCMenu.h"        // BCMenu class declaration
#include <afxpriv.h>       // SK: makes A2W and other spiffy AFX macros work
 
...
 
static CPINFO CPInfo;
// how the menu's are drawn in win9x/NT/2000
UINT BCMenu::original_drawmode=BCMENU_DRAWMODE_XP;   //Use BCMENU_DRAWMODE_ORIGINAL for Original
BOOL BCMenu::xp_select_disabled=FALSE;
// how the menu's are drawn in winXP
UINT BCMenu::xp_drawmode=BCMENU_DRAWMODE_XP;
BOOL BCMenu::original_select_disabled=TRUE;
 
enum Win32Type{
        Win32s,
        Windoze95,
        WinNT3,
        WinNT4orHigher
};
 
...
 
That’s it; the scope program now has professional looking owner drawn MS Office XP style bitmap menus. 
 
Step 10: This class is also applied to the right click pop-up menu: -
void CMainFrame::OnContextMenu(CWnd* pWnd, CPoint point)
{
        BCMenu popmenu;
 
        popmenu.LoadMenu(IDR_GRID_POPUP);
        popmenu.LoadToolbar(IDR_MAINFRAME);
       
        BCMenu *psub = (BCMenu *)popmenu.GetSubMenu(0);
        psub->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this);
        popmenu.DestroyMenu();
}
 

 
7.12. Windows 95/98 Compatibility Problem (scope v1.008a, 20/12/2001)
 
The ‘Scope’ program is being developed on Windows 2000 (service pack 2); Windows 2000 is a stable and reliable operating system, unlike other Microsoft operating systems (e.g. Windows 95, Windows 98 and Windows ME). It is important that the scope program runs on all versions of Microsoft Windows, as Windows 95, 98 and ME are still widely used.
 
On the 20th of December 2001, the scope program (Version 1.008a) was tested on Windows 98 and it was discovered there was a significant problem. The CPU load was high, and the system memory resources were disappearing extremely quickly, after about a minute the system was completely unusable. At first it was not understood why this was happening, as the program worked on Windows 2000 without any problems, with a CPU load of less than 2%. The tests were carried out on the same system (dual boot: Windows 2000 or Window 98 OSR 2), 800MHz AMD processor, 256MB PC133 RAM, and Creative Labs 3D Blaster GeForce 3 Titanium 200 64MB 3D accelerator video card.
 
Figure 7.12a: Screen dump of ‘System Monitor’, ‘Resource Meter’ and ‘Scope’ V1.008a running in Windows 98
 
Figure 7.12a clearly shows that there is a problem; CPU load is high, systems resources are dropping fast and free physical memory has been completely used up. Note that this screen dump was taken after 10 seconds, the system resources continue dropping; after a minute the system resources were less than 10% and the system was completely unusable.
 
The reason why the screen dump (figure 7.12a) was taken after 10 seconds was because the system resources got so low that it was impossible to capture the screen. Screen dump (figure 7.12b) was taken after 40 seconds, notice that it was impossible to get a colour image, after a minute it was completely impossible to capture the screen.
 
Clearly there must be a big memory leak problem.
 
 
Figure 7.12b:
Screen dump of Windows 98 running ‘Scope’ V1.008a taken after 40 seconds
Figure 7.12c: Screen dump of Windows 2000 running ‘Scope’ V1.008a
 
Figure 7.12c clearly shows that Windows 2000 does not have any problems running scope V1.008a, the CPU load is less and 2% and the memory usage is stable. At first it was not understood why it was working in Windows 2000 and not Windows 98.
 
On the 24th December 2001 the problem was solved, there was indeed a memory leak. Every 100ms the screen is refreshed, e.g. a new bitmap is created and the waveform is drawn to the bitmap, this bitmap is then drawn to the screen, but the existing screen bitmap was being lost in memory. Therefore 10-times a second, a bitmap the size of the graphical display was being lost in memory, eating-up memory resources.
 
The solution was to setup a pointer to this old bitmap, and delete the object.
void CScopeView::OnDrawCH1()
{
        ...
        CBitmap *m_bitmapOldCH1 = NULL;
        ...
        if (m_dcCH1.GetSafeHdc() == NULL) // if we don't have one yet, set up a memory dc for ch1
        {
                       m_dcCH1.CreateCompatibleDC(&dc);
                       m_bitmapCH1.CreateDiscardableBitmap(&dc, m_nClientWidth, m_nClientHeight);
                       m_bitmapOldCH1 = m_dcCH1.SelectObject(&m_bitmapCH1);
        }
        else
        {
               m_bitmapCH1.CreateDiscardableBitmap(&dc, m_nClientWidth, m_nClientHeight);
               m_bitmapOldCH1 = m_dcCH1.SelectObject(&m_bitmapCH1);
        }
 
        m_bitmapOldCH1->DeleteObject(); // Make Sure there is not a memory leak
        ...
}
 
 
Figure 7.12d: Screen dump of Windows 98 running ‘Scope’ V1.010a
 
Figure 7.12e: Screen dump of Windows 2000 running ‘Scope’ V1.010a
 
Screen dump (figure 7.12d) clearly shows that this solved the problem, CPU load is now 12%, and system resources (including physical memory) are stable.  Figure 7.12e clearly shows that the modifications did not affect the operation of the ‘scope’ program in Windows 2000, CPU load is less than 2% and memory usage is stable.
 
The reason why ‘Scope’ version 1.008a worked in Windows 2000 is because it is a far superior operating system that can deal with memory leaks, while Windows 98 cannot deal with memory leaks (one of the reasons why it’s so unstable). This experiment clearly demonstrates how much better Windows 2000 is than Windows 98, also notice that the CPU load is much lower in Windows 2000 (<2%) when compared with Windows 98 (≈12%).
 
 

 
7.13. Windows XP Compatibility Problem (scope v1.017a, 05/02/2002)
 
On the 3rd of February 2002 it was discovered that there is a problem when running the scope program in Windows XP, CPU load was high, which increased when the scope display was enlarged. AT first it was not understood why this could be happening as the program had no problems running in Windows 98 and Windows 2000. Note Windows XP running on laptop (AMD 1GHz, 8MB 3D video card, 256MB of PC133 RAM) and Windows 2000/98 dual boot running on desktop (AMD 800MHz, 64MB 3D video card, 256MB of PC133 RAM).
 
The colours settings in the display control panel (Win XP) was changed from 24-bit to 16-bit, this reduced CPU load. But in Windows 2000 the colours are set to 32-bit and the CPU load is less than 2%, and in Windows 98 the colours are also set to 32-bit and the CPU load is less than 10%. Clearly Windows XP is trying to display the scope graphics at full colour specified in the display control panel.
 
The program creates a new bitmap (in XP same number of colours as the system) every time the screen is refreshed and deletes the old bitmap to make sure there is not a memory leak. Clearly this is bad programming practice as it is not necessary to create a new bitmap every time, but only when the screen is resized. The code will be modified in the near future so that this is the case, this will reduce CPU load but the problem remains when the window is being resized. It makes sense to try and also modify the code so that only a 256 colour bitmap is created, and not one with same number of colours as the system, in reality the scope display will use less than 16 colours (no need for 16-bit/24-bit/32-bit colour).
 
The big question is why this problem is not present in Windows 2000, there are two possible reasons: -
 
1.      Windows 2000 may limit the number of colours that can be used by the program, e.g. the scope display is not 32-bit colour as used by the system.
 
2.      The high end Creative Labs 3D Blaster GeForce 3 Titanium 200 64MB (400MHz DDR RAM) 3D accelerator video card in the desktop (running dual boot Windows 98/Windows 2000) is able to do some of the work. Although the scope program is not using DirectX or OpenGL hence this is unlikely.
 
Figure 7.13a. Screen dump of Compatibility option in Win XP
Figure 7.13b. Screen dump of display properties in Win XP
Figure 7.13a shows the compatibility options available for the scope program running in Windows XP; notice the “Run in 256 colours” option. If this option is clicked the program is limited to 256 colours (this works). Figure 7.13b shows the display properties in Window XP, note when running in 16-bit colour mode the scope program runs smoothly.
 
 

7.14. Additional Video Card Features That May Explain Low CPU Usage
 
The scope program when running on the desktop system running Windows 2000 (800MHz AMD processor, 256MB PC133 RAM, and Creative Labs 3D Blaster GeForce 3 Titanium 200 64MB 3D accelerator video card) uses less than 2% of the CPU load. But when tested on a UUJ computer (lab: 6C49, computer: Intel P4 1.5GHz, 256MB RAM, Windows 2000), it was shocking to note that the scope program was using 10% of the CPU, that’s 5-times more than that of the 800Mhz desktop system even though its clock speed is almost half the speed. OK AMD processors are faster than Intel’s (e.g. 800MHz AMD is equal to 1GHz Intel P4), but not by this amount so something else much be making up the difference.
 
Clearly the video card must be accelerating windows graphics as well as direct X, and open GL and may be the reason for the low CPU usage on the desktop, when compared to the laptop and university machines. It was noticed that Geforce 3 video card drivers have added new features to windows that makes it probable that the video card is hardware accelerating standard windows operations.
 
 
Figure 7.14a. Demonstrating the transparent window feature, added to the windows system menu by the video card drivers.
 
Figure 7.14a demonstrates the transparent window feature that was added to the windows system menu by video card drivers. Clearly this feature requires hardware acceleration; hence it is plausible that all windows based drawing is being acceleration by hardware.
Figure 7.14b. nView Menu Extension Options
Figure 7.14c. nView Desktop Manager
 
Clearly the scope program is far too processor hungry and future modification is required to make it more efficient. Allow if 10% of the processor is used for a 1.5GHz machine, this means the program should run on a 150 MHz machine (perhaps poorly). By reducing the refresh rate of the scope display (or using a high-end video card) the scope program could be ran on slower machines, it has been tested on a p90 and runs smoothly with a refresh rate of 200 milliseconds, but the CPU load is at 100% and scope controls are sluggish to react to user input.
 

Final Year Project

Content Page

Colin's Home Page


This Web Page was last updated on Friday June 28, 2002


Home    About me    National Record Of Achievement    Hobbies / Interests   Guest Book    Contact Me    Links    Snooker   Amateur Radio    Site Map


© 2002 Designed by Colin K McCord


This website is best viewed by Microsoft Internet Explorer 6.0 at a resolution of 1024 x 768