Lamba list

A question for one of you c++ experts here. I am modifying some existing windows code so I can run in on android. But I am changing to use a FileChooser as done in DialogsDemo.h and I don’t understand the code. Here is the snippet:
fc.reset (new FileChooser (“Choose a file to open…”, File::getCurrentWorkingDirectory(),
“*”, useNativeVersion));

            fc->launchAsync (FileBrowserComponent::canSelectMultipleItems | FileBrowserComponent::openMode
                                 | FileBrowserComponent::canSelectFiles,
                             [] (const FileChooser& chooser)
                             {
                                 String chosen;
                                 auto results = chooser.getURLResults();

                                 for (auto result : results)
                                     chosen << (result.isLocalFile() ? result.getLocalFile().getFullPathName()
                                                                     : result.toString (false)) << "\n";

                                 AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
                                                                   "File Chooser...",
                                                                   "You picked: " + chosen);
                             });

This compiles.

But calling a function instead of the alert window such as:
MyFunction(chosen);
This gives an error about local variables in a lambda body.cannot be referenced unless int he capture list. How resolve I do this???

you need to put the variables you want to capture in the [] in the lambda. that’s the [] (const FileChooser& chooser) part.

you can name the variables you want to capture explicitly, or just put & to capture all of the variables in the current scope by reference.

if you want to capture class member variables and use class member functions, capture this

void functionThatTakesALambda(std::function<void()> func) { func(); }; 
void MyClass::privateFunction() {}
void MyClass::func()
{
    auto lambda = [this](/* args */)
    {
        this->privateFunction();
    };
    functionThatTakesALambda(lambda);
}

https://en.cppreference.com/w/cpp/language/lambda

You have to be careful with async stuff that you don’t capture anything in the lambda that could be destroyed before the async function has been executed. So for example local variables should not be captured by reference when in an async context. Local variables captured by copy are OK, but of course that might not work like you want, either.

Also when capturing “this”, there are obviously chances the “this” object may have been deleted before the async operation has run.

1 Like

I don’t think I get it yet. I think I need this broken down a little more and the link to lambda confuses me more.

the auto lambda = this{this->f();};
in this case the the ‘this’ the one coming from the caller of func(); if so, why not make the func take the callers this as an argument like this:
void MyClass(auto callersthis){
callersthis->privateFunction();
functionThatTakesALambda(callersThis);
}

What you are constrained here by, on purpose to reduce coupling of classes, is the std::function that is in the signature of FileChooser::launchAsync. Additional data is not part of the callback function signature but you can still have it by making the lambda capture the needed additional state. (The additional state here being the “this” pointer so you can call its methods from inside the lambda.)

The code you need to have, assuming MyFunction is a method of your “this” class is :

fc->launchAsync (FileBrowserComponent::canSelectMultipleItems | FileBrowserComponent::openMode
                                 | FileBrowserComponent::canSelectFiles,
                             [this] (const FileChooser& chooser)
                             {
                                MyFunction(chooser.getResult());
                             });

if you understand operator overloading, lambdas basically build off of the ability to overload operator(), the function call operator. take a look at this structure:

struct MainContentComponent;
struct Anonymous
{
    int& a;
    int& b;
    MainContentComponent& mcc;
    Anonymous(const int& a_, const int& b_, MainContentComponent& mcc_) : 
    a(a_), 
    b(b_), 
    mcc(mcc_) 
    {
        //constructor 
    }

    void operator()(int c, int d) 
    {
        mcc.setBounds( a, b, c, d ); 
    } 
};

You could create an instance of this struct locally, and supply it with the appropriate arguments, like this:

void MainContentComponent::someFunc()
{
    int a = 2;
    int b = 2;
    Anonymous anon(a, b, *this);
    anon(4,5); //calls the Anonymous::operator(int c, int d) member function 
}

Do you see how the struct’s constructor captures the local variables in the surrounding scope and stores references to them?

Lambdas perform the same task, but with syntactic sugar, so to speak.

This does the same thing:

void MainContentComponent::someFunc()
{
    int a = 2;
    int b = 2;
    auto anon = [&a, &b, this](int c, int d)
    {
         this->setBounds(a, b, c, d);
    };    
    anon(4, 5);
}
1 Like

Thanks, I think all is working now.

