First Component

Having some trouble getting my first component up and running. The component is a series of 9 digits implemented as labels.

class VFOComponent : public Component
contains a grid of 1x9 into which labels are added.
This is then just ‘addAndMakeVisible’ on my main component to test it.

I get the component painted as its a different background color but not the labels. I presume the grid is not getting added for some reason. I’ve looked at all the demo code but can’t see what I’m doing wrong. This is just test code to figure out what I’m doing. The demos seem to be creating the grid in the resize() which I don’t understand although I did try it to no effect. Do I only need to add the digits to the grid or do I need to do something else to make them visible? I assume ‘performLayout’ actually sets the layout into the container. I didn’t see any other methods being called in the demos.

VFOComponent::VFOComponent(int p_vfo_type, int p_vfo_id, int x, int y, int w, int h)
{
	// Local vars
	vfo_type = p_vfo_type;
	vfo_id = p_vfo_id;

	// Bounds given by caller to position within callers container
	setBounds(x, y, w, h);

	// Add digits to the grid
	grid = new Grid();
	add_digits(grid);
	grid->performLayout(getLocalBounds());
}
    void VFOComponent::add_digits(Grid *thegrid) {

    	// Create digits
    	d_100MHz = new VFODigit( String("0"), MHZ_COLOR, MHZ_FONT );
    	d_10MHz = new VFODigit(String("0"), MHZ_COLOR, MHZ_FONT);
    	d_1MHz = new VFODigit(String("0"), MHZ_COLOR, MHZ_FONT);
    	d_100KHz = new VFODigit(String("0"), KHZ_COLOR, KHZ_FONT);
    	d_10KHz = new VFODigit(String("0"), KHZ_COLOR, KHZ_FONT);
    	d_1KHz = new VFODigit(String("0"), KHZ_COLOR, KHZ_FONT);
    	d_100Hz = new VFODigit(String("0"), HZ_COLOR, HZ_FONT);
    	d_10Hz = new VFODigit(String("0"), HZ_COLOR, HZ_FONT);
    	d_1Hz = new VFODigit(String("0"), HZ_COLOR, HZ_FONT);

    	// Add digits in one horizontal row
    	using Track = Grid::TrackInfo;
    	thegrid->templateRows = { Track(1_fr) };
    	thegrid->templateColumns = { Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr) };
    	
    	thegrid->items = { GridItem(d_100MHz), GridItem(d_10MHz), GridItem(d_1MHz), GridItem(d_100KHz), GridItem(d_10KHz), GridItem(d_1KHz), GridItem(d_100Hz), GridItem(d_10Hz), GridItem(d_1Hz) };
    }

    VFODigit::VFODigit(String text, Colour colour, float size) {
    	// Create a digit ready to add to the grid
    	setText(text, dontSendNotification);
    	setJustificationType(Justification::centred);
    	setColour(Label::textColourId, colour);
    	setFont(Font(size, Font::bold));
    };

You cannot do layout operations in the constructor, since there are no bounds at that point. Most of the time you want to do this in resized(), as it is called whenever size changes (including when it is initially set).

I’ve moved that into resize but as I mentioned still does not work. I don’t think it can be right that everything has to be recreated on every resize but anyway it does not fix the problem.

I haven’t used Grid, but a quick read of the docs seems to indicate that it is a layout tool, but I think the Components which are being controlled by the Grid still need to be added as children, and made visible, to the parent Component.

As well, it isn’t clear from the docs but it seems like you may need to also set the size of the children components (unless the stretched option is used).

I haven’t seen in your code any mention of addAndMakeVisible(), which is used to add children to the hierarchy, so they can get painted…
see addAndMakeVisible()

I’ve used grid a lot in Qt but this seems entirely different. So I’ve made these changes but still it does not show anything except a darkgrey background. I was using addAndMakeVisible but only to place the VFOComponent in the MainComponent.

void VFOComponent::resized()
{
	// This is called when the VFOComponent is resized.
	// If you add any child components, this is where you should
	// update their positions.
	Grid grid;
	add_digits(grid);
	grid.performLayout(getLocalBounds());
}

