Thursday, February 15, 2018

Use of Windows Controls from C++, Part 2



It is now February 12 following a considerable delay to do my taxes and I have just succeeded in displaying a button on the window of the previous post using non-visual C++.

After displaying the window of the previous post, prior to my timeout, I had numerous unsuccessful tries attempting to display a control on the window.  Yet all I could manage were child windows that did not display in a satisfactory manner. 

The first attempt just displayed the upper-right set of minimize, maximize, and terminate buttons that had strange behaviors when used but could be recovered by hovering where they should be.  Or at least sort of recovered.  The second attempt produced a child window that used all the space within the parent, main window.

After playing with the second child window for a while I gave up and did my taxes.  This morning I returned to my attempt to display a control within the main window and in about an hour was able to do so.

I first found an example for doing a MessageBox and added it which immediately worked as it should.  By luck, I suppose, I added the code for it immediately after creating the main window (after removing the previous code that created the child window within the main window) and before invoking the ShowWindow and UpdateWindow functions that cause the main window to be displayed.

So there was the popup for the MessageBox and after clicking on its OK button the ShowWindow and UpdateWindow fuctions were invoked and the main window was displayed as in the previous post.

This was encouraging but it wasn't what I was looking for since I wanted to display window controls within the main window.  So I again did a search to display a button via C++ without using the visual compiler.  And this time I struck pay dirt immediately.

Again the example showed creating a child window.  The kind of search results that I had discovered before that had just produced an actual window (or the rudiments of a window) when I had tried before.  Only this time it worked.

The search example was
HWND hwndButton = CreateWindow(
    L"BUTTON",  // Predefined class; Unicode assumed
    L"OK",      // Button text
    WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,  // Styles
    10,         // x position
    10,         // y position
    100,        // Button width
    100,        // Button height
    m_hwnd,     // Parent window
    NULL,       // No menu.
    (HINSTANCE)GetWindowLong(m_hwnd, GWL_HINSTANCE),
    NULL);      // Pointer not needed.
which was quite similar to what I had found before.  But, because of my experience with the MessageBox, I added the code immediately after the creation of the main window. 

Using the GNAT GPS compiler the use of the L constructs prior to the "BUTTON" and "OK" text wouldn't compile but removing the L's while using my reference to the parent window did compile as in
    HWND hwndButton = CreateWindow
                      ( "BUTTON",   // Predefined class; Unicode assumed
                        "OK",       // Button text
                        WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
                        10,         // x position
                        10,         // y position
                        100,        // Button width
                        100,        // Button height
                        hMainWnd,   // Parent window
                        NULL,       // no menu
                        (HINSTANCE)GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );     // Pointer not needed.

And, voila!, the window displayed with the button as below when I ran the application.



So now I was finally able to continue with finishing up the Learn to Code GR application in non-visual C++ using a window to obtain the file to be parsed and displaying the results.

Then in the afternoon I found out how to detect the click event and how to associate it with a particular control.  That is, in the callback the uMsg message identifier is 273 indicating WM_COMMAND (where WM_NOTIFY is supposed to another possibility) and the lParam turns out to be the handle of the button.  Therefore, the value of lParam can be compared to a table of created control handles to determine which was clicked. 

Note: The callback for the main window is
LRESULT CALLBACK MainWndProc( HWND hMainWnd,    // handle to window
                              UINT uMsg,        // message identifier
                              WPARAM wParam,    // first message parameter
                              LPARAM lParam )   // second message parameter
so the uMsg parameter is to be checked for WM_COMMAND and, if it is, the lParam is to be checked to determine if it is the handle of the particular control.

To do this I added an array of a struct containing the created control handles along with a supplied name – in this instance "Select File".  Therefore, when the callback receives a WM_COMMAND the array can be searched for the handle and, upon a match, obtain the name supplied for the control to check if the control of the event is the one wanted.  That is,
    case WM_COMMAND:
    {
      for (int c = 0; c < numberOfControls; c++)
      { if (control[c].handle == (HWND)lParam) // found the control
        {
          std::string name = control[c].name;
          if (name == "Select File")
          { if (!fileSelected)
            {
              fileSelected = fileLocate();
              return TRUE;
            }
          }
        }
      } // end for loop
      return 0;
    }

