About child component access and design philosophy


#1

Hello,

I am confused about something.
Child components (or “subcomponents”) are by default private members of the parent component class when building in the JUCER. So far, so good. I understand the need for encapsulation here. Especially when each component can be defined in a separate .h and .cpp file.

Let’s say I have some model parameter, call it param123, connected to a slider inside a component called myComponent. I would pass the component a pointer to my model so that when the user changes the slider, we can modify the value in the model. Or the component has a pointer to some other thing that controls the model; whatever. That works fine.

If my param123 change originates somewhere else, how do I update the slider? Since the subcomponents are private, I will have to say myComponent->setParam123(value). And write this method to change the slider value. If I want to place myComponent in a tab component, I have to write another proxy method to shuttle the event down. (In this case, the layer of private encapsulation is superficial and unnecessary, I only meant to clear some space on the window!)
Then we have MainController.onParam123Changed() => tabbedComponent.setParam123() => myComponent.setParam123() => param123Slider.setValue(). :cry: Is this horror an artifact of the Jucer or something I shall learn to live with?

Surely I am missing something, surely there is a better way. In Qt I have used SIGNALS and SLOTS to build some coupling of this sort at runtime. But in Juce I’m in a little new territory.

Matt


#2

The newest method is Values. In the creation code of your master component, you would use code that links the controls to values. Changes to those values from elsewhere in your system propagate to those controls.

The first time you access a Value (from anywhere) it is created. You can pass it freely, like a reference.

In your file open code, you might say something like:

props is a Juce PropertyFile - one of the built-in classes that uses Values.

To link a control, use something like this, loosely from JuceDemo:

Value sharedValue = props->getValue ("defaultVolume", 0.7f); slider->getValueObject().referTo (sharedValue);
It would be lovely to set Value links in the Jucer, but Jucer has gotten a bit dusty as IntroJucer has been actively improved. Jules has indicated (way back) an intention to get this sort of thing into the system.

If you have other things to do - trigger notes, whatever, use a ValueListener.

Bruce


#3

Okay. So the fact remains, then- the child components are private.
Will I still have to dig down one component method at a time to link the Value?

[code]Value sharedValue;
tabbedComponent->setParam123SharedValue (sharedValue)

void TabbedComponent::setParam123SharedValue (Value sharedValue)
{
myComponent->setParam123SharedValue (sharedValue)
}

void MyComponent::setParam123SharedValue (Value sharedValue)
{
param123Slider->getValueObject().referTo (sharedValue)
} [/code]

:cry: :frowning:

In my perfect world, I can say:

OR in the last component’s constructor:

MyComponent::MyComponent (ControllerComponent *controller) 
{
    param123Slider->getValueObject().referTo (controller->sharedValue);
}

Another alternative is becoming clear - If I don’t want to use the shared Value class, make MyComponent a ChangeListener and ControllerComponent a ChangeBroadcaster:

MyComponent:public Component,
                  public ChangeListener
...
ControllerComponent:public Component
                  public ChangeBroadcaster
...
MyComponent::MyComponent (ControllerComponent *controller) 
{
    controller->addChangeListener(this);
}

...
MyComponent::changeListenerCallback (ChangeBroadcaster *source)
{
    param123Slider.setValue(static_cast<ControllerComponent>(source)->param123);
}

is that decent?


edit: Forgot to cast to my controller type in the callback.


#4

If you have a very small range of changeable items, maybe?

You have a point about child access. I have various bits of code where I’ve had to make a subclass just to implement a ‘setThis ()’ sort of method to work with sub-components.

There are getChildComponent and getParentComponent type methods - they may do what you want?

Maybe this is a simple feature request - direct access to child components of items created in the jucer?

In an ideal world, the ‘top’ component - the one you save, would be able to access all sub-components, even those created within other components. Is that something simple Jules?

Bruce


#5

Well, it’d be simple enough to make them public, but I’m not sure whether it’d be a good idea… Exposing the child components would encourage inter-class dependencies, which probably isn’t a good thing.


#6

It occurs to me there’s already a tight class linkage - the topmost class has to know it has a child, and that child has a specific method that tells it to set values. The subclass has to make that method for the top component to access. They are inextricably linked. Otherwise another, non-Jucer class has to know all about both/all child components.

A Jucer generated component with access methods, like:

Slider& getSlider4 ();

Would lead to less linkage, and let the user of the topmost generated component do stuff like:

getSlider1().setValue (x);
getTabComponent1().getSlider4().setValue (y);

I’m sure you would do some const-y magic to make the pointer > reference thing safe.

Is there some other way built into Components that is cleaner and you use? & getChildComponentByName or something?

Bruce


#7

Bruce & Jules:

yes, to me, the issue at stake is unnecessary componentization (aka, class encapsulation) of GUI elements.

In the Jucer, there should be a distinction between composing an Application GUI and a Reusable Component. Sometimes you are not creating a general-purpose reusable widget. Sometimes you are just laying out an application window.

When laying out an Application GUI in Jucer, you want to be able to say: This tabbedComponent is purely superficial, just a presentation tool for making the layout look nice, and the top-level component should have access to all its child components. In other cases, you might want to plop a more generic component in one of the tab pages. Okay, in that case, sure, keep the child components private.

