Use of Windows
Controls from C++
On January 22 I wondered if a language like C could invoke
JavaScript of an html file or vice versa.
If so, then JavaScript could be used to display Windows forms and
controls while C or some other language could be used for computations, reading
and updating files, and the like with the ability to actually compile the C and
have errors detected via the compile.
I soon decided against that approach since it didn't seem
like cross language usage was possible.
So I thought I would find out how to directly invoke Windows control
functions from C or C++ without the use of a visual compiler.
The next day I found a Microsoft post with sample code to
display a window and spent the day getting it to work using the GNAT compiler for
C and C++. This involved finding where
I could find the needed windows.h header file and working out why the Microsoft
code wouldn't compile.
Another internet search found the answer as to where
windows.h was located. It said "Check it out
under Program Files (x86)\Windows Kits\8.1\Include\um\Windows.h". Doing a Windows Explorer search of the
Program Files (x86) folder produced a number of other folders where I had these
headers on my computer. So I picked the
one with the most recent date and copied it to a subfolder in the folder where
I was building the new application and changed the GNAT project to include it.
However, the Microsoft example wouldn't compile and I found
another function that I needed to include and that some of variables needed to
be typecast to enable GNAT, at least, to successfully compile the project. I also found that the GNAT folders contained
the needed headers that, for some reason, the compiler hadn't used when I first
started.
After that I could run the application and it would display
the window with its three buttons in the upper right corner. And the buttons worked correctly. But the
maximize only had the original portion of the window with a white background
and, after the restore of a minimize, the background was black instead of
white.
Therefore, I neatened up the code (including added code to
find what error I was getting before I solved the last refusal to compile
problem when I had removed one statement).
No matter what I did trying to get an errorless compile with WHITE_BRUSH
mentioned, I couldn't so I used 0 instead where the wingdi.h header defines
WHITE_BRUSH as 0.
The result is as follows.
// from
https://msdn.mircosoft.com/en-us/library/windows/desktop/ms633575(v=vs.85).aspx
// as modified
#include <windows.h>
#include <wingdi.h>
// Global variable
HINSTANCE hinst;
// Function prototypes.
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR,
int);
BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM,
LPARAM);
// Application entry point.
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
BOOL
result;
result =
InitApplication(hinstance);
if
(!result)
return FALSE;
if
(!InitInstance(hinstance, nCmdShow))
return FALSE;
BOOL
fGotMessage;
while
((fGotMessage = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0 &&
fGotMessage != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return
msg.wParam;
UNREFERENCED_PARAMETER(lpCmdLine);
} // end WinMain
LRESULT CALLBACK MainWndProc( HWND hwnd, // handle to window
UINT uMsg, //
message identifier
WPARAM wParam, // first message parameter
LPARAM lParam) // second message parameter
{
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;
//
//
Process other messages.
//
default:
return
DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
} // end MainWndProc
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)0;
// 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.
ATOM x =
RegisterClassEx(&wcx);
DWORD y;
if (x==0)
{ y =
GetLastError();
}
// return
RegisterClassEx(&wcx);
return
(BOOL)x;
} // end InitApplication
BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
{
HWND hwnd;
// Save the
application-instance handle.
hinst =
hinstance;
// Create
the main window.
hwnd =
CreateWindow
(
"MainWClass", // name
of window class
"Sample", //
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 (!hwnd)
return
FALSE;
// Show the
window and send a WM_PAINT message to the window
//
procedure.
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
return
TRUE;
} // end InitInstance
While trying to find a way to repaint the screen after a
resize so that the background would be white again instead of black, I found
out that GNAT had the needed headers all along. So a mystery why it didn't use its own windows.h and wingdi.h
from the start.
I found that I could use
wcx.hbrBackground = (HBRUSH)WHITE_BRUSH; // white background brush
if I didn't use SetStockObject which the GNAT linker
couldn't find.
However, changing the window size with its hide button and
then restoring it or with its full screen button still resulted in a black
background. Black for the extra screen
space in the one case and black after restoring the hidden window in the
other. If the full screen is hidden and
then restored, the entire screen is then black.
I added a case statement for WM_ERASEBKGND to the callback
and fetched the class info. It is
invoked a few times. But each time it
returns that the HBRUSH color for the background is 0 and hence still white
even thou the window is black after the restore from minimize. So the class info attributes are unchanged
but painting the window doesn't seem to be using them after a minimize and
restore or a maximize.
I finally found the answer in another internet search where
c++ - How
to write text into window? - Stack Overflow
https://stackoverflow.com/.../5946596/how-to-write-text-into-window
appeared as one of the options. It had the use of
appeared as one of the options. It had the use of
wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
rather than what the Microsoft example had had. This worked. [Of course, the Microsoft example had had
wcx.hbrBackground = GetStockObject(WHITE_BRUSH);
which didn't work with the GNAT compiler since it couldn't
link in GetStockObject.]
The window was created with a white background. Then after the minimize and restore it was
still white. And upon maximize the
entire screen window was all white.
Problem solved on Thursday afternoon. So a good share of three days overall to
display a window that works correctly using just C++ via the GNAT compiler with
its GPS windows interface.
Next Step:
After getting the display of the window to work correctly I
decided to implement the same application (Learn to Code GR) of my recent posts
in non-visual C++.
As a first step I tried using the OpenFileDialog function of
commdlg.h that allows the user to navigate folders and open a particular file so
that the file wouldn't need to be hard coded.
In the visual C# application, this was done along with a Windows control
to click to enter the event handler code from which this function could be
invoked. So I was thinking of having
such a control on the window. But first
just invoking the function after the window had been displayed.
After numerous attempts I couldn't get a GNAT build to
include the code corresponding to the commdlg.h header.
Therefore, I stopped work on that approach for the time being
and decided to just implement the previous Learn to Code GR application in C++
with the needed file path hard coded in the application.
Since the application is in C++ I used classes to retain the
file data for the different keys, their associated values, and the number of
times a particular value was specified for a key.
The C++ application code is as follows. As with the previous versions of the
application that used languages that support the concept of a class, most of
the code is classless with only the structures to retain the data encoded as a
class. However, unlike the others, this
one only reads the file a record at a time rather than the entire file all at
once. The KeyTable class retains the
data with multiple instances of the Key class for a particular key with its
arrays to retain the key and arrays to retain the various values and the
corresponding number of times the value was referenced (the sums array).
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 <keytable.h>
// Global variable
HINSTANCE hinst;
// Function prototypes.
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR,
int);
BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM,
LPARAM);
BOOL StartApp();
// Application entry point.
int WINAPI WinMain( HINSTANCE hinstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow )
{
MSG msg;
BOOL
fileOpenEvent;
fileOpenEvent = TRUE; // act as if File control clicked
BOOL
result;
result = InitApplication(hinstance);
if
(!result)
return FALSE;
if
(!InitInstance(hinstance, nCmdShow))
return FALSE;
BOOL
fGotMessage;
while
((fGotMessage = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0 &&
fGotMessage != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if
(fileOpenEvent)
{
fileOpenEvent = FALSE; // dummy indication that can only be used once
// Start main portion of the application.
result = StartApp();
}
}
return
msg.wParam;
UNREFERENCED_PARAMETER(lpCmdLine);
} // end WinMain
// To treat Windows events
LRESULT CALLBACK MainWndProc( HWND hwnd, // 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;
//
Process other messages.
default:
return
DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
} // end MainWndProc
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)GetStockObject((int)WHITE_BRUSH); // white background brush
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.
ATOM x = RegisterClassEx(&wcx);
DWORD y;
if (x==0)
{ y =
GetLastError();
}
// return
RegisterClassEx(&wcx);
return
(BOOL)x;
} // end InitApplication
BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
{
HWND hwnd;
// Save the
application-instance handle.
hinst =
hinstance;
// Create
the main window.
hwnd =
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 (!hwnd)
return
FALSE;
// Show the
window and send a WM_PAINT message to the window
//
procedure.
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
return
TRUE;
} // end InitInstance
/*
* From here
to the end is the C++ code for the LearnToCodeGR application.
*/
KeyTable keyTable; // KeyTable class object
// Data to be retained as the key and its associated
value with the maximum
// number of references
int maxKey = 0;
int maxValue = 0;
int maxSum = 0;
int savedData[3];
// Report the key and value with the most references
void Report()
{
// Report
individual results
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
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';
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()
// Open the LearnToCodeGR file and treat it.
{
FILE* file;
char
buffer[30];
file =
fopen("C:\\Source\\LearnToCodeGR\\max-col-sum-by-Key.tsv",
"r");
int
currPos;
int lastPos
= 0;
int length;
if(file ==
NULL)
{
perror("Error opening file");
return
FALSE;
}
else
{ // Read
file records until end-of-file
while
(!feof(file))
{
fgets(buffer, 30, (FILE*)file);
currPos
= ftell(file);
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);
}
fclose(file);
// Report
the results.
Report();
}
return
TRUE;
} // end StartApp
KeyTable.h
#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
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 < 20; 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 < 30) // 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
The window that was created by the initial portion of the
WinMain.cpp file is below followed by the last portion of the console output
from running the application.
Next I will return to my attempt to populate the window with Windows controls using a non-visual version of C++.
No comments:
Post a Comment