As I knew must be the case and which brought to my attention later when I invoked the parse of the Learn to Code GR file from within the callback, the callback runs in a different thread from the WinMain application.  This is because it is an extension of the Windows operating system passing Windows events to the application.  Therefore, longer running code needs to be done in another thread.  Not that the read of the disk file and the parse of its records takes very long (since the particular file is small) but because it just doesn't belong in the op sys thread and because the callback never returns to the WinMain application thread.

I couldn't find that the GNAT C++ had the OpenFileDialog() function to allow the user to select the file to be parsed.  Since to make my own function would be a significant task – especially while trying to find the windows functions needed for non-visual C++ – I decided to have an edit box as a control into which the user could enter the path.  Perhaps I can do my own version of the function later.

In one day I discovered how to do the code to have bounded and unbounded (what I got to start as the default) edit boxes that I could use to display instructions, have the user fill in as the file path, and announce an error if the entered text didn't result in the file being found to open.  And to clear the error when the correct path was entered.

To do this I had to move the button and change its meaning since ENTER wasn't accepted to terminate the entry of the path.  Therefore, I used the click on the button to indicate that the path had been entered.

I also added an error label (an unbounded edit box) for when the entered path was invalid and a set of labels and text boxes to report the results of parsing the file (assuming the entered file is in the expected format).  These extra edit boxes I made visible only when they were needed.  This is automatic with the error box since it doesn't have a boundary and hence nothing is visible when there isn't any text being displayed.

So after failing to find a way to display controls within the window and only getting funky child windows for over a week, after the wait it all came together in 3 days.

The initial window appears as

The window when an error is displayed appears as




The window after the Learn to Code GR portion of the application has run is


Notice how the labels (the edit boxes without the boundary lines) get clipped along their bottom edge for some reason whereas they are displayed initially with the letters fully formed as can be seen in the first of these three captured windows.  Perhaps this is because the edit boxes with borders have a height of 30 units whereas those without have a height of 15.  As can be seen from the first screen this is sufficient to display the text but perhaps iffy as window updates occur.  (In cleaning up the code to display it below I changed the height of the labels to 20 and the text is no longer clipped along the bottom edge.)


The regular upper right X button could, of course, be used to terminate the application.  The Done button just calls attention to the ability to terminate.

The non-visual C++ code that creates the window and its controls is as follows.

#ifndef KEYTABLECPP
#define KEYTABLECPP

// Key class
class Key
{
  public:
    Key(); // constructor

    int key;        // value of key
    int valueCount; // number of different values associated with key
    static const int VALUESSIZE = 20; // initial size of values & sums
                                      //  arrays for particular key
    int *values;    // will point to the array
    int *sums;      //  "

}; // end class Key

// KeyTable class
class KeyTable //: public Component
{

  public:
    KeyTable(); // constructor

    int getKey(int index);
    int getKeyCount();
    // Return number of different values for key at index
    int getValueCount(int index);

    int setKey(int key);
    int setValue(int index, int value);
    int getValue(int indexKey, int indexValue);
    int incSum(int indexKey, int indexValue);
    int getSum(int indexKey, int indexValue);

  private:
    int keyCount; // number of different keys
    static const int TABLESIZE = 30; // initial size of keys array
    Key* keys = new Key[TABLESIZE];  // instances of "struct" class

}; // end class KeyTable

#endif // KEYTABLECPP

Key and KeyTable class within KeyTable.cpp
#include <iostream>

#include "keytable.h"

Key::Key() // constructor
{
  valueCount = 0;
  key = 0;
  values = new int[VALUESSIZE];
  sums = new int[VALUESSIZE];
  for (int i = 0; i < VALUESSIZE; i++)
  {
    values[i] = -1; // so no match until a value added
    sums[i] = 0;    // so any first increment of the sum will be to 1
  }
} // end Key constructor


KeyTable::KeyTable() // constructor
{
  this->keyCount = 0;
} // end KeyTable constructor

