Czy to wydajny sposób na przechowywanie danych odnośnie menu?

0

Witam, piszę aplikacje do zarządzania kontaktami. Do tworzenia aplikacji będę używał biblioteki pdcurses.
Mam pewien dylemat na temat tego jak powinienem przechowywać moje dane o menu, które później będę generował.

Każde menu będzie miało swoją nazwę, opis oraz ilość opcji do wyboru (ilość submenu). Możliwe, że jeszcze będę przechowywał jakiś wskaźnik
do funkcji, tak aby po kliknięciu w konkretne menu program wiedział jaką funkcję wykonać (jeszcze nad tym zbytnio się nie zastanawiałem jak to wykonać, a ten wskaźnik mi przyszedł póki co do głowy).

Ale przejdźmy do rzeczy, wymyśliłem póki co takie rozwiązanie:

std::vector<std::tuple <std::string, std::string, std::vectorstd::string>> menus;

Jest to wektor, który przechowuje std::tuple - każde std::tuple to będzie jedno menu. Menu będzie złożone z nazwy, opisu i ilości opcji.

Poniżej przedstawiam sposób użycia (w ramach testów i przejrzystości nie korzystam tutaj z biblioteki pdcurses, a dane po prostu wyświetlam w konsoli za pomocą std::cout):

int main()
{
	std::vector<std::tuple <std::string, std::string, std::vector<std::string>>> menus;

    std::vector <std::string> booksOptions = { "przegladaj", "dodaj" };
    menus.push_back(std::make_tuple("Ksiazki", "Wybierz jedna z opcji", booksOptions));

    for (const auto& menu : menus) {
        std::cout << "- Nazwa: " << std::get<0>(menu) << std::endl;
        std::cout << "- Opis: " << std::get<1>(menu) << std::endl;
        std::cout << "- ilosc elementow:" <<std::endl;

        for (const auto& option : std::get<2>(menu))
        {
            std::cout << '\t' << option << std::endl;
        }
    }

    system("pause");

    return 0;
}

I tu nasuwa się moje pytanie, czy takie rozwiązanie jest wydajne? Z początku myślałem żeby zrobić osobną klasę - Menu, która byłaby odpowiedzialna za to. Jednak nie potrzebuje tak właściwie robić (raczej) żadnych innych operacji na tych menu oprócz przechowywania danych.

Z rysowaniem menu nie powinno być komplikacji, gdyż mogę wywoływać funkcje w stylu drawMenu(std::tuple <std::string, std::string, std::vectorstd::string>), która przyjmuje konkretne menu jako parametr, oraz odpowiednio je rysuje. Każde menu i submenu będzie podobne, wiec wystarczy jedna funkcja. Jedynym problemem tego rozwiązania będzie rysowanie tych menu w konkretnych obszarach konsoli - będe korzystał z róznych okienek WINDOW z biblioteki pdcurses. Jednak nie jest to teraz zbytnio ważne, gdyż rozpisanie mojego konceptu tutaj zajęło by trochę, a wątpie, że ktoś chciałby czytać te wypociny.

Problemem nie będzie również aby opcja z wektora "menus" była odpowiedzialna za wywołanie konkretnej funkcji, typu:
"addContactBook()", jakoś sobie z tym poradzę.

Struktura mojego programu będzie następująca:
klasa App - jest odpowiedzialna za rysowanie menu, okienek - czyli wszelaka interakcja z użytkownikiem.
klasa Contact - przechowuje informacje o pojedynczym kontakcie, oraz odpowiednie gettery i settery do ustawień tych zmiennych.
klasa ContactBook - jest odpowiedzialna za tworzenie, usuwanie książek kontaktowych, przechowywanie informacji o książkach, w polu tej klasy jest wektor obiektów Contact, a co za tym idzie w klasie tej będzie możliwość przeszukiwania kontaktów, sortowania ich, itp.

Moim kolejnym zamysłem jest zrobić wyświetlanie konkretnych kontaktów w formie menu (przy użyciu pdcurses) po którym będzie można się poruszać za pomocą strzałek i po kliknięciu w dany kontakt - dodać możliwość jego edycji. A więc myślę, że potrzebuje bardziej zaawansowanego typu danych.

z początku opracowałem funkcje w klasie ContactBook zwracają wektor stringów:

std::vector<std::string> ContactBook::getContacts()
{
	std::vector<std::string> vstr = {};
	for (auto& contact : this->contacts)
	{
		// tworzenie pelnego stringa, korzystając, że ContactBook zawiera wektor obiektów Contact
  		std::string str = std::to_string(contact.getId()) + contact.getName() + contact.getLastName() + std::to_string(contact.getPhoneNumber()) + contact.getEmail();
		vstr.push_back(str);
	}
	return vstr;
}

