7.0. The Scope Program

    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.


This Windows based program, graphically displays waveforms without flicker, it has a good user interface that is easy to use, and directly communicates with the PIC. RS232 transport medium has been selected, this medium is easy to program, reliable and every PC comes with at least one RS232 port. But it is slow and will limit the maximum sampling rate in the real-time mode. Therefore it is important that the application is design to be flexible, as its probable that the program will be modified some time in the future for use with another medium (e.g. Parallel, USB, etc…).  

The development of this program is not fully completed, but has progressed sufficiently to display real-time waveforms using various triggering methods, selectable time-base, selectable voltage scales, etc... Storage mode operation and direct control of the PIC has not been implemented yet, as well as some advantaged features like: streaming real-time data to disk, automatic frequency calculation, and the addition of user configurable virtual channels (e.g. VC1 = CH1 + CH2, VC2 = CH1 * CH2, etc…).

Please note that the full source code is contained on the attached CD ROM, as a full printout would require hundreds of pages.  Allow important parts of the source code have been included in this chapter, along with a brief English description on how the code works.


Figure 7.0a. Screen dump of scope.exe (V1.041a, 19/04/2002)

Notice that the display is split into two (see figure 7.0a), the left half displays the waveform and the right half contain the controls. The attached control dialog can be hidden; by right clicking on the waveform display and selecting [Sidebar] or by using the [View] menu (see Figure 7.0c).

Figure 7.0b. Right click menu

At present four channels has been implemented. Data can be received in the real-time mode using the RS232 serial port. The real-time mode communications were first tested using the PIC simulator program, where sinewaves, squarewaves and random waves at variable frequencies and amplitudes were successfully sent to the scope program which correctly displayed the waveforms. (Desktop PC running scope.exe linked to Laptop running sim.exe via RS232 Null mode cable). Once the program was working on the simulator it was tested using a PIC sampling real waveforms, the scope program successfully displayed these waveform correctly, see chapter 11 (testing) for test results at various stages of development.

 

 


Figure 7.0c.
Screen dump of scope.exe with slide bar hidden


Figure 7.0d.
Screen dump demonstrating auto-resize

Notice the small red triangle at the left of the graphical display, this triangle shows the zero point of the displayed waveform. The user using the mouse can click on the triangle and drag it to a new position, hence changing the zero point (see figure 7.0e). Notice if the waveform is too high or too low for the display the top/bottom of the waveform will be cut off.

Figure 7.0e. Three screen dumps of scope.exe demonstrating the change of zero point

Notice the small red triangle at the top of the graphical display, this triangle shows the offset position of the displayed waveform (middle of the screen represents zero offset). The user using the mouse can click on the triangle and drag it to a new position, hence changing the waveform offset (see figure 7.0f). Notice that if the waveform is offset to the right, it is possible to see data before the waveform triggered. This feature is mainly useful for helping in the measurement of waveform period time (use in calculation of frequency) where the waveform can be scrolled so that a peak is directly in line with a division line, making actuate measurement easy.

Figure 7.0f. Three screen dumps of scope.exe demonstrating the change of waveform offset

Also notice that the waveform triggers on a low to high edge (software trigger), this is user adjustable in the [Trig] tab of the controls dialog (see figure 7.0g). Notice the user has many options, level triggering means that a trigger occurs when the waveform rises from below to above the user configurable offset level for positive edged triggering and the waveform falls from above to below the user configurable offset level for negative edged triggering. Edge triggering means that the program detects the positive or negative edges of any waveform and at any voltage level, this guarantees that the waveform triggers without the need for user intervention, unlike level mode were the waveform must pass through the selected offset level.


 


 

Figure 7.0g. Six screen dumps of scope.exe demonstrating different trigger settings

 

The user can select which channel to trigger off: CH1, CH2, CH3, or CH4. Notice that there is another option called “IND.”, this means independent triggering, where each channel has its one unique trigger point. It is not sure how useful this feature is as generally for dual and quad trace operation waveforms are normally related is some way and timing analysis is normally required. It was decided to include this function for increased program flexibility as this allows for four unrelated waveforms to be monitored simultaneously on the same scope display. Besides it normal practice for commercial products to have useless features (Bells and Whistles) which will probably never be used in practice, but helps give their product an edge over the competition.

The [Disp] tab of the controls dialog can enable/disable channels simply by clicking the tick box beside each channel, change scope time-base and channel voltage scales (see figure 7.0h). Notice in figure 7.0h there are two enabled channels both of which have zero offset, hence the offset triangle for both channels are in exactly the same place (this is clear due to the colour of the triangle). This is not a problem (e.g. offset of both channels does not change if user clicks on the offset triangle) as channel 1 has absolute priority, hence only channel 1 offset will change separating the channel offsets. Note channel 2 has priority over channel 3 and channel 3 has priority over channel 4. A quick way of moving channel 4 offset for example, if all four channel are enabled and the offset of each is in the same place, is to temporarily disabled channels 1 to 3, move channel 4 offset and enable channels 1 to 3 again.

Figure 7.0h. Two screen dumps of scope.exe demonstrating different display settings

 

Figure 7.0i. [Sample] Tab of controls dialog

The [Sample] tab of the controls dialog sets the sample rate, mode, and input range of the scope.

Note at present the ‘control’ communication protocol has not been implemented, hence the selected sample rate will not actually change the rate that the PIC is sampling. But it is important that the correct sample rate is selected as this is used in the calculation of the time-base, else the time-base selected in the [Disp] tab will be incorrect.

Note the input range is selected here, it was expected that this information is sent to the PIC to adjust the gain of the op-amps, but this has not been implement yet. Allow it is important that the correct input range is selected as this changes the binary representation of voltage (e.g. for a range of -2.5 to +2.5V, 0V = -2.5, 512 = 0V, 1024 = 2.5V and for a range of -5 to +5V, -5V = 0, 0V = 512, 5V = 1024).

Sampling mode is also selected here: Real-time scroll mode means that the waveform is sampled using the real-time mode, but stored onto the internal channel arrays in such away that the waveform appears to scroll across the screen (low frequencies only, e.g. useful for ECG monitor). Real-time buffered mode means that data is buffered before copying to the internal channels arrays and is triggered so that the waveform appears to be stable on the display (e.g. useful for periodical waveforms). Storage mode has not been implemented yet, recall that the PIC samples to RAM and then transmits the data to the PC in one large block with added CRC.

The grid configuration dialog box is shown in figure 7.0j; X specifies the number of horizontal squares (1 to 99), Y specifies the number of vertical squares (1 to 99). Note each square has 10 points; hence a maximum resolution of 999 by 999 is possible (assuming Window’s desktop resolution is larger than this). The default grid resolution is 10 x 8 (100 x 80), but 20 x 16 (200 x 160) is probably better suited for quad channel operation. Note the higher the grid resolution the more processor hungry the scope program becomes, hence extremely high resolutions are only recommend for high speed modern computers (e.g. 500MHz or above). 


 Figure 7.0j.
Grid configuration dialog box

The refresh rate specifies the time in milliseconds between screen refreshes. For example 200 milliseconds is the default, that’s a refresh rate of 1/0.2 = 5 Hz. Some traditional analogue engineers who are use working with CRT displays will think that this refresh rate is too low and will result in the screen flickering. This is not the case, unlike CRT displays, the screen holds the current display contains between refreshes, and only redraws the changes made to the waveform when the screen is refreshed (perhaps this should have been called the ‘update rate’ rather than the ‘refresh rate’). The actual refresh rate is that specified by Windows, which for a modern PC is above 100Hz for a CRT display and 50/60Hz for a TFT display.

Note the lower this refresh rate (update rate) the more processor hungry the scope program becomes. A 200 milliseconds update rate will require a PC running at a minimum of 200MHz and at 50 milliseconds update rate will probably require a PC running at least 800MHz to run smoothly, depending on the selected grid resolution and type of video card in the system (hardware acceleration).

The scope program was designed so that multiple scope windows can be displayed; each one with its own selected channels enabled, impendent triggering, time-bases and scales. But it was not sure how useful this feature might be and it may have caused problems during the development process; hence it was considered this feature may be dropped on the finished application. Fortunately this did not cause as many problems as first expected and it was decided to keep this feature, as it may have some use for specialist applications and it’s another one of those features (“Bells and Whistles”) that sets this project apart from other similar products on the market.


Figure 7.0k. Screen dump scope.exe demonstrating multiple scope views (Window’s screen resolution was 1600 x 1200dpi)

Figure 7.0k demonstrates the multiple windows feature, there are 5 views: The top left display, shows all four channels triggering on CH1 at a time-base of 50ms/DIV. The middle left display, shows CH1, with a time-base of 10ms/DIV. The bottom left display, shows CH4, with a 10ms/DIV time-base. The top right display shows CH1 and CH3 at a time-base of 0.1s/DIV. The bottom right display shows all four channels at an extremely high resolution at a time-base of 5ms/DIV and triggering off CH2. The only problem is that all scope windows have the same name “Untilled”, clearly this needs to be improved (e.g. “Untilled1”, “Untilled2”, etc…) as this is confusing to user, the reason for this is because the default automatic naming of the windows was overwritten so that filenames could be placed on the title bar. There is no limit on how many windows that can be opened simultaneously, perhaps until the computer runs out of memory, which on a modern PC could be 1000s of windows.

The scope program is also capable of exporting the scope display to the windows clipboard in the form of a bitmap and exporting the scope display to a bitmap file (*.bmp). The scope program also has its own file format (*.sdf) for saving and opening waveform data and scope settings. Note functionally for printing the scope display directly from the scope program is not fully complete, some additional work is required in this area.


Figure 7.0l. Screen dump of about dialog


Figure 7.0m. Screen dump of about configuration

 


Figure 7.0n. Screen dump of debug  menu


Figure 7.0o. Screen dump of ASCP

The scope program is capable of generating software waveforms (see figure 7.0n), this was used to test the graphical display and triggering methods before the simulation program and PIC hardware was ready for testing. Note this menu will be removed in the final released version.

Another view exists (figure 7.0o), but has not been implement. It was expected that the user would enter a file name and press [Rec] to stream real-time data to disk and press [Play] to play back recorded real-time data.

When in pause mode the scroll bar labelled ‘position’ would have been used to manually scroll through the record data.

The tick boxes for channels 1 to 4 were to be used for telling the PIC which channel to sample. The reason why the enable / disable channels (in [Disp] tab) could not be used for this task is because multiple scope windows can be opened with different channels enable / disable hence a global set of enable / disable channels is required to specify which channels to sample.

 

 


7.1. Data Flow Diagram


7.1.1. Data Flow Paths

The “Data Flow Diagram” shows how data flows between classes, but it does not give any details on the type of data being transferred. The following is detailed information about data flow in all numbered paths shown on the “Data Flow Diagram”: -

Path 1: CControlsP2 ßà CScopeDoc

int m_TimeBaseSelection
int m_nCH3_VOLTS

int m_nCH1_VOLTS
int m_nCH4_VOLTS

int m_nCH2_VOLTS

 

Path 2: CControlsP3 ßà CScopeDoc

int m_nTrigger
int m_nTriggerFormat

int m_nTriggerOffsetLevel

int m_nTriggerType

 

Path 3: CMainFrame ßà CScopeApp

int m_nSampleMode
int m_nCH1Volt[10004]
int m_nCH4Volt[10004]

int m_nBufferSize
int m_nCH2Volt[10004]
int m_nInputRange

int m_nPOS
int m_nCH3Volt[10004]
int m_SampleRateSelection

 

Path 4: CScopeView ßà CGridConfigDlg

int x
COLORREF m_ColorGridBackGround;

int y
m_nRefreshRate

 

Path 5: ChildFrame ßà CScopeView

m_bShowPanel

 

Path 6: CGridConfigDlg ßà CScopeApp

m_nBufferSize

 

Path 7: CScopeView ß CScopeDoc

int m_TimeBaseSelection int m_nCH3_VOLTS

int m_nCH1_VOLTS
int m_nCH4_VOLTS

int m_nCH2_VOLTS
int m_nTriggerType

int m_nTrigger
bool m_bCH1_ENABLE
bool m_bCH4_ENABLE

int m_nTriggerOffsetLevel
bool m_bCH2_ENABLE

int m_nTriggerFormat
bool m_bCH3_ENABLE

 

Path 8: CScopeView ß CScopeApp

int m_nCH1Volt[10004]
int m_nCH4Volt[10004]

int m_nCH2Volt[10004]

int m_nCH3Volt[10004]

 

Path 9: CScopeDoc ßà *.sdf File

pApp-> m_nCH1Volt[10004]
pApp->m_nCH4Volt[10004]
SView->m_nCH1POS
SView->m_nCH4POS
SView->m_nCH3_Offset
pApp->m_nSampleMode
bool m_bCH2_ENABLE
int m_TimeBaseSelection
int m_nCH3_VOLTS
int m_nTriggerType

pApp->m_nCH2Volt[10004]
SView->x
SView->m_nCH2POS
SView->m_nCH1_Offset
SView->m_nCH4_Offset
pApp->m_SampleRateSelection
bool m_bCH3_ENABLE
int m_nCH1_VOLTS
int m_nCH4_VOLTS
SView->m_ColorGridBackGround

pApp->m_nCH3Volt[10004]
SView->y
SView->m_nCH3POS
SView->m_nCH2_Offset
pApp->m_nInputRangebool
m_bCH1_ENABLE
bool m_bCH4_ENABLE
int m_nCH2_VOLTS
int m_nTrigger
SView->m_nRefreshRate

 

Path 10: CScopeDoc ßà CScopeApp

int m_nCH1Volt[10004]
int m_nCH4Volt[10004]
int m_SampleRateSelection

int m_nCH2Volt[10004]
int m_nInputRange

int m_nCH3Volt[10004]
int m_nSampleMode


7.2. The Grid

Figure 7.2a. Grid layout

The grid design is shown in figure 7.2a, there are 10 vertical and 10 horizontal points per square. The standard resolution has 10 horizontal squares and 8 vertical squares.

The grid is dynamic, flexible and is one of the key aspects of the scope program.

If the scope window is resized, the grid will automatically resize. The grid resolution is also user configurable; allow the resolution of each square is fixed the number of vertical and horizontal squares are user configurable.

There are two important arrays that must be filled after the scope window has been resized or the grid resolution has been changed (m_nGridY & m_nGridX), these arrays are used to access individual grid points.

For example the grid layout shown in figure 7.2a, has 100 horizontal (X) points and 80 vertical (Y) points. The scope window size is retrieved from the system and stored in variables m_nClientWidth and m_nClientHeight. m_nClientWidth is divided by 100 (X) and m_nClientHeight is divided by 80 (Y), this gives the gap in pixels between each grid point (note variables have been converted to floating point numbers for increased accuracy). The array m_nGridX[n] is filled by looping from n = 1 to n = 100 (X) and multiplying n by the gap between the horizontal grid points (convert back to data-type int after multiplication has taken place). The array m_nGridY[n] is filled by looping from n = 1 to n = 80 (Y) and multiplying n by the gap between the vertical grid points (convert back to data-type int after multiplication has taken place). Note there is also a configurable boarder offset (nBD) that must be included in the calculation 

The two arrays can now be used as a coordinate system to access any part of the grid, for example the bottom left edge of the grid is at coordinate m_nGridX[1] by m_nGridY[1] and the top right edge of the grid is at coordinate m_nGridX[100] by m_nGridY[80]. Drawing of the grid and waveform traces is achieved using this coordinate system, the main advantage of this is that it does not matter what size the scope display is, the only thing that needs to change is the values contain within the coordinate arrays (extremely flexible).

Function PerpareGrid() fills the grid coordinate arrays: -

void CScopeView::PrepareGrid()

{

        double fW, fH;

        int n;

        int nBD = 10; /* Border */

        fW = ((double)(m_nClientWidth-(nBD*2)))/((double)x);
        fH = ((double)(m_nClientHeight-(nBD*2)))/((double)y);

        m_nGridX[0] = nBD;
        m_nGridY[0] = nBD;

        for (n = 1; n <=x;n++)

        {
               m_nGridX[n] = ((int)(fW * n))+nBD;

        }

        for (n = 1; n <=y;n++)

        {
                m_nGridY[n] = ((int)(fH * n))+nBD;

        }

}

 

Function OnDrawGrid() draws the grid using the grid coordinate system: -

void CScopeView::OnDrawGrid()

{

     /* ----------------------------------------------------------
        | Fuction:     void CScopeView::OnDrawGrid()              |
        | Description: Draws grid in memory then updates display. |
        | Return:      Void.                                      |
        | Date:        09/12/2001                                 |
        | Verison:     1.4                                        |
        | By:          Colin McCord                               |
        ----------------------------------------------------------- */

        int n,i;
        CClientDC dc(this);
        CBitmap m_bitmapGrid, *m_bitmapOldGrid = NULL;

        // if we don't have one yet, set up a memory dc for the grid
        if (m_dcGrid.GetSafeHdc() == NULL)    m_dcGrid.CreateCompatibleDC(&dc);

        m_bitmapGrid.CreateCompatibleBitmap(&dc, m_nClientWidth, m_nClientHeight);
        m_bitmapOldGrid = m_dcGrid.SelectObject(&m_bitmapGrid);
        m_bitmapOldGrid->DeleteObject(); // Make sure there is not a memory leak

        /* If the pen is not setup, do not continue */
        if(penGrid == NULL) return;

        /* Select the grid pen for use with the DC */
        CPen* pOldPen = m_dcGrid.SelectObject(penGrid);

        CBrush Brush;
        Brush.CreateSolidBrush(m_ColorGridBackGround);       // Grid Back Colour
        m_dcGrid.FillRect(&m_ClientRect,&Brush);

        /* Draw Border */
        m_dcGrid.MoveTo(m_nGridX[0],m_nGridY[0]);
        m_dcGrid.LineTo(m_nGridX[x],m_nGridY[0]);
        m_dcGrid.LineTo(m_nGridX[x],m_nGridY[y]);
        m_dcGrid.LineTo(m_nGridX[0],m_nGridY[y]);
        m_dcGrid.LineTo(m_nGridX[0],m_nGridY[0]);

        /* Draw V Gray Lines */
        for (n=10; n < x;n=n+10)
        {      
               for(i = m_nGridY[0]; i<m_nGridY[y];i++) m_dcGrid.SetPixel(m_nGridX[n],i,GRAY);
        }

        /* Draw H grey line */
        for (n=10; n < y; n=n+10)
        {
               for(i = m_nGridX[0]; i<m_nGridX[x];i++) m_dcGrid.SetPixel(i,m_nGridY[n],GRAY);
        }

        /* Draw Centre Axis */
        m_dcGrid.MoveTo(m_nGridX[x/2],m_nGridY[0]);
        m_dcGrid.LineTo(m_nGridX[x/2],m_nGridY[y]);
        m_dcGrid.MoveTo(m_nGridX[0],m_nGridY[y/2]);
        m_dcGrid.LineTo(m_nGridX[x],m_nGridY[y/2]);

        /* Draw V Ticks on H Centre */
        for (n=2; n < x; n=n+2)
        {
               m_dcGrid.MoveTo(m_nGridX[n],m_nGridY[(y/2)-1]);
               m_dcGrid.LineTo(m_nGridX[n],m_nGridY[(y/2)]);
        }

 

        /* Draw H Ticks on V Center */
        for (n=2; n < y; n=n+2)
        {
               m_dcGrid.MoveTo(m_nGridX[(x/2)],m_nGridY[n]);
               m_dcGrid.LineTo(m_nGridX[(x/2)+1],m_nGridY[n]);    
        }

 

        m_dcGrid.SelectObject(pOldPen);

        /* Update Display */

        Invalidate(TRUE);

}


7.3. The Traces

The drawing of the traces looks extremely complex when looking at the big picture as channel offsets, zero positions, time-base, sample rate, triggering method, channel disabled/enabled and voltage scales all affect how the scope traces are drawn. Allow the basic principles behind the drawing of the waveforms are simple, so let’s try and describe how the waveform traces are drawn step-by-step.

There are four large arrays (one for each channel) in class CScopeApp: -

int m_nCH1Volt[10000]; // Voltage * 1000
int m_nCH2Volt[10000]; // Voltage * 1000
int m_nCH3Volt[10000]; // Voltage * 1000
int m_nCH4Volt[10000]; // Voltage * 1000

These arrays are global and can be accessed from any part of the program. Sampled readings for each channel are stored in these arrays; filled by the communication protocol, note the method used to fill these arrays is depended on which mode is selected e.g. for scroll mode every location is moved one placed to the left and the new reading is placed at the end of the array.  Note the 10-bit ADC readings have already been converted into voltage depending on the selected input voltage range and multiplied by 1000 to increase resolution without having to use floating point numbers (e.g. for range -10 to 10V: -10V = -10000, 0V = 0 and 10V = 10000).

The basic principle is that the program continuously scans through these arrays updating the display at a user configurable refresh rate.

Software interrupt timer for redrawing traces: -

void CScopeView::OnTimer(UINT nIDEvent)
{

        if (nIDEvent == nTimerRefresh)
        {
               KillTimer(nTimerRefresh);

               Trigger();     /** Cal Trigger Points */

               /* Set scales */
               m_nVPD_CH1 = pDoc->m_nCH1_VOLTS;
               m_nVPD_CH2 = pDoc->m_nCH2_VOLTS;
               m_nVPD_CH3 = pDoc->m_nCH3_VOLTS;
               m_nVPD_CH4 = pDoc->m_nCH4_VOLTS;

               /** Draw Traces */
               OnDrawCH1();
               OnDrawCH2();
               OnDrawCH3();
               OnDrawCH4();

               /* Enable Timer again */
               SetTimer(nTimerRefresh,m_nRefreshRate,NULL);

        }
        CView::OnTimer(nIDEvent);

}

The first thing to note is that the timer is stopped while executing the OnTimer() function and started again once the redraw is finished. The reason for this is to reduce the chance of the program locking up because of accumulating delays caused by the refresh rate selected by the user being shorter than the time required to redraw the display. In reality the true refresh rate is not the rate specified by the user, but the time taken to redraw the display plus the rate specified by the user, this insures that the program does not lockup especially when running on slow PCs (<200 MHz).

Function trigger() is called to find the trigger point for all channels, then the channel voltages scales are set to that selected by the combo box, then the channel traces are drawn.

This function calculates the trigger point: -

void CScopeView::Trigger()

{
        int a = pDoc->m_nTriggerOffsetLevel;

        /* CH1 Trigger */
        for(int i = 1;i <= 1000;i++)
        {
               if (pDoc->m_nTriggerType == 0)
               {
                       if(pDoc->m_nTriggerFormat == 1) a = pApp->m_nCH1Volt[i-1];  // Edge Triggered

                       /* trigger POS to NEG */
                       if (pApp->m_nCH1Volt[i] >= a && pApp->m_nCH1Volt[i+1] < a)
                       {

                               m_nTriggerCH1 = i;                              

                              /* Disable trigger for scroll mode */

                               if (pApp->m_nSampleMode == 0) m_nTriggerCH1 = 0; // disbale trigger

                               i = 1001;

                               break;
                       }

               }

               else
               {
                       if(pDoc->m_nTriggerFormat == 1) a = pApp->m_nCH1Volt[i-1];  // Edge Triggered

                       /* trigger NEG to POS */
                       if (pApp->m_nCH1Volt[i] <= a && pApp->m_nCH1Volt[i+1] > a)
                       {
                              m_nTriggerCH1 = i;

                              /* Disable trigger for scroll mode */
                              if (pApp->m_nSampleMode == 0) m_nTriggerCH1 = 0; // disbale trigger

                              i = 1001;
                              break;
                       }
               }
        }
 

         ...

        NOTE channel 2-4 trigger point calculated the same way as channel 1

        ...

        switch (pDoc->m_nTrigger)
        {

               case 0: // trig on CH1
                              m_nTriggerCH2 = m_nTriggerCH1;
                              m_nTriggerCH3 = m_nTriggerCH1;
                              m_nTriggerCH4 = m_nTriggerCH1;
                               break;

               case 1: // trig on CH2
                              m_nTriggerCH1 = m_nTriggerCH2;
                              m_nTriggerCH3 = m_nTriggerCH2;
                              m_nTriggerCH4 = m_nTriggerCH2;
                              break;

               case 2: // trig on CH3
                               m_nTriggerCH1 = m_nTriggerCH3;
                               m_nTriggerCH2 = m_nTriggerCH3;
                               m_nTriggerCH4 = m_nTriggerCH3;
                               break;

               case 3: // trig on CH4
                               m_nTriggerCH1 = m_nTriggerCH4;
                               m_nTriggerCH2 = m_nTriggerCH4;
                               m_nTriggerCH3 = m_nTriggerCH4;
                               break;
        }
}

Detection of the trigger point is simple; basically the program scans the array and looks for a high-to-low or a low-to-high edge. The location of which is stored (m_nTriggerCH1..4), this producer is carried out for all four channels. If a specific channel is selected as the trigger, the trigger points of the other channels are made equal to the trigger point of the selected channel.

A memory DCs and pen is required for each trace: -

CPen* penCH1;
CDC   m_dcCH1;

CDC   m_dcCH2;
CPen* penCH2;

CDC   m_dcCH3;
CPen* penCH3;

CDC   m_dcCH4;
CPen* penCH4;

 

Variables are initialised in the CScopeView constructor: -

CScopeView::CScopeView()

