I'm writing an app in C++ wxWidgets. I'm trying to create a custom button class, although there are two problems:
Code:
gridButton.h
#pragma once
#include "wx/wx.h"
enum gbSTYLE {
NUM,
OP,
EXTRA
};
class gridButton : public wxWindow
{
bool pressed;
bool hovered;
wxString text;
static const int width = 50, height = 50;
wxFont* gbFont = new wxFont(20, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
wxFONTWEIGHT_LIGHT, false, "Lato");
// RICORDATI DI CANCELLARE font_Button DALLA CLASSE MAIN QUANDO HAI FINITO
wxBrush* background;
wxColour* textColor;
wxBrush* hoveredBackground;
wxColour* hoveredTextColor;
wxBrush* pressedBackground;
wxColour* pressedTextColor;
public:
gridButton(wxFrame* parent, wxWindowID id, wxString text, gbSTYLE style);
~gridButton();
void applyStyle(gbSTYLE style);
void paintEvent(wxPaintEvent& evt);
void paintNow();
void render(wxDC& dc);
void mouseMoved(wxMouseEvent& evt);
void mouseDown(wxMouseEvent& evt);
void mouseWheelMoved(wxMouseEvent& evt);
void mouseReleased(wxMouseEvent& evt);
void rightClick(wxMouseEvent& evt);
void mouseLeftWindow(wxMouseEvent& evt);
void mouseEnterWindow(wxMouseEvent& evt);
void keyPressed(wxKeyEvent& evt);
void keyReleased(wxKeyEvent& evt);
DECLARE_EVENT_TABLE()
};
gridButton.cpp
#include "gridButton.h"
BEGIN_EVENT_TABLE(gridButton, wxWindow)
EVT_MOTION(gridButton::mouseMoved)
EVT_LEFT_DOWN(gridButton::mouseDown)
EVT_LEFT_UP(gridButton::mouseReleased)
EVT_RIGHT_DOWN(gridButton::rightClick)
EVT_ENTER_WINDOW(gridButton::mouseEnterWindow)
EVT_LEAVE_WINDOW(gridButton::mouseLeftWindow)
EVT_KEY_DOWN(gridButton::keyPressed)
EVT_KEY_UP(gridButton::keyReleased)
EVT_MOUSEWHEEL(gridButton::mouseWheelMoved)
EVT_PAINT(gridButton::paintEvent)
END_EVENT_TABLE()
// funzione costruttrice (wxFULL_REPAINT_ON_RESIZE per evitare un glitch grafico quando si ridimensiona la finestra)
gridButton::gridButton(wxFrame* parent, wxWindowID id, wxString text, gbSTYLE style)
: wxWindow(parent, id, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
{
SetMinSize(wxSize(width, height));
this->text = text;
hovered = false;
pressed = false;
applyStyle(style);
}
// funzione distruttrice
gridButton::~gridButton()
{
delete gbFont;
delete background;
delete textColor;
delete hoveredBackground;
delete hoveredTextColor;
delete pressedBackground;
delete pressedTextColor;
}
void gridButton::applyStyle(gbSTYLE style)
{
switch (style)
{
default:
background = new wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID);
textColor = new wxColour(25, 25, 25);
hoveredBackground = new wxBrush(wxColour(125, 125, 125), wxBRUSHSTYLE_SOLID);
hoveredTextColor = new wxColour(185, 185, 185);
pressedBackground = new wxBrush(wxColour(25, 25, 25), wxBRUSHSTYLE_SOLID);
pressedTextColor = new wxColour(200, 200, 200);
break;
case NUM:
background = new wxBrush(wxColour(98, 101, 138), wxBRUSHSTYLE_SOLID);
textColor = new wxColour(190, 197, 200);
hoveredBackground = new wxBrush(wxColour(66, 69, 96), wxBRUSHSTYLE_SOLID);
hoveredTextColor = new wxColour(36, 38, 52);
pressedBackground = new wxBrush(wxColour(16, 17, 23), wxBRUSHSTYLE_SOLID);
pressedTextColor = new wxColour(128, 134, 192);
break;
}
}
//chiamato da EVT_PAINT ogni frame
void gridButton::paintEvent(wxPaintEvent& evt)
{
wxPaintDC dc(this);
render(dc);
}
//eseguire un render quando si vuole
void gridButton::paintNow()
{
wxClientDC dc(this);
render(dc);
}
//renderizzazione, qui si mette la grafica
void gridButton::render(wxDC& dc)
{
int w = this->GetSize().GetWidth();
int h = this->GetSize().GetHeight();
if (pressed) {
dc.SetBrush(*pressedBackground);
dc.SetTextForeground(*pressedTextColor);
}
else if (hovered) {
dc.SetBrush(*hoveredBackground);
dc.SetTextForeground(*hoveredTextColor);
}
else {
dc.SetBrush(*background);
dc.SetTextForeground(*textColor);
}
dc.DrawRectangle(0, 0, w, h);
dc.SetFont(*gbFont);
wxSize textSize = GetTextExtent(text);
dc.DrawText(text, w / 2 - (textSize.GetWidth()), h / 2 - (textSize.GetHeight()));
}
void gridButton::mouseDown(wxMouseEvent& evt)
{
pressed = true;
paintNow();
}
void gridButton::mouseReleased(wxMouseEvent& evt)
{
pressed = false;
paintNow();
}
void gridButton::mouseEnterWindow(wxMouseEvent& evt)
{
hovered = true;
paintNow();
}
void gridButton::mouseLeftWindow(wxMouseEvent& evt)
{
hovered = false;
pressed = false;
paintNow();
}
// eventi inutilizzati
void gridButton::mouseMoved(wxMouseEvent& evt) {}
void gridButton::mouseWheelMoved(wxMouseEvent& evt) {}
void gridButton::rightClick(wxMouseEvent& evt) {}
void gridButton::keyPressed(wxKeyEvent& evt) {}
void gridButton::keyReleased(wxKeyEvent& evt) {}
This is how I layout the grid (it looks bad right now because I'm still working on the buttons themselves):
#include "main.h"
void Main::AddButtons()
{
int w = 5, h = 4;
MainGrid = new wxGridSizer(w, h, 0, 0);
gridButton* test = new gridButton(this, wxID_ANY, "(", NUM);
MainGrid->Add(test, 0, wxEXPAND);
AddOpButton(4);
for (int i = 0; i < 2; i++) MainGrid->AddSpacer(1);
for (int i = 7; i < 10; i++) AddNumButton(i);
AddOpButton(3);
for (int i = 4; i < 7; i++) AddNumButton(i);
AddOpButton(2);
for (int i = 1; i < 4; i++) AddNumButton(i);
AddOpButton(1);
MainGrid->AddSpacer(1);
AddNumButton(0);
//dot
wxButton* dot = new wxButton(this, wxID_ANY, ".");
dot->SetFont(*font_Button);
dot->SetBackgroundColour(wxColour(200, 200, 200));
dot->SetForegroundColour(wxColour(125, 125, 125));
dot->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Main::OnDotClicked, this);
MainGrid->Add(dot, 0, wxEXPAND);
AddOpButton(0);
MainSizer->Add(MainGrid, 10, wxEXPAND);
}
void Main::AddNumButton(int n)
{
gridButton* btn = new gridButton(this, BUTTON_NUM + n, wxString(std::to_string(n)), NUM);
btn->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Main::OnNumClicked, this);
MainGrid->Add(btn, 0, wxEXPAND);
}
void Main::AddOpButton(int n)
{
std::string s(1, ops[n]);
gridButton* btn = new gridButton(this, BUTTON_OP + n, wxString(s), OP);
btn->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Main::OnOpClicked, this);
MainGrid->Add(btn, 0, wxEXPAND);
}
The events it should trigger:
void Main::OnNumClicked(wxCommandEvent& evt)
{
int id = evt.GetId() - BUTTON_NUM;
DoNumClick(id);
evt.Skip();
}
void Main::DoNumClick(int id)
{
char c = '0' + id;
char exp[EXPBUFFER];
strcpy(exp, MainText->GetValue());
if (strlen(exp) == 1 && exp[0] == '0')
{
exp[0] = c;
MainText->SetLabel(exp);
}
else
MainText->AppendText(std::to_string(id));
}
I'm new to wxWidgets and any help is appreciated.
I think the extra grey border is just the extra pixels that are leftover from the grid. To get rid of the leftover pixels, you can use a flex grid sizer instead and make all the rows and columns flexible.
void Main::AddButtons()
{
MainGrid = new wxFlexGridSizer(w, h, 0, 0);
for ( int i = 0 ; i < w ; ++ i)
{
MainGrid->AddGrowableRow(i);
}
for ( int j = 0 ; j < h ; ++j )
{
MainGrid->AddGrowableCol(j);
}
...
To get rid of the rectangle around the buttons themselves, instead of drawing a rectangle, you can set a background brush and call the Clear()
method. The Clear()
method completely paints the window with the background brush.
void gridButton::render(wxDC& dc)
{
int w = this->GetSize().GetWidth();
int h = this->GetSize().GetHeight();
if (pressed) {
dc.SetBackground(*pressedBackground);
dc.SetTextForeground(*pressedTextColor);
}
else if (hovered) {
dc.SetBackground(*hoveredBackground);
dc.SetTextForeground(*hoveredTextColor);
}
else {
dc.SetBackground(*background);
dc.SetTextForeground(*textColor);
}
dc.Clear();
The border you were seeing before was do to the edges of the rectangles being drawn with the default pen.
I have one small recommendation. I would store the colors instead of the brushes themselves. Each brush is a gdi object and on windows there's a limit to the number of gdi objects available.
For example, if you have stored the pressedBackgroundColor, you can create a brush on the stack like this:
if (pressed) {
wxBrush temp(*pressedBackgroundColor));
dc.SetBackground(temp);
or you can let the brush be created implicitly like this:
if (pressed) {
dc.SetBackground(*pressedBackgroundColor);
This is admittedly a slight deoptimization since brushes have to be created in each call to the render
method. So feel free to ignore this if you aren't concerned about the gdi limit on windows.
To answer the first question. You'll need to manually generate the button press for your custom button. The easiest way to do this is in the mouseReleased
method. To generate the button event, the method needs do something like this:
void gridButton::mouseReleased(wxMouseEvent& evt)
{
pressed = false;
paintNow();
// Create a button event and send it for processing.
wxCommandEvent event(wxEVT_BUTTON);
event.SetId(GetId());
event.SetEventObject(this);
// Set any other data needed here.
ProcessWindowEvent(event);
}
In order to avoid confusion with native buttons, you can also create your own event types with the wxDECLARE_EVENT
and wxDEFINE_EVENT
macros. There is more detail in the documentation.