ListBox with custom components crashes


#1

Hi,

I have created a listbox with custom component(juce::TextEditor). Suppose the listbox contain rows more than it can display then in that case vertical scroll bar appears and then when it scroll it downward it working as expected but as soon as it scroll the vertically then the program crashes at the following line. I am not getting any clue why it is behaving so? Not getting where i am missing something.

juce_ListBox.cpp

  void update (const int row_, const bool selected_)
    {
        if (row != row_ || selected != selected_)
        {
            repaint();
            row = row_;
            selected = selected_;
        }

        if (owner.getModel() != 0)
        {
            Component* const customComp = owner.getModel()->refreshComponentForRow (row_, selected_, getChildComponent (0));

            if (customComp != 0)
            {
                addAndMakeVisible (customComp);
               [u][b][i] customComp->setBounds (getLocalBounds());[/i][/b][/u]

                for (int i = getNumChildComponents(); --i >= 0;)
                    if (getChildComponent (i) != customComp)
                        delete getChildComponent (i);
            }
            else
            {
                deleteAllChildren();
            }
        }
    }

then

juce_Component.cpp

void Component::addAndMakeVisible (Component* const child, int zOrder)
{
    if (child != 0)
    {
        child->setVisible (true);
        addChildComponent (child, zOrder);
    }
}

I have spend three hour debugging my code, but still unable to get the fix.

Thanks
Vivek


#2

seeing as you’ve not posted any of your code here, there’s not much to go on…


#3
class ListBoxComponent : public juce::ListBoxModel,
				      public juce::Component
{
public:
	ListBoxComponent();
	virtual ~ListBoxComponent();

private:
	virtual int	getNumRows();
	virtual void paintListBoxItem (int rowNumber, juce::Graphics& g, int width, int height, bool rowIsSelected);
	virtual juce::Component*	refreshComponentForRow (int rowNumber, bool isRowSelected, juce::Component* existingComponentToUpdate);
	void	AddRow();
	void	DeleteRow();

	juce::ListBox				*mListBox;
	std::vector<juce::TextEditor*>	mCustomComp;
};
ListBoxComponent::ListBoxComponent() : mListBox(0)
{
	mListBox = new juce::ListBox("New words list", this);
	mListBox->setRowHeight(22);
	mListBox->setBounds(20, 210, 250, 135);
	mListBox->setOutlineThickness(2);
	mListBox->setColour(juce::ListBox::outlineColourId, juce::Colours::lightgrey);
}

ListBoxComponent::~ListBoxComponent()
{
	this->deleteAllChildren();
	for(int i = mCustomComp.size(); i > 0; i--)
	{
		delete mCustomComp[i-1];
		mCustomComp.pop_back();
	}
}

int ListBoxComponent::getNumRows()
{
	return mCustomComp.size();
}

void ListBoxComponent::paintListBoxItem (int rowNumber, juce::Graphics& g, int width, int height, bool rowIsSelected)
{
}

juce::Component* ListBoxComponent::refreshComponentForRow(int rowNumber, bool isRowSelected, juce::Component* existingComponentToUpdate)
{
	if(rowNumber < mCustomComp.size())
		return (juce::Component *)mCustomComp[rowNumber];
	else
		return NULL;
}

void ListBoxComponent::AddRow()
{
	mCustomComp.push_back(new juce::TextEditor());
	mListBox->updateContent();
}

void ListBoxComponent::DeleteRow()
{
	if(mCustomComp.size() > 0)
	{
		delete mCustomComp[mCustomComp.size()-1];
		mCustomComp.pop_back();
		mListBox->updateContent();
	}
}

#4

You’re double-deleting your components.

For the millionth time, folks, GOOD C++ CODE NEVER USES THE DELETE OPERATOR. http://www.rawmaterialsoftware.com/wiki/index.php/Coding_Standards#Object_lifetime_and_ownership


#5

Hey…I’m using deleteAllChildren()…I have these generic panels to which other Components are added. The components are self-contained (i.e. handle all their own events and what not). I’m using deleteAllChildren in the panel destructor. It would be extra work to have to put scoped pointers to these objects in the panel’s class declaration.

Is this a valid usage?


#6

To be fair, I also found a few places in my code where deleteAllChildren() was the most efficient way to do the job, and where it meant I could avoid adding a whole bunch of extra pointers to the class, so I can’t really say you should never use it. But it does make it easy to mess-up like the example in this thread.


#7

I am downloaded the latest version of juce library and this time if I am using the same code then I am not getting any crashes while scrolling the vertical scroll-bar in upward direction.

But getting crash with the previous version of the juce library.

here I have added as many row so as to display the vertical scroll-bar, and scrolled it to the bottom extent.
[attachment=0]3.JPG[/attachment]

Now, at the instance i am trying to scrolled the vertical scrollbar in upward direction I am getting the crash.
[attachment=1]2.JPG[/attachment]


#8

Your code is badly broken and needs fixing, like I said above.


#9

I am very sorry for asking you so much questions. But I am getting problem in using juce when I wants to add custom components for the rows in the ListBox components.

I think I should explain what I am trying for getting this done.

  1. I have created a Dialog window.

  2. Added three components on it(ListBox, and two TextButton) and a std::vector for holding the custom component which I wants to add for the rows in the ListBox.

  3. Implemented the following two pure virtual function os TableListBoxModel class
    [color=#0000FF]Component* TestListBoxDialogComponents::refreshComponentForRow(int rowNumber, bool isRowSelected, juce::Component* existingComponentToUpdate)
    {
    if(rowNumber < mCustomComp.size())
    return (juce::Component *)mCustomComp[rowNumber];
    else
    return NULL;
    }[/color]

    and the second functions as
    [color=#BF0040]int TestListBoxDialogComponents::getNumRows()
    {
    return mCustomComp.size();
    }[/color]

  4. At the moment user click the add button.
    void TestListBoxDialogComponents::AddRow()
    [color=#0000FF] {
    mCustomComp.push_back(new CustomComponent(this));
    mRowCount = mCustomComp.size();
    mListBox->updateContent();
    }[/color]

  5. At the moment user delete button.
    [color=#0000FF] void TestListBoxDialogComponents::DeleteRow()
    {
    if(mCustomComp.size() > 0)
    {
    delete mCustomComp[mCustomComp.size()-1];
    mCustomComp.pop_back();

     mListBox->updateContent();
    

    }
    }[/color]

I am using the vector for holding for the holding the custom components. when the i click on the delete row then I delete the last custom components. and call the updateContent function.

All things are working fine, except when i scroll the vertical scroll-bar in upward direction I am getting a crash.
I will try to find my error in my code. But if possible please let me know is this is right way to add custom components in the row.


#10

No, it isn’t - let the ListBox manage the component lifetimes, don’t keep a list of them yourself, or any reference to them. Just create them when you’re asked to, return them, and let the ListBox look after them.


#11

[attachment=0]c.JPG[/attachment]

then also I am getting the same crash may be I am doing something wrong somewhere!.

Thank a lot for your time.


#12

How on earth is anyone supposed to understand what’s going wrong from that screenshot? You’re clearly using a junk pointer, but we’re not psychic, we can’t magically know what you’re doing wrong based on a piece of library code and the fact that it crashes!


#13

I’m a hobbyist psychic, so I’m just gonna assume that you’re still trying to keep a pointer on a component that is held in the ListBox and add it to another component although it might not exist anymore. Or something like that.

Again:
in TestListBoxDialogComponents::refreshComponentForRow(), return a new component or update the existing component, but don’t try to use a previous reference of your own.
ListBox (and before you ask, TreeViews too) are made to handle sensibly identical components, that could be easily updated so that an old component can be updated to display new information. There can be a lot of components, so ListBox handles the memory for you. You don’t want to keep in memory the 10000 components in your ListBox when only 10 are visible, do you?
If that behaviour doesn’t fit what you’re trying to do, create your own custom ListBox.


#14

yeah from the previous posts, I came to learn that ListBox manages all our custom components for efficient handling and separates the model from the UI.

When I am not keeping any reference to the custom component I have added in the list box and wants to delete any row from ListBox then I decrements current rowNumber of the ListBox by one and for adding a new I increment rowNumber by 1. But in this case the row appears the same with the same content. Also I am unable to get the content of all the rows.


#15

If your model changes, call updateContent() to make the tree update itself.


#16

I have tried to make a clean project from beginning so that I can know where I am getting stuck.

[b]/* File : MyApp.cpp */

#include “MainDialog.h”

class MyApp : public JUCEApplication
{
public:
MyApp(){}
~MyApp(){}
void initialise(const String& commandLine)
{
mMainDialog.centreWithSize(400, 400);
mMainDialog.setVisible(true);
}
void shutdown()
{
//deleteAndZero(mMainDialog);
}

const String getApplicationName()
{
    return "JUCE_TUTORIAL_PROJECT_1";
}

const String getApplicationVersion()
{
    return "0.0";
}

bool moreThanOneInstanceAllowed()
{
    return true;
}

void anotherInstanceStarted( const String& commandLine ) {}

private:
MainDialog mMainDialog;
};

START_JUCE_APPLICATION(MyApp)[/b]


[b]/* File : MainDialog.h */
#pragma once
#ifndef MAINDIALOG_H
#define MAINDIALOG_H

#include “juce.h”
#include “TestListBox.h”

class MainDialog : public DocumentWindow
{
public:
MainDialog();
~MainDialog();
ApplicationCommandManager commandManager;

private:
void closeButtonPressed();
TestListBox mContentComponent;
};

#endif /* End MAINDIALOG_H */[/b]


[b]
/ * File : MainDialog.cpp */
#include “MainDialog.h”

MainDialog::MainDialog() : DocumentWindow(“JUCE_TUTORIAL_PROJECT_2”, Colours::azure, DocumentWindow::allButtons, true),
mContentComponent()
{
setResizable(true, false);
setTitleBarHeight(20);
setContentComponent(&mContentComponent);
}

MainDialog::~MainDialog()
{
}

void MainDialog::closeButtonPressed()
{
JUCEApplication::getInstance()->systemRequestedQuit();
}[/b]


[b]
/* File : TestListBox.h */

#pragma once
#ifndef TESTLISTBOX_H
#define TESTLISTBOX_H

/* Includes */
#include “juce.h”
#include

/* Forward declaration. */
class CustomComponent;

class TestListBox : public juce::Component,
public juce::ListBoxModel,
public juce::ButtonListener
{
public:
TestListBox();
~TestListBox();

private:
virtual void resized();
virtual int getNumRows();
virtual void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected);
virtual void buttonClicked(juce::Button button);
virtual Component
refreshComponentForRow (int rowNumber, bool isRowSelected, juce::Component* existingComponentToUpdate);
void AddRow();
void DeleteRow();
void GetAllRowText();

public:
juce::ListBox mListBox;
juce::TextButton mAddRowButton;
juce::TextButton mDeleteRowButton;
juce::TextButton mTestButton;
int mRowCount;
};

#endif /* End TESTLISTBOX_H */ [/b]


[b]
/* File : TestListBox.cpp */

/* Includes */
#include “TestListBox.h”

TestListBox::TestListBox() : mRowCount(0),
mListBox(“New words list”, NULL),
mAddRowButton("+"),
mDeleteRowButton(L"—"), /* em dash (on win os ALT + 0151) */
mTestButton(“T”)

{
mListBox.setRowHeight(19);
this->addAndMakeVisible(&mListBox);
mListBox.setBounds(10, 10, 372, 300);
mListBox.setOutlineThickness(2);
mListBox.setColour(juce::ListBox::outlineColourId, juce::Colours::lightgrey);
mListBox.setModel(this);

/* mAddRowButton setup & initialization. */
this->addAndMakeVisible(&mAddRowButton);
mAddRowButton.setBounds(20, 340, 18, 18);
mAddRowButton.addListener(this);
mAddRowButton.setConnectedEdges(juce::Button::ConnectedOnLeft | juce::Button::ConnectedOnRight);

/* mDeleteRowButton setup & initialization. */
this->addAndMakeVisible(&mDeleteRowButton);
mDeleteRowButton.setBounds(38, 340, 18, 18);
mDeleteRowButton.addListener(this);
mDeleteRowButton.setConnectedEdges(juce::Button::ConnectedOnLeft | juce::Button::ConnectedOnRight);

/* mTestButton setup & initialization. */
this->addAndMakeVisible(&mTestButton);
mTestButton.setBounds(60, 340, 18, 18);
mTestButton.addListener(this);
mTestButton.setConnectedEdges(juce::Button::ConnectedOnLeft | juce::Button::ConnectedOnRight);

}

TestListBox::~TestListBox()
{
}

void TestListBox::resized()
{
}

int TestListBox::getNumRows()
{
return mRowCount;
}

void TestListBox::paintListBoxItem(int rowNumber, Graphics& g, int width, int height, bool rowIsSelected)
{
}

void TestListBox::buttonClicked(juce::Button *button)
{
if(button == &mAddRowButton)
{
AddRow();
}
else if(button == &mDeleteRowButton)
{
DeleteRow();
}
else if(button == &mTestButton)
{
GetAllRowText();
}
}

Component* TestListBox::refreshComponentForRow(int rowNumber, bool isRowSelected, juce::Component* existingComponentToUpdate)
{
if(rowNumber <= mRowCount)
{
TextEditor textEditor = (TextEditor) existingComponentToUpdate;
if(textEditor == 0)
textEditor = new TextEditor();

    return textEditor;
}
else
{
    //jassert(existingComponentToUpdate == 0);
    return 0;
}

}

void TestListBox::AddRow()
{
mRowCount++;
mListBox.updateContent();
}

void TestListBox::DeleteRow()
{
if(mRowCount > 0)
{
mRowCount–;
mListBox.updateContent();
}
}

void TestListBox::GetAllRowText()
{
int childCompCount = mListBox.getNumChildComponents();
juce::String str;
for(int i = 0; i < mRowCount; ++i)
{
TextEditor textEditor = (TextEditor)mListBox.getComponentForRowNumber(i);
str += textEditor->getText();
str += “\n”;
}
juce::AlertWindow::showMessageBox(juce::AlertWindow::WarningIcon, “ALL Words In List:”, str);
}[/b]


#17

My Doubts:

  1. [b] refreshComponentForRow(int rowNumber, bool isRowSelected, juce::Component* existingComponentToUpdate)[/b]
     How many times this functions will be called ifI have mRowCount = 5 (for example) when I call mListBox.updateModel().
    
  2. Is it possible to delete any particular row from the ListBox?

  3. When I decrements mRowCount by 1 and then calls mListBox.updateModel(). Although is not displaying in UI but the model is still not delete by this ListBox. What I have to do if I wants to delete the component for that row.

  4. When I am calling this function after adding 20 rows. Still I am getting childCompCount value as 1.
    int childCompCount = mListBox.getNumChildComponents();
    What I can guess is I am missing somethings in "refreshComponentForRow" method.


#18

It seems the custom components for listboxes is an area of frequent confusion for Juce users…


#19

I think I am missing something in using custom components in ListBox. There is no problem with list box. I my fault that i am not getting it!


#20

If you really must dump huge amounts of code on the forum and expect us to read it, please at least use the ‘code’ tag to make it readable!

It makes no sense to just leave a bunch of texteditors floating around - they may be deleted at any moment, losing whatever is in them, or may be assigned to a different row, which would obviously mean they were showing the wrong content. The refreshComponentForRow is the place where you’re supposed to update the component to reflect its new row, so that’s where you’d need to set the editor’s content to whatever text is in that row.

True. I wonder how I could make it less confusing? It seems that the main confusion is that people expect to actually be adding and removing components themselves, and assume that there’s a one-to-one mapping between a row and a component.