{
        m_bitmapCH1 = NULL;
        m_bitmapCH2 = NULL;
        m_bitmapCH3 = NULL;
        m_bitmapCH4 = NULL;

        m_nClientHeight= 0;
        m_nClientWidth = 0;
        x = 100;
        y = 80;

        /** Zero Point Positions **/
        m_nCH1POS = 40;
        m_nCH2POS = 20;
        m_nCH3POS = 50;
        m_nCH4POS = 60 

        m_nCH1_Offset = 0;
        m_nCH2_Offset = 0;
        m_nCH3_Offset = 0;
        m_nCH4_Offset = 0;
 
        m_nTriggerCH1 = 0;
        m_nVPD_CH1 = 100; /* 1 voltage per division */
        m_nVPD_CH2 = 100;
        m_nVPD_CH3 = 100;
        m_nVPD_CH4 = 100;
 
        penGrid =NULL;
        penCH1 = NULL;
        penCH2 = NULL;
        penCH3 = NULL;
        penCH4 = NULL;
 
        m_bChangeZeroCH1 = false;
        m_bChangeZeroCH2 = false;
        m_bChangeZeroCH3 = false;
        m_bChangeZeroCH4 = false;
 
        m_bChangeOffsetCH1 = false;
 
        m_nRefreshRate = 200; // Default refresh rate 200ms
}

 

Refresh timer, trace colours, grid colour and the grid background colour are setup here: -
void CScopeView::OnInitialUpdate()
{
        CView::OnInitialUpdate();
        pDoc =(CScopeDoc*)m_pDocument;
        pApp = (CScopeApp*)AfxGetApp();
 
        SetTimer(nTimerRefresh,m_nRefreshRate,NULL);
 
        penGrid = new(CPen);
        penGrid->CreatePen(PS_SOLID,1,WHITE);
        penCH1 = new(CPen);
        penCH1->CreatePen(PS_SOLID,2,LIGHTRED);
        penCH2 = new(CPen);
        penCH2->CreatePen(PS_SOLID,2,LIGHTGREEN);
        penCH3 = new(CPen);
        penCH3->CreatePen(PS_SOLID,2,LIGHTBLUE);
        penCH4 = new(CPen);
        penCH4->CreatePen(PS_SOLID,2,YELLOW);
 
        m_ColorGridBackGround = BLACK;
 
        OnDrawGrid();
}

 

This function is called when a channel voltage scale combo box is changed: -
void CControlsP2::OnChange()
{
        CString temp;
 
        /** Setup Channel 1 Volts per Dev **/
        GetDlgItemText(IDC_CH1_VOLT, temp);
        if (temp == _T("10mV/DIV")) pDoc->m_nCH1_VOLTS = 1;
        if (temp == _T("20mV/DIV")) pDoc->m_nCH1_VOLTS = 2;
        if (temp == _T("50mV/DIV")) pDoc->m_nCH1_VOLTS = 5;
        if (temp == _T(".1V/DIV")) pDoc->m_nCH1_VOLTS = 10;
        if (temp == _T(".2V/DIV")) pDoc->m_nCH1_VOLTS = 20;
        if (temp == _T(".5V/DIV")) pDoc->m_nCH1_VOLTS = 50;
        if (temp == _T("1V/DIV")) pDoc->m_nCH1_VOLTS = 100;
        if (temp == _T("2V/DIV")) pDoc->m_nCH1_VOLTS = 200;
        if (temp == _T("5V/DIV")) pDoc->m_nCH1_VOLTS = 500;
        if (temp == _T("10V/DIV")) pDoc->m_nCH1_VOLTS = 1000;
        if (temp == _T("20V/DIV")) pDoc->m_nCH1_VOLTS = 2000;
        if (temp == _T("50V/DIV")) pDoc->m_nCH1_VOLTS = 5000;
       

        ...
       
Channel 2-4 done in exactly the same way as channel 1
        ...

}

 

 
This function prepares the traces: -
void CScopeView::PrepareTraces()
{
/* -----------------------------------------------------------
   | Fuction:     void CScopeView::PrepareTraces()           |
   | Description: Creates trace memory DCs and bitmaps.      |
   | Return:      Void.                                      |
   | Date:        15/03/2002                                 |
   | Verison:     1.1                                        |
   | By:          Colin McCord                               |
   ----------------------------------------------------------- */
 
        CClientDC dc(this);    // Screen DC.
 
        /*** Channel 1 ****/
        CBitmap *m_bitmapOldCH1 = NULL;       // Pointer to old display bitmap
                             
        m_bitmapCH1 = new CBitmap;    // Create a new bitmap
 
        /** Create new bitmap and delete old, (Channel 1) **/
        if (m_dcCH1.GetSafeHdc() == NULL) m_dcCH1.CreateCompatibleDC(&dc);
 
        m_bitmapCH1->CreateCompatibleBitmap(&dc, m_nClientWidth, m_nClientHeight);
        m_bitmapOldCH1 = m_dcCH1.SelectObject(m_bitmapCH1);
        m_bitmapOldCH1->DeleteObject(); // Make Sure there is not a memory leak
 

        ...

        Channels 2-4 prepared in exactly the same way as channel 1

        ...

}

 
This function draws Channel 1: -
void CScopeView::OnDrawCH1()
{
/* -----------------------------------------------------------
   | Fuction:     void CScopeView::OnDrawCH1()               |
   | Description: Draws CH1 waveform, in memory then updates |
   |              display.                                   |
   | Return:      Void.                                      |
   | Date:        04/04/2002                                 |
   | Verison:     1.9                                        |
   | By:          Colin McCord                               |
   ----------------------------------------------------------- */
 
        CClientDC dc(this);
        int i,n,y1,y2,a=1,m_Skip, m_MUX;
        CBrush Brush;                               
        Brush.CreateSolidBrush(BLACK);  // Set brush colour to black
        CRect CRectTEMP;
 
        // Channel 1 Member DC not setup yet
        if (m_dcCH1.GetSafeHdc() == NULL)
        {
               PrepareTraces();
        }
       
        /* If channel 1 pen is not created yet, do not continue */
        if (penCH1 == NULL) return;
 
        /* Select CH1 pen */
        CPen* pOldPen = m_dcCH1.SelectObject(penCH1);
 
        /* Clear display*/
        m_dcCH1.SetBkColor(BLACK);
        m_dcCH1.FillRect(&m_ClientRect,&Brush);
 
        /* If Channel 1 is disabled do not continue */
        if (pDoc->m_bCH1_ENABLE == false)
        {
               m_dcCH1.SelectObject(pOldPen);
               Invalidate(TRUE); // Redraw Display
               return;
        }
 
 
        if (pDoc->m_TimeBaseMUX < 0)  // Increase Timebase
        {
               m_Skip = pDoc->m_TimeBaseMUX * -1;
               m_MUX  = 1;
        }
        else                          // Reduce Timebase
        {
               m_Skip = 1;
               m_MUX = pDoc->m_TimeBaseMUX;
        }
 
/* Draw waveform to memory DC */
        for(i = m_nTriggerCH1-(m_nCH1_Offset*m_MUX/m_Skip);i <= 10000; i= i+m_MUX)
        {      
               if(m_Skip >5000) break;       // Check for possible overflow
 
               y1 = m_nCH1POS+pApp->m_nCH1Volt[i-m_MUX]/m_nVPD_CH1;
               if (y1 > y) y1 = y;
               if (y1 < 0) y1 = 0;
               if (a-m_Skip<0) a= m_Skip;
               if(i >=0) m_dcCH1.MoveTo(m_nGridX[a-m_Skip],m_nGridY[y1]);
              
               y2 = m_nCH1POS+pApp->m_nCH1Volt[i]/m_nVPD_CH1;
               if (y2 > y) y2 = y;
               if (y2 < 0) y2 = 0;
               if(i >=0) m_dcCH1.LineTo(m_nGridX[a],m_nGridY[y2]);
 
               a = a + m_Skip;
 
               if ( a > x)
               {
                       i = 10001;
                       break;
               }
        }
 
        /***  draw zero point Arrow */
        m_dcCH1.MoveTo(8,m_nGridY[m_nCH1POS]);
        m_dcCH1.LineTo(2,m_nGridY[m_nCH1POS]-5);
        m_dcCH1.LineTo(2,m_nGridY[m_nCH1POS]+5);
        m_dcCH1.LineTo(8,m_nGridY[m_nCH1POS]);
 
        /* Draw Zero point dotted line */
        for(i = 10; i<m_nGridX[x]-3;i+=6)
        {
               for(n=0;n<=3;n++)      m_dcCH1.SetPixel(i+n,m_nGridY[m_nCH1POS],LIGHTRED);
        }
 
        /*** draw offset Arrow */
        m_dcCH1.MoveTo(m_nGridX[x/2+m_nCH1_Offset],8);
        m_dcCH1.LineTo(m_nGridX[x/2+m_nCH1_Offset]-5,2);
        m_dcCH1.LineTo(m_nGridX[x/2+m_nCH1_Offset]+5,2);
        m_dcCH1.LineTo(m_nGridX[x/2+m_nCH1_Offset],8);
 
        m_dcCH1.SelectObject(pOldPen);
        Invalidate(TRUE); // Redraw Display
}

The code marked in red is the actual drawing of the waveform; basically the array is scanned and drawn to the channel DC (m_dcCH1) using the grid coordinate arrays m_nGridX[] and m_nGridY[] . The code looks more complex because of the following variables:  m_nTriggerCH1 specifies the array trigger offset, m_nCH1_Offset specifies the channel offset, m_Skip specifies the number of array locations to skip to achieve the required time-base, m_MUX specifies the number of array location to duplicate to achieve the required time-base, and m_nVPD_CH1 sets the voltage scale. Note drawing of the zero point and offset arrows are also drawn in this function and all four channels are drawn in exactly the same way.

This function is called by the system when the scope display is resized: -
void CScopeView::OnSize(UINT nType, int cx, int cy)
{
        CView::OnSize(nType, cx, cy);
 
        m_nClientWidth = cx;
        m_nClientHeight = cy;
 
        m_ClientRect.bottom = 0;
        m_ClientRect.left = 0;
        m_ClientRect.right = m_nClientWidth;
        m_ClientRect.top = m_nClientHeight;
 
PrepareGrid();
        PrepareTraces();
        OnDrawGrid();
        OnDrawCH1();
        OnDrawCH2();
        OnDrawCH3();
        OnDrawCH4();
}
 
Function OnSize() first retrieves the new size of the scope display from the system and updates the m_nClientRect variable. Then it calls function PrepareGrid() to recalculate and fill the grid coordinate arrays. Then prepares the traces, redraws the grid and all of the traces.
 

 
7.4. Storing and Restoring the Previous Position of the Main Frame.
 
Basically the window size and position is saved to the windows registry before the application closes and restored when the program starts up.
 
Two functions used for saving and restoring windows position: -
/////////////////////////////////////////////////////////////////////////////
// saving/restoring window state
 
static TCHAR BASED_CODE szSection[] = _T("Settings");
static TCHAR BASED_CODE szWindowPos[] = _T("WindowPos");
static TCHAR szFormat[] = _T("%u,%u,%d,%d,%d,%d,%d,%d,%d,%d");
 
static BOOL PASCAL NEAR ReadWindowPlacement(LPWINDOWPLACEMENT pwp)
{
        CString strBuffer = AfxGetApp()->GetProfileString(szSection, szWindowPos);
        if (strBuffer.IsEmpty())
               return FALSE;
 
        WINDOWPLACEMENT wp;
        int nRead = _stscanf(strBuffer, szFormat,
               &wp.flags, &wp.showCmd,
               &wp.ptMinPosition.x, &wp.ptMinPosition.y,
               &wp.ptMaxPosition.x, &wp.ptMaxPosition.y,
               &wp.rcNormalPosition.left, &wp.rcNormalPosition.top,
               &wp.rcNormalPosition.right, &wp.rcNormalPosition.bottom);
 
        if (nRead != 10)
               return FALSE;
 
        wp.length = sizeof wp;
        *pwp = wp;
        return TRUE;
}
 
static void PASCAL NEAR WriteWindowPlacement(LPWINDOWPLACEMENT pwp)
        // write a window placement to settings section of app's registry
{
        TCHAR szBuffer[sizeof("-32767")*8 + sizeof("65535")*2];
 
        wsprintf(szBuffer, szFormat,
               pwp->flags, pwp->showCmd,
               pwp->ptMinPosition.x, pwp->ptMinPosition.y,
               pwp->ptMaxPosition.x, pwp->ptMaxPosition.y,
               pwp->rcNormalPosition.left, pwp->rcNormalPosition.top,
               pwp->rcNormalPosition.right, pwp->rcNormalPosition.bottom);
        AfxGetApp()->WriteProfileString(szSection, szWindowPos, szBuffer);
}
 
/////////////////////////////////////////////////////////////////////////////
 
