Listbox Example


#1

Just back from 2 weeks sailing around the Baltic and my Listbox thang is already giving my arse a nippy taste: anyone have a copy of whammybars Simple RSSReader lying around? The link on moduLR’s site is extinct, and I could use some example code to rip off cough er…guide me.


#2

It’s quite straightforward to do, really, although it’s daunting at first! I present to you…

[size=150]haydxn’s basic ListBox tutorial![/size]

You need to decide where to keep the data the list will represent; you may want it in your main program component, or you may choose to have it live inside the ListBox derived component.

For these examples, consider:

  • the data for a single row to display lives in a custom object of type ListDataObject.

  • The whole list’s data is therefore stored in an Array

  • … the instance we’ll use here we’ll call listData. we’ll assume it lives inside the ListBox, but it could go elsewhere as long as the ListBox knows where it is.

  • The ListBox component will be derived from ListBox, let’s call it MyList.

  • The MyList will display the data in individual i[/i] row components (maintained by itself), which we’ll call ListItemComponent.

So, first (assuming you’ve already designed ListDataObject) you might want to design a ‘ListItemComponent’. This will represent an item of the list. It’s important to be aware that these components are made by the ListBox itself, and are reusable (meaning that the contents of a component will be changed depending on which row the list has decided to use it for). You’ll probably want to include a boolean flag called something like ‘isSelected’; your paint() function can then fill the background an appropriate colour depending on the state of this flag. e.g:

void ListItemComponent::paint( Graphics& g )
{
   if (isSelected) 
   {
      g.setColour( Colours::blue);
      g.fillAll();
   }
}

A simple interface function ‘void setSelected( bool sel );’ can set the value of this flag.

Make a component that will display all of the things you need from your data. If your data was simply a string array, you could just have a label or a paint() based drawText(…) to echo the data. Of course it’s more likely that your data is an array of custom objects, so you’ll want to put in all the display elements (or controls) you need, as well as the interface to set/update them. So, for a simple text thing, you’d be okay with a setText(const String&) function. The interface of the item component will be used primarily by the ListBox.

You derive a subclass of ListBox ( MyList ), and the important functions to override are:

first…
[size=150]getNumRows();[/size]

This is the simplest to override! All it needs to do is return the number of rows that are in the list. As the list is made up of an array of your data objects, you can simply have…

int MyList::getNumRows()
{
   return listData.size();
}

second…
[size=150]createRowComponent();[/size]

This can simply return a new ListItemComponent. You may want to have a constructor in your item component that takes a ‘parent’ MyList pointer, so that the component is able to access anything you store in the list. In which case, you’d have…

Component* MyList::CreateRowComponent()
{
   return new ListBoxItemComponent( this );
}

… of course this probably won’t be necessary. Depending on the structure of your program, and the complexity of the relationship between the list and the data being displayed, you may find it useful to bear this idea in mind.

finally…
[size=150]updateRowComponent(…);[/size]

This is called by the ListBox to update the row component when it has changed, or - in a more straightforward way of thinking - this is where the ListBox gives the row component the data it needs to display. Remember that you’ll never call this - the ListBox will always be the caller, which means you can always depend on the three parameters being there to use. The three things it gives you to help setup your row are…

  • a pointer to the Component you’ll be updating (cast this to a ListItemComponent* ),

  • the row number it will represent (use this to pick the data element for it from your array)

  • the ‘selected’ status of the row, which you can use to set the flag described earlier.

You may choose to use the item’s interface to individually set its child components to the appropriate values/contents… (here assuming just a simple text value)

...
ListItemComponent* thisRow = (ListItemComponent*) rowComponent;
thisRow->setText( listData[rowNumber]->getText() );
thisRow->setSelected( isSelected );

Or, you could simply pass it the data object from the array and have it update itself. How you do this depends on the design of your ListItemComponent. For example…

...
thisRow->useThisData( listData[rowNumber] );
thisRow->setSelected( isSelected );
thisRow->update();

Here, all three of these functions are for you to have written. So for this type of operation, your ListItemComponent will want a
void useThisData( ListDataObject* );
function, and a
void update()
function that automatically puts the data into its child components. You could, of course, take out the update() function and have it do all that stuff as soon as it’s been given the object.

That’s the basics covered. Obviously your ListItemComponent will need to have its children positioned appropriately in your resized() function. Other than that, any other customisations you may wish to do will depend entirely on your situation. All the ‘exciting’ stuff you put in will be in your ListItemComponent. If you’re going to be editing the data directly on a row element, for example, then it helps to have passed the row component a pointer to the object itself as i described above. That way you can have it be a change listener and update the contents of the data object based on any child component controls.