Taki zwrócony wektor stringów już mogę dodać do opcji w menu w std::tuple, a następnie je narysować. Jednak pojawia się problem, gdy chce przechwycić kliknięty kontakt w menu, tak aby wywoływał funkcje, załóżmy "editContact()" dla konkretnego obiektu. Nie mam pojęcia jak to połączyć ze sobą. W klasie App znajdowałby się wektor obiektów ContactBook, w każdym ContactBook jest wektor obiektów Contact. W klasie App byłaby metoda drawContactMenu(), która musiałaby wyrysować wszystkie kontakty. I jak nie ma problemu ze zwróceniem kontaktów z ContactBook i wyrysowania ich, tak kliknięcie w opcje w menu musiałoby mieć jakieś odniesienie do obiektu ContactBook.

Także liczę na waszą pomoc :) cenna będzie każda wskazówka jak to poukładać. Nie wiem czy dobrze rozpisałem o co mi chodzi w drugim problemie z tym menu kontaktów, najwyżej bardziej wyjaśnie o co chodzi i pokaże jakiś kod. Jednak na razie zatrzymałem się na obmyślaniu struktury projektu i głowie się nad tym kilka dni szukając jakichś rozwiązań, a kodu mam niezbyt wiele :D

0

Wygląda na to, że potrzebujesz struktury.

Coś w stylu:

struct Menu
{
  std::string x;
  std::string y;
  std::vector<std::string> z;
};

0
less napisał(a):

Wygląda na to, że potrzebujesz struktury.

Coś w stylu:

struct Menu
{
  std::string x;
  std::string y;
  std::vector<std::string> z;
};

Czy struktura byłaby dobrym podejściem, jeśli program ma być w całości obiektowy?

3

ale w jaki sposób menu jest powiazane z podmenu? Masz tylko vector z nazwami elementów elementów podmenu, zamiast vectora całych elementów

1

@Miang:

Dokładnie.

Za słabe to, przez to ze intelektualnie tylko zapatrzone w string

Zigor36 napisał(a):

jeśli program ma być w całości obiektowy?

Jesli program ma być obiektowy
a) akcja ma być związana ze stringiem
b) kontrola na etapie pre-wykonania menu, co do np uprawnień, sensu i zdolności do wykonania w kontekście (edycja zatwierdzonej faktury / braku faktury w kontekście)
c) dokroryzacja: wspomaganie translacji na inne języki (ale bazą jest tożsamość oparta na czymś innym niż string użytkownika)

Frameworki GUI dośc często mają taką abstrakcję jak Action, jest tam void Action(); i jest string (jest dynamiczny bool do pozwolenia na wykonanie). Widget graficzny Menu / Button / Link / Hotkey powołuje się na taką action (co więcej, ta sama Action może być dostępna z Menu / Hotkeya /i Buttona)

Ale przejdźmy do rzeczy, wymyśliłem póki co takie rozwiązanie:

std::vector<std::tuple <std::string, std::string, std::string>> menus;

Jest to wektor, który przechowuje std::tuple - każde std::tuple to będzie jedno menu. Menu będzie złożone z nazwy, opisu i ilości opcji.

Słabe.
Tak jakbyś implementował pierwszy pomysł, który sie trafił. "Coś" załatwia i mnóstwa rzeczy nie załatwia. WSZYTSKO INNE będziesz łączył jakas drabinką if'ów
jednego wieczora identyfikacja zagadnień, kwadraty na kartce, zakaz kodowania - innego wieczora kodowanie

0
AnyKtokolwiek napisał(a):

a) akcja ma być związana ze stringiem
b) kontrola na etapie pre-wykonania menu, co do np uprawnień, sensu i zdolności do wykonania w kontekście (edycja zatwierdzonej faktury / braku faktury w kontekście)
c) dokroryzacja: wspomaganie translacji na inne języki (ale bazą jest tożsamość oparta na czymś innym niż string użytkownika)

w WinAPI były o ile pamiętam identyfikatory pozycji w menu tworzone przez IDE na podstawie tekstu, ale będące stałymi symbolicznymi

0

Dziękuje panowie za odpowiedzi i dyskusje. Długo się wcześniej zastanawiałem nad tymi strukturami, ale przekonaliście mnie. Dlatego postanowiłem rozpisać trochę kod. Kombinuje na ten moment ze wskaźnikami na funkcje w strukturach. Zrobiłem jakieś pierwsze testowe menu i uporządkowałem troszkę myśli w głowie, więc zobaczę co z tego wyniknie. Myślę, że dziś wieczorem wrzucę jakiś rozpisany kod i podpytam jeszcze co o tym myślicie.

1
Miang napisał(a):

w WinAPI były o ile pamiętam identyfikatory pozycji w menu tworzone przez IDE na podstawie tekstu, ale będące stałymi symbolicznymi

