c++ creating a "Button" class

Go To Last Post
20 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There has to be a neat and and graceful way to do this.

 

I already have a "Screen" class for drawing a display on a tft display. I'd like to make a "Button" class for attaching any number of buttons to these screens. 

 

class Button ;

class Screen
{
public:
	Button * pButton = nullptr ;
	virtual void Switch() ;
	virtual  int Loop(unsigned long ms)=0;
};

class Button
{
public:
	Button * pNext ;
	Button( int px, int py, int pw, int ph, int pid, Screen *pS) ;
	int x ; int y; int w; int h; int id ;
};

The button's constructor method puts the linked list together:

Button::Button( int px, int py, int pw, int ph, int pid, Screen *pS) 
{
    pNext = pS->pButton ;
    pS->pButton = this ;
    x = px; y = py; w = pw; h = ph; id = pid ;
}

And I can make a list of buttons on a screen thusly (I think):

 

class TempScreenT: public Screen
{
	int TempCol = 20 ;
	unsigned long nextMeatTime = 0 ;
	unsigned long nextChamberTime = 0 ;
	unsigned int BkColor = tftCHOCOLATE;
	int ShowingBars = -1 ;
	void ShowTemp( int Pin, int y);
	void ShowBars();

public:
	virtual void Switch() ;
	virtual  int Loop(unsigned long ms);
};

TempScreenT TempScreen ;
Button ParamButton(5, 200, 60, 30, 1, &TempScreen);
Button URLButton( 5, 170, 60, 30, 2, &TempScreen) ;

Now, I would like to add a function to each of these buttons. The code that loops through this linked list would call the OnPress function when it finds a touch inside one of the buttons. I'd like the code to look something like this:

 

Button ParamButton(5, 200, 60, 30, 1, &TempScreen, ParamClick);
Button StartButton( 5, 170, 60, 30, 2, &TempScreen, StartClick) ;

I've tried making this onpress function a virtual method, but it complains when I try to fill in the virtual method like this:

 

ParamButton::OnPress()
{
    //do some stuff
}

and I think I would have to define each button as a class, rather than an object like I did with screen. There has to be a neater way.

 

Maybe I could make these OnPress functions just take a pointer to the class, so you'd build the list like this?

 

Button ParamButton(5, 200, 60, 30, 1, &TempScreen, &ParamClick, &ParamDraw);
Button StartButton( 5, 170, 60, 30, 2, &TempScreen, &StartClick, &ParamDraw) ;

So the button class might look something like:

 

class Button
{
public:
	Button * pNext ;
	Button( int px, int py, int pw, int ph, int pid, Screen *pS) ;
	int x ; int y; int w; int h; int id ;
	void (*OnPress)(*Button);
	void (*Draw)(*Button, bool);
};

What do you think? Madness?

The largest known prime number: 282589933-1

In my humble opinion, I'm always right. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Maybe use inheritance?

class Button
{
public:
    virtual void OnPress () { }   // optional
    virtual void Draw (bool) = 0; // must implement
};

class ParamButton : public Button
{
public:
    virtual void OnPress ();
    virtual void Draw (bool);
};

void ParamButton::OnPress ()
{
    // the param button was pressed
}

void ParamButton::Draw (bool b)
{
    // the param button needs to be drawn
}

 

--Mike

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I suggest downloading and reviewing the Arduino library code for push-switch buttons by Alex Brevig.  It creates an small object for each push-switch button.  It's quite dense code, but it works for simple presses and long-hold presses.   It should work for double-click presses but I've been unable to get that implemented. 

  I use this code for all push-switches:  http://log.liminastudio.com/itp/...

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Simonetta wrote:

I suggest downloading and reviewing the Arduino library code for push-switch buttons by Alex Brevig.  It creates an small object for each push-switch button.  It's quite dense code, but it works for simple presses and long-hold presses.   It should work for double-click presses but I've been unable to get that implemented. 

  I use this code for all push-switches:  http://log.liminastudio.com/itp/...

 

 

