Adding Column to TableListBox table


#1

Hi,
I’m having trouble adding columns to my TableListBox table.

addAndMakeVisible(table);
	font.setHeight(14.0);
	table.setModel(this);
	table.setColour(ListBox::outlineColourId, Colours::grey);
	table.setHeaderHeight(1);
	table.setOutlineThickness(1);
	table.getHeader().addColumn("Col_1", 1, 25);
	table.getHeader().addColumn("Col_2", 2, 50);
	table.getHeader().addColumn("Col_3", 3, 25);
	table.getHeader().addColumn("Col_4", 4, 500);  /*causes read access violation in isEmpty()*/

Adding a 4th column (Col_4) will still compile, but if I scroll while the mouse is over the table area then the program throws an exception. Is there a limit to the number of columns a table can have? I have no idea what could be causing this issue…


#2

Maybe your model doesn’t handle the amount of columns properly?


#4

Thank you for your response Xenakios,
How can I determine if my model is handling the amount of columns properly? The only call I make is table.setModel(this); but I have not overridden this function so I cannot control how it is handling the columns. I was under the impression that if I wanted to add another column I just had to add another column to the header as I have done. Is there any other requirement for TableListBox that I need to set in order for it to handle all columns correctly?


#5

It’s impossible to say since you didn’t show the code for your model class. Which would appear to be the same class that is holding the TableListBox component since you do the setModel(this); call…


#6

Yes you are right. The code I showed in my first post is in the constructor of my TableListBoxModel class. The table is a TableListBox. Here is the entire code:

#include "../JuceLibraryCode/JuceHeader.h"
#include "ControlTrackTable.h"


ControlTrackTable::ControlTrackTable()
{
	addAndMakeVisible(table);
	font.setHeight(14.0);
	table.setModel(this);
	table.setColour(ListBox::outlineColourId, Colours::grey);
	table.setHeaderHeight(1);
	table.setOutlineThickness(1);
	
	table.getHeader().addColumn("Col_1", 1, 25);
	table.getHeader().addColumn("Col_2", 2, 50);
	table.getHeader().addColumn("Col_3", 3, 25);
	table.getHeader().addColumn("Col_4", 4, 500);

	m_isPlaying = false;
	m_selectedTrack = -1;
	m_lastPlayingRow = -1;
}


ControlTrackTable::~ControlTrackTable()
{
}

void ControlTrackTable::setTrackName(int rowNumber, String text)
{
	stateInfo.m_TrackName[rowNumber] = text;	
}


void ControlTrackTable::setTrackDescription(int rowNumber, String text)
{
	stateInfo.m_TrackDescription[rowNumber] = text;
}


void ControlTrackTable::setTrackLoadState(int rowNumber, int state)
{
	stateInfo.m_TrackLoadState[rowNumber] = state;
}


void ControlTrackTable::setTrackType(int rowNumber, int type)
{
	stateInfo.m_TrackType[rowNumber] = type;
}


void ControlTrackTable::setTrackFilePath(int rowNumber, String filePath)
{
	stateInfo.m_TrackFilePath[rowNumber] = filePath;
}


String ControlTrackTable::getTrackName(int rowNumber)
{
	return stateInfo.m_TrackName[rowNumber];
}
String ControlTrackTable::getTrackDescription(int rowNumber)
{
	return stateInfo.m_TrackDescription[rowNumber];
}
int ControlTrackTable::getTrackLoadState(int rowNumber)
{
	return stateInfo.m_TrackLoadState[rowNumber];
}
int ControlTrackTable::getTrackType(int rowNumber)
{
	return stateInfo.m_TrackType[rowNumber];
}
String ControlTrackTable::getTrackFilePath(int rowNumber)
{
	return stateInfo.m_TrackFilePath[rowNumber];
}


int ControlTrackTable::getNumRows()
{
	return StateInfo::NUM_TRACKS;
}