//==============================================================================
// Private
void VFOComponent::add_digits(Grid thegrid) {

	// Create digits
	d_100MHz = new VFODigit( String("0"), MHZ_COLOR, MHZ_FONT );
	addAndMakeVisible(d_100MHz);
	d_10MHz = new VFODigit(String("0"), MHZ_COLOR, MHZ_FONT);
	addAndMakeVisible(d_10MHz);
	d_1MHz = new VFODigit(String("0"), MHZ_COLOR, MHZ_FONT);
	addAndMakeVisible(d_1MHz);
	d_100KHz = new VFODigit(String("0"), KHZ_COLOR, KHZ_FONT);
	addAndMakeVisible(d_100KHz);
	d_10KHz = new VFODigit(String("0"), KHZ_COLOR, KHZ_FONT);
	addAndMakeVisible(d_10KHz);
	d_1KHz = new VFODigit(String("0"), KHZ_COLOR, KHZ_FONT);
	addAndMakeVisible(d_1KHz);
	d_100Hz = new VFODigit(String("0"), HZ_COLOR, HZ_FONT);
	addAndMakeVisible(d_100Hz);
	d_10Hz = new VFODigit(String("0"), HZ_COLOR, HZ_FONT);
	addAndMakeVisible(d_10Hz);
	d_1Hz = new VFODigit(String("0"), HZ_COLOR, HZ_FONT);
	addAndMakeVisible(d_1Hz);

	// Add digits in one horizontal row
	using Track = Grid::TrackInfo;
	thegrid.templateRows = { Track(1_fr) };
	thegrid.templateColumns = { Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr) };
	
	thegrid.items = { GridItem(d_100MHz), GridItem(d_10MHz), GridItem(d_1MHz), GridItem(d_100KHz), GridItem(d_10KHz), GridItem(d_1KHz), GridItem(d_100Hz), GridItem(d_10Hz), GridItem(d_1Hz) };
   Grid::JustifyItems::stretch;
}

So your VFODigit, aren’t they Components as well? In which case you would need to call addAndMakeVisible for those as well, to make them children of the VFOComponent…

IIRC in QT this step was not necessary, since you had to provide the parent QWidget upon creation (it’s been 10 years since I used QT the last time)

Again, I’m not a Grid user, so I am just trying to help by reading the docs. So, maybe you need to tell each GridItem that it should stretch it’s contents?

As well, you don’t want to be creating and adding the VFODigits in resized, that should happen in the constructor. only the layout should be happening in resized.

We usually keep Grid locally in the stack of SomeComponent::resized(). The important part that you are missing is then to call grid.performLayout(getLocalBounds()). And yes the components need to be added and made visible before.

Thanks for the help, much appreciated. I think there is a bit of confusion here. If you look at the modified code I am calling addAndMakeVisible for each VFODigit and I am calling grid.performLayout(getLocalBounds()). Also cpr says build in the constructor and luzifer says create a local grid in resized() which means it has to be built in resized(). I did try splitting it and it just crashed for unknown reason. I think it might be to do with stretch. I did add Grid::JustifyItems::stretch; but how do I do that for each item.

You are passing the grid by value. This way you are only adding the grid items to the local copy, which gets destroyed at the end of the function.You should pass by reference.

Also, that line does nothing.
It should be:
theGrid.justifyItems = Grid::JustifyItems::stretch;

(which is the default anyway)

Slight progress. Yes, I didn’t understand why Grid was a local but that’s how all the demo code is. I’ve put it back to a pointer and added/fixed a few things. If I try and prep the grid in the constructor and than perform Layout in resized it just crashes so doing it all in resized. So with the few changes below I get 9 blue stripes with 10px gaps. So the grid is there but the VFODigit components are not. If I take the g.fillAll out I’m back to a lightgrey box.

thegrid->justifyItems = Grid::JustifyItems::stretch;
thegrid->rowGap = Grid::Px::Px(10.0f);
thegrid->columnGap = Grid::Px::Px(10.0f);

void VFODigit::paint(Graphics& g) {
	g.fillAll(Colours::blue);
};

If you don’t change it to use a pointer but instead to a reference, you have the compiler checking, that it can only be called with a valid Grid instance:

void VFOComponent::add_digits(Grid* thegrid);  // can be called with nullptr
void VFOComponent::add_digits(Grid& thegrid);  // can only be called with a valid Grid

The second way still references the original, but you go back to use . instead of ->

Just a side note about modern style…