These are drawn on a tft display.

The largest known prime number: 282589933-1

In my humble opinion, I'm always right. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avr-mike wrote:

Maybe use inheritance?

class Button
{
public:
    virtual void OnPress () { }   // optional
    virtual void Draw (bool) = 0; // must implement
};

class ParamButton : public Button
{
public:
    virtual void OnPress ();
    virtual void Draw (bool);
};

void ParamButton::OnPress ()
{
    // the param button was pressed
}

void ParamButton::Draw (bool b)
{
    // the param button needs to be drawn
}

 

--Mike

 

 

That was my first idea, but it won't let me make 

void ParamButton::OnPress ()
{
    // the param button was pressed
}

void ParamButton::Draw (bool b)
{
    // the param button needs to be drawn
}

unless I first declare a class of ParamButton, not just an object. I guess I can do that, but looking for something more graceful.

The largest known prime number: 282589933-1

In my humble opinion, I'm always right. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What you are describing here is actually remarkably close to QT5. You might want to see how that works (loads of internet tutorials) then steal their ideas for the AVR application ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Torby wrote:
but it complains when I try to fill in the virtual method like this:
"Complains"??

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

Torby wrote:
but it complains when I try to fill in the virtual method like this:
"Complains"??

 

Yes. If I make the button class:

 

class Button
{
public:
	Button * pNext ;
	int x ; int y; int w; int h; int id ;
	Button( int px, int py, int pw, int ph, int pid, Screen *pS) ;
	virtual void OnPress() ;
};

Then make a button like so:

 

TempScreenT TempScreen ;

Button ParamButton(5, 200, 60, 30, 1, &TempScreen);

void ParamButton::OnPress()
{

}

the compiler complains:

 

tempscreen.cpp:22:6: error: 'ParamButton' is not a class, namespace, or enumeration
 void ParamButton::OnPress()
      ^

If I make a new class for each button:

 

class cParamButton: Button{}; cParamButton ParamButton(5, 200, 60, 30, 1, &TempScreen);

it doesn't seem to inherit the Button constructor and complains:

 

tempscreen.cpp:19:86: error: no matching function for call to 'cParamButton::cParamButton(int, int, int, int, int, TempScreenT*)'
 class cParamButton: Button{}; cParamButton ParamButton(5, 200, 60, 30, 1, &TempScreen);

And this would seem to defeat the whole purpose of inheritance?

The largest known prime number: 282589933-1

In my humble opinion, I'm always right. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For every type of button you want to have you are going to have to create a class that publicly inherits (assuming you want to be able to use the class through a base class pointer or reference) from the base button class and implements OnPress() (unless you parameterize the behavior of OnPress() in either the base class or a derived implementation). As far as construction is concerned, you need to explicitly inherit the base class's constructors (assuming you are using C++11 or later), or you have to implement constructors in the derived class that call the base class's constructors.

 

class Button {
    public:
        Button * pNext;
        int x; int y; int w; int h; int id;
        Button( int px, int py, int pw, int ph, int pid, Screen * pS );
        virtual void OnPress();
};

class cParamButton : public Button {
    public:
        using Button::Button;

        virtual void OnPress() override; // 'override' requires C++11. Use 'final' if you don't want OnPress overridden in a further derived class.
};
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
Button ParamButton(5, 200, 60, 30, 1, &TempScreen);

void ParamButton::OnPress()

But that just isn't valid C++. When you use text:: then the "text" should either be a namespace or the name of a class to which the following function name belongs. (which is pretty much what the error message told you).

 

"ParamButton" here is just the name of the instance of an object of type Button class.

 

Being a dyed in the wool C programmer I'd just go with your call back idea. The callback will need to be "static" so it can be called outside the context of the class. See the middle bit ("Function Pointer") of this page:

 

