Menu konsolowe z wykorzystaniem funkcji getch

Stronę tą wyświetlono już: 372 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:

Listing 1
  1. #include <iostream>
  2. #include <vector>
  3. #include <string>
  4. #include <windows.h>
  5. #include <conio.h>
  6. #include <time.h>
  7. 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:

Listing 2
  1. HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); // tutaj uchwyt do standardowego wyjścia konsoli pozyskuję
  2. void gotoxy(int x, int y) // pozycjonowanie rysowania na ekranie
  3. {
  4. COORD c; // struktura stosowana do pozycjonowania
  5. 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
  6. c.Y = y-1; // to samo co powyżej
  7. SetConsoleCursorPosition(handle, c); // ustaw pozycję karetki
  8. }

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:

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

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

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

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:

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

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

Listing 6
  1. int main(){
  2. setlocale(LC_CTYPE, "Polish");
  3. vector<string> tMenuGl; // tutaj będą zapisywane pozycje z menu
  4. tMenuGl.push_back("Wyjdź z programu\t"); // tutaj na końcu dodałem tabulator bo funkcja menu na końcu doda numer opcji
  5. tMenuGl.push_back("Wylosuj liczby\t\t");
  6. tMenuGl.push_back("Wylosuj znaki\t\t");
  7. int id = 0; // id wyboru
  8. srand(time(NULL)); // a to dla losowania, żeby za każdym razem inny zestaw znaków się wyświetlał
  9. do{
  10. 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
  11. system("cls"); // czyszczenie ekranu, gdy funkcja menu głównego zostanie wykonana
  12. switch(id){ // a tutaj zachowanie programu w zależności od wyboru opcji
  13. case 1: // dla losowania liczb
  14. cout<<"Losowanko:"<<endl<<endl;
  15. for(int i = 0; i < 10; i++){
  16. cout<<rand() % 100<<endl;
  17. }
  18. break;
  19. case 2: // dla losowania znaków
  20. cout<<"Losowanko:"<<endl<<endl;
  21. for(int i = 0; i < 10; i++){ // dziesięciu liter losowanie
  22. if(rand() % 2){ // losowanko, czy małe litery mają być wylosowane, czy duże
  23. cout<<char((rand() % ((int)'z' - (int)'a')) +(int)'a'); // losowanko małych liter
  24. }else{
  25. cout<<char((rand() % ((int)'Z' - (int)'A')) +(int)'A'); // losowanko dużych liter
  26. }
  27. }
  28. cout<<endl;
  29. break;
  30. case 0: // dla wyjścia z programu
  31. {
  32. cout<<"Wciśnij t, jeśli naprawdę chcesz wyjść z programu...";
  33. if(getch() == 't')
  34. id = -1;
  35. cout<<endl;
  36. }
  37. break;
  38. }
  39. if(id > 0){
  40. WriteLine(50);
  41. cout<<"Wciśnij enter, aby przejść do menu...";
  42. getch();
  43. }
  44. system("cls");
  45. }while(id > -1);
  46. cout<<"Wciśnij enter, aby zamknąć program...";
  47. cin.get();
  48. return 0;
  49. }

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.

Komentarze