Attaching Sliders to ValueTree from sub component

sliderattachment
projucer
gui

#1

Hey there!

THE PREFACE
I just began experimenting with creating UI Components using the JUCE wysiwyg editor, and it’s working well. However, with my other hand-written components I am using the AudioProcessorValueTreeState and SliderAttachments to connect the UI to the state, not implementing SliderListener by hand.

I am having a very hard time, making a connection between Sliders stored as ScopedPointers in sub components and the ValueTreeState. It seems the constructor for SliderAttachment can not accept a pointer to a dynamically allocated Slider instance. In my other components where I’ve used Sliders and SliderAttachments successfully I’ve had to store the Sliders as objects, not pointers.

Slider midiSlider = new Slider();  // SliderAttachment works with this type, but that's not what is generated by the component editor.

Slider *midiSlider; // SliderAttachment will not work with a pointer
ScopedPointer<Slider> midiSlider; // Calling midiSlider.get() returns a pointer, which will not work with SliderAttachment

THE QUESTION
So the question is; the visual component editor stores the Sliders as ScopedPointers, so how can I use those underlying Slider objects (without changing generated code) with the AudioProcessorValueTreeState through a SliderAttachment (which only accepts non-pointer Slider objects), when I’m unable to obtain a basic Slider object from the ScopedPointer?

Thank you so much! :smiley:


#2

Just add a * before the slider variable, which means dereferencing the pointer to a reference:

attachment = new AudioProcessorValueTreeState::SliderAttachment 
             (p.getValueTreeState(), "param", *midiSlider);

See it in action in a playground project I did a year ago:

HTH


#3

Thank you for this answer. This compiles by dereferencing the pointer.

However, as the Slider is owned by another object, it is not so easily accessible. The Projucer UI code generation tool makes all UI components private members. I created a public getter to expose the Sliders to the PluginEditor so I can attach them to the VTS. By doing so, I run in to memory access issues, as Slider objects become unavailable after garbage collection as the getters seem to hide that they are being referenced to in the PluginEditor.

Would you think I should let the sub component own the SliderAttachments instead, and pass it a reference to the VTS and do the hooking up inside the sub component, opposed to doing it all in the PluginEditor? I’m trying to minimize all extra programming/adding code steps needed in order to hook up a lot of generated Components to a single VTS.


#4

My approach worked in this case. I created a method attach on the subcomponent, which is created and owned by PluginEditor. In PluginEditor setup code I call attach on the subcomponent, passing it a reference to the VTS doing the SliderAttachment hook up inside this method of the subcomponent.

Works like a charm and does not yield null pointer issues. (I store the SliderAttachments in a OwnedArray)

void OutputChannelComponent::attach(juce::AudioProcessorValueTreeState &vts) {
this->sliderAttachments.add(new juce::AudioProcessorValueTreeState::SliderAttachment(vts,
                                     "midiChannel",
                                     *midiChannelSlider));
    // More Sliders to hook up here etc...
}

and is called simply from PluginEditor:
outputChannelComponent->attach(vts);

I would love to hear if there are any opinions on this pattern, but at least It works for me and introduces minimal additions to the generated Component class.


#5

I think that is a good approach. For maintainability of the code I find it important to have the sliders and the attachments stored next to each other (if I understand you correctly this is how you did it).

For the destruction sequence, make sure that the OwnedArray is defined AFTER all sliders, so the attachments are destroyed BEFORE the sliders.
Alternatively you can call attachmentArray.clear(); in the destructor, to enforce the attachments being deleted before the sliders go out of scope.

EDIT: just double checked, that this works:

You can add the state as parameter to your component’s constructor:
AudioProcessorValueTreeState& state

Results in this code, where you only need to add one line:

//==============================================================================
NewComponent::NewComponent (AudioProcessorValueTreeState& state)
{
    //[Constructor_pre] You can add your own custom stuff here..
    //[/Constructor_pre]

    addAndMakeVisible (slider = new Slider ("new slider"));
    slider->setRange (0, 10, 0);
    slider->setSliderStyle (Slider::Rotary);
    slider->setTextBoxStyle (Slider::TextBoxBelow, false, 80, 20);
    slider->addListener (this);


    //[UserPreSize]
    //[/UserPreSize]

    setSize (600, 400);


    //[Constructor] You can add your own custom stuff here..
    attachments.add (new AudioProcessorValueTreeState::SliderAttachment (state, "param", *slider));
    //[/Constructor]
}

And your owned array in the header:

private:
    //[UserVariables]   -- You can add your own custom variables in this section.
    OwnedArray<AudioProcessorValueTreeState::SliderAttachment> attachments;
    //[/UserVariables]

And last but not least, make sure, the attachments are deleted first. Put in the destructor:

NewComponent::~NewComponent()
{
    //[Destructor_pre]. You can add your own custom destruction code here..
    attachments.clear();
    //[/Destructor_pre]

@jules / @fabian /@t0m: a common base class for the Attachments would simplify this pattern. Any chance?
And also reminding on this simplification, so you would never have to touch that attachment again: