ООПные изыскания на тему многоуровневого меню

5N62V
Offline
Зарегистрирован: 25.02.2016

Традиционно всем бобра! :)

С кнопками с помощью Евгения (спасибо еще раз! ) разобрались . Настал черед меню. Можно взять готовое? Наверняка можно, и даже нужно, но в моем случае надо еще и учиться писать, тренироваться на кошках так сказать.  Так что не гневайтесь! :)

Написал в Visual Studio т.к. дебаг нужен был. В общих чертах - работает. Даже внял некоторым советам из ветки про кнопки.

Итак, количество уровней - не ограничено, разве что памятью, количество пунктов в уровне  - в принципе тоже не ограничено, но в написанном макс = sizeof(uint8_t). Создание пукта меню - простым созданием экземпляра класса, с указанием родительского пункта. Сделал типа на двунаправленых связаных списках, но экзепляр содержит указатели на дочение пункты в контейнере. Как сделать по-другому - не допер.

собсно код:

#include <iostream>
#include <conio.h>
using namespace std;

class MyMenu {
protected:
    string name;
    MyMenu* parent;//указатель на родительское меню
    MyMenu** ptr;//контейнер ссылок на дочение меню
    unsigned char children;//количество дочерних меню
    uint8_t index;//индекс данного экземпляра в контейнере  у родителя
    const static uint8_t _size = sizeof(MyMenu*);

public:
    MyMenu(string n = "base", MyMenu* p = nullptr) : name(n), parent(p), ptr(nullptr), children(0), index(0) {
        if (p != nullptr) {
            if(parent->children < sizeof(unsigned char)-2)parent->children++;// у родителя увеличили кол-во детей
            else { delete this; return; }// не уверен что так можно :)
            void* some = realloc(parent->ptr, parent->children * _size);//перевыделили память под контейнер
            if (some != nullptr)parent->ptr = (MyMenu**)some;
            parent->ptr[parent->children - 1] = this;//указатель на экземпляр занесли в контейнер родителю
            index = parent->children - 1;// и запомнили интекс данного экземпляра в контейнере у родителя
        }
    }

    void moveUp(MyMenu*& p) const{// ф-ия перемещения курсора вверх
        if (p->parent == nullptr)return;// если нет родителя - уходим
        if (p->index > 0) {//если этот экз не первый на уровне
            p = p->parent->ptr[p->index - 1];//то курсор переходит на позицию выше
            p->drawMenu(p);// прорисовка меню и курсора
            return;
        }
        else {// если курсор смотрит на первую строчку меню на уровне
            if (p->parent->parent == nullptr)return;
            else p = p->parent->parent->ptr[0];//то курсор переходит на первую строчку на уровень выше
            p->drawMenu(p);// прорисовка
        }
    }

    void moveDown(MyMenu*& p)  const {// ф-ия перемещения курсора вниз
        if (p->index < p->parent->children - 1) {//если курсор смотрит не на последнюю строку
            p = p->parent->ptr[p->index + 1];// то курсор переходит на строку вниз
            p->drawMenu(p);// прорисовка
            return;
        }        
    }

    void moveIn(MyMenu*& p)  const {// ф-ия выбора пункта меню
        if (p->children == 0) {// если у данного экз нет детей
            cout << "jumped to " << p->name << " handler\r\n";// то прыгает в обработчик данного пункта
            return;
        }
        p = p->ptr[0];// либо же курсор перемещается на первую строчке уровнем вниз
        p->drawMenu(p);
        return;
    }


    void drawMenu(MyMenu* p)const {// ф-ия прорисовки меню
        if (p == nullptr)return;
        cout << endl;
        for (int i = 0; i < p->parent->children; i++) {// цикл по кол-ву детей у родителя данного экземпляра           
            cout << i << ". " << parent->ptr[i]->name;// печатаем имя
            if (p == parent->ptr[i])cout << "<-";// отмечаем пункт на который указывает курсор
            cout << endl;
        }        
    }

    MyMenu* operator=(MyMenu* p) { return this; }// перегружает = в нужном нам контексте
};

MyMenu base;// создаем самый базовый экземпляр, он не имеет родителя
MyMenu line1("line1", &base);// создаем пункты меню , указывая родителя
MyMenu line2("line2", &base);
MyMenu line3("line3", &base);
MyMenu line4("line4", &base);
MyMenu line5("line5", &base);
MyMenu line6("line6", &base);
MyMenu line7("line7", &base);
MyMenu line8("line8", &base);
MyMenu line9("line9", &base);

MyMenu line11("line11", &line1);
MyMenu line12("line12", &line1);
MyMenu line13("line13", &line1);

MyMenu line21("line21", &line2);
MyMenu line22("line22", &line2);
MyMenu line23("line23", &line2);

MyMenu line211("line211", &line21);
MyMenu line212("line212", &line21);
MyMenu line213("line213", &line21);

MyMenu line221("line221", &line22);
MyMenu line222("line222", &line22);
MyMenu line223("line223", &line22);
MyMenu line224("line224", &line22);
MyMenu line225("line225", &line22);

MyMenu line231("line231", &line23);

MyMenu line31("line31", &line3);
MyMenu line32("line32", &line3);
MyMenu line33("line33", &line3);

MyMenu line111("line111", &line11);
MyMenu line112("line112", &line11);
MyMenu line113("line113", &line11);
MyMenu line114("line114", &line11);

MyMenu line1111("line1111", &line111);
MyMenu line1112("line1112", &line111);
MyMenu line1113("line1113", &line111);
MyMenu line1114("line1114", &line111);

#define KEY_ESC 27
#define KEY_ARROW_UP 72
#define KEY_ARROW_DOWN 80
#define KEY_ARROW_RIGHT 77
int main()
{// представим, что при возникновении события меню мы попадает сюда
    static MyMenu* p = &line1;// создаем курсор , он указывает на верхний пункт верхнего уровня
    p->drawMenu(p);// прорисовка

    int c = 0;
    while ((c = _getch()) != KEY_ESC) {// пока не нажали Escape
        switch (c) {
        case KEY_ARROW_UP: p->moveUp(p); break;// если стрелка вверх
        case KEY_ARROW_DOWN: p->moveDown(p); break;//          вниз
        case KEY_ARROW_RIGHT: p->moveIn(p); break;//          вправо
        }
    }
}

В чем я не уверен, так а стоит ли так вообще делать: динамический контейнер для хранения указателей на дочерние пункты. Не приведет ли это к перерасходу памяти из-за фрагментации?

Потом, с точки зрения расхода памяти, что лучше , дополнительный член  1 байт, или дополнительные метод, чтоб вычислить то значение, что хранится сейчас в члене index. 

При количестве пунктов 100 обработку пунктов(пока не прикрутил) делать колбеками или виртуальными ф-циями? Все конечно интересует применительно к МК.

как в конструкторе убить экземпляр, если например выходишь за пределы по кол-ву пунктов одного уровня? 

Глум, веселье, советы и критика - все велкам! :)

 

 

nik182
Offline
Зарегистрирован: 04.05.2015
Виннету, Чингачгук и Чапаев сидят в вигваме, курят трубку мира. На улице уже ночь, темно. Виннету выходит по нужде, слышен грохот, Виннету возвращается с фонарем под глазом. Через некоторое время по нужде выходит Чингачгук, слышен удар, Чингачгук возвращается с фингалом под глазом. Потом понадобилось выйти и Чапаю, слышны два удара, мат-перемат, Чапай возвращается с фонарями под обоими глазами. Чингачгук затягивается и произносит:
- Только бледнолицый мог дважды наступить на одни и те же грабли!
 
Как я понимаю ещё один бледнолицый? Евгений уже подробно все разжевал. И даже ссылку Вам давал. Пробуйте, посмотрите и сделайте своё, но лучше существующего и тогда велкам. Можно будет обсудить. Как по мне достаточно микроменю или с меньшими запросами микроменю V2. Отлизано до невозможности. Для МК самое оно.
5N62V
Offline
Зарегистрирован: 25.02.2016

nik182 пишет:

 Евгений уже подробно все разжевал. И даже ссылку Вам давал. Пробуйте, посмотрите и сделайте своё, но лучше существующего и тогда велкам. Можно будет обсудить. К

Пардон, я не понял о чем Вы. 

UPD. видимо об этом: http://arduino.ru/forum/pesochnitsa-razdel-dlya-novichkov/sozdanie-mnogourovnevogo-graficheskogo-menyu#comment-530773

я не видел этой ветки, жаль.

nik182
Offline
Зарегистрирован: 04.05.2015

Всё смешалось в доме Облонских... Значит не Вам, но форум читать полезно. Например http://arduino.ru/forum/pesochnitsa-razdel-dlya-novichkov/sozdanie-mnogo... и много других. Он точно на днях давал ссылку на его видение построения меню. И на раздел ссылки обратите внимание.

nik182
Offline
Зарегистрирован: 04.05.2015

Вообще правила хорошего тона форума предполагают всестороннее ознакомление с существующими темами и только потом, если остались непонятки задавать вопрос. Честно говоря очень трудно найти тем не обсуждавшихся на форуме. И поиск работает. А писать по нному кругу одно и тоже разным новичкам - точно не то чего хотелось бы на форуме. 

b707
Offline
Зарегистрирован: 26.05.2017

nik182 пишет:
Всё смешалось в доме Облонских... Значит не Вам, но форум читать полезно. Например http://arduino.ru/forum/pesochnitsa-razdel-dlya-novichkov/sozdanie-mnogo... и много других.

о, там и мой скромный опыт построения меню есть. Без классов, но с деревьями :)

5N62V
Offline
Зарегистрирован: 25.02.2016

Да уж. Только глянул на структуру Евнения, и пазл в башке сошелся :) все переписалось  легко и красиво. И заработало сразу! Всем спасибо!

nik182
Offline
Зарегистрирован: 04.05.2015

А результат на общее обозрение?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Я, правда, при разработке меню исходил не из идей ООП, а из идей минимизации:

- памяти, занимаемой структурой меню (в проекте по факту менее одного байта на пункт меню, при том, что все оно хранится в ОЗУ, т.к. формируется динамически),

- работы программиста по его созданию. Поэтому, собственно, и динамическое меню - чтобы программист не проектировал вручную все структуры данных с перекрестными ссылками, а просто присоединял нужные пункты меню в нужные места.

http://arduino.ru/forum/proekty/menyu-dlya-dvukhstrochnogo-displeya

В новом проекте этот подход уже обобщен на создание многострочного меню с прокруткой пунктов на графическом дисплее, но этот вариант пока нигде не выкладывал.

5N62V
Offline
Зарегистрирован: 25.02.2016

nik182 пишет:

А результат на общее обозрение?

Да без проблем! :)


#include <iostream>
#include <conio.h>
using namespace std;

class MyMenu {
protected: 
         string name;
         MyMenu* parent;
         MyMenu* previous;
         void (*handler)();
         MyMenu* daughter;
         MyMenu* next;   
         
public:
    MyMenu(string n = "base", MyMenu* pa = nullptr, MyMenu* pr = nullptr, void (*h)() = nullptr, MyMenu* da =nullptr, MyMenu* ne = nullptr) :
        name(n), parent(pa), previous(pr), handler(h), daughter(da), next(ne){
        if (pr != nullptr)pr->next = this;
        if (pa != nullptr)pa->daughter = this;
    }


    void drawLevel(MyMenu* p) const {// ф-я прорисовки текущего уровня
        MyMenu* cursor = p;
        while (p->previous != nullptr) p = p->previous;//передвигаемся на первый пункт
// текущего уровня
        int i = 0;
        cout << endl;
        do {
            cout << ++i << ". " << p->name;
            if (cursor == p)cout << "<-";
            cout << endl;
            p = p->next;
        } while (p != nullptr);
    }

    void moveDown(MyMenu*& p) const{//ф-я курсов вниз
        if (p->next != nullptr)p = p->next;
        drawLevel(p);
    }

    void moveUp(MyMenu*& p) const{//ф-я курсов вверх
        if (p->previous != nullptr) {
            p = p->previous;
            drawLevel(p);
            return;
        }
        else if (p->parent != nullptr and p->parent->name != "base") {
            p = p->parent;
            drawLevel(p);
        }
    }

    void moveIn(MyMenu*& p) const {//ф-я выбора пункта
        if (p->daughter == nullptr ){
            if(p->handler != nullptr) p->process();//вызов колбека 
        }
        else{
            p = p->daughter;
            while (p->previous != nullptr) p = p->previous;//или вход в подменю
            drawLevel(p);
        }
    }

protected: 
    void process() const {
        (*handler)();
    }
};
//_______________ колбеки для пунктов, у которых нет дочерей (тут указаны не все)

void handler_line11(){
    cout << "handler_line11" << endl;
}
void handler_line12() {
    cout << "handler_line12" << endl;
}

void handler_line13() {
    cout << "handler_line13" << endl;
}

MyMenu base;
MyMenu line1("line1", &base);
MyMenu line2("line2", &base, &line1);
MyMenu line3("line3", &base, &line2);
MyMenu line4("line4", &base, &line3);

MyMenu line11("line11", &line1, nullptr,  &handler_line11);//так объявляются объекты
MyMenu line12("line12", &line1, &line11,  &handler_line12);
MyMenu line13("line13", &line1, &line12,  &handler_line13);

MyMenu line21("line21", &line2);
MyMenu line22("line22", &line2, &line21);
MyMenu line23("line23", &line2, &line22);

MyMenu line31("line31", &line3);
MyMenu line32("line32", &line3, &line31);
MyMenu line33("line33", &line3, &line32);

MyMenu line41("line41", &line4);
MyMenu line42("line42", &line4, &line41);
MyMenu line43("line43", &line4, &line42);

#define KEY_ESC 27
#define KEY_ARROW_UP 72
#define KEY_ARROW_DOWN 80
#define KEY_ARROW_RIGHT 77

int main()
{
    static MyMenu* p = &line1;// создаем курсор, указывающий куда нам надо
    p->drawLevel(p);// прорисовываем меню

    int c = 0;
    while ((c = _getch()) != KEY_ESC) {// пока не нажали Escape
        switch (c) {
        case KEY_ARROW_UP: p->moveUp(p); break;// если стрелка вверх
        case KEY_ARROW_DOWN: p->moveDown(p); break;//          вниз
        case KEY_ARROW_RIGHT: p->moveIn(p); break;//          вправо
        }
    }
}


Код стал на два порядка надежнее, ну и попроще. Создание экземпляров усложнилось, но это мелочи.

47я строка каробит правда... надо было ее так написать:

else if (p->parent != nullptr and p->parent->parent != nullptr ) {

5N62V
Offline
Зарегистрирован: 25.02.2016

andriano пишет:

Я, правда, при разработке меню исходил не из идей ООП, а из идей минимизации:

А на каком проце делали?

ПС. видосик прикольный

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

andriano пишет:
Я, правда, при разработке меню исходил не из идей ООП, а из идей минимизации:

Вот только если бы пойти по пути цифрового автомата и создания поддержки упрощенной html то пошло еще эффектней.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

5N62V пишет:

А на каком проце делали?

Вообще-то от "проца" это не зависит. В конкретных проектах использовался либо Mega, либо stm32. Но, исходя из ресурсоемкости, вполне может быть использовано семейство Ардуин на 328.

Цитата:

ПС. видосик прикольный

А жена раскритиковала, говорит, "засыпаешь на второй минуте".

5N62V
Offline
Зарегистрирован: 25.02.2016

andriano пишет:

А жена раскритиковала, говорит, "засыпаешь на второй минуте".

Много они понимают! :) я на видосиках про их пиллинги-шмиллинги засну на 5й секунде! :))

andriano пишет:

Я, правда, при разработке меню исходил не из идей ООП, а из идей минимизации....

Вообще-то от "проца" это не зависит....

  Тогда на минимизации чего?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

5N62V пишет:

Тогда на минимизации чего?

Занимаемой оперативной памяти.