http://tedfelix.com/software/c++-callbacks.html

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yea, I'm leaning to just putting a function pointer as a member and passing the &function to the constructor. That might let me use the same function passing teh "id" member as a parameter for different buttons.

 

using Button::Button;

 

in the derived class might let the derived class use the constructor. Somebody told me the C++ way to store these is a "vector" rather than linked list. I'll have to read about that.

The largest known prime number: 282589933-1

In my humble opinion, I'm always right. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Torby wrote:

Yea, I'm leaning to just putting a function pointer as a member and passing the &function to the constructor. That might let me use the same function passing teh "id" member as a parameter for different buttons.

 

Passing function pointers and using ID's instead of

the built-in features (inheritance), means you're

implementing your own version of C++.

 

Quote:

Somebody told me the C++ way to store these is a "vector" rather than linked list. I'll have to read about that.

 

In an embedded project?  I'd say your simple linked

list is fine and even preferred.  Vector dynamically

allocates (and may be wasteful of) memory.

 

Though when you have a fast machine with lots of

memory, you need a good reason to not use vector.

 

--Mike

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah, I agree with #10, have the button class store a private pointer to whatever function is supposed to be executed and wrap that inside a class function so that you have some kind of common interface. But then, I'm just an assembly programmer pretending that he is a C programmer pretending that he knows C++cheeky

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:

But then, I'm just an assembly programmer pretending that he is a C programmer pretending that he knows C++cheeky

 

hehe Same here. I write my C with a very strong Pascal accent.

 

To answer the C++ question, here's what I have:

 

class Button ;

class Screen
{
public:
	Button * pButton = nullptr ;
	virtual void Switch() ;
	virtual  int Loop(unsigned long ms)=0;
};

class Button
{
public:
	Button * pNext ;
	int x ; int y; int w; int h; int id ;
	Button( int px, int py, int pw, int ph, int pid, Screen *pS) ;
	virtual void DoIt() ;
	virtual void Draw(bool pDown);
};

Button::Button( int px, int py, int pw, int ph, int pid, Screen *pS)
{
	pNext = pS->pButton ;
	pS->pButton = this ;
	x = px; y = py; w = pw; h = ph; id = pid ;
}

void Button::DoIt()
{

}

void Button::Draw(bool pDown)
{

}
class TempScreenT: public Screen
{

public:
	virtual void Switch() ;
	virtual  int Loop(unsigned long ms);
	int TempCol = 100 ;
	unsigned long nextMeatTime = 0 ;
	unsigned long nextChamberTime = 0 ;
	unsigned int BkColor = tftCHOCOLATE;
	int ShowingBars = -1 ;
	void ShowTemp( int Pin, int y);
	void ShowBars();
	void ShowButtons();
};

TempScreenT TempScreen ;

class pParamButton: Button
{
  using Button::Button ;
public:
  virtual void DoIt() ;
  virtual void Draw(bool pDown);
}; 

class pURLButton: Button
{
  using Button::Button ;
public:
  virtual void DoIt() ;
  virtual void Draw(bool pDown);
};

pParamButton ParamButton(5, 240-100, 90, 90, 1, &TempScreen);
pURLButton URLButton( 5, 240-200, 90, 90, 2, &TempScreen) ;

void pParamButton::Draw(bool pDown)
{
    tftFillRoundRect(x,y,w,h,20,tftWHITE) ;
    tftDrawRoundRect(x+3, y+3, w-6, h-6, 18, TempScreen.BkColor) ;
    int16_t x0 = x + (w-gearicon_width)/2 ;
    int16_t y0 = y + (w-gearicon_height)/2 ;
    tftDrawXBitmap(x0,y0,gearicon_width,gearicon_height, gearicon_bits, TempScreen.BkColor, tftWHITE) ;
}

