在制作GUI时清洁OOP设计

时间:2015-04-11 16:15:48

标签: c++ user-interface oop

假设我有两个主要类,ApplicationApplicationGUIApplication做了很多事情,并且可以在不知道ApplicationGUI存在的情况下愉快地运行。 ApplicationGUI在许多方面与Application相关联,它可能有50或100个不同的旋钮,可以改变Application的行为。

ApplicationGUI是一个层次结构,因此它有许多ControlGroup个实例,每个实例包含任意数量的ButtonKnob s,甚至包含ControlGroupApplicationGUI 1}}。

当前设计:实例化ApplicationApplication已经运行了一些默认参数),我将 my_gui.sound_controlgroup.knob.link_to_param(&(my_application.volume)); 参数的指针传递给GUI的各个组件。例如:

Application

如果我需要做一些更复杂的事情,比如打电话给my_application.update_something()my_gui.sound_controlgroup.knob的成员函数,这是怎么做到的?

简单的答案是将一个指向my_application的指针传递给my_application.update_something_unrelated(),但是如果我只需要调用my_application的一个函数,似乎我正在给我的旋钮一个选项来改变各种各样的东西它甚至应该知道(例如ApplicationGUI)。在这种情况下,最干净的事情是什么?

此外,这要么要求{{1}}所有子组件公共,要么在层次结构的每个阶段都有一个函数将该指针转发到底层。这导致了很多功能。这是带有很多旋钮的用户界面的必然结果吗?

2 个答案:

答案 0 :(得分:1)

快速简答

为了实现非GUI相关Application对象与GUIApplication对象之间的交互,我建议应用“属性和方法和事件处理程序”范例。

扩展复杂答案

G.U.I。开发是O.O.P.最实用的实现之一。理论

什么是“属性和方法及事件处理程序”范例?

这意味着构建,非GUI类和GUI类,应该具有:

  • 属性
  • 方法
  • 活动处理程序

“事件”(处理程序)也称为“信号”,并使用函数指针实现。不确定,但是,我认为你的“旋钮”就像事件处理程序。

这是一种应用my_application.update_something_unrelated()的技巧,你有问题。

因为C ++和Java一样没有属性语法,所以你可以使用“getter”和“setter”方法,或者使用“属性”模板。

例如,如果您的应用程序使用Close方法,则可以声明类似以下示例的内容。

注意:它们不是完整的程序,只是一个想法:

// Applications.hpp

public class BaseApplicationClass
{
  // ...
};

public class BaseApplicationClientClass
{
  // ...
};

typedef
  void (BaseApplicationClientClass::*CloseFunctor) 
    (BaseApplicationClass App);

public class ApplicationClass: public BaseApplicationClass
{
  // ...

  public:
    Vector<BaseApplicationClientClass::CloseFunctor>
      BeforeCloseEventHandlers;
    Vector<BaseApplicationClientClass::CloseFunctor>
      AfterCloseEventHandlers;

  protected:
    void ConfirmedClose();

  public:

    virtual void Close();
} Application;

// Applications.cpp

void ApplicationClass::ConfirmedClose()
{
   // do close app. without releasing from memory yet.
} // void ApplicationClass::ConfirmedClose()

void ApplicationClass::Close()
{
   // Execute all handlers in "BeforeCloseEventaHandlers"

   this.ConfirmedClose();

   // Execute all handlers in "AfterCloseEventaHandlers"
} // void ApplicationClass::Close()

// AppShells.cpp

public class AppShell: public BaseApplicationClientClass
{
  // ...
};

void AppShell::CloseHandler(ApplicationClass App)
{
  // close GUI
} // void AppShell.CloseHandler(ApplicationClass App)

void AppShell::setApp(ApplicationClass App)
{
  App->BeforeCloseEventHandlers->add(&this.CloseHandler);
} // void AppShell.setApp(ApplicationClass App)

void main (...)
{
   ApplicationClass* AppKernel = new ApplicationClass();

   ApplicationGUIClass* AppShell = new ApplicationGUIClass();

   AppShell.setApp(App);

   // this executes "App->Run();"
   AppShell->Run();

   free AppShell();

   free AppKernel();
}

UPDATE:修复了从全局函数指针(a.k.a。“global functor”)到对象函数指针(a.k.a。“方法函子”)的类型声明。

干杯。

答案 1 :(得分:1)

你知道模型 - 视图 - 控制器(MVC)范例吗?可以将Application类视为模型,将GUI控件的整个层次结构视为视图,将ApplicationGUI类视为控制器。您不希望Application了解控件,并且您不希望控件知道Application;他们应该只与控制器ApplicationGUI交谈。

使用ApplicationGUI作为控件和Application之间通信的管道,意味着您可以通过将另一个替换为模拟对象来测试Application或控件。更重要的是,您可以更改控件或Application而不会影响另一个。个人控件不需要了解Application的任何信息 - 他们只需要知道在更改时将值发送到何处。并且Application不应该关心输入来自旋钮,滑块还是文本字段。将这两个区域分开将简化每个区域。

  

此外,这要么需要制作所有子组件   ApplicationGUI公共或在每个阶段都有一个功能   将该指针转发到底层的层次结构。这导致   相当多的功能。这是UI的必然结果吗?   很多旋钮?

给定的控件不应该关心它管理的值。它不需要知道该值是确定屏幕上的外来入侵者的数量还是核反应堆中的冷却剂水平。它确实需要知道诸如最小值和最大值,要显示的标签,要使用的比例(线性,日志等)以及其他直接影响控件工作方式的事情。它还需要知道在发生变化时告诉谁,并且可能需要某种方式来识别自己。

考虑到这一点,ApplicationGUI不需要为Application的每个可能参数公开访问器。相反,它应该有一个允许控件发送更新的通用方法。当控件发生更改时,它应向ApplicationGUI发送包含新值及其标识符的消息,ApplicationGUI负责将该标识符映射到Application的某个特定参数。控件的标识符可以是给予它的一些标识号,或者它可以只是指向控件的指针。

当然,有时候通信也必须采用另一种方式...... GUI通常同时具有输入和输出,因此您希望ApplicationGUI有一些方法可以从{{1}获取更新并更新GUI的状态。出于与上述相同的原因,Application应将这些更新发送到Application,然后让后者找到需要更改的实际UI组件。