我有一个基本的 4x20 字符 LCD,我想用它来显示带有按钮的菜单,使用 Arduino 作为驱动程序(有限的标准库支持)。
我正在考虑生成一个接口(interface)类 GraphicObject,所有图形对象都继承自该接口(interface)类(例如 Button、MenuItem 等)。 GraphicObject 类应该有一个可以被覆盖的绘制方法,这似乎是合乎逻辑的。
目前,我有一个名为 Lcd 的类(class),它涵盖了文本和字符定位的底层绘制。为了绘制任何东西,我需要访问这些 Lcd 对象之一。如果我在我的 GraphicObject 或派生对象中包含指向 Lcd 对象的指针,我会将它们耦合并使它们成为仅 Lcd 对象。如果我更改显示设备的类型,这些类将不再适用。
如何组织类以保持松散耦合并允许日后更改显示类型?我是否应该拥有 LCDButton LCDMenuItem,然后从 Button 和 MenuItem 接口(interface)继承,然后为其他显示设备 (OtherDisplayButton OtherDisplayMenuItem) 创建附加对象?
有什么推荐的读物吗?我看过很多示例,但似乎没有一个详细说明绘制方法的功能以及是应该直接访问设备还是通过另一个控制对象访问设备。
谢谢
编辑1
简要代码思路概览
#include "Arduino.h"
class Lcd {
public:
struct Parameters {
uint_fast8_t numberLines;
uint_fast8_t numberCharacters;
// uint_fast8_t* lineOffsets;
Print* serial; // Can be any Serial device (all inherit from Print).
};
protected:
Parameters parameters_;
const uint_fast8_t escapeCode_ = 0x1B;
const uint_fast8_t directCode_ = 0xFE;
void sendCommand_(uint_fast8_t command, uint_fast8_t parameter = 0);
void sendCommandDirect_(uint_fast8_t command);
public:
Lcd(Parameters parameters);
void clearDisplay(void);
void moveCursor(uint_fast8_t line, uint_fast8_t character);
void resetDisplay(void);
void customCharacter(const uint_fast8_t address,
const uint_fast8_t characterMap[8]);
void write(uint8_t character);
// Boilerplate print forwarders.
void print(const char character);
void print(const String &string);
void print(const char string[]);
// Boilerplate println forwarders.
void println(const char character);
void println(const String &string);
void println(const char string[]);
void println(void);
};
class GraphicObject {
virtual void draw(void)=0;
};
class Button: public GraphicObject {
public:
typedef void (*buttonAction)(void);
virtual void setText(const String text)=0;
virtual const String getText() =0;
virtual bool isActive()=0;
virtual void setActive(bool)=0;
virtual void setAction(buttonAction action)=0;
};
class MenuItem: public Button {
public:
typedef void (*menuAction)(void);
virtual MenuItem* parentItem()=0;
virtual const MenuItem* addItem(String text, menuAction action)=0;
};
class VScrollbar: public GraphicObject {
public:
virtual void setAtTop(bool atTop);
virtual void setAtBottom(bool atBottom);
};
class LcdButton: public Button {
private:
Lcd* lcd_;
String text_;bool active_;
public:
LcdButton(Lcd* lcd);
void draw(void);
void setText(String text);
const String getText();bool isActive();
void setActive(bool);
void setAction(Button::buttonAction action);
};
class LcdWindow: public GraphicObject {
private:
LcdButton* lcdButtons_ = nullptr;
public:
enum class Position
: uint_fast8_t {
LEFT,
RIGHT
};
bool addButton(LcdButton* lcdButton, uint_fast8_t line, uint_fast8_t offset);
bool addVScrollbar(VScrollbar* vScrollbar, Position position);
void draw();
};
int main(void) {
Lcd::Parameters lcdParameters;
lcdParameters.numberCharacters = 20;
lcdParameters.numberLines = 4;
lcdParameters.serial = &Serial1;
Lcd lcd = Lcd(lcdParameters);
LcdButton myButton(&lcd);
myButton.setText("Select");
myButton.setActive(true);
LcdWindow lcdWindow;
lcdWindow.addButton(&myButton, 1, 1);
lcdWindow.draw();
while (1){}
return 0;
}
最佳答案
有多种方法可以做到这一点。原则上,您应该为您的低级 LCD 驱动模块定义一个接口(interface) (API),并仅调用您的低级 API 的函数来绘制一些东西。然后可以交换此 api 的实现,而无需更改高级代码。
执行此操作的最简单方法是定义一个抽象的 C++ 基类,所有 lcd 驱动程序实现都必须从中派生。基类应具有需要由派生实现重载的虚方法。
但是一点信息:c++类中的虚方法要求编译器为实例化对象时创建的每个对象生成一个方法指针表;这需要更多的内存。此外,所有对这些类的对象的函数调用都是间接的(编译器生成的代码首先查找真正的函数指针,然后使用该指针调用函数),这使得生成的代码稍微慢一些。
关于c++ - 绘图类组织,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35268010/