patterncppMinor
Base classes for a GUI
Viewed 0 times
guiforclassesbase
Problem
I am creating a GUI library to better learn C++ and wanted to see if there is anything to improve/simplify.
And I also wanted to be able to call an event of a parent and then for it to iterate all its children and those children to iterate their children and so forth, but I did not want to have a foreach loop in every subclass thus the IterateEvents loop. Is there a better way?
```
#pragma once
#include
#include
#include
#include
namespace bui {
class Element {
public:
int x = 0;
int y = 0;
int width = 0;
int height = 0;
Element* parent;
std::function function;
std::vector children;
Element(int x, int y, int width, int height, Element* parent) {
this->x = x;
this->y = y;
this->width = width;
this->height = height;
if (parent != nullptr) {
parent->children.push_back(this);
}
}
~Element() {
}
template
bool pointInElement(T x, T y) {
return (x > this->x && y > this->y && x x + this->width && this->y y + this->height);
}
virtual void Event() = 0;
virtual void Draw() = 0;
void IterateEvents() {
this->Event();
for (auto &i : children) {
i->Event();
}
}
};
extern std::vector TopLevelWindows;
class TopLevelWindow : public Element {
public:
TopLevelWindow(int x, int y, int width, int height, Element* parent) : Element(x, y, width, height, parent) {
TopLevelWindows.push_back(this);
}
void Event() {
std::cout << "TopLevelWindow\n";
}
void Draw() {
//Draw stuff
}
~TopLevelWindow() {
}
};
class Window : public Element {
public:
Window(int x, int y, int width, int height, Element* parent) : Element(x, y, width, height, pare
And I also wanted to be able to call an event of a parent and then for it to iterate all its children and those children to iterate their children and so forth, but I did not want to have a foreach loop in every subclass thus the IterateEvents loop. Is there a better way?
```
#pragma once
#include
#include
#include
#include
namespace bui {
class Element {
public:
int x = 0;
int y = 0;
int width = 0;
int height = 0;
Element* parent;
std::function function;
std::vector children;
Element(int x, int y, int width, int height, Element* parent) {
this->x = x;
this->y = y;
this->width = width;
this->height = height;
if (parent != nullptr) {
parent->children.push_back(this);
}
}
~Element() {
}
template
bool pointInElement(T x, T y) {
return (x > this->x && y > this->y && x x + this->width && this->y y + this->height);
}
virtual void Event() = 0;
virtual void Draw() = 0;
void IterateEvents() {
this->Event();
for (auto &i : children) {
i->Event();
}
}
};
extern std::vector TopLevelWindows;
class TopLevelWindow : public Element {
public:
TopLevelWindow(int x, int y, int width, int height, Element* parent) : Element(x, y, width, height, parent) {
TopLevelWindows.push_back(this);
}
void Event() {
std::cout << "TopLevelWindow\n";
}
void Draw() {
//Draw stuff
}
~TopLevelWindow() {
}
};
class Window : public Element {
public:
Window(int x, int y, int width, int height, Element* parent) : Element(x, y, width, height, pare
Solution
Point and Size structuresI would go even further than @glampert and create
Point and Size structures as well as a Rectangle one. Experience has shown that this kind of small classes are extremely useful and can be used at many places in a GUI library (for example, getting the coodinates of the mouse can return a Point, computing the centre of a rectangle too, etc...). Moreover, once you have Point and Size classes, you can overload some of their operators to implement expressive and unambiguous operations such as multiplication by an integer.In the long run, you will see that it is easier to reason with these objects, and you can even return them from functions while you can't return
x and y if they're not in a structure.Back to the
RectangleHere is a concrete example of what your class does but should be actually performed by a
Rectangle class instead:template
bool pointInElement(T x, T y) {
return (x > this->x && y > this->y && x x + this->width && this->y y + this->height);
}This should look like this, assuming that you have a
Rectangle member named box and that your Rectangle class has a method named contains which takes a Point and returns a bool:template
bool pointInElement(const Point& point) const {
return box.contains(point);
}Constructor initialization list
Whenever possible, try to use the constructor initialization list. As it is now, your code constructs an
Element instance, then assigns values to its fields. If you leave the job to the constructor initialization list, your object will be directly constructed with the appropriate values:Element(int x, int y, int width, int height, Element* parent):
x(x), y(y),
width(width), height(height) {
if (parent != nullptr) {
parent->children.push_back(this);
}
}As you can see, it even allows to drop the
this-> needed to differentiate the variables. The syntax only allows the member names on the left and the name resolution picks the parameters first on the right.About the
virtual destructorI bet that you will want runtime polymorphism with your widgets. In this case, follow @glampert's advice: make the destructor
virtual and public. If it does not do anything, the simplest thing to do is to explicitly default it:virtual ~Element() = default;Now, if a destructor is
public and virtual in a base class, then the derived class automagically have a virtual destructor, you don't have to write anything. Therefore, the simplest thing to do would be to simply remove the destructors from TopLevelWindow, Window and Button if they don't do anything more than Element's destructor; you program will then be correct but simpler.Code Snippets
template <typename T>
bool pointInElement(T x, T y) {
return (x > this->x && y > this->y && x < this->x + this->width && this->y < this->y + this->height);
}template<typename T>
bool pointInElement(const Point<T&>& point) const {
return box.contains(point);
}Element(int x, int y, int width, int height, Element* parent):
x(x), y(y),
width(width), height(height) {
if (parent != nullptr) {
parent->children.push_back(this);
}
}virtual ~Element() = default;Context
StackExchange Code Review Q#107709, answer score: 4
Revisions (0)
No revisions yet.