保持GUI分离

时间:2009-02-03 09:09:36

标签: c++ user-interface signals-slots

我有一个程序(除此之外)有一个命令行界面,让用户输入字符串,然后通过网络发送。问题是我不确定如何将在GUI内部生成的事件连接到网络接口。例如,假设我的GUI类层次结构如下所示:

GUI - > MainWindow - > CommandLineInterface - > EntryField

每个GUI对象都包含一些其他GUI对象,一切都是私有的。现在,entryField对象生成一个已输入消息的事件/信号。目前我正在将信号传递给类层次结构,因此CLI类看起来像这样:

public:
    sig::csignal<void, string> msgEntered;

在c'tor中:

entryField.msgEntered.connect(sigc::mem_fun(this, &CLI::passUp));

passUp函数只是为拥有类(MainWindow)再次发出信号才能连接,直到我最终可以在主循环中执行此操作:

gui.msgEntered.connect(sigc::mem_fun(networkInterface, &NetworkInterface::sendMSG));

现在这似乎是一个非常糟糕的解决方案。每次我向GUI添加内容时,我都必须通过类层次结构将其连接起来。我确实看到了几种方法。我可以公开所有对象,这将允许我在主循环中执行此操作:

gui.mainWindow.cli.entryField.msgEntered.connect(sigc::mem_fun(networkInterface, &NetworkInterface::sendMSG));

但这违背了封装的想法。我还可以在整个GUI上传递对网络接口的引用,但我希望尽可能保持GUI代码的分离。

感觉我在这里缺少必要的东西。有干净的方法吗?

注意:我正在使用GTK + / gtkmm / LibSigC ++,但我没有标记它,因为我和Qt有同样的问题。这是一个普遍的问题。

6 个答案:

答案 0 :(得分:8)

根本问题在于您将GUI视为单片应用程序,只有gui通过比平时更大的线路连接到逻辑的其余部分。

您需要重新考虑GUI与后端服务器交互的方式。通常,这意味着您的GUI将成为一个独立的应用程序几乎不执行任何操作并与服务器通信,而GUI之间没有任何直接耦合(即您的信号和事件)和服务器的处理逻辑。也就是说,当您单击一个按钮时,您可能希望它执行某些操作,在这种情况下您需要调用服务器,但几乎所有其他事件只需要更改GUI中的状态并且不对服务器执行任何操作 - 直到你准备好了,或者用户想要一些回复,或者你有足够的空闲时间在后台拨打电话。

技巧是完全独立于GUI定义服务器的接口。您应该可以在以后更改GUI而无需修改服务器。

这意味着您将无法自动发送事件,您需要手动连接它们。

答案 1 :(得分:3)

尝试Observer设计模式。 Link包含截至目前的示例代码。

您缺少的重要事项是,如果该引用被强制转换为对象实现的接口(抽象类),则可以在不违反封装的情况下传递引用。

答案 2 :(得分:2)

由于这是一个普遍的问题,即使我只是“Java”程序员,我也会尝试回答它。 :)

我更喜欢在程序的两面使用接口(抽象类或C ++中相应的机制)。一方面是包含业务逻辑的程序核心。它可以生成例如GUI类可以接收,例如, (对于你的例子)“stringReceived。”另一方面,核心实现了一个“UI监听器”接口,其中包含类似“stringEntered”的方法。

这样UI就完全脱离了业务逻辑。通过实现适当的接口,您甚至可以在核心和UI之间引入网络层。

[编辑]在我的应用程序的入门级中,几乎总是有这种代码:

Core core = new Core(); /* Core implements GUIListener */
GUI gui = new GUI(); /* GUI implements CoreListener */
core.addCoreListener(gui);
gui.addGUIListener(core);

[/编辑]

答案 3 :(得分:2)

如果没有一些全球性的发布/订阅中心,你就不会放弃在层次结构中上下传递内容。即使您将侦听器抽象为通用接口或控制器,您仍然必须以某种方式将控制器附加到UI事件。

使用发布/订阅中心,您可以添加另一层间接,但仍然存在重复 - entryField仍然显示“发布消息就绪事件”,并且侦听器/控制器/网络接口显示“侦听消息就绪事件”,因此有一个共同的事件ID,双方都需要知道,如果你不打算在两个地方进行硬编码,那么它需要传递到两个文件中(虽然全局它不作为参数传递;哪个在本身并没有什么大的优势)。

我已经使用了所有四种方法 - 直接耦合,控制器,监听器和pub-sub - 并且在每个后继中你稍微放松了耦合,但是你永远不会有一些重复,即使它只是已发布事件的ID。

这真的归结为差异。如果您发现需要切换到接口的不同实现,那么将具体接口抽象为控制器是值得的。如果您发现需要使用其他逻辑来观察状态,请将其更改为观察者。如果您需要在进程之间解耦,或者想要插入更通用的体系结构,那么pub / sub可以工作,但它引入了一种全局状态,并且不适合编译时检查。

但如果你不需要独立改变系统的各个部分,那么可能不值得担心。

答案 4 :(得分:0)

在我看来,CLI应该与GUI无关。在MVC架构中,它应该扮演模型的角色。

我会设置一个管理EntryField和CLI的控制器:每次EntryField更改时,CLI都会被调用,所有这些都由控制器管理。

答案 5 :(得分:0)

您可以使用templatious virtual packs解耦任何GUI并轻松与消息进行通信。还可以查看this project