For your paint problem, what type is VFODigit? (i.e. what does it inherit?)
Did you override the paint()? If so, can you post the function?
Is MHZ_COLOR what you expect it to be? i.e. an opaque colour?

Please post a full example, so we can see it all in context. It’s hard to help with these snippets.

1 Like

There is quite a lot of code. I am struggling with syntax a bit as well which isn’t helping. I think/hope I’ve put in all the recommendations. I started with the GUI template and have left that as is and just added:

MainComponent::MainComponent()
{
    setSize (600, 400);
	addAndMakeVisible(new VFOComponent(0,0,0,0,getWidth(),getHeight()));
}

Here is the vfo.h file.

//==============================================================================
// Defines
#define MHZ_FONT 30.0f
#define KHZ_FONT 30.0f
#define HZ_FONT 20.0f
#define MHZ_COLOR Colours::white
#define KHZ_COLOR Colours::white
#define HZ_COLOR Colours::orange
#define RX 0
#define TX 1

/*
	A VFO Digit component
*/
class VFODigit : public Label
{
public:
	//==============================================================================
	VFODigit(String text, Colour colour, float size);
	~VFODigit();

	//==============================================================================
	void paint(Graphics&) override;
	void resized() override;

private:
	//==============================================================================
	// State variables

	//==============================================================================
	// Method prototypes

	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VFODigit)
};

//==============================================================================
/*
	Multi-instance VFO component
*/
class VFOComponent : public Component
{
public:
	//==============================================================================
	VFOComponent(int p_vfo_type, int p_vfo_id, int x, int y, int w, int h);
	~VFOComponent();

	//==============================================================================
	void paint(Graphics&) override;
	void resized() override;

private:
	//==============================================================================
	// State variables
	int vfo_type = RX;		// RX|TX
	int vfo_id = 0;			// Numerical id of VFO instance
	int freq_int = 0;		// Last frequence increment in MHz
	int current_freq = 0;	// Current frequency in MHz

	// Map for lookup of increments
	std::map<String, float> freq_inc_map {
		{String("100MHz"), 100.0f},
		{String("10MHz"), 10.0f},
		{String("1MHz"), 1.0f},
		{String("100KHz"), 0.1f},
		{String("10KHz"), 0.01f},
		{String("1KH"), 0.001f},
		{String("100Hz"), 0.0001f},
		{String("10Hz"), 0.00001f},
		{String("1H"), 0.000001f},
	};

	// Grid to hold VFO digits
	Grid grid;
	Grid& ref_grid = grid;

	// Digits
	VFODigit *d_100MHz;
	VFODigit *d_10MHz;
	VFODigit *d_1MHz;
	VFODigit *d_100KHz;
	VFODigit *d_10KHz;
	VFODigit *d_1KHz;
	VFODigit *d_100Hz;
	VFODigit *d_10Hz;
	VFODigit *d_1Hz;


	//==============================================================================
	// Method prototypes
	void add_digits(Grid &grid);

	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VFOComponent)
};

and the vfo.cpp file:

//==============================================================================
// Constructor/Destructor
VFOComponent::VFOComponent(int p_vfo_type, int p_vfo_id, int x, int y, int w, int h)
{
	// Local vars
	vfo_type = p_vfo_type;
	vfo_id = p_vfo_id;

	// Bounds given by caller to position within callers container
	setBounds(x, y, w, h);
}

VFOComponent::~VFOComponent()
{
}

//==============================================================================
// GUI Events
void VFOComponent::paint(Graphics& g)
{
	// (Our component is opaque, so we must completely fill the background with a solid colour)
	g.fillAll(Colours::darkgrey);
}

void VFOComponent::resized()
{
	// This is called when the VFOComponent is resized.
	add_digits(ref_grid);
	grid.performLayout(getLocalBounds());
}