I hope this helps, and i’m sorry if i’ve missed out something really stupid!


#3

(also, sorry if i’ve completely failed to answer your question, seeing as i’ve not given you any actual ‘example’ code! personally i found building it from scratch once i’d figured it out quite a bit easier than trying to tweak an existing list to my needs) :oops:


#4

Woah! a mighty tome, thanks a lot for that! It’s difficult to see how that Listbox component hangs together with everything else.


#5

once you’ve got that much of it figured out, the rest of the ListBox interface stuff as described in the JUCE docs is quite straightforward; you just think of what you need to do and look at what functions are there to use. the tricky part is figuring out how all the pieces go together, which hopefully i’ve described above :slight_smile:


#6

(…and don’t forget it’s usually easier just to use the SimpleListBox - that’s what I normally use unless there’s a good reason not to)


#7

yup indeed! now that i’m used to the ListBox tho it can take me a little while to get my head around the SimpleListBoxModel again! :lol: My current project has several pages of different list types, each of which has a number of controls on every row, so that means heavy ListBox customisation!


#8

Yah. this listbox I need has a multitude of different controls displayed depending on the context and state of other controls on the page. Maybe I’ll rename my control “Lostbox” :shock:

thanks again gentlemen


#9

I am still not quite understaning how elements are placed within the ListBox.

I am able to create the ListBox and it appears on my plugins GUI however I am totally confused how rows of text are added to the ListBox. I would provide some code but I don't have any. 

Thanks. 


#10

I just started with JUCE myself and struggled for a bit on this too. But it's quite simple and elegant in JUCE.

 

You have your array of data in your class that inherits from ListBoxModel

and in you class you overload 2 methods:

virtual int getNumRows();

and

virtual void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected);

 

the getNumRows() simply return the size of the array (tells the listbox you have data you want to paint):

    int getNumRows()
    {
        return myData.size();
    }

and the paintListBoxItem like:

void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected)
{
        if (rowIsSelected)
            g.fillAll (Colours::lightblue);

        String someData = myData[rowNumber];

        g.setColour (Colours::black);
        g.drawText (someData, 4, 0, width - 4, height, Justification::centredLeft, true);

}

When you add data to your array just call

myListBox->updateContent();

 

Hope this helps!

 

// Erik

 

 


#11

Hi Eric,

Thanks for you speedy reply and patience. I am lame beginner.

So far I have implemented a 'ListBoxBuild.cpp' class based on the JuceDemo and the help you have given me.

#include "PluginEditor.h"


class ListBoxSource  : public ListBox, public ListBoxModel
{

public:

    ListBoxSource() : ListBox ("d+d source", 0)
    {
        // tells the ListBox that this object supplies the info about its rows.

        setModel (this);
        setMultipleSelectionEnabled (false);
    }

    // The following methods implement the necessary virtual functions from ListBoxModel,
    // telling the listbox how many rows there are, painting them, etc.

    int getNumRows()
    {
        return 5;
    }
    
    void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected)
    {
        if (rowIsSelected)
            g.fillAll (Colours::lightblue);
      
        g.setColour (Colours::black);
        g.setFont (height * 0.7f);
        
        g.drawText ("Row Number " + String (rowNumber + 1), 5, 0, width, height, Justification::centredLeft, true);

    }

    // this just fills in the background of the listbox

    void paint (Graphics& g)
    {
        g.fillAll (Colours::white.withAlpha (0.7f));
    }
};


class ListBoxBuild  : public Component
{
public:
    ListBoxBuild()
    {
        setName ("Temprament");
        addAndMakeVisible (&source);
    }
 
    ~ListBoxBuild()
    {
    }

    
    void resized()
    {
        source.setBounds (10, 10, 250, 150);
    }

private:
    ListBoxSource source;
};


Component* createListBox()
{
    return new ListBoxBuild();
}

What I am trying to do is replace one of the rotary sliders on the JuceDemoPlugin with a ListBox.

I am able to add the Listbox by simply creating the ListBox object in the header file

    ListBox tempramentListbox;

and setting 

 

    addAndMakeVisible (&tempramentListbox);

in the PluginEditor.cpp file

My problem is I do not know the syntax or how to use the ListBoxBuild.cpp file I have written in order to produce a ListBox without empty rows. 

Im probably not explaining myself very clearly I apologize. I cant find an example of a simple ListBox implementation anywhere. 

Again thanks for your help.

 

Richard

    addAndMakeVisible (&tempramentListbox);

#12

Do the override methods get placed in the juce_ListBox.cpp file in the JUCE library?


#13