I have a loop running that displays data read from a serial port on a console screen, the same screen lists some options for the user, such as the option to enter a file name that data would be logged to, or exit. The user presses the required key to enter the desired option.
After the key press and the option function entered any key presses can be ignored/discarded but I am not able to find a way to discard the key presses/ clear the cin buffer resulting in the key press being shown on the console.
Is there any way to clear the cin buffer without the user having to take any action? Or possibly some better way to provide the same functionality?
A stripped down version of code here:
(this should display the local time while giving the user the option to enter a "file name" or exit, no serial port stuff included)
#include <iostream>
#include <limits>
#include <chrono>
#include <windows.h>
using namespace std;
// global variables
char SavedFileName[20];
HANDLE hConsole;
CONSOLE_SCREEN_BUFFER_INFO csbi;
// functions
void GetTime(void){
char buffer [20];
time_t now = time(NULL);
strftime(buffer , 20, "%H:%M:%S %d/%m/%Y", localtime(&now));
cout << "local time: " << buffer << endl;
}
bool is1keypressed(void){
if(GetAsyncKeyState(49) & 0x8000){
return true;
}
return false;
}
bool is9keypressed(void){
if(GetAsyncKeyState(57) & 0x8000){
return true;
}
return false;
}
int ClearConsole(void){
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SMALL_RECT scrollRect;
COORD scrollTarget;
CHAR_INFO fill;
// Get the number of character cells in the current buffer.
if (!GetConsoleScreenBufferInfo(hConsole, &csbi))
return 0;
// Scroll the rectangle of the entire buffer.
scrollRect.Left = 0;
scrollRect.Top = 0;
scrollRect.Right = csbi.dwSize.X;
scrollRect.Bottom = csbi.dwSize.Y;
// Scroll it upwards off the top of the buffer with a magnitude of the entire height.
scrollTarget.X = 0;
scrollTarget.Y = (SHORT)(0 - csbi.dwSize.Y);
// Fill with empty spaces with the buffer's default text attribute.
fill.Char.UnicodeChar = TEXT(' ');
fill.Attributes = csbi.wAttributes;
// Do the scroll
ScrollConsoleScreenBuffer(hConsole, &scrollRect, NULL, scrollTarget, &fill);
return 0;
}
int main(){
bool exit = false;
// clear the console
ClearConsole();
// main loop
while(!exit){
// Move the cursor to the top left corner too.
csbi.dwCursorPosition.X = 0;
csbi.dwCursorPosition.Y = 0;
SetConsoleCursorPosition(hConsole, csbi.dwCursorPosition);
// prints the local time each on iteration of loop
GetTime();
// user options
cout << "\npress (1) enter file name - saved file name is: " << SavedFileName;
cout << "\npress (9) to exit." << endl;
// test user options
if(is1keypressed()){
cout << "option 1 - press ENTER to proceed " << endl;
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
bool validentry = false;
while(!validentry){
cout << "Enter a file name, max 19 characters: ";
cin.getline(SavedFileName, 20, '\n');
if(cin.fail()){
validentry = false;
cout << endl << "File name is too large, enter 19 characters or less. " << endl;
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}else
validentry = true;
}
ClearConsole();
}
exit = is9keypressed();
}
cout << "\n end program" << endl;
return 0;
}
I have experimented with cin.ignore, this leaves the user to press Enter to proceed. Without cin.ignore the user has to delete the "option" characters themselves.
Okay, Windows console stuff. Only works on Windows.
Playing with this stuff is never as easy as advertised, but for what you want to do, we can make some pretty stripped-down Windows-only code. (If, at some future point, you wish to port to Linux, the design here can be pretty easily swapped-out for similar code on Linux.)
Minimal Windows Console Functions
#include <ciso646>
#include <utility>
#include <windows.h>
namespace console
{
inline auto hStdIn () { return GetStdHandle( STD_INPUT_HANDLE ); }
inline auto hStdOut() { return GetStdHandle( STD_OUTPUT_HANDLE ); }
auto CSBI()
{
CONSOLE_SCREEN_BUFFER_INFO _csbi;
GetConsoleScreenBufferInfo( hStdOut(), &_csbi );
return _csbi;
}
void clear_screen()
// Function
// Clears the screen to blanks using the current text color and puts the
// cursor in the upper-left cell.
{
DWORD _count;
DWORD _cell_count;
COORD _home_coords = { 0, 0 };
auto _csbi = CSBI();
_cell_count = _csbi.dwSize.X * _csbi.dwSize.Y;
if (!FillConsoleOutputCharacter( hStdOut(), (TCHAR)' ', _cell_count, _home_coords, &_count )) return;
if (!FillConsoleOutputAttribute( hStdOut(), _csbi.wAttributes, _cell_count, _home_coords, &_count )) return;
SetConsoleCursorPosition( hStdOut(), _home_coords );
}
void goto_xy( int x, int y )
// Function
// Position the text cursor on the screen.
//
// Arguments
// x - column coordinate, starting at zero from the left.
// y - row coordinate, starting at zero from the top.
{
COORD _coords = { (SHORT)x, (SHORT)y };
SetConsoleCursorPosition( hStdOut(), _coords );
}
bool is_key_pressed( int timeout_ms = 0 )
// Function
// Wait for key input to become available.
//
// Argument
// The maxiumum amount of time to wait, expressed in milliseconds.
// May also be one of the following:
// 0 - return immediately.
// -1 - wait indefinitely.
//
// Returns
// true : If key input is available
// false : If not
{
auto is_key_event_waiting = []()
{
DWORD _n;
INPUT_RECORD _rec;
while (PeekConsoleInputW( hStdIn(), &_rec, 1, &_n) and _n)
{
if (_rec.EventType != KEY_EVENT) continue;
if (_rec.Event.KeyEvent.bKeyDown) return true;
if ((_rec.Event.KeyEvent.wVirtualKeyCode == VK_MENU) and _rec.Event.KeyEvent.uChar.UnicodeChar)
return true;
ReadConsoleInputW( hStdIn(), &_rec, 1, &_n );
}
return false;
};
// Get rid of all unwanted events
if (is_key_event_waiting()) return true;
// Else wait until event available or timeout
if (WaitForSingleObject( hStdIn(), (DWORD)timeout_ms ) != WAIT_OBJECT_0) return false;
// We only want key events
return is_key_event_waiting();
}
char32_t read_key()
// Function
// Wait for and return the next key input available.
//
// Returns
// 0 : next read_key() returns a Windows Virtual Key Code, like `VK_F1` or `VK_UP`
// Unicode code point : any normal input key
{
static char32_t _vkey = 0;
if (_vkey) return std::exchange( _vkey, 0 );
DWORD _n;
INPUT_RECORD _rec;
while (true)
{
ReadConsoleInputW( hStdIn(), &_rec, 1, &_n );
if (_rec.EventType == KEY_EVENT)
{
// Key release events for the ALT/MENU key where UnicodeChar != 0
// means an Alt-code was entered on the numeric keypad. We'll report that.
// All other key release events are ignored.
if (!_rec.Event.KeyEvent.bKeyDown)
if ((_rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU) or !_rec.Event.KeyEvent.uChar.UnicodeChar)
continue;
// Normal Unicode code point
if (_rec.Event.KeyEvent.uChar.UnicodeChar)
return _rec.Event.KeyEvent.uChar.UnicodeChar;
// Special keys and function keys
_vkey = _rec.Event.KeyEvent.wVirtualKeyCode;
return 0;
}
}
}
} // namespace console
Notes:
It works on both the Windows Terminal and on both current and past versions of the Windows Console.
There is no initialization or finalization necessary to use this particular magic.
Assumes a human is present. More robust code would check that the standard streams are attached to a console/terminal at initialization and complain (and quit) if not.
Only one thread should ever be messing with the console, so if you are doing anything multi-threaded, only console:: from one of them, ever. (This is pretty much true of any user-application UI.)
Your program, updated
#include <ctime>
#include <iostream>
#include <string>
std::string SavedFileName;
void DrawMenu(){
console::clear_screen();
console::goto_xy(0, 1);
std::cout << "\npress (1) enter file name - saved file name is: " << SavedFileName;
std::cout << "\npress (9) to exit.\n";
}
void DrawTime(){
char buffer [20];
time_t now = time(NULL);
strftime(buffer , 20, "%H:%M:%S %d/%m/%Y", localtime(&now));
console::goto_xy(0, 0);
std::cout << "local time: " << buffer << " ";
}
int main(){
DrawMenu();
bool done = false;
while(!done){
DrawTime();
if (console::is_key_pressed(500))
switch (console::read_key()){
case '1':
console::goto_xy(0, 5);
std::cout << "option 1\nEnter a file name: ";
getline(std::cin, SavedFileName);
DrawMenu();
break;
case '9':
console::goto_xy(0, 5);
std::cout << "option 2\nReally quit (y/[n])? ";
{
auto c = console::read_key();
if (!c) console::read_key();
else done = (c == 'y') or (c == 'Y');
}
if (!done) DrawMenu();
break;
case 0:
// Ignore special and function keys
console::read_key();
break;
}
}
std::cout << "\n\nGood-bye\n";
}
Notes:
I tend to alphabetize #includes, which makes it easier when the list gets long. Also, <chrono> is for time-based C++ functions, but you use the C date-time stuff from <ctime>. Be careful to include the correct headers.
It is a good idea to get used to writing std:: in front of everything. It is generally a bad idea to simply dump the entire Standard Library into your program’s namespace[citation needed].
Strings in C++ are, in general, best handled with the std::string class. Please use it. It’ll make your life infinitely easier.
The Draw*() functions are carefully organized to work together. This requires you to have a good idea of how your output is going to look.
DrawMenu() clears the screen and draws only the menu options
DrawTime() only draws the time
We do not clear the display every frame. Clearing the console is an expensive operation, often producing a noticeable blink. Avoid it if possible. In our case, we only want to update the time every frame, and redraw everything only after we have finished with some user interaction.
The frame rate is set by how long we are willing to wait for input to be available. In this example I choose 500 ms, which is a reasonably snappy response time. Avoid going below 150–250 ms, as that bogs the processor down with your wait loop. (This is why we don’t use _kbhit() from <conio.h>.)
Be careful with names. The way you name things makes a difference.
DrawTime() is a better name than GetTime() because it better informs you what it is doing — drawing the time for the user. It doesn’t actually get (and return) the time (to the caller).One way to think about it is that the fact that getting the time is a subtask of showing the time.
done is a better name than exit because it is a nominative state, not an action. (And also because it does not clash with the Standard Library exit() function name.)
The general rule of thumb is to use nouns for objects and verbs for functions.
Commentary should only exist to clarify code, not to describe it. For example, commentary in the console:: namespace describes how user functions are to be used. The only commentary in the main program reminds us why we are calling console::get_key() again, which is otherwise non-obvious.
Compiling and running the code is as easy as concatenating the two code-blocks together and running it through the compiler.
MSVC
cl /EHsc /W4 /Ox /std=c++17 example.cpp /D_CRT_SECURE_NO_WARNINGS
Clang (or MinGW)
clang++ -Wall -Wextra -Werror -pedantic-errors -O3 -std=c++17 example.cpp -o example.exe -D_CRT_SECURE_NO_WARNINGS
Related
Building a simple GUI in QT. I want to collect a list of strings every time a user inserts a name from the insert button. I got the raw logic to work for cli, however it is not the same when I convert logic to QT/C++ because the program just closes forcefully. I am using lineEdit from the input widgets. What am I doing wrong and what could have been a better alternative?
CLI Logic:
std::cout<<"Enter a name\tor enter nothing to quit"<<std::endl
<<">>>";
getline(std::cin, getNames);
listOfNames[enteredNames] = getNames;
while(getNames.length() > 0){
enteredNames++;
std::cout<<"Enter another name\tor enter nothing to quit"<<std::endl
<<">>>";
getline(std::cin, getNames);
listOfNames[enteredNames] = getNames;
}
QT Logic:
void MainWindow ... buttonClicked(){
v.namesEntered = 0;
v.listOfNames[50]={0};
v.getNames = ui->nameInputBox->text().toStdString();
while(v.getNames.length() > 0){
v.namesEntered++;
v.listOfNames[v.namesEntered] = v.getNames;
}
}
In case you are wondering the dot notations on v data is because I made a structure in mainwindow.h and needed my variables to be global for other functions.
You did not show what v.listOfNames is declared as, but it like a static array, is that right? You should use a std::vector<std::string> instead.
In any case, in your QT code, your while loop runs endlessly if the user enters a non-blank string, since you don't modify v.getNames so that its length() can decrease over time. You probably don't need the while loop at all if the user can enter only 1 string per button click.
Also, you are incrementing namesEntered too soon, and not validating when namesEntered exceeds the bounds of listOfNames if it is indeed a static array.
Try something more like this instead:
CLI
enteredNames = 0;
...
std::cout << "Enter a name\tor enter nothing to quit" << std::endl << ">>>";
while (getline(std::cin, getNames) && !getNames.empty())
{
listOfNames[enteredNames] = getNames;
++enteredNames;
std::cout << "Enter another name\tor enter nothing to quit" << std::endl << ">>>";
}
QT
void MainWindow ... initialize(){
v.namesEntered = 0;
}
void MainWindow ... buttonClicked(){
v.getNames = ui->nameInputBox->text().toStdString();
if (!v.getNames.empty()) {
if (v.namesEntered >= std::size(v.listOfNames)) {
// the list is full, do something else ...
}
else {
v.listOfNames[v.namesEntered] = v.getNames;
v.namesEntered++;
}
}
}
I thought of making a sort of Point Of Sales (POS) program. So, as you open the program, a menu (the main menu) would open-up which is a scrolling menu (you can move up & down to select an item in the menu with the arrow keys) and the items in it are like "Start business day", "Stats", "Inventory" blah blah.
Now, when you press "Start Business day"(using enter), you would get another menu which would ask for things like "Take order " ........"Return to the main menu". This is where I am finding the problem. When I press "Return to the main menu", I am not able to move back to the main menu.
MY ATTEMPT
#include<iostream>
#include<conio.h>
#include<string>
#include<windows.h>
using namespace std;
int chk=0;
int sbd(void) //the order menu (start business day -> ' press ENTER')
{
int pointer=0;
string order[4]={"TAKE ORDER","CHECK MENU","MEMO","RETURN TO MAIN MENU"};
while(true)
{
system("cls");
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),14);
cout<<"\t\t ZAIKA KATHI ROLLS\n";
cout<<"\t\t\tORDER MENU\n\n";
for(int i=0;i<=3;i++)
{
if( i==pointer)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),11);
cout<<"-> "<<order[i]<<endl<<endl;
}
else
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),15);
cout<<" "<<order[i]<<endl<<endl;
}
}
while(true)
{
if(GetAsyncKeyState(VK_UP)!=0)
{
pointer-=1;
if(pointer==-1)
{
pointer=3;
}
break;
}
else if(GetAsyncKeyState(VK_DOWN)!=0)
{
pointer+=1;
if(pointer==4)
{
pointer=0;
}
break;
}
else if(GetAsyncKeyState(VK_RETURN)!=0)
{
switch(pointer)
{
case 3 : return 1;
}
}
}
Sleep(150);
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main()
{
int pointer=0;
int flag=1;
string menu[6]={"START BUSINESS DAY","CONTINUE BUSINESS DAY","END BUSINESS DAY","INVENTORY MANAGEMENT","STATISTICS","SETTINGS"};
Mainmenu : while(true)
{
system("cls");
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),14);
int i=0;
cout<<"\t\t ZAIKA KATHI ROLLS\n";
cout<<"\t\t\tMAIN MENU\n\n";
for(i=0;i<=5;i++)
{
if(i==pointer)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),11);
cout<<"-> "<<menu[i]<<endl<<endl;
}
else
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),15);
cout<<" "<<menu[i]<<endl<<endl;
}
}
while(true)
{
if(GetAsyncKeyState(VK_UP)!=0)
{
pointer-=1;
if(pointer==-1)
{
pointer=5;
}
break;
}
else if(GetAsyncKeyState(VK_DOWN)!=0)
{
pointer+=1;
/* if(flag==0 && pointer==1)
pointer=3;
if(flag==1 && pointer==0)
pointer=1;*/
if(pointer==6)
{
pointer=0;
}
break;
}
else if(GetAsyncKeyState(VK_RETURN)!=0)
{
switch(pointer)
{
case 0 :chk=sbd();
if(chk==1)
goto Mainmenu;
}
}
}
Sleep(150);
}
return 0;
}
The best idea that came to my mind was to make the "start business day" as a function and in that function as we press "Return to main menu",the function will return a value which the main fuction will detect and then by using the goto fuction the program control will transfer to the main menu.Apparently thats not working, so can anyone please help me out of this?
What's happening is that when you press enter on "Return to main menu" it is in fact going back to the main menu. If you debug and step through it you'll see this. The problem is that as soon as it steps out of the sub-menu loop it goes into the main menu loop, checks to see if "enter" was pressed, passes the condition and enters the sub-menu loop again.
The thing is that you're not using GetAsyncKeyState function properly in your code:
if (GetAsyncKeyState(VK_RETURN) != 0)
It checks whether the value is zero. But according to the docs this function returns a SHORT.
The most significant bit is 1 if the key is pressed down, and 0 if not.
The least significant bit is 1 if the key was pressed since last calling GetAsyncKeyState. I assume this is what you want. So to begin with change the two spots in your code:
if (GetAsyncKeyState(VK_RETURN) != 0)
to:
if (GetAsyncKeyState(VK_RETURN) & 0x01 != 0)
We only want to check the least significant bit to see if the key was pressed since last time that function was called. If you do this it should work.
You can also write it like this using the Windows macro:
if (LOBYTE(GetAsyncKeyState(VK_RETURN)) != 0)
I should also mention you should probably get around to doing it this same way for key up and key down.
I really like it though, nice work.
Edit: GetAsyncKeyState() returns a short integer. It's most likely two bytes. When the function returns with this value it sets certain bits or flags to tell you information. Like I said, the most significant bit (the highest) tells you whether the key is down or not, the lowest significant bit tells you if the key has been pressed since the last call to that function.
So the returned number in binary will look like this (MSB) is most significant and (LSB) is least significant:
10000000 00000001
^ MSB ^ LSB
If this were an unsigned integer its value would be 32769. You're not interested in whether the key is currently down, but more interested in whether the key has been pressed since the last call to the function. You're only interested in the bit on the right. The bitwise operator & AND will compare two bit patterns and if and only if BOTH bits are 1, it will set the resulting bit to one. This is used with a mask so you can pluck out certain values, so to speak.
10000000 00000001 // Original value
00000000 00000001 // The mask
00000000 00000001 // Result
The result is 1. Then you can check whether the value is 1, and if it is then the key was pressed since the last call. It's a very low level way of doing things, but that's kinda how Windows works and Windows actually helps out by providing that macro LOBYTE() which does the same thing. The alternative is the function could have returned class/struct with bool values for each thing instead.
SECOND EDIT (FURTHER EXPLANATION):
The least significant bit will only be 1 if the key was pressed down since the last call to GetAsyncKeyState. So if you press down the enter key and keep it pressed for 3 seconds, the LSB will be set only on the first call to GetAsyncKeyState. However the MSB will continue to return with the MSB set, because this indicates whether the key is down. The following quick program should illustrate this nicely I think.
A short in our case has 2(bytes) 16 bits, so we're going to use the right bitshift operator >> to get it. I'm going to convert the return type from SHORT to USHORT. This is because bitshifting is undefined for signed integers which are negative because right shifting can propagate the leftmost bit. For example:
// Right shifting 7 bits
UNSIGNED INT
1000 0000 >> 7 = 0000 0001
SIGNED INT
1000 0000 >> 7 = 1111 1111 // We may get this instead which is not what we want
Start a new project and paste this and run, try pressing the enter key:
#include <iostream>
#include <Windows.h>
using namespace std;
int main()
{
while (true)
{
int count = 0;
USHORT funcResult = 0;
while ((funcResult = GetAsyncKeyState(VK_RETURN)) != 0)
// If MSB or LSB is set then condition is true
{
count++;
cout << "Return pressed " << count << " times in one loop\n";
cout << "MSB = " << (funcResult >> 15) << '\n';
cout << "LSB = " << (funcResult & 1) << '\n';
}
// You will see that the MSB is always 1, because it tells us
// if the key is down.
// However the LSB is 1 only on the first run of the while loop
}
}
So this is why adding Sleep() also fixes the problem, because if you press down the enter key for 400 ms and let go, and make the thread sleep for 500 ms, next time it checks the key won't be down, and the MSB won't be set.
I am coding my way to the last project of the semester and I have a code duplication issue.
I am using ncurses or pdcurses to make a menu to interact with the user.
The problem: For each choice of the menu(Five in total) I need a submenu. The submenu's only difference from the main menu is, the array of Items to be printed, and the parameters that go into some functions, as a result of the Items array size. Since I need five submenus, I need five times the same code(six if you add the main menu).
Could any of you help me make a function that does the same thing, which I'll then call six times to create my menu?
Here's my code
void Menu(){
const char* Items[]={
"[1]...New tax declaration",
"[2]...Modify tax declaration",
"[3]...Cancel tax declaration",
"[4]...Additional Information",
"[5]...Exit"
};
int Cur=0;
int ch, i;
int flag=0;
do{
werase(wm);
mvwaddstr(wm, 2, 16, "MENU");
for(int i=0; i<5;i++){
if(i==Cur)
wattr_on(wm, A_REVERSE, 0);
mvwaddstr(wm, 4+i, 4, Items[i]);
wattr_off(wm, A_REVERSE, 0);
}
mvwaddstr(wm, 14, 3, "Choice: ");
wprintw(wm, "%1d", Cur+1);
wrefresh(wm);
ch=wgetch(wm);
switch(ch){
case '1':Cur=0;Sub2();break;
case '2':Cur=1;Sub1();break;
case '3':Cur=2;break;
case '4':Cur=3;break;
case '5':flag=1;break;
case KEY_UP:
case KEY_LEFT: Cur--; if (Cur<0) Cur=4; break;
case KEY_DOWN:
case KEY_RIGHT: Cur++; if(Cur>4) Cur=0; break;
case 27: flag=1; break;
case 32:
case 13:
switch (Cur){
case 0:Sub2();break;
case 1:Sub1();break;
case 2:break;
case 3:break;
case 4:flag=1;break;
}
}
}while(!flag);
}
Thank you.
p.s The code is from a book. I have little experience with ncurses
A simple menu-driven program. This is based on using std::map instead of conditional logic. This map stores a list of menuitem structures that define what the menu looks like and what each option does.
This is best explained as we work through the code, so let's dive in!
// headers for everything used in this example
#include <iostream>
#include <string>
#include <map>
#include <functional>
#include <cctype>
// function to perform menu option B sub option 1
void optionB1()
{
std::cout << "perfoming B1" << std::endl;
}
// function to perform menu option B sub option 2
void optionB2()
{
std::cout << "perfoming B2" << std::endl;
}
// function to perform menu option A
void optionA()
{
std::cout << "perfoming A" << std::endl;
}
// defines a menu item. Good naming can often save the need to comment
struct menuitem
{
std::function<void()> doIt; // function to run if option chosen
std::string description; // pretty message describing option
};
// draw menu and wait for the user to select an option.
void domenu(const std::map<char, menuitem> & menu)
{
while (true) // loop until user gives a good option. Or use a retry count.
// You decide.
{
for (auto &items : menu)
{ // for all items in the menu, print out the item and it's description text
// for what first and second mean, read up on std::map and std::pair
std::cout << items.first << ") " << items.second.description << std::endl;
}
char ch;
std::cin >> ch; // get the user's choice
// often you may want to eliminate one of the cases to reduce the amount
// of possible inputs you need to provide handling code for.
// the line below allows us to use the same code for input of A and a.
ch = std::tolower(ch); // convert input to lower case
try
{
menu.at(ch).doIt(); // call the function mapped to user's choice.
// this may do produce something or it may
// display another menu. It could end the wor--
return; // done.
}
catch (...)
{ // print error message on unsupported input
std::cout << "Error. Invalid option!" << std::endl;
}
}
}
// the B menu
std::map<char, menuitem> bmenu
{ // User input doIt function Description
{'1', {optionB1, "Option B1"}},
{'2', {optionB2, "Option B2"}}
// add more options here. Or don't. Up to you.
};
// the main menu
std::map<char, menuitem> mainmenu
{ // User input doIt function Description
{'a', {optionA, "Option A"}},
{'b', {std::bind(domenu, bmenu), "Option B"}}
// OK, so that last one was a bit weird. std::bind makes a function and
// specifies the arguments with which it will be called. This takes
// domenu binds it with bmenu so that std::function<void()> is
// satisfied. As far as the world is concerned, the bound function
// returns nothing and takes no parameters. Very complicated functions
// can be bound so long as the end result returns nothing and requires
// no parameters.
// what it's doing here is allowing us to call domenu to draw the B
// submenu, wait for valid input, and call the chosen function.
};
// good 'ol trusty main so we can test that the above code isn't utter BS.
int main()
{
while (true) // loop forever. Or use whatever exit logic is required.
{
domenu(mainmenu); // kick-start by calling do menu to run the main menu
}
return(0);
}
This will keep the code down to a minimum. All of the duplicated code is reduced to the domenu function and a smurfload of code hidden from sight in the standard library and written by folks who likely have far more experience in getting this stuff right than you or I. Whenever possible, stand on the shoulders of giants.
domenu is driven by lists of options and execution instructions for the option. Want another option? Add an item to a list and possibly provide a new function to fulfill the obligations of that option.
All you have to do is fill in the blanks.
I have an object with the following method:
int PathSubstitution::updateField(Field4memo &field, int record_id, int field_id) const
{
int type = field.type();
if((type == r4str) || (type == r4memo) || (type == r4unicode))
{
string value = field.str();
trim(value);
if(!substituteDriveLetters(value))
return -1;
if(!substituteGridMount(value))
return -1;
return field.assign(value.c_str(), value.length());
}
return r4success;
}
When I build this code with my Debug profile in Visual Studio C++ 2010 everything works just fine. This method gets called 4 times, on four unique Field4memo objects, and it works.
When I build this code with my Release profile the method works the first time it's called, but causes Vista Enterprise to display a "program.exe has stopped working" dialog window. The "View problem details" area of the window says:
Problem signature:
Problem Event Name: BEX
Application Name: program.exe
Application Version: 0.0.0.0
Application Timestamp: 4ef4edc6
Fault Module Name: program.exe
Fault Module Version: 0.0.0.0
Fault Module Timestamp: 4ef4edc6
Exception Offset: 0000668a
Exception Code: c0000409
Exception Data: 00000000
OS Version: 6.0.6002.2.2.0.256.4
Locale ID: 1033
Additional Information 1: 6243
Additional Information 2: 0d5daf38e26c963685a835e6f40ff03d
Additional Information 3: aa53
Additional Information 4: 5d02a603659cce53ff840117c3a9c7a7
The BEX event name indicates a buffer overflow. But which buffer, I cannot tell.
Here's where it gets weird for me though...
When I change this method and add an unnecessary cout line to it, it works with the Release profile:
int PathSubstitution::updateField(Field4memo &field, int record_id, int field_id) const
{
int type = field.type();
if((type == r4str) || (type == r4memo) || (type == r4unicode))
{
// THIS IS THE NEW LINE I ADDED RIGHT BELOW HERE!!!
cout << endl;
string value = field.str();
trim(value);
if(!substituteDriveLetters(value))
return -1;
if(!substituteGridMount(value))
return -1;
return field.assign(value.c_str(), value.length());
}
return r4success;
}
I can't tell why the method crashes with the Release profile or why adding the cout line resolves the crashing issue. I'm uncomfortable just accepting the "cout fixes it" answer -- can someone help me understand what my problem is here and why the cout fixes it? How does the cout call save me from a buffer overflow here?
Edit: some additional context for the call to this method was asked for. It's called in a loop. With the test input I'm using, it's called 4 times. The function that calls it looks like so:
int PathSubstitution::updateRecord(Data4 &dbf, int record_id) const
{
// Update all fields
int numFields = dbf.numFields();
for(int i = 1; i <= numFields; i++ )
{
Field4memo field(dbf, i);
int rc = updateField(field, record_id, i);
if(rc != r4success)
return rc;
}
return r4success;
}
Edit 2: Flushing the cout buffer also fixes the overflow problem as long as cout.flush() is called from within the PathSubstitution::updateField method and before the return field.assign(value.c_str(), value.length()); line.
Edit 3: This is promising. If I comment out the calls the substituteDriveLetters() and substituteGridMount() methods the program doesn't crash. So it's something to do with those method calls (which use pcre to do some regular expression string substitutions).
Edit 4: If I comment out just the substituteDriveLetters() method it works. So I've got a prime suspect now. This method is supposed to replace a drive letter in a path with the corresponding UNC value. None of the fields in my test input are file paths so this should be a null op as far as data transformation is concerned.
bool PathSubstitution::substituteDriveLetters(string &str, string::size_type offset) const
{
int offsets[6];
int groups = pcre_exec(drivePattern, NULL, str.c_str(), str.size(), 0, 0, offsets, sizeof(offsets));
if(groups < 0)
{
switch(groups)
{
case PCRE_ERROR_NOMATCH:
case PCRE_ERROR_PARTIAL:
return true;
case PCRE_ERROR_NOMEMORY:
cerr << "WARNING: Out of memory." << endl;
break;
case PCRE_ERROR_BADUTF8:
case PCRE_ERROR_BADUTF8_OFFSET:
cerr << "WARNING: Bad UNICODE string." << endl;
break;
default:
cerr << "WARNING: Unable to substitute drive letters (Err: " << groups << ")" << endl;
break;
}
return false;
}
char driveLetter = toupper(str[offsets[2]]);
DriveMap::const_iterator i = driveMap.find(driveLetter);
if(i == driveMap.end())
{
cerr << "ERROR: The " << driveLetter << " drive is not mapped to a network share." << endl;
return false;
}
string::iterator start = str.begin() + offsets[0];
string::iterator end = str.begin() + offsets[1];
str.replace(start, end, i->second);
return substituteDriveLetters(str, offsets[1]);
}
Without a complete test case it's almost impossible to say what the exact problem is but given the behaviour, it is highly likely that your code has some form of undefined behavior and works in debug and with the extra cout statement through blind luck.
You should analyse the code and fix the underlying issue otherwise it's highly likely that a related bug will recur at the least convenient moment.
If you want help analysing the actual problem then you need to post a complete compilable example. At the moment we don't know anything about Field4memo, trim, substituteDriveLetters or substituteGridMount for starters.
Edit You may want to insert more checks for the string operations that you perform.
// need to check that offsets[2] >= 0 and < str.size()
char driveLetter = toupper(str[offsets[2]]);
// need to check that offsets[0] >= 0 and <= str.size()
string::iterator start = str.begin() + offsets[0];
// need to check that offsets[1] >= 0 and <= str.size()
string::iterator end = str.begin() + offsets[1];
I am trying to make a C++ Button class in which the user inputs a function to execute when the button is pressed. So far, this is my code:
class Button {
public:
char* text;
void* buttonclick();
Button (char* constext, void* consbuttonclick()) {
text = constext;
//What do I do here?
}
};
I am trying to make the constructor pass on the value of consbuttonclick() to buttonclick(). I believe there is a way to do this using function pointers, but I have been having trouble with them, so I would greatly appreciate some help on how to proceed.
What you've declared is a function returning a void *. You want a pointer to function returning void. So to start with you need to change the prototype: In place of void * foo() you want void (*foo)(). (You'll have to sort out exactly what you want, but that's the idea.)
Once you have it, you call it simply by using the function invocation operator (), so you'l get something like
class Button {
public:
char* text;
void* buttonclick();
Button (char* constext, void(* consbuttonclick())) {
text = constext;
consbuttonclick();
}
};
Better yet, though, is to create what's called a functor class, like:
class ClickFunctor {
public:
void doIt(){
// code
}
}
then pass a ClickFunctor object.
class Button {
public:
char* text;
Button (char* constext, ClickFunctor cf) {
text = constext;
cf.doIt();
}
};
Now, go read the C++ FAQ book on these things. (Update: Marshall calls these things "functionoids" rather than "functors", just to warn you.)
Sorry to burst your bubble, but there's no sane way of doing that. Function names only exist for easy writing. As soon as the compiler gets hold of them, the names become garbled and then turned into function addresses only. The names (at least in C++) are meaningless at runtime.
Your best bet is either a long chain of if / else statements OR possibly a dictionary that maps a string to a function pointer. But you'd have to set up that dictionary manually.
Look, if you are trying to make a button in a c++ ms-dos window, it can't be done unless you have a library that I don't about. But I found a way around that (which is long but gets the job done). What I did was I found the top left position of a button drawn out with simple cout<<"whatevergoeshere";, and the bottom right. Then, by creating a function that returns true when the left button is pressed and if the click is within the arguments provided for the top left corner and bottom right corner, control will move on. It is a pretty complicated process, but I can give you the source code I used in another answer.
(If you wan't me to explain any of this code, I will. I didn't know how to do this either until recently. One thing that is important though is keeping the window in the same place everytime. In the environment I'm using (code::blocks), the window stays in the same place, but moving it can mess up the button. Also remember to continuosly use the system("cls"); to clear the screen so stuff doesn't change position. (system"cls" is located in the cstdio or cstdlib libraries.)
Here is the code I used:
#include <iostream>
#include <windows.h>
#include <cstdlib>
#include <cstdio>
#include <stdio.h>
#ifndef MOUSE_HWHEELED
#define MOUSE_HWHEELED 0x0008
#endif
using namespace std;
int main()
{
POINT point;
HANDLE csbiHandle;
CONSOLE_SCREEN_BUFFER_INFO csbi;
int counter = 0;
DWORD cNumRead, i,fdwMode, fdwSaveOldMode;
INPUT_RECORD irInputBuffer[128];
HANDLE stdHandle;
stdHandle = GetStdHandle(STD_INPUT_HANDLE);
MOUSE_EVENT_RECORD mer;
cout << "|-------------|" << endl
<< "| A |" << endl
<< "|-------------|" << endl;
buttonpress:
ReadConsoleInput(stdHandle, irInputBuffer,128, &cNumRead);
GetCursorPos(&point);
for(i=0; i<cNumRead; i++)
{
switch(irInputBuffer[i].EventType)
{
case MOUSE_EVENT:
{
mer = irInputBuffer[i].Event.MouseEvent;
if(mer.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED)
{
cout << "left button press" << endl;
cout << point.x << " " << point.y << endl;
if(point.x>=16&&point.x<=182&&point.y>=30&&point.y<=63){cout << "You clicked A!!" << endl;}
}
else
{
goto buttonpress;
}
break;
}
default:{
printf("unknown\n");
break;}
}
}
goto buttonpress;
return 0;
}