一个用户界面系统






4.60/5 (7投票s)
一个用户界面基础设施,可以快速附加到您的应用程序,并附带一个简单的、基于文本的、平台无关的用户界面,该界面建立在基础设施之上。
- 下载 UI (无 Boost) - 26.64 KB
(此版本不需要 Boost 库,但通用性不够。) - 下载 UI (Boost) - 25.54 KB
(此版本需要 Boost 库 (variant),并且通用性很强。)
引言
几乎每个程序都需要某种方式与用户沟通。那些不沟通的程序(驱动程序、系统进程等)仍然可以从与开发人员沟通中受益,从而使调试更容易一些。
可以采取多种方法进行信息交换,从初始化配置文件和命令行开关,到运行时信号以及使用 shell 实际提示输入。实现这些功能通常需要程序员的特别关注,给他带来繁琐、冗长的工作。
但是这项工作可以大大减少。毕竟,用户所做的唯一事情就是更改变量和调用函数。因此,最佳解决方案是只告诉机器哪些变量和函数可以向用户公开(发布)。然后,用户通信就可以导出到其他标准的一次性实现的代码,从而节省程序员的工作。换句话说,程序员只需遵循基础设施定义的(非常少的)函数,而不必担心用户界面的实现。
这种方法还允许不同的用户界面实现,提供了极大的灵活性。这些实现不仅可以区分基于文本和图形,还可以区分用户提示、配置文件、命令行开关、网络协议等。
可以在文章末尾找到一系列(不)优点。
注意:虽然这里提出的概念是通用的,但代码仅适用于面向对象而非模块化编程。文件UI.hpp描述了为了使基础设施适合模块化编程需要进行的更改。
基础设施
本文提出的基础设施的基础是一个名为ui_export
的单一抽象
类。
class ui_export
{ ...
virtual void addoptions()=0;
void postoption(string name, string desc, xxx);
...
}
想要向用户发布某些成员变量的每个类都需要继承自该类。这样做,它需要实现addoptions
函数。addoptions
的实现应该只包含对postoption
函数的调用,该函数也继承自ui_export
。
class yourClass : public ui_export
{ void addoptions()
{ postoption(...);
postoption(...);
...
}
}
对于要发布的每个选项,都必须在addoptions
中调用postoption
函数。它有 14 种不同的实现,允许发布不同的数据类型和函数。它们是
- 从
ui_export
派生的类
void postoption(string name, string desc, class ui_export* post)
void f()
的简单函数template<class X, class Y>
void postoption(string name, string desc, X* Object, void (Y::* F)() )
bool
、int
、double
、char
、char*
、string
的变量void postoption(string name, string desc, xxx* post)
bool
、int
、double
、char
、char*
、string
的获取和设置函数template<class X, class Y>
void postoption(string name, string desc, X* Object, xxx (Y::* getF)(),
void (Y::* setF)(xxx) )
以下示例说明了对上述函数的调用会是什么样子
class car : public ui_export
{ ...
class engine eng;
void start() {};
int weight;
bool _lights;
bool getlights() {return(_lights);}
void setlights(bool par) {_lights=par; repaint();}
...
void addoptions()
{ postoption("eng", "manipulate the engine object", &eng);
postoption("start", "start the car", this, &car::start);
postoption("weight", "weight of the car in kg", &weight);
postoption("lights", "headlights of the car", this, &car::getlights,
&car::setlights);
}
};
完成此操作后,用户界面就可以使用该类了。
用户界面
本代码提供的用户界面是基于文本的。它只使用 C++ 标准库,这使得它成为平台无关的。由于基础设施的面向类的方法,它具有菜单驱动的感觉。
同样,整个用户界面的基础是一个名为user_interface
的单一类。要使用它,需要创建一个该类的实例。作为ui_export
的子类,user_interface
实现了所有postoption
函数。这些用于初始化用户界面。operator()
函数启动用户界面。
user_interface ui;
ui.postoption("o", "manipulate obj", &obj);
ui.postoption(...);
...
ui();
就是这样。
示例
以下是 'Demo1YourApp.cpp' 的简化版本。它演示了使用用户界面的简单程序是什么样的
#include"UI.hpp"
class yourClass : public ui_export
{ public:
bool b; int i;
void addoptions()
{
postoption("b", "mess with a boolean", &b);
postoption("i", "mess with an integer", &i);
}
};
int main(int argc, char* argv)
{
yourClass c;
user_interface ui;
ui.postoption("yourClass", "mess around with yourClass", &c);
ui();
}
保存功能
用户界面具有简单的“保存状态”机制。它可以用于初始化您的应用程序,但并非旨在实际保存复杂应用程序的状态(启动会花费大量时间)。当用户界面退出时,它会询问您是(Q)uit(不保存)还是(S)ave(然后退出)。
它的工作方式是简单地记住所有用户输入并将其保存在文件中。启动时,用户界面会打开文件(默认文件名是 'input')并执行所有之前保存的输入。加载和保存文件的文件名可以通过用户界面对象访问
ui.loadfilename="init";
ui.savefilename="save";
在用户界面执行期间也可以更改保存文件的名称。这是通过使用默认发布的SaveFile
选项来完成的。
您还可以手动编辑包含已保存输入的保存文件。
C++ 标准库
该基础设施概念带来的另一个巨大优势是,像 C++ 标准库这样的常用库可以提前使其支持 UI。这再次减少了程序员的工作量,并确保了不同程序行为的统一性,这对于用户来说非常有用。
已经开始。文件stdUI.hpp旨在通过使用 C++ 标准库类在stdUI
命名空间中构建支持 UI 的类。以下类已可用
stdUI::vector
stdUI::ios_base
stdUI::basic_ios
stdUI::basic_istream
stdUI::basic_ostream
stdUI::basic_iostream
stdUI::basic_stringstream
stdUI::basic_fstream
非基本版本也可用。
示例
在文件 'Demo2StreamLib.cpp' 中可以找到对stdUI::iostream
、stdUI::stringstream
和stdUI::fstream
类以及stdUI::cout
和stdUI::cin
对象 UI 功能的小型演示。
结论
优点
- 不再需要完全实现用户交互机制
- 程序员只需了解(非常少的)基础设施命令
- 允许多种用户界面实现(基于文本、图形、配置文件解析、命令行开关)
- 简单的初始化或状态保存功能
- 不同程序的统一用户体验
- (几乎)过多的程序参数可编辑性(有利于调试,不利于用户)
- 像 C++ 标准库这样的常用库可以提前发布,进一步减少工作量
缺点
- 代码非常面向类(参见关于模块化编程的注释)
- 没有信息隐藏/一旦发布选项就无法隐藏
- (导致大型项目变得有些“选项臃肿”)
延伸阅读
许多更专业的技术文档可以在源代码中找到。主题是
- 模块化编程的修改
- 为什么选择代理
- 通过使用MVI构建 C++ 标准库类的修改版本