0
Offline
Newbie
Karma: 0
Posts: 10
Arduino rocks
|
 |
« on: May 15, 2009, 10:15:39 pm » |
[UPDATE] Anyone wanting a menu class for a LCD should look at This Post. This class supports infinitely nested menus, scrolling, and callbacks for each menu item. I've made a hierarchal menu class for a 20x4 LiquidCrystal, and figured it might be useful to someone else. I'd also like to get some feedback as to whether I'm doing anything stupid or anything that should be fixed. It's not super flexible but it gets the job done. #include <LiquidCrystal.h> LiquidCrystal lcd(2,-1,3,4,5,6,7);
typedef boolean (*MYCALL)(void);
class menu { private: int numchildren; //Current number of children menu * children[8];//Pointers to children int inchild; //This variable is not checked for < numchildren, so be careful when setting it MYCALL callback; //Function to call when this menu is clicked. //Return true to go into the menu, false if the menu is just an item to perform a function
public: char *name;
menu(char *n,MYCALL c) { name=n; numchildren=0; inchild=-1; callback=c; }
void addchild(menu &child) { if (numchildren<7) { children[numchildren++]=&child; } else { serror("Too many children"); } }
void display() { Serial.print(name); Serial.println(" Display"); if (inchild==-1) { lcd.clear(); for (int i=0;i<numchildren;i++) { lcd.setCursor(((i%2)*10)+1,i/2); lcd.print(children[i]->name); //lcd.print("Test"); } if (name!="Root") { lcd.setCursor(11,3); lcd.print("Back"); } } else { children[inchild]->display(); } }
void gochild(int thechild) { Serial.print(name); Serial.println(" Gochild"); if (inchild==-1) { if (thechild<numchildren) { if (children[thechild]->callback()) { inchild=thechild; display(); } } else if (thechild==7&&name!="Root") { goupx(); } } else { children[inchild]->gochild(thechild); } }
boolean goup() { Serial.print(name); Serial.print(" Goup "); if (inchild==-1) { Serial.println("1"); return 1; //This one isn't in a menu and is ready to be left } else if (children[inchild]->goup())//The child was ready to be left { Serial.println("0"); inchild=-1; return 0;//Keep the next one from going up } else //Shouldn't happen { serror("Error Going Up"); } }
};
void serror(char *error) { lcd.clear(); lcd.print("ERROR"); lcd.setCursor(0,1); lcd.print(error); }
boolean none() { Serial.println("none"); return 0; }
boolean some() { Serial.println("some"); return 1; }
menu Root("Root",none);
void goupx() { Root.goup(); Root.display(); }
menu Item1("Something",some); menu Item11("Stuff",none); menu Item12("More",some); menu Item121("Deeper",none); menu Item2("Other",none); menu Item3("Etc",some); menu Item31("So On",none);
void menuinit() { Root.addchild(Item1); Root.addchild(Item2); Root.addchild(Item3); Item1.addchild(Item11); Item1.addchild(Item12); Item12.addchild(Item121); Item3.addchild(Item31); }
//UP,DOWN,LEFT,RIGHT,ENTER int but[5]={8,9,10,11,12}; //Previous States of buttons boolean pbut[5]={0,0,0,0,0};
int curloc;
boolean dread(int pin) { return digitalRead(pin); }
void buttoncheck() { for (int i=0;i<5;i++) { if (dread(but[i])) { if (pbut[i]==0) { button(i); pbut[i]=1; } } else { pbut[i]=0; } } }
void button(int which) { switch (which) { case 0://UP curloc-=2; break; case 1://DOWN curloc+=2; break; case 2://LEFT curloc--; break; case 3://RIGHT curloc++; break; case 4://ENTER Root.gochild(curloc); break; } if (curloc<0){curloc+=8;} curloc%=8; }
void setup() { Serial.begin(9600); Serial.println(sizeof(menu)); lcd.clear(); menuinit(); Root.display(); lcd.command(0x0F); } void loop() { lcd.setCursor((curloc%2)*10,curloc/2); buttoncheck(); }
The dread() function is because I plan to put the buttons on a shift register eventually, so then I can just edit that function. The some and none functions are just placeholders, the point being that you could give an item a function that did something useful before it returned. Eventually I plan to read config info in from an SD card and generate the menu items with malloc. Clearly there's still debug stuff in there, I just wanted to get some feedback. 
|
|
|
|
« Last Edit: May 16, 2009, 09:14:13 pm by CWAL »
|
Logged
|
|
|
|
|
Global Moderator
Dallas
Offline
Shannon Member
Karma: 129
Posts: 10402
|
 |
« Reply #1 on: May 15, 2009, 11:29:57 pm » |
I suggest... Good luck, Brian
|
|
|
|
« Last Edit: May 15, 2009, 11:30:21 pm by bcook »
|
Logged
|
|
|
|
|
Norway@Oslo
Offline
Edison Member
Karma: 11
Posts: 2033
loveArduino(true);
|
 |
« Reply #2 on: May 16, 2009, 07:47:04 am » |
Cool! This will help many, I'm sure of it. Maybe turn this into a library for Arduino? Arduino.cc Library TutorialPlayground Library TutorialArduino code usually use Uppercase letters on classes, in order to signal to the user that this is in fact, a class/library. [edit]Not my intent to hijack this thread.[/edit] Have you seen this: http://www.arduino.cc/playground/Code/Menu - Menu Example  It's more of a general menu thing, I've found it works best as an abstract infrastructure. So, one need to code what happens on changes and on use. Maybe we could coordinate a playground page together, and maybe we could try to get our syntaxes more equal? Good job CWAL 
|
|
|
|
« Last Edit: May 16, 2009, 07:54:18 am by AlphaBeta »
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 10
Arduino rocks
|
 |
« Reply #3 on: May 16, 2009, 02:55:34 pm » |
Yeah, I have seen that. I wasn't too fond of the submenu type idea, I sort of wanted everything to just be one type. Hijacking is totally fine, as long as it's somewhat related. :p Always good to look around at other code. Brian, thanks for the input. I've changed it to a linked list and it has made it much more flexible, in addition to more than cutting in half an instance of the object! I can't seem to get the typedef boolean MYCALL(menu&) to work error: typedef 'MYCALL' is initialized (use __typeof__ instead) In constructor 'menu::menu(char*, int)': At global scope:
I'm still having a little trouble though.. I changed my code such that in your main code, you should have a menu pointer, and everything will just return menu pointers that you can put into that. Makes it very flexible I think. I'm having trouble then referencing the menu that is currently pointed to, so I think I may be doing something wrong. #include <LiquidCrystal.h>
LiquidCrystal lcd(2,-1,3,4,5,6,7);
typedef boolean (*MYCALL)(void);
class menu { private: menu * parent; //Pointer to parent menu
menu * child; //Pointer to first child
menu * sibling; //Pointer to next sibling
MYCALL callback; //Function associated with menu item void setparent(menu &p) { parent=&p; }
void addsibling(menu &s,menu &p) { if (sibling) { sibling->addsibling(s,p); } else { sibling=&s; sibling->setparent(p); } }
menu * getsibling(int which) { if (which==0) { return this; } else if (sibling) { return sibling->getsibling(which-1); } else //Asking for a nonexistent sibling { serror("Sibling does not exist"); return NULL; } }
public: char *name;
menu(char *n,MYCALL c) { name=n; callback=c; }
void addchild(menu &c) { if (child) { child->addsibling(c,*this); } else { child=&c; child->setparent(*this); } }
menu * getchild(int which) { if (child) { return child->getsibling(which); } else //This menu item has no children { serror("Has no children"); return NULL; } }
menu * goup() { return parent; }
};
void serror(char *error) { Serial.print(error); /* lcd.clear(); lcd.print("ERROR"); lcd.setCursor(0,1); lcd.print(error); */ }
boolean none() { Serial.println("none"); return 0; }
boolean some() { Serial.println("some"); return 1; }
menu * Menu;
menu Root("Root",some);
menu Item1("Something",some); menu Item11("Stuff",none); menu Item12("More",some); menu Item121("Deeper",none); menu Item2("Other",none); menu Item3("Etc",some); menu Item31("So On",none);
void menuinit() { Root.addchild(Item1); Root.addchild(Item2); Root.addchild(Item3); Item1.addchild(Item11); Item1.addchild(Item12); Item12.addchild(Item121); Item3.addchild(Item31);
Menu=&Root; }
//UP,DOWN,LEFT,RIGHT,ENTER int but[5]={8,9,10,11,12}; //Previous States of buttons boolean pbut[5]={0,0,0,0,0};
int curloc;
boolean dread(int pin) { return digitalRead(pin); }
void buttoncheck() { for (int i=0;i<5;i++) { if (dread(but[i])) { if (pbut[i]==0) { button(i); pbut[i]=1; } } else { pbut[i]=0; } } }
void button(int which) { switch (which) { case 0://UP curloc-=2; break; case 1://DOWN curloc+=2; break; case 2://LEFT curloc--; break; case 3://RIGHT curloc++; break; case 4://ENTER Menu=Menu->getchild(curloc);
break; } if (curloc<0){curloc+=8;} curloc%=8; }
void setup() { Serial.begin(9600); Serial.println(sizeof(menu)); lcd.clear(); menuinit(); lcd.command(0x0F); } void loop() { lcd.setCursor((curloc%2)*10,curloc/2); buttoncheck(); }
I would like a function (external to the class) display() This function would I think be something like this: void display() { void display() { menu * tmp; int i=0; lcd.clear(); while (tmp=Menu->getchild(i)) { lcd.setCursor(((i%2)*10)+1,i/2); lcd.print(tmp->name); i++; } }
This works fine for the first Root menu, but once you enter a menu the lcd displays garbage, so I think I'm having pointer trouble...
|
|
|
|
« Last Edit: May 16, 2009, 03:14:35 pm by CWAL »
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 10
Arduino rocks
|
 |
« Reply #4 on: May 16, 2009, 03:17:01 pm » |
Actually, I was just being stupid in my button press function and not checking that the menu item exists. I got the function type including menu&, and already have a good use for it, the ability for a callback function to back out to the menu above the one it was called from. I wasn't able to do it with the typedef, I had to hardcode it into the class, but no big loss. I am trying to create a function for when someone doesn't specify a callback, it will automatically pick a callback that just returns true. I can't seem to get the type right and I'm not sure if this is even possible.. private: boolean nothingspecial(menu &n) { return 1; }
public: char *name; boolean (*canenter)(menu&); //Called when trying to enter item //TRUE lets you in, FALSE keeps you out
menu(char *n) { name=n; canenter=nothingspecial; }
menu(char *n,boolean (*c)(menu&)) { name=n; canenter=c; } I've looked around and can't seem to figure out how to address this. It asked me to try &menu::nothingspecial which didn't work.. Every variation I've tried gives a type error related to the fact that the function is part of the class. Currently I just set canenter=NULL and check it before calling, but I'd like to not have to do that.
|
|
|
|
« Last Edit: May 16, 2009, 06:32:09 pm by CWAL »
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 10
Arduino rocks
|
 |
« Reply #5 on: May 16, 2009, 09:10:50 pm » |
I've made a library that will take my menu class and abstract it to an LCD display. It should support any display size and any number of items per row. LCDMenu LibraryIt allows you to scroll through the menu if there are more items than your screen handles. There is an example sketch in the zip.
|
|
|
|
« Last Edit: May 16, 2009, 10:48:53 pm by CWAL »
|
Logged
|
|
|
|
|
Germany, Osnabrück
Offline
Sr. Member
Karma: 12
Posts: 376
E-Technik Student - Master
|
 |
« Reply #6 on: June 15, 2009, 11:07:44 am » |
Das sieht sehr gut aus, vor allem da auch mehrere Ebenen unterstützt werden.
|
|
|
|
|
Logged
|
~Es ist so grausam, das der Intelligenz Grenzen gesetzt sind und der Dummheit keine.~
|
|
|
|
NL
Offline
Newbie
Karma: 0
Posts: 37
Arduino rocks
|
 |
« Reply #7 on: September 13, 2009, 02:49:29 pm » |
Hi CWAL,
Hope you're still monitoring this thread.
I'm having trouble implementing the library in my project. Since I'm using a separate keypad I had to change some code that takes care of the button actions. All seemed fine until I tried to scroll to any third item in de top-level or any submenu. In all cases it keeps showing the menu items 1 and 2 on the lcd and the cursor disappears from the screen. When the (invisible) 3rd item of a menu is selected it does bring you down to that menu, and even the invisible back button works.
The thing is that I'm not sure of current behavior is the result of my patching, or if it is also behaving like this in the original hardware setup. (I'm new to Arduino and C++ so analyzing your code is above my head right now).
FYI. I'm using the standard LiquidCrystal library for output and a separate keypad (with the i2ckeypad lib) for input.
Hope you can help.
Thanks,
Mario_H
|
|
|
|
« Last Edit: September 13, 2009, 02:51:46 pm by Mario_H »
|
Logged
|
|
|
|
|
Germany, Osnabrück
Offline
Sr. Member
Karma: 12
Posts: 376
E-Technik Student - Master
|
 |
« Reply #8 on: September 14, 2009, 03:55:39 am » |
Hi, i update this lib: http://nilsfeld.de/r/arduino/lib/LCDMenu2.rarnew: - cursor - cursor pos save, when going back - scroll bar - return selectet names (root.curfuncname) - return names of levels (root.funcname[0] - root.funcname[4]) - better view example: - in software: File->Examples->LCDMenu2->LCDMenu2 Have Fun !?!
|
|
|
|
« Last Edit: September 14, 2009, 03:58:45 am by Jomelo »
|
Logged
|
~Es ist so grausam, das der Intelligenz Grenzen gesetzt sind und der Dummheit keine.~
|
|
|
|
NL
Offline
Newbie
Karma: 0
Posts: 37
Arduino rocks
|
 |
« Reply #9 on: September 14, 2009, 02:07:02 pm » |
Thanks for posting the Lib, Jomelo.
Before I give it a try: did you also have the scrolling problem as described in my post above?
|
|
|
|
|
Logged
|
|
|
|
|
Germany, Osnabrück
Offline
Sr. Member
Karma: 12
Posts: 376
E-Technik Student - Master
|
 |
« Reply #10 on: September 15, 2009, 01:42:59 am » |
I thing i fixed this problem, but i dont know :-|. I removed the Software-Back-Button. Now only the Hardware-BackBuotton works.
|
|
|
|
|
Logged
|
~Es ist so grausam, das der Intelligenz Grenzen gesetzt sind und der Dummheit keine.~
|
|
|
|
NL
Offline
Newbie
Karma: 0
Posts: 37
Arduino rocks
|
 |
« Reply #11 on: September 15, 2009, 01:57:28 am » |
Ok.
Thanks.
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 3
Arduino rocks
|
 |
« Reply #12 on: February 20, 2010, 02:58:32 pm » |
I cant seem to get this to work. when I run it on my setup I only get a blinking cursor... I need to get a system online that uses this menu structure on a 16x2 4 or 8 bit LCD. I dont know what information I need to provide you with, but I have more info on this other post http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1266688367 Your help is greatly appreciated
|
|
|
|
|
Logged
|
|
|
|
|
Germany, Osnabrück
Offline
Sr. Member
Karma: 12
Posts: 376
E-Technik Student - Master
|
 |
« Reply #13 on: February 21, 2010, 02:36:32 pm » |
hmm,
i have`t done something sice august last year with arduino, i am have nothing ideas at the moment.
|
|
|
|
|
Logged
|
~Es ist so grausam, das der Intelligenz Grenzen gesetzt sind und der Dummheit keine.~
|
|
|
|
|