Menu konsolowe z wykorzystaniem funkcji getch

Autor podstrony: Krzysztof Zajączkowski

Stronę tą wyświetlono już: 10180 razy

Na początek smutna wiadomość, a mianowicie taka, że funkcja getch(), która jest dostępna po załączeniu pliku nagłówkowego conio.h nie jest dostępna pod innymi systemami niż Windows, a więc na Linuksie i Mac-OS-ie ta funkcja nie będzie dostępna. Do czego służy ta tajemnicza funkcja getch(), a no teoretycznie do pobierania kodu znaku wpisywanego z klawiatury. Dlaczego piszę teoretycznie? Ano bo w praktyce zdarza się tak, że wciśnięcie jakiegoś jednego klawisza wymaga więcej niż jednego znaku, aby taki klawisz opisać. Do takich klawiszy należą strzałka w górę i strzałka w dół. Ktoś zapyta: a dlaczego akurat te strzałki mnie tak interesują. Ano strzałki będą potrzebne do zmiany zaznaczonej pozycji w menu. Co zatem się dzieje, gdy wciśnięta zostanie strzałka np. w górę? Funkcja getch() zwróci liczbę 224 a następny getch() przechwyci liczbę 72 i to od razu po wciśnięciu jednego przycisku klawiatury.

Zanim przejdę do omawiania kodu, najpierw lista potrzebnych plików do załączenia:

#include <iostream> #include <vector> #include <string> #include <windows.h> #include <conio.h> #include <time.h> using namespace std;

Do szczęścia potrzebna będzie zmienna globalna zawierająca uchwyt HANDLE do standardowego wyjścia konsoli, który pozyskany zostanie za pomocą funkcji systemowej GetStdHandle. Dodatkowo potrzebna będzie funkcja gotoxy, której jedynym celem jest ustawianie położenia karetki klawiatury (to takie coś, co mruga w każdym interfejsie do wprowadzania tekstu, by zaznaczyć miejsce, w którym dane będą wstawiane po przechwyceniu z klawiatury). Kod tejże funkcji oraz wcześniej wspomniana zmienna:

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); // tutaj uchwyt do standardowego wyjścia konsoli pozyskuję void gotoxy(int x, int y) // pozycjonowanie rysowania na ekranie { COORD c; // struktura stosowana do pozycjonowania c.X = x-1; // x i y to numery wiersza i kolumny, gdzie numeracja jest od 1 a w systemie od 0 więc odejmuję jeden c.Y = y-1; // to samo co powyżej SetConsoleCursorPosition(handle, c); // ustaw pozycję karetki }

Dalej napisałem sobie funkcję pomocniczą, która będzie wyświetlała daną pozycję menu. I tutaj dzieje się ciekawa rzecz, albowiem w tej funkcji tekst będzie podświetlany, gdy idset pozycji menu będzie się pokrywało z aktualnym id wyboru. Kod funkcji wygląda następująco:

// rysowanie pojedynczej pozycji w menu void WriteMenuPos(string &str, int id, int idset){ if(id == idset){ // gdy pozycja jest wybrana SetConsoleTextAttribute(handle, 240); // to trzeba ją podświetlić std::cout<<str<<"["<<id<<"]"<<endl; // wypisuję pozycję z menu i dodaję jej numer w nawiasach kwadratowych SetConsoleTextAttribute(handle, 7); // powracam do domyślnych ustawień kolorów }else{ // w przeciwnym przypadku std::cout<<str<<"["<<id<<"]"<<endl; // wypisuję tekst danej pozycji menu i dodaję jej indeks w nawiasach kwadratowych } }

Dodatkowo utworzyłem sobie małą funkcję pomocniczą, która "rysuje" linię podziału dzięki czemu menu jest nieco bardziej przejrzyste:

// rysowanie linii składającej się z znaków = a ich liczba określa długość linii void WriteLine(unsigned int width){ for(unsigned int i = 0; i < width; i++){ cout<<"="; // width razy wypisuję = } cout<<endl; }

Ostatecznie, funkcja odpowiedzialna za obsługę wyświetlania menu oraz zmiany pozycji wyboru wykonywanego za pomocą strzałki w górę, strzałki w dół lub wciśnięcia numeru, odpowiadającemu id danej pozycji w menu. Kod tej funkcji jest następujący:

int menu(string title, vector<string> &tMenu, int &id){ // a tu menu rysuję A: // znacznik miejsca skoku gotoxy(1,1); // ustawienie karetki klawiatury w linii 1 i kolumnie 1 WriteLine(title.size()); // rysuję linię ====== o długości równej długości tekstu tytuły menu cout<<title<<endl; // wypisuję menu WriteLine(title.size()); // i znowu linia ======= for(int i = 0; i < tMenu.size(); i++){ // dla wszystkich pozycji menu WriteMenuPos(tMenu[i], i, id); // wypisuj daną pozycję korzystając z funkcji WriteLine } int c = getch(); // pobieraj znak za pomocą getch() bez oczekiwania na znak powrotu if(c == 224){ // jak 224 znak został wprowadzony to trzeba jeszcze pobrać jeden znak c = c << 8; // przesuwam bitowo o 8 pozycji c |= getch(); // i operację bitową OR robię z wartością uzyskaną z getch() -a } switch(c){ case (224 << 8) | 72: // to dla strzałki w górę id = id ? id - 1 : 0; // jeżeli id nie jest równe zero to ustawiam id = id - 1 gotoxy(1,1); // ustawienie karetki klawiatury w linii 1 i wierszu 1 goto A; // skok do znacznika A case (224 << 8) | 80: // to dla strzałki w dół id = id + 1 < tMenu.size() ? id + 1 : tMenu.size() - 1; // jak id < liczby pozycji menu to id ++ gotoxy(1,1); // ustawienie karetki klawiatury w linii 1 i wierszu 1 goto A; // skok do znacznika A default: // w przypadkach innych niż powyższe if(c > 47 && c < 48 + tMenu.size()){ // gdy dany znak odpowiada pozycji w menu (np. 0, 1, ...) to id = c - 48; // obliczanie pozycji z menu return id; // i zwracanie } break; } return id; // zwracam numer wybranej pozycji }

Pozostało już tylko stworzyć to nasze menu w funkcji main:

int main(){ setlocale(LC_CTYPE, "Polish"); vector<string> tMenuGl; // tutaj będą zapisywane pozycje z menu tMenuGl.push_back("Wyjdź z programu\t"); // tutaj na końcu dodałem tabulator bo funkcja menu na końcu doda numer opcji tMenuGl.push_back("Wylosuj liczby\t\t"); tMenuGl.push_back("Wylosuj znaki\t\t"); int id = 0; // id wyboru srand(time(NULL)); // a to dla losowania, żeby za każdym razem inny zestaw znaków się wyświetlał do{ menu("Menu główne",tMenuGl, id); // wywołanie funkcji menu, która wyświetli i wykona niezbędne instrukcje związane z rysowaniem i zmianą pozycji w menu system("cls"); // czyszczenie ekranu, gdy funkcja menu głównego zostanie wykonana switch(id){ // a tutaj zachowanie programu w zależności od wyboru opcji case 1: // dla losowania liczb cout<<"Losowanko:"<<endl<<endl; for(int i = 0; i < 10; i++){ cout<<rand() % 100<<endl; } break; case 2: // dla losowania znaków cout<<"Losowanko:"<<endl<<endl; for(int i = 0; i < 10; i++){ // dziesięciu liter losowanie if(rand() % 2){ // losowanko, czy małe litery mają być wylosowane, czy duże cout<<char((rand() % ((int)'z' - (int)'a')) +(int)'a'); // losowanko małych liter }else{ cout<<char((rand() % ((int)'Z' - (int)'A')) +(int)'A'); // losowanko dużych liter } } cout<<endl; break; case 0: // dla wyjścia z programu { cout<<"Wciśnij t, jeśli naprawdę chcesz wyjść z programu..."; if(getch() == 't') id = -1; cout<<endl; } break; } if(id > 0){ WriteLine(50); cout<<"Wciśnij enter, aby przejść do menu..."; getch(); } system("cls"); }while(id > -1); cout<<"Wciśnij enter, aby zamknąć program..."; cin.get(); return 0; }

Wynikiem działania tego programu (w początkowej fazie) jest takie oto menu:

===========
Menu główne
===========
Wyjdź z programu        [0]
Wylosuj liczby          [1]
Wylosuj znaki           [2]

Teraz strzałkami góra, dół można zmieniać pozycję wyboru a tym samym podświetlenie pozycji w menu. Wciśnięcia numerów od 0 do 2 wybierze pozycję z menu, której dana liczba odpowiada. W przypadku wciśnięcia dowolnego innego przycisku wykonany zostanie podprogram związany z zaznaczoną pozycją w menu.

Nie bez znaczenia jest również fakt wykorzystania funkcji system("cls"), której celem jest wywołanie systemowego polecenia cls mającego na celu wyczyszczenie tekstu w konsoli programu. Taki zabieg jest konieczny, aby menu poprawnie się wyświetlało.

Propozycje książek