Sorry, long post, trying to understand the concept behind the Listener class


#1

As I've posted a couple of times in another section of this forum, I'm creating a very simple GUI composit control.  This is not something that is going to be overly useful, rather it's an exercise in trying to learn how the JUCE framework works and how to understand it better. 

Some of you may have seen other posts of mine where I mention that I'm just getting back into C++ after about a 15 year haitus due to other technologies I've been using over the years On top of that, I'm trying to learn the JUCE framework as well.  Taking on a lot all at once. :)

In specifics I think what I'm trying to achieve here is figuring out messaging works in this environment.  If I have composite controls made up of composite controls, how do I get all the communications ( events ) working up and down the heirarchy. 

In C# and in Adobe Flex, that world works in a highly event driven paradigm, which in the C++ world is as well, but seems to be handled a little differently. In adobe flex you would have something like this.

myButton.addEventListener( ButtonClick, "someFunctionName" );  


Where myButton is an instance of a Button class.  ButtonClick is a defined type of event you want to listen for and "someFunctionName" is the name of the function that gets executed when a ButtonClick event occurs.  Also, those events can propagate back up the heirarchy chain. Bubbling.

I understand that the event handling here in JUCE is based on the observer pattern.  But that is where I'm getting a little thrown off. I'll address that more in a minute, but for now, I'd like to show you what I have so far in my composite component project...

FilePickerCtrl Consists of :

1) Text Button 
2) Text Editor

This is a composit control and it's goal is to simply supply a button to press which bring up the JUCE file chooser dialog box, once a user has chosen a file, the full path to the file gets populated into the text editor for display (could have just been a label I suppose ), at the same time, I save that path string into a private variable.  Oh and because this uses a TextButton class, the FilePickerCtrl also extends Button::Listener.  This is to trap the click event so I can handle it in the FilePickerCtrl itself. 

XMLFilePreviewDisplayCtrl consts of :

1) TreeView 
2) FilePickerCtrl

The purpose of this control is to bring together the functionality of the FilePickerCtrl and the ability to display the file in the tree view. Specifically I will be choosing an XML and display it's data/heirarchy in the TreeView. 

The way I'd like this control to handle user interaction is
1) User presses the button on the FilePickerCtrl
2) The file path gets saved into the FilePickeCtrl's private _path variable.
3) An event is fired from the FilePickerCtrl that lets the parent XMLFilePreviewDisplayCtrl know that a file has been chosen.
4) The XMLFilePrevewDisplayCtrl then loads that file.


Now, here is where I'm thrown off by the observer pattern the way it's been implimented in JUCE.  It's the use of the CTRL::Listener object. Where CTRL is say a Button.  I've looked at the code and can see that the Listener is a ( pardon me here not sure of the proper terminology) imbedded class in the Button class.  (Just using the button class as an example here).  What exactly is the need for this listener class?  When I look at the code shown


    class JUCE_API  Listener
    {
    public:
        /** Destructor. */
        virtual ~Listener()  {}
        /** Called when the button is clicked. */
        virtual void buttonClicked (Button*) = 0;
        /** Called when the button's state changes. */
        virtual void buttonStateChanged (Button*)  {}
    };

Is it safe to say that this is to enforce that who ever uses the TextButton class ( or any other class inheriting from Button ) is forced to implement buttonClicked ?  But if that is the case, why add the overhead of this class in the first place?  Just have the buttonClicked as a virtual function directly on the Button class?  Please understand I'm not saying this is wrong... Far from it, I'm simply trying to understand the engineering mindset behind it so I can learn from it. 

One thing that crossed my mind was flexability... Maybe someone would want a button but didn't want to create an event handler for it??? Again, I'd love to know the thought process behind it. 

And this is where I come across another question... 
The FilePickerCtrl has a button, and because of this, this class inherits from Button::Listener as well as Component.  I don't see a point in allowing any further in heritace. Lets just say for simplicity sake, nobody would ever take this control and inhert from it to create yet another control... It is what it is... 

What is at this point, the proper architecture (as it relates to JUCE) to ensure that the XMLFilePreviewCtrl gets notified when that FilePickerCtrl button is clicked?  Would I want to create my own FilePickerCtrl::Listener class that looks identical to the code above?  Or do I at this point drop concept of the Listener class and go with a more traditional observer patten and if so... Then the FilePickerCtrl would have either a single observer or a collection of observers that it would notify by calling a notify method on the observer. 

Sorry for the long post... I'm just trying to ensure that whatever I do fits seemlessly with the juce framework... 

 

 

 

 


#2

I won't be able to give you a comprehensive answer, but maybe I can get you started on some basics.

Let's get the nit-picky things out of the way:

This is a quote

// this is code

The second nit pick:

Where CTRL is say a Button.

Button is a Component. What you would call a "control" in the MS Windows API is called a Component in the JUCE library.

the Listener is a ( pardon me here not sure of the proper terminology) imbedded class in the Button class.

In C++, this is called a nested class:

http://msdn.microsoft.com/en-us/library/x23h0937.aspx

Is it safe to say that this is to enforce that who ever uses the TextButton class ( or any other class inheriting from Button ) is forced to implement buttonClicked ?

No. You use classes to define new types, and Button::Listener is therefore a new type. You can instantiate a TextButton without having to provide an implementation of anything. When you instantiate a TextButton, this instantiation does not include an instance of Button::Listener. In C++, Button::Listener is called an abstract class because there is at least one "pure virtual function" (marked with =0 at the end). You cannot instantiate an abstract class. Try this:

TextButton myTextButton;      // this is okay
Button::Listener myListener;  // object of abstract class type not allowed

So....

Just have the buttonClicked as a virtual function directly on the Button class?

JUCE uses multiple inheritance to do something like:

class MyComponent : public Component,
                    public Button::Listener
{...};

Now you should provide an implementation for Button::Listener::buttonClicked(). So, then you'll have:

void MyComponent::buttonClicked (Button* butonThatWasClicked)
{
    if (buttonThatWasClicked == textSearch) {...}
    else if (buttonThatWasClicked == textSave) {...}
    ...
}

What it means is that MyComponent does not have to be a Button in order to receive callbacks. If you had a virtual function called Button::buttonClicked() as you suggest, then how would MyComponent receive callbacks?! (Again, MyComponent is not a Button.) Compare Button::Listener::buttonClicked() with Button::clicked()--these are used for two very different purposes.

To wire it all up, you just do something like this in the constructor:

MyComponent::MyComponent ()
{
    ...
    addAndMakeVisible (textSearch = new TextButton ("search button");
    textSearch->addListener (this);
    ...

 I don't see a point in allowing any further in heritace.

C++11 has this facility:

http://en.cppreference.com/w/cpp/language/final

I think you are going in roughly the right direction, you are just missing some of the nuances.


#3

Thanks matty for the reply.

Regarding quote vs // in the block.  Yeah, I just noticed that I can create a "code" block.  I didn't look at the styles drop down button thinking it would have been font styles. 

Thanks for the heads up on "control" vs. "component".  I agree being consistant in termonology is helpful.  Also thanks for the proper naming for a nested class. I know what they are, but the proper termonolgy excaped me at the moment.

Again I want to stress, I'm not arguing, I'm trying to learn this new (to me - or forgotten) way of handling thingssmiley

Is it safe to say that this is to enforce that who ever uses the TextButton class ( or any other class inheriting from Button ) is forced to implement buttonClicked ?

No. You use classes to define new types, and Button::Listener is therefore a new type. You can instantiate aTextButton without having to provide an implementation of anything. When you instantiate a TextButton, this instantiation does not include an instance of Button::Listener. In C++, Button::Listener is called an abstract class because there is at least one "pure virtual function" (marked with =0 at the end). You cannot instantiate 

Sure, I understand that the listener object is defining a new type. Basic OOO.  My question was really around why it was implemented this way... Meaning, if I have a MainComponent class that has a TextButton on it.  If MainComponent does not inhert from Button::Listener as well as Component, then I can have buttons on MainComponent that do nothing, can't do anything. Which I don't understand the reason behind this. Which again makes me wonder if it's for flexability, or is it just a side affect of this particular implementation.

Hypothetically speaking, lets say for example, I need to know when someone right clicks on a button. In this paradigm, how would that get handled?  Right now the Button::Listener class does not support right clicks from what I can tell.  Would you need to create another component that inherits from TextButton or even Button that extends the Listener class to know about right clicks?

 

Now you should provide an implementation for Button::Listener::buttonClicked(). So, then you'll have:

void MyComponent::buttonClicked (Button* butonThatWasClicked) { if (buttonThatWasClicked == textSearch) {...} else if (buttonThatWasClicked == textSave) {...} ... }

Which is a different paridigm.  Different but nice.  Nice because you have one function that can service mutliple buttons. One red flag that comes to mind is that it would be easy to create a very ugly buttonClicked() function. 
 

What it means is that MyComponent does not have to be a Button in order to receive callbacks. If you had a virtual function called Button::buttonClicked() as you suggest, then how would MyComponent receive callbacks?! (Again, MyComponent is not a Button.) Compare Button::Listener::buttonClicked() withButton::clicked()--these are used for two very different purposes.

Understand what I'm saying is a different aproach. But, from within Component::buttonClicked( Button* btn ), you would dispatch an event... Something like this.


Understand this is very C# and or Adobe Flex. The code example may not be exact syntax as I'm just drawing off memory and really just trying to demonstrate the concept.  

In the constructor or some other initialization code you'd have something like. 

myButton.addEventHandler( ButtonClick, "callThisMethod" );

Then you'd have a function definition that looked like this.

void callThisMethod( Event evt )
{
   ..do something..

   Event someEventTheParentCaresAbout
   dispatchEvent( someEventTheParentCaresAbout );
}


 

Then in the parent control, window or whatever, it has a handler that listens for the someEventTheParentCaresAbout.   Also, the parent can choose to impliment the handler for it or not. maybe it cares about the fact that the button was clicked on, maybe it does not. 

True, this is Windows, and maybe this concept does not apply to other operating systems.  I've never developed for anything other than Windows and the one platform that I did program in that DOES target mulitple OS's handled things the same way... Adobe Flex

The callback? Well, in this methodology, you wouldnt be using callback persay, rather, it's the dispatch event mechanism that allows the partent to know when something happened in one of it's children.  It's nice, very loosely coupled.  Well, should be... I suppose there are ways to easily break the loosly coupled paridigm.

At any rate.. I'm still unsure about the best way to handle my specific situation where I have a composit component (FilePicker) that has a Button and a TextEditor, another composite component (XMLPreviewDisplayCtrl) that Has-A FilePicker and a TreeView...  How do I wire up the comms between the two?  

Because remember, I want FilePicker to actually be what handles the button click.  I want it to be a smart component. It's going to manage the button click, manage opening the file chooser, manage populating the text box that has the file path...  All i want to do here is let the outter parent (XMLPreviewDisplayCtrl) know when a user chose a file.

If I don't have the mechanism to dispatch an event up to the parent how do I handle it?  
* Create an observer pattern where the FilePicker is the subject and the XMLPrevewDisplayCtrl is the observer
     http://www.juce.com/forum/topic/sorry-long-post-trying-understand-concept-behind-listener-class#comment-302178

* Have the FilePicker have a reference back to XMLPrevewDisplayCtrl
* Have the XMLPrevewDisplayCtrl have an update() method
* Have FilePicker call the XMLPreviewDisplayCtrl's update() method?

 

I don't think forcing XMLPreviewDisplayCtrl to inhert from Button::Listener is going to work in this situation... 

Thoughts? Feedback?
Again, sorry for the long post... Just wanting to learn. 


 

 


#4

Just wanted to pitch in with a useful construct I'm using with JUCE: http://www.juce.com/forum/topic/simple-button-listener-handler

 


#5

  I need to know when someone right clicks on a button. In this paradigm, how would that get handled?

Maybe you're looking for the MouseListener class?

http://www.juce.com/api/classMouseListener.html

You then use Component::addMouseListener(MouseListener *newListener, bool) to wire it up.

A MouseEvent is passed to your MouseListener and contains more details:

http://www.juce.com/api/classMouseEvent.html

In particular, check out the ModifierKeys class, which gives you info about the right mouse button.

http://www.juce.com/api/classModifierKeys.html

Also note that there is the Component::mouseDown(const MouseEvent&) function that you might be able to use.


#6

Hi robiwan, nice work.  Gives more of a C# feel.

 


#7

Hi Matty!  Thanks for the tip on these classes. I'm sure they will become very valuable.  

Having said that, I'm not sure this directly resolves my conflict though which is, how do I get the parent (XMLPreviewDisplay) to know that a mouse click was clicked within the FilePicker class given the fact that the FilePicker class handled the event... Is there a way to through a mouse event up to the parent?

 

 


#8

If you really need to do that, you could override Button::clicked (const ModifierKeys&) and the mods should tell you which button was down (I think).

But I would warn that the idea of a button which handles right mouse-clicks sets off alarm bells for me. If your widget looks and acts a familiar button, then making it right-clickable breaks the rule of "least surprise" for your users, and IMHO is a very bad piece of UX. And if your widget doesn't look or behave anything like a normal button, then there's probably no reason for it to be derived from the Button class, so you wouldn't have this problem.


#9

Let's see if we can get to the bottom of this.

how do I get the parent (XMLPreviewDisplay) to know that a mouse click was clicked within the FilePicker class

OPTION 1. Use listeners

You can use the TextButton in your FilePickerCtrl component as an intermediary to "bubble" (as you called it) an "event" from the FilePickerCtrl to the XMLPreviewDisplayCtrl. You want XMLPreviewDisplayCtrl to be a (or inherit from) TextEditor::Listener. When the user clicks the button, your FilePickerCtrl::buttonClicked() method then presents the FileChooser dialog. Update your TextEditor with the file name that was returned by calling TextEditor::setText(). Note the default parameter of setText() will cause a text change message to be sent to all listeners. And who is listening? Your XMLPreviewDisplayCtrl....once you hook it up.

To hook it up, you'll want to first set the ID of your TextEditor in the FilePickerCtrl:

FilePickerCtrl::FilePickerCtrl ()
{
    addAndMakeVisible (textEditor = new TextEditor);
    textEditor->setReadOnly(true);
    textEditor->setComponentID("FilePickerCtrl textEditor"); // ID is not the same as a Name
...

Now you can use Component::findChildWithID() to locate that component by name. You can then add an instance of XMLFilePreviewDisplayCtrl as a listener to the text editor text change messages:

XMLFilePreviewDisplayCtrl::XMLFilePreviewDisplayCtrl ()
{    
    addAndMakeVisible (filePickerCtrl = new FilePickerCtrl());
    filePickerCtrl->setName ("file picker");

    dynamic_cast<TextEditor*>(filePickerCtrl->findChildWithID("FilePickerCtrl textEditor"))->addListener(this);
...

XMLFilePreviewDisplayCtrl::~XMLFilePreviewDisplayCtrl()
{
    dynamic_cast<TextEditor*>(filePickerCtrl->findChildWithID("FilePickerCtrl textEditor"))->removeListener(this);
    filePickerCtrl = nullptr;
...

So where were we....user presses a button, the file dialog box was presented, and the result was put into a TextEditor. A text change message was dispatched to XMLFilePreviewDisplayCtrl::textEditorTextChanged(TextEditor&) and the rest should be history! I made the TextEditor read only so that you wouldn't get invalid/incomplete text change messages being sent.

Finally, you might check out the JUCE demo "Audio: File Playback" in the AudioPlaybackDemo.cpp file. The AudioPlaybackDemo class inherits from four different types of listeners, so this might be a source of inspiration regarding how JUCE uses listeners.

OPTION 2. Roll your own

If you don't like the idea of using the text editor as a go-between to pass messages, you might as well implement your own listener, something like FilePickerCtrl::Listener. You can probably copy what is done in the JUCE library.

OPTION 3.

Also check out Component::getParentComponent(). You can do some sort of delegation like this:

void FilePickerCtrl::mouseDown(const MouseEvent& event) override
{
    Component* parent = getParentComponent();
    jassert(parent); // XXX FilePickerCtrl must have a parent (XMLFileDisplayCtrl)
    
    // let XMLFilePreviewDisplayCtrl::mouseDown() handle the event
    parent->mouseDown(event);
}

You can also use Component::postCommandMessage(int) to send messages up/down the hierarchy; this is done quite a bit internally in JUCE. Note that you'd be passing integers IDs.

SUMMARY

Option 1 is probably the least amount of work, if you don't mind using the text editor as a go-between. It works and is pretty easy to follow. I'm not sure why you are making a composite component in quite this way (I'd probably flatten it and make it one component), but I've tried to follow your use case.

Option 2 is probably what models the JUCE paradigm the best (in my mind), especially if you want your component to have an interface that mimics the rest of the JUCE components. If it were me, I'd probably do it exactly how jules does it in JUCE.

Option 3 breaks the event driven paradigm a bit by delegating instead of actually posting a message or event. It might be what you need, though, if you need to filter events before they are sent up/down the hierarchy.

There are certianly other options, but hopefully this will get you up and running until somebody else can post a more robust solution.


#10

Hi Matty!

Once again a solid thank you... The reason why I'm asking all these questions is because I do want my code to mold well into the JUCE framework. Sure I could have come up with something that works, that wouldn't be a problem, I just want to follow the existing ways of doing things.  

The reason I'm doing it this way?  That's a very fair question... In this particular example, the FilePicker composite control is overkill, no doubt about it, and I 100% agree, just flatten it.  The whole reason behind it though is just a learning exercise for me.  How do I create a black box control where the client of the control does not know anything about the inner workings, but needs to know when certain things happen.  No direct access to any of the controls... Yet, solve the problem in such a way that the client will get the necessary data it needs to perform it's tasks. 

One of the things in the Adobe Flex framework that is easy to do is to extend a given Event class, add your own specialized data to it, then dispatch an Event of that type out.  The client app can then listen for that new Event type and have immediate access to the data that was passed in.  

Anyway, thank you Matty for all the help!


#11

If you want to see the JUCE way of doing this task have a look at juce::FilenameComponent. It does all this for you including keeping a list of recently used files, a browse button, file drag and drop and its own listener interface.

I do agree creating basic Components that copy library functionaility can be a good learning exercise so I guess this is the solution. It's also good to remember JUCE contains a lot of useful classes like this so check the library before spending days re-creating what is already provided so nicely.


#12

 

Hi Dave, I agree.  I'm sure I've only scratched the surface, however, it's hard knowing about those features and classes. With all due respect, LOT of code, little documentation. 


#13

Hi Jules,

Sure, I doubt I'd have right click on a button. It was just some arbitrary example.


#14

> If MainComponent does not inhert from Button::Listener as well as Component, then I can have buttons onMainComponent that do nothing, can't do anything. 

This is not a correct statement!   There's absolutely no requirement for a button to talk to the Component it's contained in.  Why should the MainComponent have to handle its buttons?

The nice thing about Button::Listener is that you can attack it to anything you like.  If your MainComponent doesn't have a Button::Listener, some other class else can.  

In particular, it's likely that MainComponent is already busy enough keeping track of its components geometry and visibility.   Surely it's a lot better to put the actual logic in another class entirely?


#15

Hi Tom... 

This is not a correct statement!   There's absolutely no requirement for a button to talk to the Component it's contained in.  Why should the MainComponent have to handle its buttons?

For simplicity, when you create a window based app in Introjucer, you get the mainComponent and the main app.  If you place a button control on the mainComponent but don't also inhert from Button::Listener, then that button wont do anything. That's all I'm saying. 

In my case, I have a situation as you describe, a subcomponent of a subcomponent is handling the mouse click.