//==============================================================================
// Private
void VFOComponent::add_digits(Grid &thegrid) {

	// Create digits
	d_100MHz = new VFODigit( String("0"), MHZ_COLOR, MHZ_FONT );
	addAndMakeVisible(d_100MHz);
	d_10MHz = new VFODigit(String("0"), MHZ_COLOR, MHZ_FONT);
	addAndMakeVisible(d_10MHz);
	d_1MHz = new VFODigit(String("0"), MHZ_COLOR, MHZ_FONT);
	addAndMakeVisible(d_1MHz);
	d_100KHz = new VFODigit(String("0"), KHZ_COLOR, KHZ_FONT);
	addAndMakeVisible(d_100KHz);
	d_10KHz = new VFODigit(String("0"), KHZ_COLOR, KHZ_FONT);
	addAndMakeVisible(d_10KHz);
	d_1KHz = new VFODigit(String("0"), KHZ_COLOR, KHZ_FONT);
	addAndMakeVisible(d_1KHz);
	d_100Hz = new VFODigit(String("0"), HZ_COLOR, HZ_FONT);
	addAndMakeVisible(d_100Hz);
	d_10Hz = new VFODigit(String("0"), HZ_COLOR, HZ_FONT);
	addAndMakeVisible(d_10Hz);
	d_1Hz = new VFODigit(String("0"), HZ_COLOR, HZ_FONT);
	addAndMakeVisible(d_1Hz);

	// Add digits in one horizontal row
	using Track = Grid::TrackInfo;
	
	thegrid.templateRows = { Track(1_fr) };
	thegrid.templateColumns = { Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr), Track(1_fr) };
	
	thegrid.items = { 
		GridItem(d_100MHz),
		GridItem(d_10MHz), 
		GridItem(d_1MHz), 
		GridItem(d_100KHz), 
		GridItem(d_10KHz), 
		GridItem(d_1KHz), 
		GridItem(d_100Hz), 
		GridItem(d_10Hz), 
		GridItem(d_1Hz) 
	};

	thegrid.justifyItems = Grid::JustifyItems::stretch;
	thegrid.rowGap = Grid::Px::Px(10.0f);
	thegrid.columnGap = Grid::Px::Px(10.0f);
}

//==============================================================================
// A VFO Digit
//==============================================================================

VFODigit::VFODigit(String text, Colour colour, float size) {
	
	// Create a digit ready to add to the grid
	setText(text, dontSendNotification);
	setJustificationType(Justification::centred);
	setColour(Label::textColourId, colour);
	setFont(Font(size, Font::bold));
};

VFODigit::~VFODigit() {

}

void VFODigit::paint(Graphics& g) {
	g.fillAll(Colours::blue);
};

void VFODigit::resized() {

};

A few quick observations:

The digits components should be created and addAndMakeVisible()'d in the VFOComponent constructor and then resized later in its resized method. You don’t want to recreate these everytime someone resizes the window, just reposition them.

If VFOComponent is the top level (root) component of your UI calling setBounds() in the constructor is fine, if this is sub component of some other component then that parent’s resize will call it’s setBounds(). In your case that parent class is MainComponent, so perhaps having MainComponent::resize() call setBounds() for VFOComponent would be clearer for you.

From scanning the Grid class, this isn’t an actual component Container afaik, just a utility to help you position other components, this is probably why passing by value in the example isn’t a massive problem as it’s just a bag of behaviour for the resize operation.

In terms of the UI heirarchy, you add the VFODigits to the VFOComponent not to the grid.

HTH,
Dave

Thanks, but something is really wrong here. I totally agree the digits should be created in the constructor. If I do that it no longer even paints the digit background. This is the first component of many and its only in the MainComponent for testing. Its way short of finished, lots of mouse behavior to add, value updates etc. The digits are added to the VFOComponent, Grid has no addAndMakeVisible method.

The grid is really only to call setBounds on the components it manages.

A Component has child components. They are created in the constructor of the parent and added to the paint hierarchy via addAndMakeVisible. Then in resized() the bounds of each child component are adapted according to the parent’s bounds.

If you are new to JUCE, I would suggest to do that once with fixed numbers, just to see it in action.

Now you can replace that resized() method with the code that is adding all the items to be managed and at the end call performLayout(), which will only call setBounds on each child component, that’s all the magic…

Hope that makes it a bit clearer

I’m learning, but not there yet. Yes its my first JUCE program. I found the reason for the crash when I put the create in the constructor and the layout in resized. The template puts
setSize (600, 400); in the MainComponent constructor. This, for a reason I do not understand causes resized to be called before the constructor completes and because at that point my VFODigits are not created it crashes. I’ve just removed it for now and let the size default.
Second I’ve got two methods now, layout_digits_with_bounds() and layout_digits_in_grid(). When I use bounds directly I get exactly the same result so the problem is nothing to do with grid but something to do with VFODigit but I’m at a loss to know what.