// Return key at index
KeyTable::getKey(int index)
{
  int tempKey;
  if (index < KeyTable::keyCount)
  {
    int key = KeyTable::keys[index].key;
    return key;
  }
  else
  {
    return 0;
  }
} // end getKey

// Return number of keys in table
KeyTable::getKeyCount()
{
  return keyCount;
} // end getKeyCount

KeyTable::getValueCount(int index)
{
  return this->keys[index].valueCount;
} // end getValueCount

KeyTable::setKey(int key)
{
  for (int i = 0; i < keyCount; i++)
  {
    if (this->keys[i].key == key) // key already in table
    {
      return i;
    }
  } // end for loop
  if (this->keyCount < TABLESIZE) // add key to table if room
  {
    this->keys[keyCount].key = key;
    int index = keyCount;
    keyCount++;
    return index;
  }
  else
  {
    std::cout << "Error: Too many different keys" << "\n";
    return -1;
  }
} // end setKey

// Add value for key at index, return value index
KeyTable::setValue(int index, int value)
{
  for (int i = 0; i < this->keys[index].valueCount; i++)
  {
    if ( this->keys[index].values[i] == value )
    {
      // Increment sum
      this->keys[index].sums[i]++;
      return i;
    }
  } // end for loop

  // Add value to array for key since didn't return for existing value
  this->keys[index].values[this->keys[index].valueCount] = value;
  this->keys[index].sums[this->keys[index].valueCount]++;
  int valueIndex = this->keys[index].valueCount;
  this->keys[index].valueCount++; // increment number of values for key
  return valueIndex;
} // end setValue

// Get value for key at indexes, return value
KeyTable::getValue(int indexKey, int indexValue)
{
  return this->keys[indexKey].values[indexValue];
} // end getValue

// Increment Sum for value of key at indexKey and value at indexValue
KeyTable::incSum(int indexKey, int indexValue)
{
  // Increment sum
  int count = this->keys[indexKey].sums[indexValue]++;

  return count;
} // end incSum

// Get sum for key at indexes, return sum
KeyTable::getSum(int indexKey, int indexValue)
{
  return this->keys[indexKey].sums[indexValue];
} // end getSum

WinMain.cpp
// from https://msdn.mircosoft.com/en-us/library/windows/desktop/ms633575(v=vs.85).aspx
// as modified for the WinMain portion of this file.
#include <stdio.h>
#include <iostream>
#include <cstring>

#include "windows.h"
#include "wingdi.h"
#include "winuser.h"

#include "afxres.h"

#include <keytable.h>

// Global variables

HINSTANCE hinst;

HWND hMainWnd;
ATOM mainWindow;

int ncmdShow;

// Structure to keep track of controls created for the window
int numberOfControls = 0; // number of controls created
struct Controls {
  std::string name; // name of control
  HWND handle;      // handle of control
};
Controls control[10];

// Other variables used by both the app thread and the callback thread
BOOL fileSelected = FALSE; // file to parse selected
BOOL fileOpenEvent = TRUE; // only run the Parse, etc once
FILE* selectedFile;

BOOL finished = FALSE; // finished looking at window, time to exit

// Function prototypes.
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);

BOOL StartApp();

// Note: MainWndProc runs in a Windows OpSys thread
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
//       as does fileLocate since called from MainWndProc
BOOL fileLocate(HWND editWnd); // Find file to be used


// Application entry point.
int WINAPI WinMain( HINSTANCE hinstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine,
                    int nCmdShow )
{
  MSG msg;

  ncmdShow = nCmdShow;

  // Create window and the initial controls
  BOOL result;
  result = InitApplication(hinstance);
  if (!result)
    return FALSE;

  if (!InitInstance(hinstance, nCmdShow))
    return FALSE;

  // Main "forever" loop
  BOOL fGotMessage;
  while ((fGotMessage = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0 && fGotMessage != -1)
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);

    if ((fileSelected) & (fileOpenEvent))
    {
      fileOpenEvent = FALSE; // dummy indication that can only be used once
      // Start main portion of the application.
      result = StartApp();
    }

    if (finished)
    {
      break; // exit while loop
    }

  } // end while loop

  // Exit the application
  return msg.wParam;
  UNREFERENCED_PARAMETER(lpCmdLine);

} // end WinMain

// To treat Windows events while executing in a separate thread
LRESULT CALLBACK MainWndProc( HWND hMainWnd,    // handle to window
                              UINT uMsg,        // message identifier
                              WPARAM wParam,    // first message parameter
                              LPARAM lParam )   // second message parameter
{

  PAINTSTRUCT ps;
  HDC hdc;

  switch (uMsg)
  {
    case WM_CREATE:
      // Initialize the window.
      return 0;

    case WM_PAINT:
    {
      // Paint the window's client area.
      return 0;
    }

    case WM_SIZE:
      // Set the size and position of the window.
      return 0;

    case WM_DESTROY:
      // Clean up window-specific data objects.
      return 0;

    case WM_DRAWITEM:
    {
      return 0;
    }

    case WM_SETTEXT:
    {
      return true;
    }

    // WM_COMMAND (control events) and WM_NOTIFY

    case WM_COMMAND:
    { // Output to console indicating when control event occurs
      std::string report = ("WM_COMMAND ");
      report = report + std::to_string(uMsg);
      report = report + " wParam " + std::to_string(wParam);
      report = report + " lParam " + std::to_string(lParam);
      std::cout << report << '\n';

      // Find if control of interest used
      HWND errorHandle = 0;
      for (int c = 0; c < numberOfControls; c++)
      { if (control[c].handle == (HWND)lParam) // found the control
        {
          std::string name = control[c].name;
          if (name == "Select File") // Select File button clicked
          { if (!fileSelected)
            { // Find the handle for the Enter File edit control
              for (int f = 0; f < numberOfControls; f++)
              {
                if (control[f].name == "Enter File")
                {
                  // Pass handle of edit control to read the entered path
                  fileSelected = fileLocate(control[f].handle);
                  for (int e = 0; e < numberOfControls; e++)
                  {
                    if (control[e].name == "Error Label")
                    { // Set handle of error control to either report error or clear
                      errorHandle = control[e].handle;
                      exit;
                    }
                  }
                  if (!fileSelected)
                  { // Output error to error label
                    SetWindowText( errorHandle,
                                    (LPCTSTR)"Invalid File Selected");
                    UpdateWindow(hMainWnd);
                  }
                  else
                  { // Clear error label
                    SetWindowText( errorHandle,
                                   (LPCTSTR)"");
                    UpdateWindow(hMainWnd);
                  }
                  return fileSelected; // remember that these returns are to Windows
                }
              }
              return TRUE;
            }
          } // end Select File

          if (name == "Enter File")
          {
            std::string comment = name;
            std::cout << comment << '\n';
          }

          if (name == "Done")
          {
            finished = TRUE; // set for use by WinMain thread
            return TRUE;
          }

        }
      } // end for loop
      return 0;
    }

    case WM_NOTIFY:
    {
      std::string report = ("WM_NOTIFY ");
      report = report + std::to_string(uMsg);
      std::cout << report << '\n';
      return TRUE;
    }

    // Process other messages.
    default:
      return DefWindowProc(hMainWnd, uMsg, wParam, lParam);
  }
  return 0;

} // end MainWndProc

// Register the main window class
BOOL InitApplication(HINSTANCE hinstance)
{
  WNDCLASSEX wcx;

  // Fill in the window class structure with parameters
  // that describe the main window.
  wcx.cbSize = sizeof(wcx);          // size of structure
  wcx.style = CS_HREDRAW | CS_VREDRAW;     // redraw if size changes
  wcx.lpfnWndProc = (WNDPROC) MainWndProc; // points to window procedure
  wcx.cbClsExtra = 0;                // no extra class memory
  wcx.cbWndExtra = 0;                // no extra window memory
  wcx.hInstance = hinstance;         // handle to instance
  wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);    // predefined app. icon
  wcx.hCursor = LoadCursor(NULL, IDC_ARROW);      // predefined arrow
  wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // white background brush
  wcx.lpszMenuName =  "MainMenu";    // name of menu resource
  wcx.lpszClassName = "MainWClass";  // name of window class
  wcx.hIconSm = (HICON)LoadImage( hinstance, // small class icon
                                  MAKEINTRESOURCE(5),
                                  IMAGE_ICON,
                                  GetSystemMetrics(SM_CXSMICON),
                                  GetSystemMetrics(SM_CYSMICON),
                                  LR_DEFAULTCOLOR );

  // Register the window class.
  mainWindow = RegisterClassEx(&wcx);
  DWORD y;
  if (mainWindow==0)
  { y = GetLastError();
  }

  // Return RegisterClassEx(&wcx);
  return (BOOL)mainWindow;

} // end InitApplication

// Create the main window and the initial controls.
BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
{
  // Save the application-instance handle.
  hinst = hinstance;

  // Create the main window.
  hMainWnd = CreateWindow
             ( "MainWClass",        // name of window class
               "LearnToCodeGR",     // title-bar string
               WS_OVERLAPPEDWINDOW, // top-level window
               CW_USEDEFAULT,       // default horizontal position
               CW_USEDEFAULT,       // default vertical position
               CW_USEDEFAULT,       // default width
               CW_USEDEFAULT,       // default height
               (HWND) NULL,         // no owner window
               (HMENU) NULL,        // use class menu
               hinstance,           // handle to application instance
               (LPVOID) NULL );     // no window-creation data

  if (!hMainWnd)
    return FALSE;

  if (hMainWnd!=0)
  {
    // Create Select File button control.
    HWND hwndSelButton = CreateWindow
                         ( "BUTTON",      // Predefined class; Unicode assumed
                           "Select File", // Button text
                           WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
                           425,           // x position
                           38,            // y position
                           100,           // Button width
                           35,            // Button height
                           hMainWnd,      // Parent window
                           NULL,          // no menu
                           (HINSTANCE)GetWindowLong(hMainWnd, GWL_HINSTANCE),
                           NULL );        // Pointer not needed.
    control[numberOfControls].handle = hwndSelButton; // Capture handle
    control[numberOfControls].name = "Select File";   //  and name of the
    numberOfControls++;                               //  button control

    // Create label with instruction to enter the path of the file to be parsed.
    HWND hLabelWnd = CreateWindow
                     ( "EDIT",          // predefined class
                       "Enter file path and then click Select File", // label text
                       WS_CHILD | WS_VISIBLE | ES_LEFT, // Styles
                       10, 10, 310, 20, // set size and location
                       hMainWnd,        // parent window
                       NULL, //(HMENU) ID_EDITCHILD,   // edit control ID
                       (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                       NULL );          // pointer not needed

    // Create edit box in which the path is to be entered.
    HWND hEditWnd = CreateWindow
                    ( "EDIT",   // predefined class
                      "",       // no initial edit text
                      WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
                      10, 40, 410, 30,  // set size in WM_SIZE message
                      hMainWnd,         // parent window
                      NULL,             // edit control ID
                      (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                      NULL );           // pointer not needed
    control[numberOfControls].handle = hEditWnd;   // Capture handle
    control[numberOfControls].name = "Enter File"; //  and name of the
    numberOfControls++;                            //  button control

    // Create label to be used to display an error for invalid file path.
    HWND hErrorWnd = CreateWindow
                     ( "EDIT",                 // predefined class
                       "                    ", // no initial label text
                       WS_CHILD | WS_VISIBLE | ES_LEFT, // Styles
                       10, 80, 310, 20, // set size and location
                       hMainWnd,        // parent window
                       NULL,            // edit control ID
                       (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                       NULL );          // pointer not needed
    control[numberOfControls].handle = hErrorWnd;   // Capture handle
    control[numberOfControls].name = "Error Label"; //  and name of the
    numberOfControls++;                             //  error control

    // Create button to use to exit the application.
    HWND hwndDoneButton = CreateWindow
                          ( "BUTTON",      // Predefined class; Unicode assumed
                            "Done",        // Button text
                            WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
                            10,            // x position
                            310,           // y position
                            100,           // Button width
                            35,            // Button height
                            hMainWnd,      // Parent window
                            NULL,          // no menu
                            (HINSTANCE)GetWindowLong(hMainWnd, GWL_HINSTANCE),
                            NULL );        // Pointer not needed.
    control[numberOfControls].handle = hwndDoneButton; // Capture handle
    control[numberOfControls].name = "Done";           //  and name of the
    numberOfControls++;                                //  button control


    // Create OK Popup with meaningless title and text.
//  int msgBox;
//  msgBox = MessageBox(hMainWnd, "abc", "cap", 0);

  } // end if for main window created

  // Show the main window and send a WM_PAINT message to the window procedure.

  ShowWindow(hMainWnd, nCmdShow);
  UpdateWindow(hMainWnd);

  return TRUE;
}

// Get the text entered into the edit box and attempt to open the entered path.
// Note: This function is called from the callback so runs in a Windows thread.
BOOL fileLocate(HWND editWnd)
{

  // Read the text from the edit control.
  char path[101];
  for (int i = 0; i < 100; i++) // clear the path array
  {
    path[i] = char(0);
  }
  int count = GetWindowText
              ( editWnd,     // edit control handle
                (LPSTR)path, // string read from control
                100 );       // maximum number of characters to read

  // Attempt to open the file.
  if (count > 0)
  {
    path[count] = char(0);
    selectedFile = fopen(path, "r");

    if (selectedFile == NULL)
    {
      return FALSE;
    }
  }
  else
  {
    return FALSE;
  }

  return TRUE; // indicate that the file was opened

} // end fileLocate

/*
 * From here to the end is the C++ code for the LearnToCodeGR application.
 * This code runs in the WinMain application thread.
 */

KeyTable keyTable; // Instance of KeyTable class object

// Report the key and value with the most references
void Report()
{
  // Report individual results to the console.
  printf("\n");
  printf("Report   ");
  printf("\n");

  for (int i = 0; i < keyTable.getKeyCount(); i++)
  {
    for (int j = 0; j < keyTable.getValueCount(i); j++)
    {
      std::string report = ("Key ");
      report = report + std::to_string(keyTable.getKey(i));
      report = report + (" Value ");
      report = report + std::to_string(keyTable.getValue(i,j));
      report = report + (" Sum ");
      report = report + std::to_string(keyTable.getSum(i,j));
      std::cout << report << '\n';
    } // end for loop
  } // end for loop

  // Items identifying the key with the largest number of a particular value.
  int key = 0;
  int value = 0;
  int sum = 0;

  for (int i = 0; i < keyTable.getKeyCount(); i++)
  {
    for (int j = 0; j < keyTable.getValueCount(i); j++)
    {
      if (keyTable.getSum(i,j) > sum) // save key and value with greater sum
      {
        key = keyTable.getKey(i);
        value = keyTable.getValue(i,j);
        sum = keyTable.getSum(i,j);
      }
    } // end for loop
  } // end for loop

  // Report the key and value with the greatest number of references to the console.
  printf("\n");
  std::string report = ("Key and Value with greatest Sum ");
  report = report + std::to_string(key) + " ";
  report = report + std::to_string(value) + " ";
  report = report + std::to_string(sum);
  std::cout << report << '\n';

  // Create and report the results to window controls.

  // Convert int's to char arrays.
  std::string keyStr = std::to_string(key);
  int size;
  size = keyStr.size();
  char *keyVal = new char[size+1];
  keyVal[size] = 0;
  memcpy(keyVal, keyStr.c_str(), size);

  std::string valueStr = std::to_string(value);
  size = valueStr.size();
  char *valueVal = new char[size+1];
  valueVal[size] = 0;
  memcpy(valueVal, valueStr.c_str(), size);

  std::string sumStr = std::to_string(sum);
  size = sumStr.size();
  char *sumVal = new char[size+1];
  sumVal[size] = 0;
  memcpy(sumVal, sumStr.c_str(), size);

  HWND hResultsWnd = CreateWindow
                     ( "EDIT",           // predefined class
                       "Key and Value with greatest Sum", // label text
                       WS_CHILD | WS_VISIBLE | ES_LEFT,   // Styles
                       10, 150, 300, 20, // set size and location
                       hMainWnd,         // parent window
                       NULL,             // edit control ID
                       (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                       NULL );           // pointer not needed

  HWND hKeyLabelWnd = CreateWindow
                      ( "EDIT",          // predefined class
                        "Key ",          // label text
                        WS_CHILD | WS_VISIBLE | ES_LEFT, // Styles
                        10, 180, 60, 20, // set size and location
                        hMainWnd,        // parent window
                        NULL,            // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );          // pointer not needed
  HWND hKeyValueWnd = CreateWindow
                      ( "EDIT",          // predefined class
                        (LPCTSTR)keyVal, // result for key
                        WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
                        60, 178, 60, 30, // set size in WM_SIZE message
                        hMainWnd,        // parent window
                        NULL,            // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );          // pointer not needed

  HWND hValLabelWnd = CreateWindow
                      ( "EDIT",          // predefined class
                        "Value",         // label text
                        WS_CHILD | WS_VISIBLE | ES_LEFT, // Styles
                        10, 220, 60, 20, // set size and location
                        hMainWnd,        // parent window
                        NULL,            // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );          // pointer not needed
  HWND hValValueWnd = CreateWindow
                      ( "EDIT",           // predefined class
                        (LPCTSTR)valueVal,// result for key
                        WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
                        60, 218, 60, 30,  // set size in WM_SIZE message
                        hMainWnd,         // parent window
                        NULL,             // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );           // pointer not needed

  HWND hSumLabelWnd = CreateWindow
                      ( "EDIT",          // predefined class
                        "Sum ",          // label text
                        WS_CHILD | WS_VISIBLE | ES_LEFT, // Styles
                        10, 260, 60, 20, // set size and location
                        hMainWnd,        // parent window
                        NULL,            // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );          // pointer not needed
  HWND hSumValueWnd = CreateWindow
                      ( "EDIT",          // predefined class
                        (LPCTSTR)sumVal, // result for key
                        WS_CHILD | WS_VISIBLE | ES_LEFT | WS_BORDER,
                        60, 258, 60, 30, // set size in WM_SIZE message
                        hMainWnd,        // parent window
                        NULL,            // edit control ID
                        (HINSTANCE) GetWindowLong(hMainWnd, GWL_HINSTANCE),
                        NULL );          // pointer not needed

  UpdateWindow(hMainWnd); // cause the controls to be displayed in the main window

  return;

} // end Report

// Update keyTable with data from a parsed line
void Update(int nKey, int nValue1, int nValue2)
{
  // This function first checks if the key is new and, if so, adds it to the
  // keys array.  It then does similar for the values associated with it.

  // Check whether the key is already in the table
  int keyIndex = -1;
  int valueIndex = -1;

  int keyCount = keyTable.getKeyCount();
  for (int i = 0; i < keyCount; i++)
  {
    if (keyTable.getKey(i) == nKey)
    {
      keyIndex = i;  // key already in table
      break; // exit loop
    }
  } // end loop
  if (keyIndex < 0) // key not in the table
  {
    keyIndex = keyTable.setKey(nKey); // add key
    valueIndex = keyTable.setValue(keyIndex,nValue1); // add value for key
    if (nValue1 != nValue2)
    { keyTable.setValue(keyIndex,nValue2); }
    else
    { keyTable.incSum(keyIndex,valueIndex); }
  }
  else // key in the table
  {
    int valueCount = keyTable.getValueCount(keyIndex);
    valueIndex = keyTable.setValue(keyIndex,nValue1); // add value for key
    if (nValue1 != nValue2)
    { keyTable.setValue(keyIndex,nValue2); }
    else
    { keyTable.incSum(keyIndex,valueIndex); }
  }

  return;

} // end Update

#define NINE 57 // ASCII character for digit 9
#define ZERO 48 // ASCII character for digit 0

// Convert character array to int
int toInt(char* data, int length) //iS, int iE)
{
  int index = length - 1; // loop in reverse
  int digit;
  int m = 1;      // multiplier for shift
  int number = 0; // Numeric result

  while (index >= 0) //iS)
  {
    digit = data[index] - ZERO; // convert ASCII to digit
    number = number + (m * digit);
    m = m * 10;
    index--;
  }

  return number;

} // end toInt

enum Scan //Fields
{ BYPASS, FKEY, VALUE1, VALUE2 };

// Extract the fields of interest from the file's line and retain the data
void Parse(int count, char* data)
{
  #define CR '\r' // carriage return
  #define HT '\t' // horizontal tab
  #define LF '\n' // line feed
  #define TRUE 1

  // Parse the supplied line of data from the file to obtain the three
  // fields of interest (key and two values), convert those fields to
  // integers, and then update a data structure (the KeyTable class) to
  // retain the data for evaluation when the complete file has been parsed.

  int keyValue = 0;
  int value1Value = 0;
  int value2Value = 0;

  char alphaDigits [10];
  int numDigits = 0;

  bool updateDone = false;

  int offset = 0; // offset into data
  Scan scanPhase = BYPASS;

  // Parse all bytes of record previously read from the file.  Each field of
  // interest is preceded by a horizontal tab.  The end of the final field
  // is marked by a CR LF or only a LF (NL) except the last record where
  // there are no more characters to be treated.
  while (offset < count)
  {
    switch (scanPhase)
    {
      case BYPASS:
        if (data[offset] == HT)
        {
          scanPhase = FKEY;
          keyValue = 0;
          value1Value = 0;
          value2Value = 0;
          numDigits = 0;
        }
        break;

      case FKEY:
        if (data[offset] != HT)
        {
          if ((data[offset] >= ZERO) & (data[offset] <= NINE))
          {
            alphaDigits[numDigits] = data[offset];
            numDigits++;
          }
        }
        else
        {
          keyValue = toInt(alphaDigits, numDigits);
          scanPhase = VALUE1;
          numDigits = 0;
        }
        break;

      case VALUE1:
        if (data[offset] != HT)
        {
          if ((data[offset] >= ZERO) & (data[offset] <= NINE))
          {
            alphaDigits[numDigits] = data[offset];
            numDigits++;
          }
        }
        else
        {
          value1Value = toInt(alphaDigits, numDigits);
          scanPhase = VALUE2;
          numDigits = 0;
        }
        break;

      case VALUE2:
        if ((data[offset] == LF) | (data[offset] == CR) | offset+1 >= count)
        {
          value2Value = toInt(alphaDigits, numDigits);
          // Update tables with the data from the record.
          Update( keyValue, value1Value, value2Value );
          updateDone = true;

          // Initialize for next record
          keyValue = 0;
          value1Value = 0;
          value2Value = 0;
          numDigits = 0;
          scanPhase = BYPASS;
        }
        else
        {
          if ((data[offset] >= ZERO) & (data[offset] <= NINE))
          {
            alphaDigits[numDigits] = data[offset];
            numDigits++;
          }
        }
        break;
    } // end switch

    // Complete processing of final record when not terminated by New Line
    offset++; // increment to next data buffer position
    if (offset >= count) // no more data
    {
      if (value2Value > 0) // last record fully parsed without trailing NL
      {
        if (!updateDone)
        {
          value2Value = toInt(alphaDigits, numDigits);
          Update( keyValue, value1Value, value2Value );
          updateDone = true;
        }
      }
      break; // exit loop
    }

  } // end loop

  return;

} // end Parse


BOOL StartApp()
// Treat the opened the LearnToCodeGR file.
{
  char buffer[30];

  int currPos;
  int lastPos = 0;
  int length;

  // Read each record/line of the file and Parse it.
  while (!feof(selectedFile))
  {
    fgets(buffer, 30, (FILE*)selectedFile);
    currPos = ftell(selectedFile);
    length = currPos - lastPos; // number of characters read into buffer
    lastPos = currPos;
    for (int i=0; i < length; i++) // output the
      printf("%c ", buffer[i]);    //   record to
    printf("\n");                  //   the console

    // Parse the line and update to retain all the necessary data.
    Parse(length, buffer);
  }

  // Close the selected file.
  fclose(selectedFile);

  // Report the results.
  Report();

  printf("return from Report()");
  printf("\n");

  return TRUE;

} // end StartApp


No comments: