ListBox: Custom row components with variable height?


#1

Hi chap(esse)s,

I'm trying to implement a GUI consisting of a ListBox where each row contains a custom Component which has a button to fold/unfold additional options.

Here's where I'm at so far, you should quickly see what I'm aiming to achieve.

listBox_with_variable_height_custom_components?

The button in the inputListItemComponent calls the inputListItemComponent's setSize() method to reveal additional options (as shown on the right), but the ListBox's rowHeight is not updating to reveal the rest of the component.

A search of the forum tells me that ListBoxes are inherently fixed height, so..

How should I be approaching this problem?


#2

Create your own custom ListBox using a ViewPort… It’s a lot easier than you’d think.

Rail


#3

Thanks for the input Rail yes - reading the ViewPort docs now.

I'm suspicious that using ViewPort may be less memory efficient than ListBox, because ListBox actually recycles row Components so it's only ever using as many as are needed to fill the visible area.


#4

I’d have an OwnedArray of the items… then in your ViewPort only display the items from the array that are visible. Since you’ll have variable heights… you’ll have to keep track of the total height of the Viewport and use ViewPort::setSize() as items are added/removed from the array.

Rail


#5

Many thanks for your input Rail!

I was wondering if anyone else might care to chime in and offer their opinion as to the best approach before I start tackling this one - as a newb, the magnitude of my progress vector is small, so correcting its direction early is highly valuable..

This thread makes me wonder whether I should be using TreeView..?

As I see it now, there are 3 possible routes to pursue:

  1. Copy Jules' ListBox component and modify it to support variable height rows
  2. Roll a VariableHeightListBox class from scratch as Rail suggested using ViewPort
  3. Use TreeView

Votes / input gladly appreciated.

It also seems from searching the forum to be a not so unusual request (ListBox with variable height row components) so would get my vote as a feature request.


#6

From looking at your image, it does seem like a TreeView would better suit your needs, especially since your wanting a little tree structure. That's what I would try to use anyways. I think it would be much easier than trying to make ListBox fit your needs, or rolling your own Component. It seems like it would actually be trivial using TreeView. Maybe you should try the easiest route first and then see what happens from there. Experience with creating custom Components from "scratch" can't hurt either, unless of course you're trying to get things done.

I'm tempted to try this as an exercise myself. I've been wanting to experiment with custom TreeViews for a while now. I need to eventually create a custom directory tree that represents the files in a directory. I can think of a lot of handy uses for trees, many similar to what it seems like you're trying to do. 


#7

I'm also looking at implementing something similar to this, and have very very briefly looked at these options too...but my impression of a treeview was that it would always be inside a single bounded rectangle.  Whereas the listbox has that desirable popup function.

am i wrong?


#8

@willrice Can you share your code to me?I want to create a ListBox same like that.
Or Please,Can anyone let me know how can insert component like PropertyPanel into row of ListBox?
In method paintListBoxItem(),i only can insert string through g.drawText(“Example”…).

Thanks.


#9

You have to implement the paintListBoxItem method, because it is pure virtual. But you can leave it totally empty, because your custom component will do the drawing.
You can set the component by implementing virtual Component* ListBoxModel::refreshComponentForRow ( int rowNumber, bool isRowSelected, Component *existingComponentToUpdate );


#10

Hi @kennie; if the custom component you want to put inside ListBox rows is fixed height, then as @daniel said, you simply override ListBoxModel::refreshComponentForRow.

On the other hand if you want variable height rows, you will need to roll your own custom List class and having a look through /modules/juce_gui_basics/widgets/juce_ListBox.h & .cpp to see how it’s put together is a great place to start.

I never wrote a general purpose variable height ListBox class that would be re-usable outside my application. But here’s an outline of what I did:

  • created a custom ListRow class which inherits from Component and represents each row of my list
  • created a custom List class which inherits from Component with private members:
  • Viewport viewport;
  • Component viewedComponent;
  • OwnedArray<ListRow> rows;
  • and methods
  • void addRowComponent();
  • void positionListRows();
  • put viewedComponent into viewport using Viewport::setViewedComponent()
void List::addRowComponent (int selectedIndex, const String& name)
{
    viewedComponent.addAndMakeVisible (rows.insert (selectedIndex, new ListRow (*this, name)));
    positionListRows();
}

void List::positionListRows()
{
    int y = 0;
    int x = 0;
    int width = viewport.getWidth();

    for (int i = 0; i < rows.size(); i++)
        {
            int height = rows[i]->getHeight();
            rows[i]->setBounds(x, y, width, height);
            y += height;
        }
        
        viewedComponent.setSize (width, y);
    }

The ListRows get instantiated with a reference to the owner List, so that whenever a ListRow’s height changes it can call owner.positionListRows() to update the vertical position of all the other rows in the list (as well as the overall height of the viewedComponent).

Ideally my List class would only use enough ListRow objects to fill the visible area and recycle them as you scroll (this is what ListBox does) but I haven’t yet implemented that - my solution is currently a lot simpler (and less memory efficient).


#11

Thank you so much. :blush: