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