Page 1 of 1

Layout inside control/component

Posted: Wed Mar 18, 2009 1:27 pm
by JohnD
In the past when developing my own components, I've had problems getting sizers to work. They work fine in my TopFrame to lay out my controls, but when I try and use Sliders internally in my components, things just go wrong.

Is there some simple rule to using sliders inside a component I may have missed? Say I have MyWindow : wxWindow, would the top level slider inside MyWindow be attached to this, or to some intermediate window/frame?

Posted: Wed Mar 18, 2009 1:43 pm
by Auria
You might need to post code, and screenshots of how they're wrong

Posted: Wed Mar 18, 2009 2:01 pm
by JohnD
I since hard-coded positions in the last lot I tried, but will do when I try again. IIRC, what happened was the sizer seemed tho be totally ignored... all the controls inside my component would be placed on top of each other, nothing resized, etc.

Posted: Wed Mar 18, 2009 5:29 pm
by Auria
JohnD wrote:I since hard-coded positions in the last lot I tried, but will do when I try again. IIRC, what happened was the sizer seemed tho be totally ignored... all the controls inside my component would be placed on top of each other, nothing resized, etc.
This usually happens when there is a parenting issue (like adding components to the frame and then adding the sizer to the panel), or you plain forgot to do ->SetSizer() or ->Add() components to the sizer.

Posted: Mon Mar 23, 2009 1:58 pm
by JohnD
So I'm trying this on a new component now and get the same problem. Let me paste some code:

First, in the TopFrame constructor, I set up my primary layout sizer:

Code: Select all

wxBoxSizer *topLevelSizerV = new wxBoxSizer(wxVERTICAL);
wxWindow *topLevelPanel = new wxPanel(this);
Many child sizers and standard wx controls are added, without problems... sizing the main window even works :)

I now add the code to add an instance of my new control, a thin bar which should stretch the entire width of the main window:

Code: Select all

topLevelSizerV->Add(new TimelineWindow(topLevelPanel));
This class is as below:

Code: Select all

class TimelineWindow : public wxWindow
{
/* Needed for wxWidgets */
DECLARE_CLASS(TimelineWindow)

public:
	TimelineWindow( wxWindow* parent, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize);

protected:
	DECLARE_EVENT_TABLE()
};

TimelineWindow::TimelineWindow( wxWindow* parent, const wxPoint &pos, const wxSize &size)
	: wxWindow(parent, wxID_ANY, pos, size,wxBORDER_SUNKEN)
{
	wxWindow *topLevelPanel = new wxPanel(this);
	wxBoxSizer *topLevelSizer = new wxBoxSizer(wxHORIZONTAL);

	wxButton *startButton = new wxButton(this,wxID_ANY,"Start");
	wxButton *pauseButton = new wxButton(this,wxID_ANY,"Pause");
	wxButton *stopButton = new wxButton(this,wxID_ANY,"Stop");

	topLevelSizer->Add(startButton,1,wxEXPAND);
	topLevelSizer->AddStretchSpacer();
	topLevelSizer->Add(pauseButton,1,wxEXPAND);
	topLevelSizer->AddStretchSpacer();
	topLevelSizer->Add(stopButton,1,wxEXPAND);

	topLevelPanel->SetSizer(topLevelSizer);
	topLevelSizer->SetSizeHints(this);
}
So note my control internally creates a horizontal sizer and a intermediary panel to attach the buttons to. The result is shown in the attached image:
Image
The control doesn't expand to fit the whoe top-frame's width, and the buttons certainly are not following any layout properly.

What am I missing?

Posted: Mon Mar 23, 2009 4:12 pm
by doublemax

Code: Select all

        wxButton *startButton = new wxButton(this,wxID_ANY,"Start");
        wxButton *pauseButton = new wxButton(this,wxID_ANY,"Pause");
        wxButton *stopButton = new wxButton(this,wxID_ANY,"Stop");
i think these should have topLevelPanel as parent

Posted: Mon Mar 23, 2009 4:24 pm
by JohnD
Good catch, but making that change makes things even worse. The control is the same size as before (about 40% as wide as the main window) but now the buttons aren't just on top of each other, they're invisible/missing!

Posted: Mon Mar 23, 2009 4:41 pm
by doublemax
How about this?

Code: Select all

class TimelineWindow : public wxPanel
{
/* Needed for wxWidgets */
DECLARE_CLASS(TimelineWindow)

public:
        TimelineWindow( wxWindow* parent, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize);

protected:
        DECLARE_EVENT_TABLE()
};

TimelineWindow::TimelineWindow( wxWindow* parent, const wxPoint &pos, const wxSize &size)
        : wxPanel(parent, wxID_ANY, pos, size,wxBORDER_SUNKEN)
{
        wxBoxSizer *topLevelSizer = new wxBoxSizer(wxHORIZONTAL);

        wxButton *startButton = new wxButton(this,wxID_ANY,"Start");
        wxButton *pauseButton = new wxButton(this,wxID_ANY,"Pause");
        wxButton *stopButton = new wxButton(this,wxID_ANY,"Stop");

        topLevelSizer->Add(startButton,1,wxEXPAND);
        topLevelSizer->AddStretchSpacer();
        topLevelSizer->Add(pauseButton,1,wxEXPAND);
        topLevelSizer->AddStretchSpacer();
        topLevelSizer->Add(stopButton,1,wxEXPAND);

        this->SetSizer(topLevelSizer);
        this->SetAutoLayout(true);

        topLevelSizer->SetSizeHints(this);
} 

Posted: Mon Mar 23, 2009 6:49 pm
by JohnD
Still not quite right:

Code: Select all

TimelineWindow::TimelineWindow( wxWindow* parent, const wxPoint &pos, const wxSize &size)
	: wxWindow(parent, wxID_ANY, pos, size,wxBORDER_SUNKEN)
{
	wxBoxSizer *topLevelSizer = new wxBoxSizer(wxHORIZONTAL);
	wxButton *btnRestart = new wxButton(this,ID_RESTART,"New procedure");
	wxButton *btnNext = new wxButton(this,ID_NEXT,"Next Step");

	topLevelSizer->Add(btnRestart,1,wxEXPAND);
	topLevelSizer->AddStretchSpacer();
	topLevelSizer->Add(btnNext,1,wxEXPAND);

	this->SetSizer(topLevelSizer);
	this->SetAutoLayout(true);
	topLevelSizer->SetSizeHints(this);
}
Image

Posted: Mon Mar 23, 2009 7:20 pm
by doublemax
you probably missed that i derived the control from wxPanel, not wxWindow

Posted: Mon Mar 23, 2009 9:53 pm
by Auria
Yup, just building on what doublemax said, deriving from wxWindows if often not the easiest. wxWindow is meant as a rather abstract base class for all widgets, and it doesn't do that much itself; it leaves much work to be done by its children. Deriving from wxPanel will make it much easier for you since wxPanel does handle usual stuff.

Posted: Mon Mar 23, 2009 10:57 pm
by JohnD
My heart jumped then - I had indeed missed wxPanel, but it makes no difference. The first button appears nicely but the 2nd one is not visible, I can only assume it is underneath the 1st.

I am really frustrated why this is so difficult... sizers on my TopFrame window all work just fine. I don't need to be manually handling resize events in my class do I?

And for a slight side-question, why do examples for wx have the topPanel approach in the top-frame, I I used in my first code posted in this thread? How exactly does that work?

Posted: Tue Mar 24, 2009 2:53 pm
by Auria
I don't know what's your problem. This works for me :

Code: Select all

#include "wx/wx.h"

class TimelineWindow : public wxPanel
    {
        /* Needed for wxWidgets */
        // DECLARE_CLASS(TimelineWindow) disabled so i don't have to define it...
        
    public:
        TimelineWindow( wxWindow* parent, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize);
        
    protected:
        DECLARE_EVENT_TABLE()
    };

BEGIN_EVENT_TABLE(TimelineWindow,wxPanel)
END_EVENT_TABLE()

TimelineWindow::TimelineWindow( wxWindow* parent, const wxPoint &pos, const wxSize &size)
: wxPanel(parent, wxID_ANY, pos, size,wxBORDER_SUNKEN)
{
    wxBoxSizer *topLevelSizer = new wxBoxSizer(wxHORIZONTAL);
    
    wxButton *startButton = new wxButton(this,wxID_ANY, wxT("Start"));
    wxButton *pauseButton = new wxButton(this,wxID_ANY, wxT("Pause"));
    wxButton *stopButton = new wxButton(this,wxID_ANY, wxT("Stop"));
    
    topLevelSizer->Add(startButton,1,wxEXPAND);
    topLevelSizer->AddStretchSpacer();
    topLevelSizer->Add(pauseButton,1,wxEXPAND);
    topLevelSizer->AddStretchSpacer();
    topLevelSizer->Add(stopButton,1,wxEXPAND);
    
    this->SetSizer(topLevelSizer);
    this->SetAutoLayout(true);
    
    topLevelSizer->SetSizeHints(this);
}

class MyApp: public wxApp
	{
		bool OnInit();
		
		wxFrame *frame;
	public:
        
	};

IMPLEMENT_APP(MyApp)


bool MyApp::OnInit()
{
    wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
    frame = new wxFrame((wxFrame *)NULL, -1,  wxT("Hello wxWidgets"), wxPoint(50,50), wxSize(800,600));
	
    wxPanel* topLevelPanel = new wxPanel(frame);
    
    sizer->Add(new TimelineWindow(topLevelPanel));
	topLevelPanel->SetSizer(sizer);
    
    frame->Show();
    return true;
} 
If this still doesn't help you, try making a minimal compilable sample demonstrating the issue

Posted: Tue Mar 24, 2009 3:28 pm
by doublemax
i tested the code and it works for me, too. Maybe the problem lies "outside", e.g. where you create an instance of that class and add it to a sizer?
And for a slight side-question, why do examples for wx have the topPanel approach in the top-frame, I I used in my first code posted in this thread? How exactly does that work?
If a wxFrame has exactly one child, it will always get automatically resized to cover the whole client area, so you don't need a sizer for that. It's recommended to use a wxPanel as container for your gui elements because it handles tab-navigation and some other stuff for you.

Posted: Tue Mar 24, 2009 4:33 pm
by JohnD
Thanks guys, since you got that to run as expected I'll dig into the top-frame code and look for something there.