This function restores window position from windows registry: -
void CMainFrame::InitialShowWindow(UINT nCmdShow)
{
        /** Restore window position from registry, by CKM 05/02/2002 **/
        WINDOWPLACEMENT wp;
        if (!ReadWindowPlacement(&wp))
        {
               ShowWindow(nCmdShow);
               return;
        }
        if (nCmdShow != SW_SHOWNORMAL) wp.showCmd = nCmdShow;
        SetWindowPlacement(&wp);
        ShowWindow(wp.showCmd);
}
 
Function CMainFrame::InitialShowWindow() is called from CScopeApp::InitInstance(): -
BOOL CScopeApp::InitInstance()
{
...
 
        // The main window has been initialized, so show and update it.
        pMainFrame->InitialShowWindow(m_nCmdShow);
        pMainFrame->UpdateWindow();
 
        return TRUE;
}
 
Before the window is destroyed its position is saved in the windows registry: -
void CMainFrame::OnClose()
{
        // before mainframe it is destroyed, save the position of the window
        WINDOWPLACEMENT wp;
        wp.length = sizeof wp;
        if (GetWindowPlacement(&wp))
        {
               wp.flags = 0;
               if (IsZoomed())
                       wp.flags |= WPF_RESTORETOMAXIMIZED;
               // and write it to the windows registry
               WriteWindowPlacement(&wp);
        }      
 
        CMDIFrameWnd::OnClose();
}
 
Note the same technique can be used for saving and restoring the position of the scope display window, but at present multiple scope display windows can be used and it’s not ideal if they all open at exactly the same place.
Figure 7.4a. Screen dump of window registry showing where the window position is stored
 

 
7.5. Setting-Up RS232 Communications
 
Figure 7.5a. Screen dump of the configuration dialog box
The configuration settings are easily configurable using the configuration dialog from menu [Tools] [Configuration]. This dialog is user friendly and easy to use, notice that a tab control was used and at present there is only one tab (communication), additional tabs will be added at a later date (e.g. calibration). Note when the user hits [OK] or [Apply] the configuration settings are saved to the windows registry, [Apply] button is only enabled if changes have been made, and the [Restore Defaults] button is only enabled when the current configuration differs from the defaults, see figure 7.5a.
 
 
 
 
 
 
 
 
When user hits [OK] or [Apply] this function is called: -
void CConfigPage1::Save_Changes()
{
        UpdateData(TRUE);
        CScopeApp* pApp = (CScopeApp*)AfxGetApp();
        pApp->WriteProfileInt("Config","COM",m_Combo_COM.GetCurSel());
        pApp->WriteProfileInt("Config","Baud",m_Combo_Baud.GetCurSel());
        pApp->WriteProfileInt("Config","Byte Size",m_Combo_Byte_Size.GetCurSel());
        pApp->WriteProfileInt("Config","Parity",m_Combo_Parity.GetCurSel());
        pApp->WriteProfileInt("Config","Stop Bits",m_Combo_Stop_Bits.GetCurSel());
        pApp->WriteProfileInt("Config","Timer",GetDlgItemInt(IDC_TIMER));
 
        OnChange();
}
 
Screen dump showing where RS232 configuration is stored: -
Figure 7.5b. Screen dump of windows registry
 
Note direct access to the hardware is not allowed under Windows NT/2000/XP. Interaction with the serial port is achieved through a file handle and various WIN32 communication API’s. This method is Windows 95/98/ME compatible.
 
This function initialises the communications using configuration settings from windows registry: -
void CMainFrame::InitalCom()
{
        /* 24/12/01 by colin mccord */
 
        CScopeApp* pApp = (CScopeApp*)AfxGetApp();
 
        int temp_int;
        DWORD baud;
        BYTE parity, byte, StopBits;
 
        temp_int = pApp->GetProfileInt("Config","COM",D_COM);
        if(temp_int == 0)             m_sComPort = "Com1";
        else if(temp_int == 1)        m_sComPort = "Com2";
        else if(temp_int == 2)        m_sComPort = "Com3";
        else if(temp_int == 3)        m_sComPort = "Com4";
        else                          m_sComPort = "Com1";
 
        temp_int = pApp->GetProfileInt("Config","Baud",D_BAUD);
        if (temp_int == 0)            baud = 1200;
        else if(temp_int == 1)         baud = 2400;
        else if(temp_int == 2)         baud = 4800;
        else if(temp_int == 3)         baud = 9600;
        else if(temp_int == 4)        baud = 14400;
        else if(temp_int == 5)        baud = 19200;
        else if(temp_int == 6)        baud = 32768;
        else if(temp_int == 7)        baud = 38400;
        else if(temp_int == 8)        baud = 57600;
        else if(temp_int == 9)        baud = 115200;
        else baud = 9600;
 
        temp_int = pApp->GetProfileInt("Config","Byte Size",D_BYTE);
        if(temp_int == 0)             byte = 7;
        else if(temp_int == 1)        byte = 8;
        else                          byte = 8;
 
        temp_int = pApp->GetProfileInt("Config","Parity",D_PARITY);
        if(temp_int == 0)             parity = EVENPARITY;
        else if(temp_int == 1)        parity = MARKPARITY;
        else if(temp_int == 2)        parity = NOPARITY;
        else if(temp_int == 3)        parity = ODDPARITY;
        else if(temp_int == 4)        parity = SPACEPARITY;
        else partiy = NOPARITY;
 
        temp_int = pApp->GetProfileInt("Config","Stop Bits",D_STOP);
        if(temp_int == 0)             StopBits = ONESTOPBIT;
        else if(temp_int == 1)        StopBits = ONE5STOPBITS;
        else if(temp_int == 2)        StopBits = TWOSTOPBITS;
        else                          StopBits = ONESTOPBIT;
 
 
        m_hCom = CreateFile(m_sComPort,
               GENERIC_READ | GENERIC_WRITE,
               0,             // exclusive access
               NULL,           // no security
               OPEN_EXISTING,
               0,             // no overlapped I/O
               NULL);          // null template
 
 
        // Check the returned handle for INVALID_HANDLE_VALUE and then
        // set the buffer sizes.
 
        m_bPortReady = SetupComm(m_hCom, 1000, 1000); // set buffer sizes
 
 
        m_bPortReady   = GetCommState(m_hCom, &m_dcb);
        m_dcb.BaudRate = baud;
        m_dcb.ByteSize = byte;
        m_dcb.Parity   = parity;
        m_dcb.StopBits = ONESTOPBIT;
 
        m_bPortReady = SetCommState(m_hCom, &m_dcb);
 
 
        m_bPortReady = GetCommTimeouts (m_hCom, &m_CommTimeouts);
 
        m_CommTimeouts.ReadIntervalTimeout = 3;
        m_CommTimeouts.ReadTotalTimeoutConstant = 3;
        m_CommTimeouts.ReadTotalTimeoutMultiplier = 3;
        m_CommTimeouts.WriteTotalTimeoutConstant = 10;
        m_CommTimeouts.WriteTotalTimeoutMultiplier = 10;
 
        m_bPortReady = SetCommTimeouts (m_hCom, &m_CommTimeouts);
}
 
This function is used to read one character from the RS232 serial port: -
bool CMainFrame::readchr(unsigned char *c)
{
        unsigned char   Buffer[128];
        unsigned long a = 0;
 
        bReadRC = ReadFile(m_hCom, &Buffer, 1, &a, NULL);
 
        if (a == 0) return false;     // No char received
        else
        {
               *c = Buffer[0];
               return true;
        }
}
 
This function is used to transmit one character to the RS232 serial port: -
void CMainFrame::trans(char c)
{
        char buffer[2];
        buffer[0] = c;
        bWriteRC = WriteFile (m_hCom, buffer, 1,&iBytesWritten,NULL);
}
 
 

 
7.6. Solving the Flickering Problem
 
When updating the screen, the screen must not flicker. This problem was solved by drawing the waveform into a memory CDC (bitmap) and then updating the display by drawing only the changes.
 
Code for updating the display (grid and trace already drawn in their CDC): -
void CScopeView::OnPaint()
{
        CPaintDC dc(this); // device context for painting
        CDC memDC;
        CBitmap memBitmap;
        CBitmap* oldBitmap; // bitmap originally found in CMemDC
 
        // no real plotting work is performed here,
        // just putting the existing bitmaps on the client
 
        // to avoid flicker, establish a memory dc, draw to it
        // and then BitBlt it to the client
        memDC.CreateCompatibleDC(&dc) ;
        memBitmap.CreateCompatibleBitmap(&dc, m_nClientWidth, m_nClientHeight);
        oldBitmap = (CBitmap *)memDC.SelectObject(&memBitmap);
 
        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
 
               /* Only paint channels that’s are enabled as this will reduce CPU load */
               if (pDoc->m_bCH1_ENABLE == true) // Only draw if CH1 is enabled
               {
                       memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
                                      &m_dcCH1, 0, 0, SRCPAINT);  //SRCPAINT
               }
 
               if (pDoc->m_bCH2_ENABLE == true) // Only draw if CH2 is enabled
               {
                       memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
                                      &m_dcCH2, 0, 0, SRCPAINT);  //SRCPAINT
               }
 
               if (pDoc->m_bCH3_ENABLE == true) // Only draw if CH3 is enabled
               {
                       memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
                                      &m_dcCH3, 0, 0, SRCPAINT);  //SRCPAINT
               }
 
               if (pDoc->m_bCH4_ENABLE == true) // Only draw if CH4 is enabled
               {
                       memDC.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
                              &m_dcCH4, 0, 0, SRCPAINT);  //SRCPAINT
               }
 
               // finally send the result to the display
               dc.BitBlt(0, 0, m_nClientWidth, m_nClientHeight,
                              &memDC, 0, 0, SRCCOPY);
 
        }
 
         memDC.SelectObject(oldBitmap);
}
 
This function (created by wizard) has been modified to override standard windows refresh features: - 
BOOL CScopeView::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext)
{
        /* Overrides the Normal Refresh, stops flicker */
        static CString className = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW);
 
        return CWnd::Create(className, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext);
}
 
 

 
7.7. Creating the Split Window View
 
This function creates the split view, it is called automatically by the framework: -
BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
        // create a splitter with 1 row, 2 columns
        if (!m_wndSplitter.CreateStatic(this, 1, 2))
        {
               TRACE0("Failed to CreateStaticSplitter\n");
       
               return FALSE;
        }
 
        // add the first splitter pane - the default view in column 0
        if (!m_wndSplitter.CreateView(0, 0,
               pContext->m_pNewViewClass, CSize(350, 100), pContext))
        {
               TRACE0("Failed to create first pane\n");
 
               return FALSE;
        }
 
        // add the second splitter pane - an input view in column 1
        if (!m_wndSplitter.CreateView(0, 1,
               RUNTIME_CLASS(CControlsView), CSize(0, 0), pContext))
        {
               TRACE0("Failed to create second pane\n");
 
               return FALSE;
        }
 
        // activate the input view
        SetActiveView((CView*)m_wndSplitter.GetPane(0,1));
 
        m_wndSplitter.LockBar(TRUE);
 
        m_wndSplitter.SetColumnInfo(1,250,100);
 
        m_bSplitterCreated = TRUE;
        m_bShowPanel = true;
        return TRUE;
}
 
This function is called when the display is resized, it sets the ratio of the split: -
 
void CChildFrame::OnSize(UINT nType, int cx, int cy)
{      
        CMDIChildWnd::OnSize(nType, cx, cy);
        CRect rect;
        GetWindowRect(&rect);
        if( m_bSplitterCreated)  // m_bSplitterCreated set in OnCreateClient
        {
               if (m_bShowPanel == true)
               {
                       if(rect.Width()> 250)
                       {
                              m_wndSplitter.SetColumnInfo(0, rect.Width()-250, 0);
                       }
                       else
                       {
                              m_wndSplitter.SetColumnInfo(0,0, 0);
                       }
 
                       m_wndSplitter.SetColumnInfo(1, 250, 0);
               }
               else
               {
                       m_wndSplitter.SetColumnInfo(0, rect.Width(), 0);
                       m_wndSplitter.SetColumnInfo(1, 0, 0);
               }
 
               m_wndSplitter.RecalcLayout();
        }
}
 
 
This function hides and shows the side panel: -
void CChildFrame::HideShowPanel()
{
        CRect rect;
        GetWindowRect( &rect );
 
        if( m_bSplitterCreated ) 
        {
               if (m_bShowPanel == true) // Hide side panel
               {
                       m_wndSplitter.SetColumnInfo(0, rect.Width(), 0);
                       m_wndSplitter.SetColumnInfo(1, 0, 0);
                       m_bShowPanel = false;
               }      
               else // Show Side Panel
               {
                       if(rect.Width()> 200)
                       {
                              m_wndSplitter.SetColumnInfo(0, rect.Width()-250, 0);
                       }
                       else
                       {
                              m_wndSplitter.SetColumnInfo(0,0, 0);
                       }
                       m_wndSplitter.SetColumnInfo(1, 250, 0);
                       m_bShowPanel = true;
               }
              
               m_wndSplitter.RecalcLayout();
        }
}
 
 

Part B


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