1

Say I have two main classes, Application and ApplicationGUI. Application does lots of things and can happily run without any knowledge that ApplicationGUI exists. ApplicationGUI is linked to Application in many ways, it has maybe 50 or 100 different knobs that can change Application's behavior.

ApplicationGUI is a hierarchical structure such that it has many instances of ControlGroup, each containing an arbitrary number of Buttons and Knobs, or even another ControlGroup.

Current design: Upon instantiation of the ApplicationGUI (Application was already running with some set of default parameters), I pass pointers of Application's parameters to various components of the GUI. For example:

 my_gui.sound_controlgroup.knob.link_to_param(&(my_application.volume));

If I need to do something more complex, say call a member function of Application, my_application.update_something(), how is this done?

The easy answer is to pass a pointer to my_application to my_gui.sound_controlgroup.knob, but if I only ever need to call one of my_application's functions, it seems like I am giving my knob an option to change all kinds of things that it should even know about (my_application.update_something_unrelated(), for instance). What is the cleanest thing to do in this case?

Additionally, this either requires making all subcomponents of ApplicationGUI public or having a function at each stage of the hierarchy to forward that pointer to the bottom level. This leads to quite a lot of functions. Is this a necessary consequence of a UI with a lot of knobs?

Chet
  • 1,209
  • 1
  • 11
  • 29
  • You have your _model_, you have your _view_ ...here I strongly miss a mediator like a _controller_ (in one of its incarnations). – Adriano Repetti Apr 11 '15 at 16:26
  • mmm...ok. I am familiar with the notion of a controller. I've been developing with JUCE which treats the controller and the view as one and the same, I suppose that's why the thought never occurred to me. – Chet Apr 11 '15 at 17:07
  • @Chet Altought separation of concepts like MVC in GUI controls is a very powerful technique, I suggest FIRST, design simple controls, and LATER, add controlls that separate the view from the controller. – umlcat Apr 11 '15 at 17:18
  • @umlcat Are you suggesting that I should stick with the above options from my post (lots of functions and passing pointers to complicated objects)? – Chet Apr 11 '15 at 17:31
  • @Chet Not exactly. My previous comment was related not rushing to View Controller separation. Check my answer below, for an extended suggestion. – umlcat Apr 11 '15 at 17:52
  • You can use interfaces to solve the specific issue. Application can implement several interfaces, each containing the methods relative to a single knob. Each knob can receive a pointer to the interface it's interested in. – Daniele Pallastrelli Apr 11 '15 at 18:35

2 Answers2

1

Quick Short Answer

In order to implement interaction between your non GUI related Application object and your GUIApplication object I suggest apply the "Property and Method and Event Handler" paradigm.

Extended Complex Answer

G.U.I. development is one of the most practical implementation of the O.O.P. theory.

What is the "Property and Method and Event Handler" paradigm ?

That means build, both Non GUI Classes, and GUI Classes, should have:

  • Properties
  • Methods
  • Event handlers

"Events" (Handlers) are also called "Signals", and are implemented with functions pointers. Not sure, but, I think your "knob" (s) are like Event Handlers.

It's a technique to apply the my_application.update_something_unrelated(), you have in your question.

Since, C++, like Java, does not have property syntax, you may use "getter" and "setter" methods, or use a "property" template.

For example, if your application has a Close method, you may declare something like the following examples.

Note: They are not full programs, just an idea:

// 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: Fixed type declaration from global function pointer (a.k.a. "global functor") to object function pointer (a.k.a. "method functor").

Cheers.

umlcat
  • 4,091
  • 3
  • 19
  • 29
  • I like this strategy a lot (and it works well with my current architecture). It actually wasn't unlike something that I considered. The main difficulty I was having is with passing the function pointers around. I know how to pass non-member functions, but not how to member functions without explicitly passing the instance that I want to call it on. http://stackoverflow.com/questions/18145874/passing-a-pointer-to-a-class-member-function-as-a-parameter Were you 'pseudocoding' around that detail with this line: App->BeforeCloseEventHandlers->add(&this.CloseHandler); ? – Chet Apr 11 '15 at 23:53
  • Indeed, I get the 'non-constant pointer to a member function error' – Chet Apr 12 '15 at 00:55
  • @Chet You are right. Check the functor type declaration upgrade. – umlcat Apr 13 '15 at 14:50
  • Does working with (Class::Function) instead of (Class::*Function) allow you to get around the 'non-constant pointer to a member function' error? I've since updated my code to use a 'callback object', a class that holds a function pointer and a private pointer to the instance that should call it. I'll give this a go when I get a free second. – Chet Apr 14 '15 at 04:00
  • @Chet Right. I forgot the `*` operator. – umlcat Apr 14 '15 at 18:06
1

Do you know about the model-view-controller (MVC) paradigm? Think of the Application class as the model, the entire hierarchy of GUI controls as the view, and the ApplicationGUI class as the controller. You don't want Application to know about the controls, and you don't want the controls to know about Application; they should both talk only to the controller, ApplicationGUI.

Using ApplicationGUI as the conduit for communication between controls and Application means that you can test either Application or controls by replacing the other with a mock object, for example. More importantly, you can change either the controls or Application without impacting the other. Individual controls don't need to know anything about Application -- they only need to know where to send their value when it changes. And Application shouldn't care whether an input comes from a knob or a slider or a text field. Keeping those two areas separate will simplify each of them.

Additionally, this either requires making all subcomponents of ApplicationGUI public or having a function at each stage of the hierarchy to forward that pointer to the bottom level. This leads to quite a lot of functions. Is this a necessary consequence of a UI with a lot of knobs?

A given control shouldn't care what value it manages. It doesn't need to know whether the value determines the number of alien invaders on the screen or the coolant level in a nuclear reactor. It does needs to know things like the minimum and maximum values, label to display, scale to use (linear, log, etc.), and other things that directly impact the way the control works. It also needs to know who to tell when something changes, and it might need some way to identify itself.

With that in mind, ApplicationGUI doesn't need to expose accessors for every possible parameter of Application. Instead, it should have a general method that lets controls send it updates. When a control changes, it should send a message to ApplicationGUI containing the new value(s) along with its identifier, and ApplicationGUI takes care of mapping that identifier to some particular parameter of Application. A control's identifier could be some identifying number that's given to it, or it could just be a pointer to the control.

Of course, sometimes communication has to go the other way, too... a GUI usually has both inputs and outputs, so you'll want some means for ApplicationGUI to get updates from Application and update the state of the GUI. For the same reasons described above, Application should send those updates to ApplicationGUI and let the latter find the actual UI components that need to be changed.

Caleb
  • 124,013
  • 19
  • 183
  • 272