One last question.
fc->launchAsync(FileBrowserComponent::canSelectMultipleItems | FileBrowserComponent::openMode
| FileBrowserComponent::canSelectFiles,
[this](const FileChooser& chooser){}

I do a get results on chooser inside the braces. But if the user cancels out of the file chooser, the code in the braces is still executed. How do I either skip {} on cancel or get the status inside {} to do nothing?

The documentation isn’t clear about it, but I would assume

if (chooser.getResult()==File())

can be used inside the callback function/lambda to detect the selection of the file was cancelled or failed in some other manner.

Thanks that worked.

But now I have another problem. works fine on windows but not Android. I am saving properties to getCurrentDir(). This returns “/” as a path so I cannot save properties there. Everything I see on the internet is that ap data goes folder data directory named for the ap. But that directory does not exist. So 2 questions I have not found yet:
1- How does the ap data directory get create? (Do I have to create it manually?)
2-how do I get the path name to it?

As a temporary method (Until someone can tell me the correct way) I am just storing the properties file in /storage/emulated/0/Android/data. Works on all my devices atleast.

Hi all
Reviving this thread as I have a related question. I too am making use of the File Chooser code from the demos and am aiming to simple load a file into a string and then set the contents of a TextEditor from that string. My TextEditor already exists (created using the GUI builder) and works fine but I can’t set the text from my loaded string … my code …

//[UserButtonCode_textButton2] -- add your button handler code here..

fc.reset (new FileChooser ("Choose a file to open...", File::getCurrentWorkingDirectory(),
                                   "*", false));
        fc->launchAsync (FileBrowserComponent::canSelectMultipleItems | FileBrowserComponent::openMode
                             | FileBrowserComponent::canSelectFiles,
                         [this] (const FileChooser& chooser)
                         {
                             String chosen;
                             auto results = chooser.getURLResults();

                             for (auto result : results)
                                 chosen << (result.isLocalFile() ? result.getLocalFile().getFullPathName()
                                                                 : result.toString (false)) << "\n";
                                                                 
                             //create a File from the returned location (chosen)
                             //load as a string
                             //and set textEditor content
                             File chosenFile (chosen) ;
                             auto fileText = chosenFile.loadFileAsString();
                             textEditor->setText (fileText);
                             
                             //The above doesn't work? although this will                                      
                             textEditor->setText (TRANS("new text content for textEditor");
                             
                                                                 
                             AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
                              "File Chooser...",
                               "You picked: " + chosen);
                            }); 

//[/UserButtonCode_textButton2]

any advice/help?

Thanks
Mark

It may just be a oversight in the example code, but one difference between the non-working and working cases are that you are using different textEditor objects textEditor in the non-working case, and textEditor_ in the working case…

    textEditor->setText (fileText);
    textEditor_.setText (TRANS("new text content for textEditor");

Thanks for replying @cpr

That’s my typo whilst typing up the code to post here! I have just edited so that it is now the same textEditor being used!

Is the loadFileAsString running as a background task maybe… so that the setText attempt is executing before the file has actually been loaded?

the load does not happen in the background. At this point the debugger is your friend. Put a break point on the setText call, and step into it. Do that for both the calls and see if they differ. I suspect the issue is not the actual setText call.

Maybe even simpler, you could call the getText function after setText to verify that the editor has the correct text.

None of these steps solves the problem, but they help whittle down where the problem is (or isn’t)

@cpr
Thanks again … I will fiddle around a bit more
Mark

@cpr
I have found a solution … creating a File from the String chosen doesn’t work … I don’t know why … no errors thrown up by the compiler etc but hey ho it just doesn’t work!

Instead I have found that using FileChooser::getResult() does create a file so this code works…

//[UserButtonCode_textButton2] -- add your button handler code here..

fc.reset (new FileChooser ("Choose a file to open...", File::getCurrentWorkingDirectory(),
                                   "*", false));
        fc->launchAsync (FileBrowserComponent::canSelectMultipleItems | FileBrowserComponent::openMode
                             | FileBrowserComponent::canSelectFiles,
                         [this] (const FileChooser& chooser)
                         {				     
                            File f = chooser.getResult();
                            auto newText = f.loadFileAsString();
                            textEditor->setText (newText);
                         });

//[/UserButtonCode_textButton2]

It’s sometimes good to have errors and things that don’t work … forces reading and research … I now know more than I did before about some of the File handling features of Juce, even if I’m not using them (yet) :slight_smile:

Mark