
Multiplication Grid Generator
After seeing one of my daughters come
home with a simple grid for multiplication I thought that this wasn't
truly teaching multiplication as much as it was showing a
sequence. I mean if the first line is simply 1 x 1, 1 x 2, 1 x 3
and so forth then the answers were just 1,2,3, etc..
So I set out to create a program that
would generate a grid that was scrambled so the student would
absolutely have to think about each answer. The following is
what I came up with.
/********************************************************************************
* *
* C++ PROGRAM *
* *
* Author : Joseph S. Hale *
* Contact : jshale.programmer@gmail.com *
* *
* Purpose : Generate multiplication practice grids in RTF format with*
* visible cell borders, supporting sequential or random headers. *
* *
*******************************************************************************/
#include <windows.h> // Include standard Windows API functions
#include <commctrl.h> // Include common controls library for GUI elements
#include <iostream> // For standard I/O (not heavily used in GUI part)
#include <fstream> // For file stream operations (writing RTF file)
#include <iomanip> // For I/O manipulators (used with time formatting)
#include <vector> // For using std::vector to store headers
#include <random> // For random number generation and shuffling
#include <ctime> // For time functions (used to generate unique ID)
#include <algorithm> // For std::shuffle function
#include <sstream> // For string stream operations
#include <string> // For using std::string
// Define constants for unique control IDs in the Win32 GUI
constexpr int IDC_BTN_GENERATE = 1001;
constexpr int IDC_EDT_TEACHER = 1002;
constexpr int IDC_EDT_START = 1003;
constexpr int IDC_EDT_END = 1004;
constexpr int IDC_CHK_ORDERED = 1005;
// Function prototypes
void writeRTFHeader(std::ofstream& out);
void writeRTFFooter(std::ofstream& out);
void writeMultiplicationTable(std::ofstream& out, const std::vector<int>& rowHeaders,
const std::vector<int>& colHeaders, bool blank, int cellWidth, int headerCellWidth,
const std::string& uniqueID);
std::vector<int> generateShuffledHeaders(int size, int minValue, int maxValue, bool ordered);
std::string generateUniqueID();
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// Helper function to convert std::wstring (used in Windows GUI) to std::string
static std::string narrow_from_wstring(const std::wstring& ws) {
return std::string(ws.begin(), ws.end());
}
// Function to write the standard RTF file header
void writeRTFHeader(std::ofstream& out) {
out << "{\\rtf1\\ansi\\deff0\n"; // Standard RTF header, ANSI character set
out << "{\\fonttbl{\\f0 Arial;}}\n"; // Define font 0 as Arial
// --- FORCE LANDSCAPE ORIENTATION ---
// \landscape must be followed by a section definition (\sectd)
// and custom page width/height swapped. (Dimensions are in twips: 1/20th of a point)
out << "\\landscape\\sectd\\pgwsxn16837\\pghsxn11905\\marglsxn720\\margrsxn720\\margtsxn720\\margbsxn720\n";
out << "\\f0\\fs20\n"; // Set default font (f0: Arial) and font size (fs20: 10pt)
}
// Function to write the standard RTF file footer
void writeRTFFooter(std::ofstream& out) {
out << "\\par}\n"; // End of the last paragraph and closing RTF bracket
}
// Function to generate a unique ID based on the current timestamp
std::string generateUniqueID() {
std::ostringstream oss; // Output string stream
std::time_t t = std::time(nullptr); // Get current time
tm tmStruct; // Time structure
// Use platform-specific safe localtime functions
#if defined(_MSC_VER)
localtime_s(&tmStruct, &t); // Microsoft Visual C++
#else
localtime_r(&t, &tmStruct); // POSIX
#endif
// Format time into a string (YYYYMMDDHHMMSS)
oss << std::put_time(&tmStruct, "%Y%m%d%H%M%S");
return oss.str(); // Return the unique ID string
}
// Function to generate the list of numbers for row/column headers
std::vector<int> generateShuffledHeaders(int size, int minValue, int maxValue, bool ordered) {
std::vector<int> headers; // Vector to hold the numbers
// Populate the vector sequentially from minValue to maxValue
for (int i = minValue; i <= maxValue; ++i) headers.push_back(i);
// If not ordered, shuffle the vector
if (!ordered) {
std::random_device rd; // Obtain a random seed from hardware
std::mt19937 g(rd()); // Standard mersenne_twister_engine seeded with rd()
std::shuffle(headers.begin(), headers.end(), g); // Shuffle the headers
}
return headers; // Return the header list
}
// Function to write the multiplication table into the RTF file
void writeMultiplicationTable(std::ofstream& out, const std::vector<int>& rowHeaders,
const std::vector<int>& colHeaders, bool blank, int cellWidth, int headerCellWidth,
const std::string& uniqueID) {
int size = (int)rowHeaders.size(); // Determine grid size (N x N)
// Write table title, centered and bold
out << "{\\pard\\qc\\b Multiplication Grid " << uniqueID << "\\par}\n";
// Start position of the table relative to the page margin (in twips)
const int tableLeft = 720;
// Top header row definition
out << "{\\trowd\\trgaph108\\trleft" << tableLeft << "\n"; // Start row definition, set table left indent
// Calculate the right boundary of the first cell (corner cell)
int cellPos = tableLeft + headerCellWidth;
// Define borders for the first cell
out << "\\clbrdrt\\brdrs\\brdrw10\\clbrdrl\\brdrs\\brdrw10"
<< "\\clbrdrb\\brdrs\\brdrw10\\clbrdrr\\brdrs\\brdrw10"
<< "\\cellx" << cellPos; // Set right boundary for the first cell
// Remaining cells (column headers)
for (int j = 0; j < size; ++j) {
// Define borders for the current cell
out << "\\clbrdrt\\brdrs\\brdrw10\\clbrdrl\\brdrs\\brdrw10"
<< "\\clbrdrb\\brdrs\\brdrw10\\clbrdrr\\brdrs\\brdrw10";
cellPos += cellWidth; // Advance the cell position by fixed width
out << "\\cellx" << cellPos; // Set right boundary for the cell
}
out << "\n"; // End of table row definition
// Top header row content (centered for headers)
out << "\\qc\\intbl "; // Center content, start first cell
// If it's the blank sheet, put 'X' in the corner cell
if (blank) {
out << "{\\b X}";
}
out << "\\cell "; // End corner cell
// Write column header numbers (bold and centered)
for (int j = 0; j < size; ++j) {
out << "\\qc\\intbl {\\b " << colHeaders[j] << "}\\cell ";
}
out << "\\row}\n"; // End of row
// Data rows (including row headers)
for (int i = 0; i < size; ++i) {
// Redefine row properties for each row (necessary for RTF)
out << "{\\trowd\\trgaph108\\trleft" << tableLeft << "\n";
// Recalculate cell positions for the row definition
cellPos = tableLeft + headerCellWidth;
// First cell (row header) definition
out << "\\clbrdrt\\brdrs\\brdrw10\\clbrdrl\\brdrs\\brdrw10"
<< "\\clbrdrb\\brdrs\\brdrw10\\clbrdrr\\brdrs\\brdrw10"
<< "\\cellx" << cellPos;
// Remaining cells (data cells) definition
for (int j = 0; j < size; ++j) {
out << "\\clbrdrt\\brdrs\\brdrw10\\clbrdrl\\brdrs\\brdrw10"
<< "\\clbrdrb\\brdrs\\brdrw10\\clbrdrr\\brdrs\\brdrw10";
cellPos += cellWidth;
out << "\\cellx" << cellPos;
}
out << "\n"; // End of table row definition
// Row content
// Write row header (bold and centered)
out << "\\qc\\intbl {\\b " << rowHeaders[i] << "}\\cell ";
// Data cells: centered content
for (int j = 0; j < size; ++j) {
if (blank) out << "\\qc\\intbl \\cell "; // Blank cell for practice sheet (centered)
else out << "\\qc\\intbl " << (rowHeaders[i] * colHeaders[j]) << "\\cell "; // Answer cell (centered)
}
out << "\\row}\n"; // End of row
}
}
// -------------------- WinMain & WindowProc --------------------
// Standard Win32 entry point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
const wchar_t CLASS_NAME[] = L"MultiplicationGridWindowClass"; // Window class name
WNDCLASSW wc = {}; // Structure to hold window class attributes
wc.lpfnWndProc = WindowProc; // Pointer to the window procedure function
wc.hInstance = hInstance; // Handle to the application instance
wc.lpszClassName = CLASS_NAME; // Pointer to the class name string
wc.hCursor = LoadCursor(nullptr, IDC_ARROW); // Load the standard cursor
RegisterClassW(&wc); // Register the window class
// Create the main application window
HWND hwnd = CreateWindowExW(
0, // Optional window styles
CLASS_NAME, // Window class name
L"Multiplication Grid Generator", // Window title
WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, // Window style (prevent maximizing)
CW_USEDEFAULT, CW_USEDEFAULT, 500, 300, // Position and size
nullptr, // Parent window handle
nullptr, // Menu handle
hInstance, // Instance handle
nullptr // Additional application data
);
if (!hwnd) return 0; // Check for window creation failure
ShowWindow(hwnd, nCmdShow); // Display the window
UpdateWindow(hwnd); // Send WM_PAINT message
MSG msg; // Structure to hold message information
// Message loop: retrieve and dispatch messages until WM_QUIT is received
while (GetMessageW(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg); // Translate virtual-key messages into character messages
DispatchMessageW(&msg); // Dispatch message to the window procedure
}
return (int)msg.wParam; // Return the exit code
}
// Window procedure function to handle window messages
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
// Static handles for persistent GUI controls
static HWND hTeacher = nullptr, hStart = nullptr, hEnd = nullptr, hButton = nullptr, hCheckOrdered = nullptr;
switch (uMsg) {
case WM_CREATE: // Message sent when the window is first created
// Create Static Text (Label) for Teacher Name
CreateWindowW(L"STATIC", L"Teacher Name:", WS_VISIBLE | WS_CHILD, 20, 20, 110, 20, hwnd, nullptr, nullptr, nullptr);
// Create Edit Control for Teacher Name
hTeacher = CreateWindowW(L"EDIT", L"", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL, 140, 20, 280, 22, hwnd, (HMENU)IDC_EDT_TEACHER, (HINSTANCE)GetWindowLongPtrW(hwnd, GWLP_HINSTANCE), nullptr);
// Create Static Text (Label) for Start Number
CreateWindowW(L"STATIC", L"Start Number:", WS_VISIBLE | WS_CHILD, 20, 55, 110, 20, hwnd, nullptr, nullptr, nullptr);
// Create Edit Control for Start Number (default "1")
hStart = CreateWindowW(L"EDIT", L"1", WS_VISIBLE | WS_CHILD | WS_BORDER, 140, 55, 100, 22, hwnd, (HMENU)IDC_EDT_START, (HINSTANCE)GetWindowLongPtrW(hwnd, GWLP_HINSTANCE), nullptr);
// Create Static Text (Label) for End Number
CreateWindowW(L"STATIC", L"End Number:", WS_VISIBLE | WS_CHILD, 20, 90, 110, 20, hwnd, nullptr, nullptr, nullptr);
// Create Edit Control for End Number (default "12")
hEnd = CreateWindowW(L"EDIT", L"12", WS_VISIBLE | WS_CHILD | WS_BORDER, 140, 90, 100, 22, hwnd, (HMENU)IDC_EDT_END, (HINSTANCE)GetWindowLongPtrW(hwnd, GWLP_HINSTANCE), nullptr);
// Create Checkbox for Sequential Order
hCheckOrdered = CreateWindowW(L"BUTTON", L"Sequential Order", WS_VISIBLE | WS_CHILD | BS_AUTOCHECKBOX, 260, 90, 150, 22, hwnd, (HMENU)IDC_CHK_ORDERED, (HINSTANCE)GetWindowLongPtrW(hwnd, GWLP_HINSTANCE), nullptr);
// Create Generate Button (Default Push Button)
hButton = CreateWindowW(L"BUTTON", L"Generate RTF Grid", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, 140, 130, 200, 36, hwnd, (HMENU)IDC_BTN_GENERATE, (HINSTANCE)GetWindowLongPtrW(hwnd, GWLP_HINSTANCE), nullptr);
return 0; // Handled WM_CREATE
case WM_COMMAND: // Message sent by controls when an event occurs
// Check if the source is the Generate button
if (LOWORD(wParam) == IDC_BTN_GENERATE) {
// Buffers to hold text from edit controls
wchar_t teacherBuf[256] = {}, startBuf[32] = {}, endBuf[32] = {};
// Get text from Teacher Name edit box
GetWindowTextW(hTeacher, teacherBuf, (int)std::size(teacherBuf));
// Get text from Start Number edit box
GetWindowTextW(hStart, startBuf, (int)std::size(startBuf));
// Get text from End Number edit box
GetWindowTextW(hEnd, endBuf, (int)std::size(endBuf));
std::wstring wTeacher(teacherBuf); // Convert to wstring
std::string teacher = narrow_from_wstring(wTeacher); // Convert to string
int minVal = 1, maxVal = 12; // Default values
try {
// Convert Start/End text to integers
minVal = std::stoi(std::wstring(startBuf));
maxVal = std::stoi(std::wstring(endBuf));
}
catch (...) {
// Catch conversion errors
MessageBoxW(hwnd, L"Start and End must be valid integers.", L"Input Error", MB_OK | MB_ICONERROR);
return 0;
}
// Input validation: check range and order (1 to 15, Start <= End)
if (minVal < 1 || maxVal < minVal || maxVal > 15) {
MessageBoxW(hwnd, L"Numbers must be integers between 1 and 15, with Start <= End.", L"Input Error", MB_OK | MB_ICONERROR);
return 0;
}
int gridSize = maxVal - minVal + 1; // Calculate the size of the grid (e.g., 12 for 1-12)
std::string uniqueID = generateUniqueID(); // Generate a unique ID for the file
// Check the state of the Sequential Order checkbox
bool ordered = (SendMessageW(hCheckOrdered, BM_GETCHECK, 0, 0) == BST_CHECKED);
// Generate row and column headers based on input and order flag
std::vector<int> rowHeaders = generateShuffledHeaders(gridSize, minVal, maxVal, ordered);
std::vector<int> colHeaders = generateShuffledHeaders(gridSize, minVal, maxVal, ordered);
// Sanitize teacher name for use in the filename
std::string safeTeacher = teacher;
for (char& c : safeTeacher) if (c == ' ' || c == '/') c = '_';
// Construct the output filename
std::string filename = safeTeacher.empty() ?
"multiplication_grid_" + uniqueID + ".rtf" :
safeTeacher + "_multiplication_grid_" + uniqueID + ".rtf";
// Open the output file stream in binary mode
std::ofstream outfile(filename, std::ios::binary);
if (!outfile) {
MessageBoxW(hwnd, L"Error opening file.", L"File Error", MB_OK | MB_ICONERROR);
return 0;
}
// Define fixed cell widths (in twips)
const int cellWidth = 900;
const int headerCellWidth = 900;
// --- Generate Answer Sheet ---
writeRTFHeader(outfile); // Write RTF opening tags and settings
if (!teacher.empty()) outfile << "\\pard " << teacher << "\\par\\par\n"; // Write teacher name
outfile << "{\\b Multiplication Answers}\\par\n"; // Write heading
// Write table with answers (blank = false)
writeMultiplicationTable(outfile, rowHeaders, colHeaders, false, cellWidth, headerCellWidth, uniqueID);
outfile << "\\page\n"; // Insert a page break
// --- Generate Practice Sheet ---
if (!teacher.empty()) outfile << "\\pard " << teacher << "\\par\\par\n"; // Write teacher name
// Write student name line and instructions
outfile << "\\pard\\tx720 Student Name________________________\\par\\par\n";
outfile << "\\pard\\tx720 Multiply the number on the left by the number at the top to find the answer.\\par\\par\n";
outfile << "\\pard\\qc {\\b Student Practice Grid}\\par\\par\n";
// Write table with blank cells (blank = true)
writeMultiplicationTable(outfile, rowHeaders, colHeaders, true, cellWidth, headerCellWidth, uniqueID);
writeRTFFooter(outfile); // Write RTF closing tags
outfile.close(); // Close the file stream
// Display success message
std::wstring wmsg = L"Grid generated:\n";
std::wstring wfilename(filename.begin(), filename.end());
wmsg += wfilename.c_str();
MessageBoxW(hwnd, wmsg.c_str(), L"Success", MB_OK);
}
return 0; // Handled WM_COMMAND
case WM_DESTROY: // Message sent when the window is being closed
PostQuitMessage(0); // Send the WM_QUIT message to exit the message loop
return 0; // Handled WM_DESTROY
default:
// Default message processing for any unhandled messages
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
}
* *
* C++ PROGRAM *
* *
* Author : Joseph S. Hale *
* Contact : jshale.programmer@gmail.com *
* *
* Purpose : Generate multiplication practice grids in RTF format with*
* visible cell borders, supporting sequential or random headers. *
* *
*******************************************************************************/
#include <windows.h> // Include standard Windows API functions
#include <commctrl.h> // Include common controls library for GUI elements
#include <iostream> // For standard I/O (not heavily used in GUI part)
#include <fstream> // For file stream operations (writing RTF file)
#include <iomanip> // For I/O manipulators (used with time formatting)
#include <vector> // For using std::vector to store headers
#include <random> // For random number generation and shuffling
#include <ctime> // For time functions (used to generate unique ID)
#include <algorithm> // For std::shuffle function
#include <sstream> // For string stream operations
#include <string> // For using std::string
// Define constants for unique control IDs in the Win32 GUI
constexpr int IDC_BTN_GENERATE = 1001;
constexpr int IDC_EDT_TEACHER = 1002;
constexpr int IDC_EDT_START = 1003;
constexpr int IDC_EDT_END = 1004;
constexpr int IDC_CHK_ORDERED = 1005;
// Function prototypes
void writeRTFHeader(std::ofstream& out);
void writeRTFFooter(std::ofstream& out);
void writeMultiplicationTable(std::ofstream& out, const std::vector<int>& rowHeaders,
const std::vector<int>& colHeaders, bool blank, int cellWidth, int headerCellWidth,
const std::string& uniqueID);
std::vector<int> generateShuffledHeaders(int size, int minValue, int maxValue, bool ordered);
std::string generateUniqueID();
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// Helper function to convert std::wstring (used in Windows GUI) to std::string
static std::string narrow_from_wstring(const std::wstring& ws) {
return std::string(ws.begin(), ws.end());
}
// Function to write the standard RTF file header
void writeRTFHeader(std::ofstream& out) {
out << "{\\rtf1\\ansi\\deff0\n"; // Standard RTF header, ANSI character set
out << "{\\fonttbl{\\f0 Arial;}}\n"; // Define font 0 as Arial
// --- FORCE LANDSCAPE ORIENTATION ---
// \landscape must be followed by a section definition (\sectd)
// and custom page width/height swapped. (Dimensions are in twips: 1/20th of a point)
out << "\\landscape\\sectd\\pgwsxn16837\\pghsxn11905\\marglsxn720\\margrsxn720\\margtsxn720\\margbsxn720\n";
out << "\\f0\\fs20\n"; // Set default font (f0: Arial) and font size (fs20: 10pt)
}
// Function to write the standard RTF file footer
void writeRTFFooter(std::ofstream& out) {
out << "\\par}\n"; // End of the last paragraph and closing RTF bracket
}
// Function to generate a unique ID based on the current timestamp
std::string generateUniqueID() {
std::ostringstream oss; // Output string stream
std::time_t t = std::time(nullptr); // Get current time
tm tmStruct; // Time structure
// Use platform-specific safe localtime functions
#if defined(_MSC_VER)
localtime_s(&tmStruct, &t); // Microsoft Visual C++
#else
localtime_r(&t, &tmStruct); // POSIX
#endif
// Format time into a string (YYYYMMDDHHMMSS)
oss << std::put_time(&tmStruct, "%Y%m%d%H%M%S");
return oss.str(); // Return the unique ID string
}
// Function to generate the list of numbers for row/column headers
std::vector<int> generateShuffledHeaders(int size, int minValue, int maxValue, bool ordered) {
std::vector<int> headers; // Vector to hold the numbers
// Populate the vector sequentially from minValue to maxValue
for (int i = minValue; i <= maxValue; ++i) headers.push_back(i);
// If not ordered, shuffle the vector
if (!ordered) {
std::random_device rd; // Obtain a random seed from hardware
std::mt19937 g(rd()); // Standard mersenne_twister_engine seeded with rd()
std::shuffle(headers.begin(), headers.end(), g); // Shuffle the headers
}
return headers; // Return the header list
}
// Function to write the multiplication table into the RTF file
void writeMultiplicationTable(std::ofstream& out, const std::vector<int>& rowHeaders,
const std::vector<int>& colHeaders, bool blank, int cellWidth, int headerCellWidth,
const std::string& uniqueID) {
int size = (int)rowHeaders.size(); // Determine grid size (N x N)
// Write table title, centered and bold
out << "{\\pard\\qc\\b Multiplication Grid " << uniqueID << "\\par}\n";
// Start position of the table relative to the page margin (in twips)
const int tableLeft = 720;
// Top header row definition
out << "{\\trowd\\trgaph108\\trleft" << tableLeft << "\n"; // Start row definition, set table left indent
// Calculate the right boundary of the first cell (corner cell)
int cellPos = tableLeft + headerCellWidth;
// Define borders for the first cell
out << "\\clbrdrt\\brdrs\\brdrw10\\clbrdrl\\brdrs\\brdrw10"
<< "\\clbrdrb\\brdrs\\brdrw10\\clbrdrr\\brdrs\\brdrw10"
<< "\\cellx" << cellPos; // Set right boundary for the first cell
// Remaining cells (column headers)
for (int j = 0; j < size; ++j) {
// Define borders for the current cell
out << "\\clbrdrt\\brdrs\\brdrw10\\clbrdrl\\brdrs\\brdrw10"
<< "\\clbrdrb\\brdrs\\brdrw10\\clbrdrr\\brdrs\\brdrw10";
cellPos += cellWidth; // Advance the cell position by fixed width
out << "\\cellx" << cellPos; // Set right boundary for the cell
}
out << "\n"; // End of table row definition
// Top header row content (centered for headers)
out << "\\qc\\intbl "; // Center content, start first cell
// If it's the blank sheet, put 'X' in the corner cell
if (blank) {
out << "{\\b X}";
}
out << "\\cell "; // End corner cell
// Write column header numbers (bold and centered)
for (int j = 0; j < size; ++j) {
out << "\\qc\\intbl {\\b " << colHeaders[j] << "}\\cell ";
}
out << "\\row}\n"; // End of row
// Data rows (including row headers)
for (int i = 0; i < size; ++i) {
// Redefine row properties for each row (necessary for RTF)
out << "{\\trowd\\trgaph108\\trleft" << tableLeft << "\n";
// Recalculate cell positions for the row definition
cellPos = tableLeft + headerCellWidth;
// First cell (row header) definition
out << "\\clbrdrt\\brdrs\\brdrw10\\clbrdrl\\brdrs\\brdrw10"
<< "\\clbrdrb\\brdrs\\brdrw10\\clbrdrr\\brdrs\\brdrw10"
<< "\\cellx" << cellPos;
// Remaining cells (data cells) definition
for (int j = 0; j < size; ++j) {
out << "\\clbrdrt\\brdrs\\brdrw10\\clbrdrl\\brdrs\\brdrw10"
<< "\\clbrdrb\\brdrs\\brdrw10\\clbrdrr\\brdrs\\brdrw10";
cellPos += cellWidth;
out << "\\cellx" << cellPos;
}
out << "\n"; // End of table row definition
// Row content
// Write row header (bold and centered)
out << "\\qc\\intbl {\\b " << rowHeaders[i] << "}\\cell ";
// Data cells: centered content
for (int j = 0; j < size; ++j) {
if (blank) out << "\\qc\\intbl \\cell "; // Blank cell for practice sheet (centered)
else out << "\\qc\\intbl " << (rowHeaders[i] * colHeaders[j]) << "\\cell "; // Answer cell (centered)
}
out << "\\row}\n"; // End of row
}
}
// -------------------- WinMain & WindowProc --------------------
// Standard Win32 entry point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
const wchar_t CLASS_NAME[] = L"MultiplicationGridWindowClass"; // Window class name
WNDCLASSW wc = {}; // Structure to hold window class attributes
wc.lpfnWndProc = WindowProc; // Pointer to the window procedure function
wc.hInstance = hInstance; // Handle to the application instance
wc.lpszClassName = CLASS_NAME; // Pointer to the class name string
wc.hCursor = LoadCursor(nullptr, IDC_ARROW); // Load the standard cursor
RegisterClassW(&wc); // Register the window class
// Create the main application window
HWND hwnd = CreateWindowExW(
0, // Optional window styles
CLASS_NAME, // Window class name
L"Multiplication Grid Generator", // Window title
WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, // Window style (prevent maximizing)
CW_USEDEFAULT, CW_USEDEFAULT, 500, 300, // Position and size
nullptr, // Parent window handle
nullptr, // Menu handle
hInstance, // Instance handle
nullptr // Additional application data
);
if (!hwnd) return 0; // Check for window creation failure
ShowWindow(hwnd, nCmdShow); // Display the window
UpdateWindow(hwnd); // Send WM_PAINT message
MSG msg; // Structure to hold message information
// Message loop: retrieve and dispatch messages until WM_QUIT is received
while (GetMessageW(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg); // Translate virtual-key messages into character messages
DispatchMessageW(&msg); // Dispatch message to the window procedure
}
return (int)msg.wParam; // Return the exit code
}
// Window procedure function to handle window messages
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
// Static handles for persistent GUI controls
static HWND hTeacher = nullptr, hStart = nullptr, hEnd = nullptr, hButton = nullptr, hCheckOrdered = nullptr;
switch (uMsg) {
case WM_CREATE: // Message sent when the window is first created
// Create Static Text (Label) for Teacher Name
CreateWindowW(L"STATIC", L"Teacher Name:", WS_VISIBLE | WS_CHILD, 20, 20, 110, 20, hwnd, nullptr, nullptr, nullptr);
// Create Edit Control for Teacher Name
hTeacher = CreateWindowW(L"EDIT", L"", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL, 140, 20, 280, 22, hwnd, (HMENU)IDC_EDT_TEACHER, (HINSTANCE)GetWindowLongPtrW(hwnd, GWLP_HINSTANCE), nullptr);
// Create Static Text (Label) for Start Number
CreateWindowW(L"STATIC", L"Start Number:", WS_VISIBLE | WS_CHILD, 20, 55, 110, 20, hwnd, nullptr, nullptr, nullptr);
// Create Edit Control for Start Number (default "1")
hStart = CreateWindowW(L"EDIT", L"1", WS_VISIBLE | WS_CHILD | WS_BORDER, 140, 55, 100, 22, hwnd, (HMENU)IDC_EDT_START, (HINSTANCE)GetWindowLongPtrW(hwnd, GWLP_HINSTANCE), nullptr);
// Create Static Text (Label) for End Number
CreateWindowW(L"STATIC", L"End Number:", WS_VISIBLE | WS_CHILD, 20, 90, 110, 20, hwnd, nullptr, nullptr, nullptr);
// Create Edit Control for End Number (default "12")
hEnd = CreateWindowW(L"EDIT", L"12", WS_VISIBLE | WS_CHILD | WS_BORDER, 140, 90, 100, 22, hwnd, (HMENU)IDC_EDT_END, (HINSTANCE)GetWindowLongPtrW(hwnd, GWLP_HINSTANCE), nullptr);
// Create Checkbox for Sequential Order
hCheckOrdered = CreateWindowW(L"BUTTON", L"Sequential Order", WS_VISIBLE | WS_CHILD | BS_AUTOCHECKBOX, 260, 90, 150, 22, hwnd, (HMENU)IDC_CHK_ORDERED, (HINSTANCE)GetWindowLongPtrW(hwnd, GWLP_HINSTANCE), nullptr);
// Create Generate Button (Default Push Button)
hButton = CreateWindowW(L"BUTTON", L"Generate RTF Grid", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, 140, 130, 200, 36, hwnd, (HMENU)IDC_BTN_GENERATE, (HINSTANCE)GetWindowLongPtrW(hwnd, GWLP_HINSTANCE), nullptr);
return 0; // Handled WM_CREATE
case WM_COMMAND: // Message sent by controls when an event occurs
// Check if the source is the Generate button
if (LOWORD(wParam) == IDC_BTN_GENERATE) {
// Buffers to hold text from edit controls
wchar_t teacherBuf[256] = {}, startBuf[32] = {}, endBuf[32] = {};
// Get text from Teacher Name edit box
GetWindowTextW(hTeacher, teacherBuf, (int)std::size(teacherBuf));
// Get text from Start Number edit box
GetWindowTextW(hStart, startBuf, (int)std::size(startBuf));
// Get text from End Number edit box
GetWindowTextW(hEnd, endBuf, (int)std::size(endBuf));
std::wstring wTeacher(teacherBuf); // Convert to wstring
std::string teacher = narrow_from_wstring(wTeacher); // Convert to string
int minVal = 1, maxVal = 12; // Default values
try {
// Convert Start/End text to integers
minVal = std::stoi(std::wstring(startBuf));
maxVal = std::stoi(std::wstring(endBuf));
}
catch (...) {
// Catch conversion errors
MessageBoxW(hwnd, L"Start and End must be valid integers.", L"Input Error", MB_OK | MB_ICONERROR);
return 0;
}
// Input validation: check range and order (1 to 15, Start <= End)
if (minVal < 1 || maxVal < minVal || maxVal > 15) {
MessageBoxW(hwnd, L"Numbers must be integers between 1 and 15, with Start <= End.", L"Input Error", MB_OK | MB_ICONERROR);
return 0;
}
int gridSize = maxVal - minVal + 1; // Calculate the size of the grid (e.g., 12 for 1-12)
std::string uniqueID = generateUniqueID(); // Generate a unique ID for the file
// Check the state of the Sequential Order checkbox
bool ordered = (SendMessageW(hCheckOrdered, BM_GETCHECK, 0, 0) == BST_CHECKED);
// Generate row and column headers based on input and order flag
std::vector<int> rowHeaders = generateShuffledHeaders(gridSize, minVal, maxVal, ordered);
std::vector<int> colHeaders = generateShuffledHeaders(gridSize, minVal, maxVal, ordered);
// Sanitize teacher name for use in the filename
std::string safeTeacher = teacher;
for (char& c : safeTeacher) if (c == ' ' || c == '/') c = '_';
// Construct the output filename
std::string filename = safeTeacher.empty() ?
"multiplication_grid_" + uniqueID + ".rtf" :
safeTeacher + "_multiplication_grid_" + uniqueID + ".rtf";
// Open the output file stream in binary mode
std::ofstream outfile(filename, std::ios::binary);
if (!outfile) {
MessageBoxW(hwnd, L"Error opening file.", L"File Error", MB_OK | MB_ICONERROR);
return 0;
}
// Define fixed cell widths (in twips)
const int cellWidth = 900;
const int headerCellWidth = 900;
// --- Generate Answer Sheet ---
writeRTFHeader(outfile); // Write RTF opening tags and settings
if (!teacher.empty()) outfile << "\\pard " << teacher << "\\par\\par\n"; // Write teacher name
outfile << "{\\b Multiplication Answers}\\par\n"; // Write heading
// Write table with answers (blank = false)
writeMultiplicationTable(outfile, rowHeaders, colHeaders, false, cellWidth, headerCellWidth, uniqueID);
outfile << "\\page\n"; // Insert a page break
// --- Generate Practice Sheet ---
if (!teacher.empty()) outfile << "\\pard " << teacher << "\\par\\par\n"; // Write teacher name
// Write student name line and instructions
outfile << "\\pard\\tx720 Student Name________________________\\par\\par\n";
outfile << "\\pard\\tx720 Multiply the number on the left by the number at the top to find the answer.\\par\\par\n";
outfile << "\\pard\\qc {\\b Student Practice Grid}\\par\\par\n";
// Write table with blank cells (blank = true)
writeMultiplicationTable(outfile, rowHeaders, colHeaders, true, cellWidth, headerCellWidth, uniqueID);
writeRTFFooter(outfile); // Write RTF closing tags
outfile.close(); // Close the file stream
// Display success message
std::wstring wmsg = L"Grid generated:\n";
std::wstring wfilename(filename.begin(), filename.end());
wmsg += wfilename.c_str();
MessageBoxW(hwnd, wmsg.c_str(), L"Success", MB_OK);
}
return 0; // Handled WM_COMMAND
case WM_DESTROY: // Message sent when the window is being closed
PostQuitMessage(0); // Send the WM_QUIT message to exit the message loop
return 0; // Handled WM_DESTROY
default:
// Default message processing for any unhandled messages
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
}