void pURLButton::Draw(bool pDown)
{
    tftFillRoundRect(x,y,w,h,20,tftWHITE) ;
    tftDrawRoundRect(x+3, y+3, w-6, h-6, 18, TempScreen.BkColor) ;
    int16_t x0 = x + (w-Thermometer_width)/2 ;
    int16_t y0 = y + (w-Thermometer_height)/2 ;
    tftDrawXBitmap(x0,y0,Thermometer_width,Thermometer_height, Thermometer_bits, TempScreen.BkColor, tftWHITE) ;
}

void pParamButton::DoIt()
{

}

void pURLButton::DoIt()
{

}

I still may just go with pointers to the draw and doit functions instead of building a class for each button.

 

The buttons look pretty cool. My original thought was to call the draw method to draw them "down" when you press one, but that's done on an interrupt and the interrupt is very likely to happen while it's already drawing something on the screen and... what a tangle.

 

 

Yes. The glass is broken. Stupid display came that way.

The largest known prime number: 282589933-1

In my humble opinion, I'm always right. 

Last Edited: Sat. Apr 6, 2019 - 01:31 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Tom,

 

Look at the Adafruit_GFX class used in Arduino.

 

As well as the traditional graphics it adds an Adafruit_GFX_Button class.

The methods are all fairly simple.   But is shows the beauty of class inheritance.

 

It means you can have the same "look and feel" regardless of your Touch hardware,  TFT,  ...

 

The Broken glass on your Touch Panel will mean that you get no Touch at all in the Y direction.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your two draw routines are very similar. They

could simply call a DrawBitmap method defined

in the base Button class.  Also create some data

members in Button for the bitmap parameters.

 

EDIT: the derived classes call the DrawBitmap

method with the proper parameters:

 

void pParamButton::Draw(bool pDown)
{
    DrawBitmap (gearicon_width, gearicon_height, gearicon_bits);
}

 

--Mike

 

Last Edited: Sat. Apr 6, 2019 - 04:22 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I was just googling and came across this
.
https://forum.arduino.cc/index.php?topic=495134.0
.
which in turn leads to...
.
https://forum.arduino.cc/index.php?topic=495134.0
.
That has button callbacks. (the graphics are pants but the actual code looks very clear)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avr-mike wrote:

Your two draw routines are very similar. They

could simply call a DrawBitmap method defined

in the base Button class.  Also create some data

members in Button for the bitmap parameters.

 

EDIT: the derived classes call the DrawBitmap

method with the proper parameters:

 

void pParamButton::Draw(bool pDown)
{
    DrawBitmap (gearicon_width, gearicon_height, gearicon_bits);
}

 

--Mike

 

 

I later thought of putting the button itself in the gear and thermometer bitmaps. Probably will do that and delete the lines to draw the round boxes.

The largest known prime number: 282589933-1

In my humble opinion, I'm always right. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Have you considered using a separate class for a simple linked list?

Maybe even write it in the form of a template.

There probably already are a lot of those floating around.

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Paulvdh wrote:
Have you considered using a separate class for a simple linked list?
The code I linked to in #17 actually uses:

	Button* filesbutton = new Button(10, 90);
	filesbutton->SetText("Open SD card");
	filesbutton->SetCallback(open_sd);
	AddChild(filesbutton);

Where AddChild() is:

void Window::AddChild(Control* ctlptr)
{
	ctlptr->SetParent(this);
	children.push_back(ctlptr);
}

And "children" is:

public:
	std::vector<Control*> children;

So they are not inventing any kind of linked list class or anything like that - they are simply pushing each object into a std::vector.

 

PS forgot to say that then when it comes to things like drawing they just iterate the vector:

	for(auto it = children.begin(); it != children.end(); it++)
	{
		Control* child = *it;
		Graphics gr_clip(graphics);
		gr_clip.Clip(child->GetX(), child->GetY(), child->GetWidth(), child->GetHeight());
		child->Draw(gr_clip);
	}

Like them, I'd use some kind of std:: structure for this rather than trying to invent anything new.

Last Edited: Mon. Apr 8, 2019 - 10:06 AM