Noooo ... i switch case na 2000 linii.
Identyfikacja jako #define o zawartości liczbowej ... lepsza niz żadna, ale reszta to kiepsko było...

Zigor36 napisał(a):

. Kombinuje na ten moment ze wskaźnikami na funkcje w strukturach

Piszesz w C++ ale myślisz w C, to nie zaprowadzi w dobrą stronę.

Proponuję coś a'la:

(abstract) class Action {
   const std::string caption;
   bool isActive() { return true }
   void execute(... context) = 0;
}
1

Dobra, zadam pytanie, które jeszcze nie padło, a jest dość istotne. Czy to program do nauki, zaliczenia czegoś na uczelnię, czy ma być realnie używany?

Pytam, bo nie widzę za bardzo plusów korzystania z XXXcurses (jest wiele tego typu bibliotek, chociażby ncurses czy właśnie wspomniana pdcurses) w nowych projektach (poza kilkoma niszami lub jeśli klient tego się jawnie domaga)*. Skoro będziesz to odpalać na środowisku okienkowym, to czemu nie korzystasz z nowoczesnych elementów UI, tylko tworzysz klon czegoś z czasów DOS'a, czyli z grubsza cofasz się 30 lat w technologii...


* Ktoś opowiadał mi kiedyś jak jakaś duża firma (chyba jakiś bank) zlecił wymianę systemu. Ogromna kasa, wiele miesięcy ciężkiej pracy zespołu koderów. W końcu, zamiast starego czegoś działającego w trybie tekstowym, zrobili piękne GUI z wszystkimi dzwonkami i wodotryskami. Wrzucili na produkcję i panika, wydajność ludzi spadła o 80% (wartość z tyłka). Nie tylko stare nawyki, ale konieczność korzystania z myszki - w starym systemie wszystko szło z klawiatury, ludzie używali pamięci mięśniowej na zasadzie F6 3x strzałka w górę insert enter strzałka strzałka w dół enter TAB enter TAB TAB F2, a na nowym tak się nie dało. W efekcie powstała proteza - na nowy hiper super wypasiony system zrobiony w najnowszych technologiach nalozono UI udający aplikacje dosową. I właśnie to są te nieliczne przypadki, kiedy tworzenie takich aplikacji ma sens. Jeśli nie mówimy o takiej niszy to serio - nie idź tą drogą.

1
cerrato napisał(a):

Dobra, zadam pytanie, które jeszcze nie padło, a jest dość istotne. Czy to program do nauki, zaliczenia czegoś na uczelnię, czy ma być realnie używany?

Jest to program na zaliczenie na uczelnie. Generalnie na zajęciach nie wyszliśmy poza podstawowe wejście, wyjście w konsoli, algorytmy, podstawy obiektowości i standardowe biblioteki cpp takie jak np. STL. Także nie mam przymusu robić tego typu interface'u, jednak bardziej to interpretuje jako właśnie program do nauki.
Dostałem dosyć proste polecenie i prowadzący zajęcia też mi powiedział, że bardziej punktowana będzie wydajność programu niż wygląd.
Więc w sumie mam świadomość, że program może być niżej oceniony, np. jak coś mocno popsuje w kodzie i że trochę sobie utrudniam, jednak bardziej zależy mi na samorozwoju niż na ocenie.
W dniu prezentacji projektu też się dowiem, czy coś mocno popsułem, także wyciągne z tego lekcje :D

Za pdcurses zabrałem się też z czystej ciekawości. "czyli z grubsza cofasz się 30 lat w technologii...", jak skompilowałem tą biblioteke i spojrzałem na przykładowy program demo dla tej biblioteki to zobaczyłem datę 1993 i nawet się zaskoczyłem :D, no ale zależy mi właśnie zrobić taki interface w konsoli, gdyż myślę, że jakby to było GUI, to program jeszcze bardziej by się różnił od zagadnień z zajęć.

Uważam też ogarnięcie tej biblioteki jako potencjalne narzędzie do zrobienia konsolowej gry nad której zrobieniem myślałem od dłuższego czasu.

1

Napisałem wstępny zamysł jak widzę operacje na menu oparte na strukturach. Mam dwie struktury Menu oraz MenuOption. Do przypisywania danej funkcji po kliknięciu klawisza, który przechwytuje wewnątrz drawMenu() używam std::function. Z racji z tego, że po kliknięciu w menu książek mają się wyświetlić submenu książek, do przekazania funkcji z parametrem (np. drawMenu()) korzystam z lambdy.

Liczę na Państwa pomoc i mam nadzieję ostateczne nakierowanie mnie, czy idę w dobrym kierunku? I czy podane rozwiązanie rzeczywiście nie odbiega za bardzo od C++.