I’m not sure what the solution is - maybe the only way it makes sense is for all the child elements of the tabbedComponent to be created and added in the top-level component’s constructor. And the Jucer would have to be heavily modified to support that. :expressionless:

So yes… I believe this is filed under Jucer Feature Requests. :smiley:


#8

I don’t use gui design tools like IntroJucer, its much easier (at least for me) to just write the code and “guesstimate” the position of controls to get an initial layout, then import a screenshot of the initial layout into Photoshop, do some repositioning and prototyping there, and then modify my code to use new coordinates and sizes.

In my case, the time spent laying out controls and making things “look nice” is a very, very tiny percentage of the total development time. Considerably more effort goes into building a robust concurrent system and solving domain-specific problems. So I’m not sure what we’re gaining by using a design tool - unless you are a slow typer, in which case a better solution is to work on your speed.


#9

Yea you make a good point Vinn. I should not think of Jucer as being essential. After all, the code Jucer generates is pretty simple.

The Jucer was making me think it’s some kind of violation of Juce philosophy to make tabbedComponent child components public. But it’s not a violation; it’s just that you can’t do it in Jucer.

BTW, am I missing something? Everyone mentions IntroJucer - but I thought that was the Visual Studio/XCode project creator, not a GUI designer.


#10

You didn’t miss anything. TheVinn is an enlightened being, and all tools are beneath him, so much so he can barely tell them apart.

I use and like the Jucer. It helps to think about what controls you need and don’t, and iterate designs.

Learning to perfectly visualize GUIs in my head - to the pixel - and/or becoming a PhotoShop expert is not time well spent for me.

I think a small tweak to Jucer generated code would cut down on the number of subclasses I need to write to use the created GUI, and centralize the GUI <> model linking code.

Juce is a benevolent dictatorship, btw. It’s generally a matter of finding a way to explain your needs, and why others have that same need to Jules :slight_smile: Or you can make a working code chunk and pitch to have it included.

Bruce


#11

Whoops…I guess I did overlook this point! I am definitely a Photoshop expert and have been for a while, building UI prototypes in Photoshop is a lot easier if you know how to use the program.


#12

I once had this idea about fine-tuning my components’ position:

You create a “tuning component” that lets you dynamically position the last focused components with the following attributes:

  1. It is a small document window, it’s hidden by default, being awaken by a keyboard shortcut.
  2. Once activated, it lets you change the x,y,width, height and alpha of the last focused component.
  3. It saves the changes to XML (using the component’s name).

I decided to not implement it the minute my pills’ influence faded…


#13

Another way around this problem just occurred to me.

:arrow: Subcomponents can be declared friend classes to the top-level component.

class MyTabPanel: public Component {
    ...
    friend class MainComponent;
    ...
}

//Somewhere in a MainComponent instance:
MyTabPanel* tabPanel = static_cast<MyTabPanel>(tabbedComponent->getChildComponent(0));
tabPanel->param123Slider.setValue(400);

Now the MainComponent is free to access MyTabPanel’s children.

I believe this uses the friend keyword for its intended purpose.
Subcomponents should rightly be private members of their parent component. But when a main GUI component is in charge of all the buttons and sliders, he should be friends with those buttons, even ones underneath tabbedComponent and other containers.


#14

Just my opinion but this is a bad design. A control should be self contained. It should not be necessary to have a “master” parent Component “in charge” of all the children.


#15

I guess this is where the design philosophy comes in. Well, in this thread I have been trying to illustrate the case where you just wish to visually group some buttons, not encapsulate them in a new control. Namely, with the TabbedComponent. I do not intend any semantic difference between these buttons and the rest of my buttons outside the TabbedComponent. So why should they be quarantined? Vinn, if you disagree, how would you answer the original question I posed?


#16

To answer your original question, I would store the setting in a Value object and broadcast the changes using juce::ListenerList. Very much like the SIGNAL / SLOT system in Qt that you referred to.


#17

Okay, fair enough.
Different question: What if the setting is stored somewhere else, like an existing model class? What if my model uses some settings stored in a vector or is otherwise not amenable to the Value class? Would I have to copy those settings into the Value object whenever they change?


#18

[quote=“mmontag”]Okay, fair enough.
Different question: What if the setting is stored somewhere else, like an existing model class? What if my model uses some settings stored in a vector or is otherwise not amenable to the Value class? Would I have to copy those settings into the Value object whenever they change?[/quote]

You don’t have to use the Value class, you can put the data wherever you want. The model sounds like an appropriate place if you already have one. As long as you expose some kind of interface to register for notifications when the value changes (could be a juce::ListenerList or roll your own) the underlying storage shouldn’t matter.


#19

Plus (incidentally), the Value class is really only a shell to wrap any kind of user data (provided its members can be represented with the var class). You don’t have to design your data around them; you can simply write a ValueSource class (pointing at your data) to use inside the Value interface.

This is quite straightforward to do, just look at the docs and the class interface if you’re interested in the idea, you should figure it out. You just create one or more ValueSource classes capable of referring to each of the variables you wish to expose. This allows you to interface your data seamlessly with many existing UI controls, as well as any you may wish to create for yourself. You need only hold a Value object and you have everything you need to access (and respond to) your data, which makes it easy to write highly reusable, automatically-binding controls.