Forum: Mikrocontroller und Digitale Elektronik Menü mit Funktionspointer via Array of Struct


von D. C. (joker89)


Lesenswert?

Hallo ich schreibe gerade an einem Menü und würde gerne das Ende der 
Liste heraus finden.
1
typedef void (*MenuFnct) (int);
2
3
struct MenuEntry
4
{
5
    char     Text[20];
6
    MenuFnct Function;
7
    int      MenuIndex;
8
};
9
10
struct MenuEntry MainMenu[] =
11
{
12
    // Der Funktion HandleEdit soll beim Aufruf 23 mitgegeben werden
13
    { "Auto Search",  HandleEdit,  101 },
14
    { "Band Scanner",  HandleCopy,   102 },
15
    { "Manual Mode", HandlePaste,  103 },
16
    { "Settings", NULL ,  104 },
17
18
    { NULL, NULL ,NULL },
19
};
20
21
void DoMenu (struct MenuEntry Menu[])
22
{
23
    
24
    // until the end of pointer list
25
    int i = 0;
26
    while (Menu[i].Function != NULL) {
27
        
28
        osd.print(Menu[i].Text, 7, 7+i);
29
        i++;
30
    }
31
}

Diese Variante funktioniert schon mal, ich will aber noch in unter Menü 
springen dh steht bei Settings auch NULL da es hier keine 
Funktionspointer gibt und ich das mit NULL symbolisiere . Ich wollte 
jetzt den String am Ende auf NULL abfragen ->
    while (Menu[i].Text != NULL) {
Leider geht das aber nicht, ich komme leider nicht darauf wieso und 
weshalb das hier nicht funktioniert.
Gruß

: Verschoben durch User
von Nop (Gast)


Lesenswert?

D. C. schrieb:
> Leider geht das aber nicht, ich komme leider nicht darauf wieso und
> weshalb das hier nicht funktioniert.

Also erstens ist Dein Dreifach-Null am Ende schonmal schlecht, weil NULL 
ein Pointer ist, Du das dritte NULL aber in einen INT setzt. Das geht 
nur, falls zufälligerweise auf Deiner Plattform int genauso breit wie 
ein Pointer ist. Das dritte NULL sollte stattdessen 0 sein.

Zweitens geht das nicht, weil Dein Funktionszeiger eben ein Zeiger ist, 
da kann also NULL drinstehen, während Dein Text kein Pointer ist, 
sondern ein Array. Da kann nicht NULL drinstehen. Arrays und Pointer 
sind nicht ganz dasselbe.

von Nop (Gast)


Lesenswert?

Idee: Schreib Dein Dreifach-NULL am Ende um zu:
"", NULL, 0
und teste dann mit:
while (Menu[i].Text[0] != 0)

von W.S. (Gast)


Lesenswert?

D. C. schrieb:
> Diese Variante funktioniert schon mal...

...aber ausgesprochen schlecht.
Fang doch erstmal damit an, dir ein Konzept zurechtzulegen:

- soll es ein reines Text-Menü sein? Wenn nein, dann mußt du mit 
Quasi-Objekten arbeiten, wen ja, dann kannst du bei Text bleiben.

- bedenke, was du für Bedienelemente hast. Tasten? und wie viele und mit 
welcher Bedeutung? oder Drehknopf mit Pushbutton? Letzlich, ob du nur 3 
Dinge hast (vor, zurück, action) oder Koordinaten (auf, ab, rechts, 
links, action) oder eine Reihe  Knöpfe, denen grafisch ne Überschrift 
zugeordnet wird.

Obendrein mußt du dich entscheiden, ob du ein ganzes Menü mit mehreren 
Einträgen anzeigen willst oder nur 1 Menüpunkt oder einen Ausschnitt aus 
dem Menü. Alle Varianten bedürfen einer etwas anderen Menügestaltung.

Aber in jedem Menü-Element brauchst du folgendes:
- Flags, die dir Eigenschaften anzeigen, also z.B. ob es das erste 
Element im Menü ist oder das letzte oder keines von beiden (also 
mittendrin), ob das Element ein Start ist für eine Aktion oder ein Edit 
für einen Parameter oder ob das Element neu gezeichnet werden muß bei 
einer bedienerunabhängigen Angelegenheit (z.B. Uhrzeit-Änderung), sodann 
Indizes, die dir anzeigen, wohin die Reise gehen soll, wenn jemand auf 
'action' drückt, sodann ein oder mehrere Texte (aber als char* und nicht 
als char array).
Eines brauchst du im Menü-Element aber garnicht: einen Menü-Index. Die 
Stelle, wo du dich im Menü befindest, wird vollständig beschrieben durch 
die Nummer des aktuellen Elementes.

Ich würde das etwa so machen:

#define erster  (1<<0)
#define letzter (1<<1)
#define intedit (1<<2)
#define floatedit (1<<3)
.. usw.

struct MenuEntry
{ dword Flags;
  void* EditPointer;   // wenn Eintrag ein Edit ist
  int   IndexOnAction; // für Submenü-Sprünge
  char* Text1;
//  char* Text2; für zweizeiliges LCD
};

W.S.

von Rolf M. (rmagnus)


Lesenswert?

D. C. schrieb:
> Ich wollte jetzt den String am Ende auf NULL abfragen ->
>     while (Menu[i].Text != NULL) {
> Leider geht das aber nicht, ich komme leider nicht darauf wieso und
> weshalb das hier nicht funktioniert.

Es liegt daran, dass du den Unterschied zwischen Zeiger und Array noch 
nicht verstanden hast. Text ist ein Array. Das kann nicht NULL sein.

von Dirk B. (dirkb2)


Lesenswert?

Nop schrieb:
> Also erstens ist Dein Dreifach-Null am Ende schonmal schlecht, weil NULL
> ein Pointer ist, Du das dritte NULL aber in einen INT setzt. Das geht
> nur, falls zufälligerweise auf Deiner Plattform int genauso breit wie
> ein Pointer ist. Das dritte NULL sollte stattdessen 0 sein.

Stimmt zwar, aber die Begründung ist falsch.
Das geht auch, wenn sizeof(int) != sizeof(NULL) ist.

Je nach Definition von NULL sollte aber eine Warnunung vom Compiler 
kommen.
http://www.cplusplus.com/reference/cstdlib/NULL/

von Daniel A. (daniel-a)


Lesenswert?

Ich habe nie verstanden, wieso man NULL überhaupt verwenden sollte, und 
nicht einfach 0 nimmt. Ich meine 0 ist 0, wiso sollte ich da extra eine 
Makro Konstante nehmen wenn ich einem Pointer 0 zuweisen will, wenn ich 
bei jedem anderen arithmetischen Datentyp einfach 0 für 0 hinschreibe?

von Dirk B. (dirkb2)


Lesenswert?

Daniel A. schrieb:
> Ich habe nie verstanden, wieso man NULL überhaupt verwenden sollte,

Man kann beim lesen schon erkennen, das es um Pointer geht.

Man schreibt auch, wenn es um C-Strings geht, '\0' und nicht 0 für das 
Stringende.

Zudem ist NULL nicht immer als 0 definiert.  Z.B ist (void*)0 auch 
möglich.

von Rolf M. (rmagnus)


Lesenswert?

Daniel A. schrieb:
> Ich habe nie verstanden, wieso man NULL überhaupt verwenden sollte, und
> nicht einfach 0 nimmt. Ich meine 0 ist 0, wiso sollte ich da extra eine
> Makro Konstante nehmen wenn ich einem Pointer 0 zuweisen will, wenn ich
> bei jedem anderen arithmetischen Datentyp einfach 0 für 0 hinschreibe?

Bei GCC wird NULL durch das compilerspezifische __null ersetzt, das 
warnt, wenn man es fälschlicherweise in einem nicht-Integer-Kontext 
benutzt.
In C++ hat Stroustrup bis C++98 tatsälich auch dazu geraten, 0 statt 
NULL für Nullzeiger zu verwenden, unter anderem weil das z.B. dort bei 
überladenen Funktionen sonst zu Verwirrung führen kann. Seit C++11 
gibt's aber das spezielle Schlüsselwort nullptr, das man nun verwenden 
soll.

Dirk B. schrieb:
> Zudem ist NULL nicht immer als 0 definiert.  Z.B ist (void*)0 auch
> möglich.

Spielt aber keine große Rolle. Ob 0 funktionert oder nicht, hängt nicht 
davon ab, wie NULL defniert ist.

von W.S. (Gast)


Lesenswert?

Naja, aus gutem Grunde ist in Pascal sowas wie 'nil' definiert, ebeb 
darum daß man damit ne saubere Unterscheidung hat zwischen Zeiger-Zeugs 
und arithmetischem Zeugs. Aber in C kann man ja hinpinseln, was man 
will.

ähemm.. und der TO hat sich inzwischen ohne Abschied verdrückt. War ihm 
wohl doch nicht so wichtig mit seinem Menü.

W.S.

von Possetitjel (Gast)


Lesenswert?

Daniel A. schrieb:

> Ich habe nie verstanden, wieso man NULL überhaupt
> verwenden sollte, und nicht einfach 0 nimmt. Ich
> meine 0 ist 0, wiso sollte ich da extra eine Makro
> Konstante nehmen wenn ich einem Pointer 0 zuweisen
> will, wenn ich bei jedem anderen arithmetischen
> Datentyp einfach 0 für 0 hinschreibe?

Ich habe auch nie verstanden, was Tristate-Logik für
ein Quatsch sein soll. Ich meine, wenn an einem Ausgang
keine Spannung anliegen soll - warum legt man da nicht
einfach 0V an? Das ist doch "keine Spannung"?

SCNR

von D. C. (joker89)


Lesenswert?

W.S. schrieb:
> D. C. schrieb:
>> Diese Variante funktioniert schon mal...
>
> ...aber ausgesprochen schlecht.
> Fang doch erstmal damit an, dir ein Konzept zurechtzulegen:

Der Code ist aus meinem Konzept entstanden

> - soll es ein reines Text-Menü sein? Wenn nein, dann mußt du mit
> Quasi-Objekten arbeiten, wen ja, dann kannst du bei Text bleiben.

Ja es wird ein Text Menü, mit maximal 3 Submenüs am Ende befinden sich 
meist als ich sag mal aktion eine Funktion die über einen 
Funktionspointer aufgerufen werden soll was auch schon mal funktioniert.

> - bedenke, was du für Bedienelemente hast. Tasten? und wie viele und mit
> welcher Bedeutung? oder Drehknopf mit Pushbutton? Letzlich, ob du nur 3
> Dinge hast (vor, zurück, action) oder Koordinaten (auf, ab, rechts,
> links, action) oder eine Reihe  Knöpfe, denen grafisch ne Überschrift
> zugeordnet wird.
Ich habe einen Button, der hat die Auswertung Klick(Menü down) 
Doubleklick( Menü tiefer) longklick (Einstellung ändern aktion ausführen 
-> funktionspointer)
> Obendrein mußt du dich entscheiden, ob du ein ganzes Menü mit mehreren
> Einträgen anzeigen willst oder nur 1 Menüpunkt oder einen Ausschnitt aus
> dem Menü. Alle Varianten bedürfen einer etwas anderen Menügestaltung.
Vom Menü ist immer das ganze Menülevel sichtbar bzw 4 Einträge wenn es 
mehr sein sollen
> - Flags, die dir Eigenschaften anzeigen, also z.B. ob es das erste
> Element im Menü ist oder das letzte oder keines von beiden (also
> mittendrin), ob das Element ein Start ist für eine Aktion oder ein Edit
> für einen Parameter oder ob das Element neu gezeichnet werden muß bei
> einer bedienerunabhängigen Angelegenheit (z.B. Uhrzeit-Änderung), sodann
> Indizes, die dir anzeigen, wohin die Reise gehen soll, wenn jemand auf
> 'action' drückt, sodann ein oder mehrere Texte (aber als char* und nicht
> als char array).
Wo liegt das Problem mit char Array ?
> Eines brauchst du im Menü-Element aber garnicht: einen Menü-Index. Die
> Stelle, wo du dich im Menü befindest, wird vollständig beschrieben durch
> die Nummer des aktuellen Elementes.
Ja das war mein erster Gedanke so einfach wie möglich aber ich bekomme 
eben wenig information daraus es hätte aber gereicht, deine Idee mit dem 
Flags ist da wohl die bessere Lösung.
Schon mal vielen Danke !!!

Ich hätte mir das mit dem verschachteln so gedacht das ich meiner 
Funktion DoMenu einfach bei Settings(nächstes Submenü) nach der Aktion 
ein neues MenuEntry übergebe mit dem Aufbau des Submenüs von Settings.

Gruß

von chris_ (Gast)


Lesenswert?

Possetitjel (Gast)
>Ich habe auch nie verstanden, was Tristate-Logik für
>ein Quatsch sein soll.

Ist zwar eine "off topic" Anmerkung, aber wenn's Deinem Verständnis 
hilft:

https://de.wikipedia.org/wiki/Tri-State:
Durch Tri-States ist es möglich, die Ausgänge mehrerer Bauelemente 
zusammenzuschalten, ohne dass es zu Kurzschlüssen, einer Überlagerung 
oder einer Wired-AND- oder Wired-OR-Verknüpfung kommt, z. B. bei 
Datenbussen.

von D. C. (joker89)


Lesenswert?

Kleines Update:
1
//Flags
2
#define startMenu   (1<<0)
3
#define endMenu     (1<<1)
4
#define editMenu    (1<<2)
5
#define save        (1<<3)
6
#define mainMenu    (1<<4)
7
8
typedef void (*MenuFnct) (int);
9
10
struct MenuEntry
11
{
12
    char*       text;
13
    MenuFnct    function;
14
    uint8_t     flags;
15
    struct MenuEntry *subMenu;
16
    int         menuIndex;
17
};
18
19
void AutoSearch (int arg)
20
{
21
}
22
23
void BandScanner (int arg)
24
{
25
}
26
27
void ManualMode (int arg)
28
{
29
}
30
31
void drawMenu(int arg){
32
33
}
34
35
36
struct MenuEntry MainMenu[] =
37
{
38
    { "Auto Search",  AutoSearch, 0b1000000 , NULL },
39
    { "Band Scanner",  BandScanner,0b0000000 , NULL  },
40
    { "Manual Mode", ManualMode,0b0000000 , NULL },
41
    { "Settings", drawMenu ,0b0100000 , NULL },
42
};
43
44
struct MenuEntry Settings[] =
45
{
46
    { "Auto Search",  AutoSearch,  0b1000000 , NULL },
47
    { "Band Scanner",  BandScanner,   0b0000000 , NULL },
48
    { "Manual Mode", ManualMode,  0b0000000 , NULL },
49
    { "Settings", drawMenu ,  0b0100000 , NULL },
50
};

von Dirk B. (dirkb2)


Lesenswert?

Aber was soll der Schmarren mit den 0b1000000 ?
Dazu hast du doch extra die Flags definiert.
Allerdings passt 0b1000000 zu keinem deiner #defines

Macros werden in C i.A. vollständig groß geschrieben.

(Jetzt kannst du deine Endekennung mit den NULL-Pointern machen.)

von Rolf M. (rmagnus)


Lesenswert?

Dirk B. schrieb:
> Aber was soll der Schmarren mit den 0b1000000 ?
> Dazu hast du doch extra die Flags definiert.
> Allerdings passt 0b1000000 zu keinem deiner #defines

Außerdem: Warum nicht einfach einen Enum benutzen statt #defines und 
int?

von Daniel A. (daniel-a)


Angehängte Dateien:

Lesenswert?

Hier mal ein Menu Beispiel mit meinem GCC Spezifischen Link-time list 
generation hack mittels sections. 
https://www.mikrocontroller.net/articles/C_Linkerhacks_%2B_Modularisierung

Kompilieren mit:
1
gcc -std=c99 -Wall -Werror -Wextra -pedantic menu.c autosearch.c bandscanner.c -o menu

Pros:
 * Man kann eine neue Funktionalität und deren Listeneintrag 
dazunehmen/entfernen, indem man die Datei Dazulinkt oder auch nicht.

Cons:
 * Übler hack
 * GCC-Spezifisch, andere noch nicht getestet

von D. C. (joker89)


Lesenswert?

Viele dank für die Antworten,
der Vorschlag von Daniel ist sicher ganz nett aber für mich jetzt 
übertrieben da ich mich erst tiefer einarbeiten muss. Und ich froh bin 
das mein Makefile jetzt endlich mal so funktioniert. Und das Menü ist 
auch nicht sehr groß.
1
struct MenuEntry MainMenu[] =
2
{
3
    { "Auto Search",  AutoSearch, { 0 , 1 } , NULL , 0},
4
    { "Band Scanner",  BandScanner, { 0 , 0 } , NULL , 0 },
5
    { "Manual Mode", ManualMode, { 0 , 0 } , NULL , 0 },
6
    { "Settings", drawMenu , { 0 , 0 } , NULL , 0 },
7
    { "Exit", exit , { 1 , 0 } , NULL , 0 },
8
9
};
10
11
struct MenuEntry Settings[] =
12
{
13
    { "Batterie BEEPS",  AutoSearch, { 0 , 0 } , NULL , 0 },
14
    { "Calibrate RSSI",  BandScanner, { 0 , 0 } , NULL , 0 },
15
    { "Video Input", ManualMode, { 0 , 0 } , NULL , 0 },
16
    { "Back", drawMenu , { 1 , 0 } , NULL , 0 },
17
};
18
19
void MenuRefresh(struct MenuEntry *Menu )
20
{
21
    osd.clearScreen();
22
    // find activ menu item, if not set the first item...
23
    uint8_t activItem   = 0;
24
    while (true) {
25
        if (Menu[activItem].flags.MENUEND) {
26
            osd.printMax7456Char(0xfC, 5, 7 + 0, true, true);
27
            Menu[0].flags.ACTIVEITEM = true;
28
            break;
29
        }
30
        activItem++;
31
    }
32
    
33
    // draw until the end of pointer list
34
    uint8_t         i = 0;
35
    do {
36
        osd.print(Menu[i].text, 7, 7+i);
37
        i++;
38
    } while (!Menu[i-1].flags.MENUEND);
39
    _delay_ms(20);
40
}
41
42
void MenuAction(struct MenuEntry *Menu ){
43
    uint8_t activItem   = 0;
44
    do{
45
//        osd.print( Menu[activItem].flags.MENUEND    + 5, 21, 7 +activItem, 1, 0);
46
//        osd.print( Menu[activItem].flags.ACTIVEITEM + 5, 22, 7 +activItem, 1, 0);
47
        if (Menu[activItem].flags.ACTIVEITEM) {
48
            Menu[activItem].function(1);
49
            return;
50
        }
51
        activItem++;
52
    }while (!Menu[activItem - 1 ].flags.MENUEND);
53
}
54
55
void MenuNext(struct MenuEntry *Menu ){
56
    uint8_t activItem   = 0;
57
58
    while (true) {
59
        if (Menu[activItem].flags.MENUEND && Menu[activItem].flags.ACTIVEITEM) {
60
            osd.printMax7456Char(0x00, 5, 7 + activItem);
61
            Menu[activItem].flags.ACTIVEITEM = false;
62
            MenuRefresh(Menu);
63
            return;
64
        }
65
        if (Menu[activItem].flags.ACTIVEITEM) {
66
            Menu[activItem].flags.ACTIVEITEM = false;
67
            Menu[activItem + 1].flags.ACTIVEITEM = true;
68
            osd.printMax7456Char(0x00, 5, 7 + activItem);
69
            osd.printMax7456Char(0xfC, 5, 8 + activItem, true ,true );
70
            return;
71
        }
72
        activItem++;
73
    }
74
}
Aktuell ist dass der Stand der Dinge, es gibt jetzt noch ein Struct in 
dem ich flags setzen und lesen kann.
Das Menü lässt sich mit einem Button bedienen sprich einmal drücken -> 
MenuNext, lang drücken bedeutet Aktion ausführen MenuAction. Bin ich am 
Ende springe ich wieder hoch.
Eins steht noch an und zwar wie übergebe ich MenuRefresh ein neues 
SubMenu bzw woher hole ich mir am besten die infos welches subMenu 
aufgerufen werden soll.
Für Anregungen bin ich natürlich offen . Gruß

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

D. C. schrieb:
> Ich hätte mir das mit dem verschachteln so gedacht das ich meiner
> Funktion DoMenu einfach bei Settings(nächstes Submenü) nach der Aktion
> ein neues MenuEntry übergebe mit dem Aufbau des Submenüs von Settings.

Nun ja, ist heut schon spät.. trotzdem:
Du denkst immer noch zu wirr.

Also: ein char* kostet im Menü nur einen Zeiger und dafür können die 
Texte beliebig lang sein, denn sie werden ja außerhalb des Menüarrays 
gespeichert. Das entspannt die Speichersituation.

Dann: Packe alle Menüs und Untermenüs und Unter..Unter.. in ein und 
dasselbe Array. Ein jedes hat ein erstes und ein letztes Element und 
diese beiden begrenzen all das, was du darstellen willst. Für das 
Aufrufen eines Submenüs brauchst du bloß den aktuellen Index neu zu 
setzen, ggf. unter Einkellern eines Rückkehr-Index. Alternative: eine 
Stelle im Submenü, die dediziert heraus in die nächsthöhere Ebene führt 
oder implizit nach Abarbeiten der aktuellen Aktion.

W.S.

von D. C. (joker89)


Lesenswert?

Ja diese Lösung hatte ich schon, aber es muss doch auch geordneter gehen 
. Ich hab jetzt an soetwas gedacht ->
1
struct MenuEntry Settings[] =
2
{
3
    { "Batterie BEEPS",  AutoSearch, { 0 , 0 } , NULL , 0 },
4
    { "Calibrate RSSI",  BandScanner, { 0 , 0 } , NULL , 0 },
5
    { "Video Input", ManualMode, { 0 , 0 } , NULL , 0 },
6
    { "Back", drawMenu , { 1 , 0 } , MainMenu , 0 },
7
};
8
9
struct MenuEntry MainMenu[] =
10
{
11
    { "Auto Search",  AutoSearch, { 0 , 1 } , NULL , 0},
12
    { "Band Scanner",  BandScanner, { 0 , 0 } , NULL , 0 },
13
    { "Manual Mode", ManualMode, { 0 , 0 } , NULL , 0 },
14
    { "Settings", drawMenu , { 0 , 0 } , Settings , 0 },
15
    { "Exit", exit , { 1 , 0 } , NULL , 0 },
16
17
};

drawMenu wäre eine Funktion die den Pointer auf Settings übergeben 
bekommt und einer Variable die alle Menüs halten kann übergeben wird 
also vom typ MenuEntry. Nach der Übergebe wird einfach MenuRefresh 
wieder aufgerufen.
Das war jetzt nur ein Gedanke....
Gruß

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.