Application ValueTree


#1

Hi,
How implement a simple valueTree to save a component value in app ?

For exemple a simple app with a slider

        class MainComponent  : public Component
    {
    public:
        MainComponent()
        {
            setOpaque (true);
            setSize (640, 480);
            addAndMakeVisible(mySlider);
            mySlider.setSliderStyle(Slider::SliderStyle::LinearVertical);
            
            
        }

        void paint (Graphics& g) override
        {
            g.fillAll (findColour (ResizableWindow::backgroundColourId));
        }

        void resized() override
        {
            mySlider.setBounds(10, 10, 64, getHeight()-20);
            
        }

    private:
        Slider   mySlider;

#2

It might depend on what you’re trying to accomplish… but a thing I find really cool is that you can make their Value refer to the ValueTree .

so you could make something like:

auto value = valueTree.getPropertyAsValue (“myProperty”, nullptr);
slider.getValueObject().referToSameSourceAs (value);

This way your ValueTree will be in sync with the slider value.
But you have to be aware if you create a new value tree, you’ll have to update the references.


#3

Thanks for your answer. Like this ? (tree is my ValueTree)
How restore value when my app opening ? I need methods to save and load my valueTree, isn’t it ?

MainComponent()
{
    setOpaque (true);
    setSize (640, 480);
    addAndMakeVisible(mySlider);
    mySlider.setSliderStyle(Slider::SliderStyle::LinearVertical);
    auto value = tree.getPropertyAsValue("mySlider", nullptr);
    mySlider.getValueObject().refersToSameSourceAs (value);
    
    
}

#4

I’m trying something like this to save, but it seems my xml is empty !?!

XmlElement *xml (tree.createXml());
    File myFile {(File::getSpecialLocation(File::SpecialLocationType::userApplicationDataDirectory).getFullPathName() + "/Property.xml")};
    xml->writeToFile(myFile, "Property");

#5

Should be slider.getValueObject().referTo (value);

Use getChildFile() instead of concatenating the paths, and check whether writeToFile returned true.


#6

Yes you are right about the getChildFild but that doesn’t solve my problem about save and restore a slider value by an Xml properties file…

File myFile {(File::getSpecialLocation(File::SpecialLocationType::userApplicationDataDirectory).getChildFile("Property.xml").getFullPathName()) };


#7

To save defaults from your app for the next time, there is the PropertiesFile class.
It inherits a PropertiesSet, so you can add a property for everything you want to save (it ends up in an XML).
You will have to propagate the value changes yourself, either by overriding the propertyChanged() method, or add a ChangeListener.

I am not sure, there might exist an automatic way to link that as well, and @matkatmusic once posted his own solution for that, if you want to use the forum search.
(here it is: ScopedValueSaver - never worry about App State again)


#8

yep, you’re right… thanks for correcting it :wink:

@nseaprotector,
You have to make sure the property already exists in the valueTree before trying to assign it to the slider.


#9

Thanks for your answers.
Thanks Daniel, I’m a beginner and English is not my mother tongue which does not make it easy for me to find and understand the good way.
jrrossi: Do you talk about to this ?


#10

Work fine

//==============================================================================
    class MainComponent  : public Component
    {
    public:
        MainComponent()
        {
            setOpaque (true);
            setSize (640, 480);
            addAndMakeVisible(mySlider);
            mySlider.setSliderStyle(Slider::SliderStyle::LinearVertical);
            initProperties();
            loadParams();
            
            
        }
        
        ~MainComponent()
        {
            saveParams();
            
        }


        void initProperties()
        {
            PropertiesFile::Options options;
            options.applicationName = ProjectInfo::projectName;
            options.filenameSuffix = ".settings";
            options.osxLibrarySubFolder = "Application Support";
            options.folderName = File::getSpecialLocation(File::SpecialLocationType::userApplicationDataDirectory).getChildFile("myApp").getFullPathName();
            options.storageFormat = PropertiesFile::storeAsXML;
            
            props.setStorageParameters(options);
        }
        void loadParams()
        {
           
            mySlider.setValue( props.getUserSettings()->getIntValue("mySlider"));


        }
        void saveParams()
        {
            props.getUserSettings()->setValue("mySlider", mySlider.getValue());
        }
        
        void paint (Graphics& g) override
        {
            g.fillAll (findColour (ResizableWindow::backgroundColourId));
        }

        void resized() override
        {
            mySlider.setBounds(10, 10, 100, getHeight()-20);
            
        }

    private:
        Slider   mySlider;
        ApplicationProperties props;

        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
    };

#11

I’m trying to add an UndoManager to see how implement it, doesn’t work for the moment…

addAndMakeVisible (undoButton);
addAndMakeVisible (redoButton);
undoButton.setButtonText("Undo");
redoButton.setButtonText ("Redo");
undoButton.onClick = [this] { undoManager.undo(); };
redoButton.onClick = [this] { undoManager.redo(); };
ValueTree myValueTree;
mySlider.getValueObject().refersToSameSourceAs(myValueTree.getPropertyAsValue ("mySlider", &undoManager));
startTimer (500);
void timerCallback() override
{
undoManager.beginNewTransaction();
}

#12

Yes I finally resolve it by myself, happy :slight_smile:

class MainComponent  : public Component,private ValueTree::Listener, private Timer
{
public:
    MainComponent()
    {
        setOpaque (true);
        setSize (640, 480);
        addAndMakeVisible(mySlider);
        mySlider.setSliderStyle(Slider::SliderStyle::LinearVertical);
        initProperties();
        loadParams();
        addAndMakeVisible (undoButton);
        addAndMakeVisible (redoButton);
        undoButton.setButtonText("Undo");
        redoButton.setButtonText ("Redo");
        undoButton.onClick = [this] { undoManager.undo(); };
        redoButton.onClick = [this] { undoManager.redo(); };
        ValueTree myValueTree ("myApp");
        myValueTree.setProperty("mySlider", mySlider.getValue(), &undoManager);
        Value valueTreeObj = myValueTree.getPropertyAsValue("mySlider", &undoManager);
        mySlider.getValueObject().referTo(valueTreeObj);
        myValueTree.addListener(this);
        
        startTimer (500);
    }
    
    ~MainComponent()
    {
        saveParams();
        
    }
    void valueTreePropertyChanged (ValueTree&, const Identifier&) override
    {
        repaint();
    }
    void valueTreeChildAdded (ValueTree& parentTree,
                              ValueTree& childWhichHasBeenAdded) override
    {
    }
    

    void valueTreeChildRemoved (ValueTree& parentTree,
                                        ValueTree& childWhichHasBeenRemoved,
                                        int indexFromWhichChildWasRemoved) override
    {
        
    }
   
    virtual void valueTreeChildOrderChanged (ValueTree& parentTreeWhoseChildrenHaveMoved,
                                             int oldIndex, int newIndex) override
    {
        
    }
    
    void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) override
    {
        
    }
    

    void initProperties()
    {
        PropertiesFile::Options options;
        options.applicationName = ProjectInfo::projectName;
        options.filenameSuffix = ".settings";
        options.osxLibrarySubFolder = "Application Support";
        options.folderName = File::getSpecialLocation(File::SpecialLocationType::userApplicationDataDirectory).getChildFile("myApp").getFullPathName();
        options.storageFormat = PropertiesFile::storeAsXML;
        
        props.setStorageParameters(options);
    }
    void loadParams()
    {
       
        mySlider.setValue( props.getUserSettings()->getIntValue("mySlider"));


    }
    void saveParams()
    {
        props.getUserSettings()->setValue("mySlider", mySlider.getValue());
    }
    
    void paint (Graphics& g) override
    {
        g.fillAll (findColour (ResizableWindow::backgroundColourId));
    }

    void resized() override
    {
        
        auto r = getLocalBounds();
        
        auto buttons = r.removeFromBottom (20);
        undoButton.setBounds (buttons.removeFromLeft (100));
        redoButton.setBounds (buttons.removeFromLeft (100));
        mySlider.setBounds(10, 10, 100, getHeight()-80);
        
    }

private:
    Slider   mySlider;
        TextButton undoButton, redoButton;
    ApplicationProperties props;
  UndoManager undoManager;
  
    void timerCallback() override
    {
        undoManager.beginNewTransaction();
    }
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

#13

Another possibility, it seems to me, is to create components that include functionalized properties. But the init function doesn’t seem to me to be Windows and Linux compatible, what do you think about that?

class Xslider  : public Slider //,private ValueTree::Listener, private Timer, public KeyListener
{
public:
    Xslider()
    {
        initProperties();

    }
    ~Xslider()
    {
        if(stringPropertyName.isNotEmpty())
            saveParams();
    }
    void setPropertyName (const String& newName)
    {
        stringPropertyName = newName;
        loadParams();
    }
    void initProperties()
    {
        PropertiesFile::Options options;
        options.applicationName = ProjectInfo::projectName;
        options.filenameSuffix = ".settings";
        options.osxLibrarySubFolder = "Application Support";
        options.folderName = File::getSpecialLocation(File::SpecialLocationType::userApplicationDataDirectory).getChildFile(ProjectInfo::projectName).getFullPathName();
        options.storageFormat = PropertiesFile::storeAsXML;
        
        props.setStorageParameters(options);
    }
    void loadParams()
    {
        
        setValue( props.getUserSettings()->getIntValue(stringPropertyName));
        Logger::writeToLog(getName());
        
    }
    void saveParams()
    {
        props.getUserSettings()->setValue(stringPropertyName, getValue());
             Logger::writeToLog(getName());
    }
    
private:
    //==============================================================================
    // Your private member variables go here...
    String stringPropertyName;
    ApplicationProperties props;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Xslider)
};

#14

Since the Slider class is already very heavy, I would avoid inheriting from.
My solution would be a little connector class, haven’t tried, but similar to this:

class SliderPropertyAttachment : private Value::Listener
{
public:
    SliderPropertyAttachment (Slider& slider, ApplicationProperties& p, Identifier& pname)
      : properties (p),
        propName (pname)
    {
        value.addListener (this);
        value.referTo (slider.getValueObject());
        ScopedValueSetter setter (updating, true);
        value = properties.getUserSettings()->getIntValue (propName);
    }

    void valueChanged (Value&) override
    {
        if (! updating)
        {
            ScopedValueSetter setter (updating, true);
            properties.getUserSettings()->setValue (propName, value);
        }
    }

private:
    ApplicationProperties& properties;
    Identifier             propName;
    Value                  value;
    bool                   updating = false;
};

You might need to play around with construction order of the ApplicationProperties object…


#15

Ok, Daniel but you say Slider is already heavy and if I understand the code you save the property each time the slider value change ? is it true ?

Note: I was wondering if the addition of properties and valueTree (to have Undo/Redo) were not redundant?