У меня запущен цикл, который отображает данные, считанные из последовательного порта, на экране консоли, на том же экране перечислены некоторые параметры для пользователя, например, возможность ввести имя файла, в который будут записываться данные, или выйти. Пользователь нажимает требуемую клавишу для ввода нужной опции.
После нажатия клавиши и ввода функции опции любые нажатия клавиш можно игнорировать/отбрасывать, но я не могу найти способ отменить нажатия клавиш/очистить буфер cin, в результате чего нажатие клавиши отображается на консоли.
Есть ли способ очистить буфер cin без каких-либо действий со стороны пользователя? Или, возможно, какой-то лучший способ обеспечить ту же функциональность?
Урезанная версия кода здесь: (это должно отображать местное время, давая пользователю возможность ввести «имя файла» или выйти, без включения последовательного порта)
#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;
}
Я экспериментировал с cin.ignore, после чего пользователю остается нажать Enter, чтобы продолжить. Без cin.ignore пользователь должен сам удалить символы «option».
@user4581301 user4581301 спасибо, я чувствую себя немного лучше, зная, что я не упустил ничего очевидного! Я чувствую, что впереди слишком сложная работа...
В целом iostreams довольно просты. Они должны работать так же на 8-битном микроконтроллере, который выводит данные через двухпроводной последовательный порт, как и на настольном ПК с полнофункциональным терминалом, так что вы не можете добавить к ПК ничего, что можете. не делать на сказал бит-фейерверк. Лучшим вариантом может быть использование вызовов Windows API.
Просто используйте библиотеку ncurses, которая отключит вас от стандартного ввода. Стандартный ввод — это абстракция для нескольких устройств: пользовательская клавиатура, файлы, сетевое подключение, сканер символов, .... . Вот почему это так работает. Используя ncurses, вы всегда будете использовать клавиатуру и получите больше контроля над тем, как она работает.
Если вы используете ReadConsole вместо std::cin
, вы можете управлять отображением ввода на экране с помощью SetConsoleMode . Если вы хотите еще больше контролировать все, вы можете вместо этого использовать функцию ReadConsoleInput.
Ваши is*keypressed()
функции могут быть сокращены до return (GetAsyncKeyState() & 0x8000);
Ладно, консоль Windows. Работает только в Windows.
Играть с этим материалом никогда не бывает так просто, как рекламируется, но для того, что вы хотите сделать, мы можем сделать довольно урезанный код только для Windows. (Если в какой-то момент в будущем вы захотите портировать на Linux, этот дизайн можно довольно легко заменить на аналогичный код для Linux.)
Минимальные функции консоли Windows
#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
Примечания:
Он работает как в терминале Windows, так и в текущей и прошлых версиях консоли Windows.
Для использования этой конкретной магии не требуется никакой инициализации или финализации.
Предполагается присутствие человека. Более надежный код будет проверять, подключены ли стандартные потоки к консоли/терминалу при инициализации, и жаловаться (и выходить), если нет.
Только один поток должен когда-либо возиться с консолью, поэтому, если вы делаете что-то многопоточное, только console::
из одного из них. (Это в значительной степени верно для любого пользовательского пользовательского интерфейса приложения.)
Ваша программа, обновленная
#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";
}
Примечания:
Я склонен располагать #include
по алфавиту, что облегчает работу, когда список становится длинным. Кроме того, <chrono>
предназначен для функций C++, основанных на времени, но вы используете материал даты и времени C из <ctime>
. Будьте внимательны, чтобы включить правильные заголовки.
Это хорошая идея, чтобы привыкнуть писать std::
на виду у всех. Как правило, это плохая идея — просто выгрузить всю Стандартную библиотеку в пространство имен вашей программы[citation needed].
Строки в C++, как правило, лучше всего обрабатываются с помощью класса std::string
. Пожалуйста, используйте его. Это сделает вашу жизнь бесконечно проще.
Функции Draw*()
тщательно организованы для совместной работы. Это требует от вас хорошего представления о том, как будет выглядеть ваш результат.
DrawMenu()
очищает экран и рисует только пункты менюDrawTime()
только рисует времяМы не очищаем дисплей каждый кадр. Очистка консоли — дорогостоящая операция, часто вызывающая заметное мерцание. Избегайте этого, если это возможно. В нашем случае мы хотим обновлять время только в каждом кадре и перерисовывать все только после того, как закончим взаимодействие с пользователем.
Частота кадров определяется тем, как долго мы готовы ждать, пока ввод будет доступен. В этом примере я выбираю 500 мс, что является достаточно быстрым временем отклика. Не опускайтесь ниже 150–250 мс, так как это перегружает процессор циклом ожидания. (Вот почему мы не используем _kbhit()
из <conio.h>
.)
Будьте осторожны с именами. То, как вы называете вещи, имеет значение.
DrawTime()
— это лучшее имя, чем GetTime()
, потому что оно лучше информирует вас о том, что он делает — рисует время для пользователя. На самом деле он не получает (и не возвращает) время (вызывающему абоненту).done
лучше, чем exit
, потому что это номинативное состояние, а не действие. (А также потому, что оно не конфликтует с названием функции стандартной библиотеки exit()
.)Комментарии должны существовать только для пояснения кода, а не для его описания. Например, комментарий в пространстве имен console::
описывает, как следует использовать пользовательские функции. Единственный комментарий в основной программе напоминает нам, почему мы снова вызываем console::get_key()
, что в остальном неочевидно.
Компилировать и запускать код так же просто, как объединить два блока кода и запустить его через компилятор.
cl /EHsc /W4 /Ox /std=c++17 example.cpp /D_CRT_SECURE_NO_WARNINGS
clang++ -Wall -Wextra -Werror -pedantic-errors -O3 -std=c++17 example.cpp -o example.exe -D_CRT_SECURE_NO_WARNINGS
большое спасибо за этот подробный ответ и примечания, они очень приветствуются. Ваш код работает точно так, как хотелось бы, сейчас я изучу его, чтобы убедиться, что понимаю, как он работает.
Нет хорошего портативного способа. Надеюсь, кто-то из присутствующих здесь знает несколько хороших трюков, специфичных для Windows.