Przepraszam @Miang za pomyłkę :D Oczywiście również dziękuje za odpowiedź!

#include <curses.h>
#include <iostream>
#include <vector>
#include <functional>

class App
{
private:
	struct MenuOption {
		std::string label;
		std::function<void()> function;
	};

	struct Menu {
		std::string name;
		std::string desc;
		std::vector<MenuOption> menu_option;
	};
	
	static void drawMenu(const Menu& mn);
	// do rysowania menu z listą kontaktów i książek planuje napisać osobną funkcję:
    // static void drawTable();
	static void testowa();
	static void quit();

public:
	bool running;
	void start();
	App();
	~App();
};
// app.cpp

void App::start()
{
    Menu mainMenu;

    Menu booksMenu = { "KSIAZKI", "operacje na ksiazkach", {
        { "przegladaj", testowa },
        {"dodaj", testowa },
        {"cofnij", [&mainMenu]() { drawMenu(mainMenu); }} 
    }};

    Menu contactMenu = { "OTWARTA KSIAZKA", "operacje na kontaktach", {
        { "przegladaj", testowa },
        {"sortuj", testowa },
        {"dodaj kontakt", testowa }}, 
        {"cofnij", [&mainMenu]() { drawMenu(mainMenu); }} 
    }};

    Menu settingsMenu = { "USTAWIENIA", "zmien ustawienia", {
        {"zmien kolory", testowa },
        {"ustaw czcionke", testowa },
        {"opcje zapisu", testowa }}, 
        {"cofnij", [&mainMenu]() { drawMenu(mainMenu); }} 
    }};

    mainMenu = { "MENU GLOWNE", "cos", {
        {"ksiazki", [&booksMenu]() { drawMenu(booksMenu); }},
        {"otwarta ksiazka", [&contactMenu]() { drawMenu(contactMenu); }},
        {"ustawienia", [&settingsMenu]() { drawMenu(settingsMenu); }},
        {"wyjdz", quit}
    }};

    this->drawMenu(mainMenu);
}

Moim początkowym zamysłem było też pozbycie się wielu instrukcji switch(case) do operowania na menu i mam nadzieję, że udało się to osiągnać.

Zastanawiam się wciąż jak mógłbym poukładać wyświetlanie kontaktów w formie interface'u z menu wyboru. Przechowywane obiekty Contact chcę przechowywać w wektorze w klasie ContactBook, więc nasuwa mi się póki co tylko na myśl, żeby w jakiś sposób przechowywać obiekty ContactBook w nowych strukturach, np. Table, TableItem, albo wyłącznie ich nazwy, wraz z funkcjami.

Jest to trudniejsza rzecz do zrobienia, niż rysowanie i interakcja menu o stałych rozmiarach, więc obawiam się, że nie osiągnę tego w podobny sposób.

AnyKtokolwiek napisał(a):
Miang napisał(a):

Piszesz w C++ ale myślisz w C, to nie zaprowadzi w dobrą stronę.

Proponuję coś a'la:

(abstract) class Action {
   const std::string caption;
   bool isActive() { return true }
   void execute(... context) = 0;
}

Jutro bardziej się zagłębie w te rozwiązanie i spróbuje napisać z tym jakiś kod. Najwyżej podpytam o wskazówki :)

7

Co do tytułu:

Czy to wydajny sposób na przechowywanie danych odnośnie menu?

Co znaczy "wydajny"?

  • mało zużywa pamięci? (ma sens jeśli twój kod ma być uruchomiony na małym urządzeniu, gdzie limit pamięci jest istotnym problemem)
  • szybki czasowo? (bezsensu w każdym wypadku dla tego scenariusza)
  • łatwy w utrzymaniu? (to jest zawsze ważne)
  • ????

Przypuszczam, że jak większość początkujących próbujesz optymalizować nieistotne rzeczy (nieważne ile razy szybszy będzie ten kod, użytkownik tego nigdy nie zauważy, szczególnie, że głównym wąskim gardłem jest IO, a nie wyświetlanie menu).
Lepiej skup się na dwóch rzeczach:

  • ma działać prawidłowo
  • ma być czytelne, łatwe w poprawianiu i dodawaniu nowych rzeczy

Co do tego:

std::vector<std::tuple <std::string, std::string, std::vector<std::string>>> menus;

Nie używaj std::tuple jeśli nie jest to zaawansowany kod szablonów.
std::tuple zostalo dodane by umożliwić przechowywanie nieznanej liczby zmiennych nieznanego typu w generic code.
W kodzie bez szablonów, zaciemnia czytelność kodu. Czy naprawdę chcesz widzieć w kodzie: std::get<2>(menu)? Nie lepiej mieć coś w stylu: menu.title?

1 użytkowników online, w tym zalogowanych: 0, gości: 1