void ControlTrackTable::paintRowBackground(Graphics& g, int rowNumber, int width, int, bool rowIsSelected)
{
	if (rowIsSelected)
	{
		g.fillAll(Colours::lightblue);
		m_selectedTrack = rowNumber;
	}
	else
	{
		g.fillAll(Colours::lightgrey);
	}

	if (rowNumber != 0)
	{
		g.drawLine(0.0, 0.0, float(width), 0);
	}	
}


void ControlTrackTable::paintCell(Graphics& g, int rowNumber, int columnId, int width, int height, bool /*rowIsSelected*/)
{
	g.setColour(Colours::black);
	g.setFont(font);

	if (columnId == 1)
	{
		if (m_isPlaying && m_lastPlayingRow == rowNumber)
		{
			Path triangle;
			float l = 12.0; float h = 10.0;
			float x1 = 5.0; float y1 = 5.0;
			triangle.addTriangle(x1+1, y1+1, x1+1, y1+1 + l, x1+1 + h, y1+1 + (l/2));
			g.fillPath(triangle);
			
			Path triangle2;
			triangle2.addTriangle(x1, y1, x1, y1 + l, x1 + h, y1 + (l/2));
			g.setColour(Colours::green);
			g.fillPath(triangle2);
		}
		else
		{
			Rectangle<float> rect(5.0, 6.0, 10.0, 10.0);
			if (stateInfo.m_TrackLoadState[rowNumber] == StateInfo::RED)
			{
				g.drawEllipse(rect, 1.0);
				g.setColour(Colours::red);
				g.fillEllipse(rect);
			}
			else if (stateInfo.m_TrackLoadState[rowNumber] == StateInfo::GREEN)
			{
				g.drawEllipse(rect, 1.0);
				g.setColour(Colours::green);
				g.fillEllipse(rect);
			}
		}
	}
	else if (columnId == 2)
	{
		g.setColour(Colours::grey);
		if (stateInfo.m_TrackType[rowNumber] == StateInfo::AUDIO
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::RealTrack
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::RealDrums
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::Loop
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::RealDrumsLoop
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::ArtisticPerformanceTrack
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::UserTracks
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::Combo)
		{
			g.setColour(Colours::green);
		}
		else if (stateInfo.m_TrackType[rowNumber] == StateInfo::MIDI
		     ||	 stateInfo.m_TrackType[rowNumber] == StateInfo::MIDICUSTOM
		     ||	 stateInfo.m_TrackType[rowNumber] == StateInfo::MIDISuperTrack)
		{
			g.setColour(Colours::yellow);
		}
		g.drawText(stateInfo.m_TrackName[rowNumber], 2, 0, width - 4, height, Justification::centredLeft, true);
	}
	else if (columnId == 4)
	{
		g.setColour(Colours::grey);
		if (stateInfo.m_TrackType[rowNumber] == StateInfo::AUDIO
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::RealTrack
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::RealDrums
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::Loop
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::RealDrumsLoop
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::ArtisticPerformanceTrack
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::UserTracks
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::Combo)
		{
			g.setColour(Colours::green);
		}
		else if (stateInfo.m_TrackType[rowNumber] == StateInfo::MIDI
		     ||	 stateInfo.m_TrackType[rowNumber] == StateInfo::MIDICUSTOM
		     ||	 stateInfo.m_TrackType[rowNumber] == StateInfo::MIDISuperTrack)
		{
			g.setColour(Colours::yellow);
		}
		g.drawText(stateInfo.m_TrackDescription[rowNumber], 2, 0, width - 4, height, Justification::centredLeft, true);
	}
}

void ControlTrackTable::resized()
{
	table.setBounds(0, 0, getWidth(), getHeight());
}


var ControlTrackTable::getDragSourceDescription(const SparseSet<int>& selectedRows)
{
	if (!selectedRows.isEmpty())
	{
		for (int i = 0; i < 8; i++)
		{
			if (selectedRows.contains(i) && stateInfo.m_TrackLoadState[i] == StateInfo::GREEN)
			{
				DragAndDropContainer * dragContainer = DragAndDropContainer::findParentDragContainerFor(this);
				StringArray str(stateInfo.m_TrackFilePath[i]);
				dragContainer->performExternalDragDropOfFiles(str, false);
				return dragContainer;
			}
		}
		return var();
	}
	else
	{
		return var();
	}
}


void ControlTrackTable::cellDoubleClicked(int rowNumber, int columnId, const MouseEvent&)
{
    if (trackIsSetToPlay(rowNumber))
	{
		if (m_isPlaying && m_lastPlayingRow == rowNumber)
			stopTrack(rowNumber);
		else
		{
			playTrack(rowNumber);
		}
	}
}

bool ControlTrackTable::trackIsSetToPlay(int rowNumber)
{
	if (rowNumber > -1 && rowNumber < StateInfo::NUM_TRACKS)
	{
		if (stateInfo.m_TrackLoadState[rowNumber] == StateInfo::GREEN 
	    && (stateInfo.m_TrackType[rowNumber] == StateInfo::AUDIO
	    || 	stateInfo.m_TrackType[rowNumber] == StateInfo::RealTrack
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::RealDrums
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::Loop
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::RealDrumsLoop
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::ArtisticPerformanceTrack
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::UserTracks
		|| 	stateInfo.m_TrackType[rowNumber] == StateInfo::Combo))
		{
			return true;
		}
		else
		{
			AlertWindow::showMessageBox(AlertWindow::NoIcon, "Audio playback:", "This track can't be played");
			return false;
		}
	}
	else {
		AlertWindow::showMessageBox(AlertWindow::NoIcon, "Audio playback:", "No track is selected");
		return false;
	}
}

void ControlTrackTable::playTrack(int rowNumber)
{
	audioPlayer.loadAudioFile(File(stateInfo.m_TrackFilePath[rowNumber]));
	audioPlayer.play();
	m_isPlaying = true;
	m_lastPlayingRow = rowNumber;
	repaint();
}

void ControlTrackTable::stopTrack(int rowNumber)
{
	audioPlayer.stop();
	m_isPlaying = false;
	repaint();
}

#7

The code is too long to go through completely, so I will just guess you have something in it that tries accessing a non existing array element or something like that. Look closely in the debugger what chain of function calls with what parameters causes the crash or assert.

edit : Just out of curiosity I checked here with some simple test code that there is no weird lowish limit for the number of columns. There isn’t, for example 1000 columns with 100000 rows works.


#8

You were absolutely correct! There is an issue with one of my array elements which was causing the program to crash.

else if (columnId == 2 || columnId == 4)
{
	g.setColour(Colours::grey);
	if(trackIsAudio(stateInfo.m_TrackType[rowNumber]))
		g.setColour(Colours::green);
	else if (trackIsMidi(stateInfo.m_TrackType[rowNumber]))
		g.setColour(Colours::yellow);
		
	if (columnId == 2) 
		g.drawText(stateInfo.m_TrackName[rowNumber], 2, 0, width - 4, height, Justification::centredLeft, true);
	else if (columnId == 4)	
		g.drawText(stateInfo.m_TrackDescription[rowNumber], 2, 0, width - 4, height, Justification::centredLeft, true); /*Problem line*/
}

The problem is with the last line that draws text. stateInfo.m_TrackDescription[rowNumber] is not being accepted as a valid string but when I print the line out to make sure it is not empty it contains the correct string every time. I am not sure exactly why it would cause the program to break then? Do I need to convert it to a standard string before passing it to g.drawText()? I am sure that the string is not empty because it prints as expected every single time…


#9

When you access invalid/out of bounds array elements, anything can happen, including things appearing to work under some circumstances. Are you absolutely sure you have sized the stateInfo.m_TrackDescription array to a correct size before trying to access the problem element? What type of strings are stored in that anyway? If those are something like char* “strings”, have you ensured they have been initialized properly?

edit : if they are char* or const char* strings, and they may contain non-ASCII characters encoded as UTF-8 you must not directly use them to initialize Juce Strings but instead do it like : String(CharPointer_UTF8(thestring))

So, in your draw code :
g.drawText(String(CharPointer_UTF8(stateInfo.m_TrackDescription[rowNumber])) …

Regarding std::string, you should not use those anywhere in your Juce based code unless absolutely necessary because of some 3rd party code.