Search code examples
wxwidgets

Creating a wxPanel to draw on


I am trying to create a wxPanel to paint lines and points on it.

My program consists of the following windows:

  1. MyFrame : the default frame class which inherits from wxFrame

  2. Final_Layout : an instance of wxPanel with parent MyFrame (i.e. this pointer) , and it contains all of my controls

Now, I am trying to create a new panel, Draw_Panel, that I can draw on it. I would like to append this panel besides Final_Layout using wxBoxSizer.

So, my first step is in initilizing the panel, I have the following code:

   void MyFrame::Initialize_Draw_Input() {

    Draw_Panel = new wxPanel(this, -1); // QUESTION : Who is the parent here? Should it be Final_Layout or "this" pointer?
    Draw_Panel ->Connect(wxEVT_PAINT, wxPaintEventHandler(MyFrame::OnPaint));

}

    void MyFrame::OnPaint(wxPaintEvent& event)
    {
        wxPaintDC dc(Draw_Panel );

        wxCoord x1 = 50, y1 = 60;
        wxCoord x2 = 100, y2 = 60;

        dc.DrawLine(x1, y1, x2, y2);
    }

However, this does not work. I think I am misunderstanding how wxPanels work... I would appreciate any help / guidance

Edit:

Based on the help below (which I greatly appreciate), I made the following update to the code. However, now I cannot draw on the panel:

    void MyFrame::Initialize_Draw_Input() {

    Draw_Panel = new wxPanel(Final_Panel, wxID_ANY,wxDefaultPosition,wxSize(1000,1000),wxTAB_TRAVERSAL,"");
    Draw_StaticBox = new wxStaticBoxSizer(wxHORIZONTAL, Draw_Panel, _T("Cross Section"));
    Draw_Sizer = new wxBoxSizer(wxHORIZONTAL);

   // Draw_Panel->Connect(wxEVT_PAINT, wxPaintEventHandler(MyFrame::OnPaint), NULL, this);

    wxStaticText* x = new wxStaticText(Draw_Panel,wxID_ANY,"");
    Draw_Sizer->Add(x);
    Draw_StaticBox->Add(Draw_Sizer,wxSizerFlags().Expand());
    Draw_Panel->SetSizer(Draw_StaticBox);

    PaintNow();

}

void MyFrame::PaintNow() {

    wxClientDC dc(Draw_Panel);
    Render(dc);

}

void MyFrame::Render(wxDC& dc) {

    wxCoord x1 = 0, y1 = 60;
    wxCoord x2 = 100, y2 = 60;

    dc.DrawLine(x1, y1, x2, y2);

}

My constructor:

MyFrame::MyFrame() : wxFrame(NULL, wxID_ANY, "Cross Sectional Properties", wxDefaultPosition, wxSize(1500, 600), wxDEFAULT_FRAME_STYLE & ~(wxRESIZE_BORDER | wxMAXIMIZE_BOX)) {

    wxBoxSizer* Element_and_Node_hSizer = new wxBoxSizer(wxHORIZONTAL);
    wxBoxSizer* Info_and_Data_Input_vSizer = new wxBoxSizer(wxVERTICAL);
    wxBoxSizer* Output_and_Draw_vSizer = new wxBoxSizer(wxVERTICAL);

    wxBoxSizer* Main_hSizer = new wxBoxSizer(wxHORIZONTAL);


    Final_Panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T(""));

    Initialize_ProjectInfo_Input(); //Info_Panel, a wxFrame instance is defined here. It contains a wxGrid and other controls for project info input.
    Initialize_Node_Input();        //Node_Panel,  a wxFrame instance is defined here. It contains a wxGrid control for node data input.
    Initialize_Element_Input();     //Element_Panel,  a wxFrame instance is defined here. It contains a wxGrid control for element data input.
    Initialize_Output();            //Out_Panel, a wxFrame instance defined here. It contians a wxGrid for cross sectional properties output. 
    Initialize_Draw_Input();
    InitMenu();                     //Initializes the menu.


    //Combines the Node and element input wxGrid into one sizer - horizontally
    Element_and_Node_hSizer->Add(Node_Panel, wxSizerFlags(1).Expand().Border(wxBOTTOM | wxLEFT |wxRIGHT));
    Element_and_Node_hSizer->Add(Element_Panel, wxSizerFlags(1).Expand().Border(wxBOTTOM | wxRIGHT));

    //Combines the project input and the (Node & Elemetn Input) into one sizer - vertically
    Info_and_Data_Input_vSizer->Add(Info_Panel, wxSizerFlags(1).Expand().Border(wxALL));
    Info_and_Data_Input_vSizer->Add(Element_and_Node_hSizer,wxSizerFlags(1).Expand());

    //Combines output data and draw panel into one sizer - vertically
    Output_and_Draw_vSizer->Add(Out_Panel,wxSizerFlags(1).Border(wxALL));
    Output_and_Draw_vSizer->Add(Draw_Panel, wxSizerFlags(1).Expand().Border(wxLEFT| wxBOTTOM  | wxRIGHT));

    //Combines all into the final layout
    Main_hSizer->Add(Info_and_Data_Input_vSizer);
    Main_hSizer->Add(Output_and_Draw_vSizer);


    SetSizer(Main_hSizer);

    PaintNow();

}

enter image description here

Also unrelated, would anyone know how to center a wxGrid? the "Coordiates of Nodes" section does not look good.


Solution

  • There are multiple issues here.

    1. You really should stick with the symbolic constant wxID_ANY instead of using -1 in the line
    Draw_Panel = new wxPanel(this, -1);. 
    

    That way in the very, very unlikely event that wxID_ANY is changed to something else, your code will continue to work.


    1. In the line
    Draw_Panel ->Connect(wxEVT_PAINT, wxPaintEventHandler(MyFrame::OnPaint));
    

    you need to supply 2 more parameters: userData (which will almost always be NULL) and eventSink (which will almost always be this). So the line should look like so:

    Draw_Panel ->Connect(wxEVT_PAINT, wxPaintEventHandler(MyFrame::OnPaint),NULL,this);
    

    1. You don't show your constructor, so based on previous questions, I assume it looks like this
    MyFrame::MyFrame...
    
        Info_Panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T(""));
        Initialize_Project_Info();
        Initialize_Draw_Input();
    }
    

    I'm assuming Final_Layout is what was called Info_Panel in previous questions. However, you might notice this causes the 2 panels to be drawn on top of each other. To get around this, add 1 more sizer to MyFrame

    MyFrame::MyFrame....
    
        wxBoxSizer* MainSizer = new wxBoxSizer(wxHORIZONTAL);
        Info_Panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T(""));
        Initialize_Project_Info();
        Initialize_Draw_Input();
    
        Info_Panel->Fit();
        MainSizer->Add(Info_Panel,wxSizerFlags(0));
        MainSizer->Add(Draw_Panel,wxSizerFlags(1).Expand());
    
        SetSizer(MainSizer);
    }
    

    This produces this layout:

    enter image description here


    1. Notice that ugly rectangle in the lower left. That's the frames background. To get rid of it, you can add 1 more panel to your class. I'll call it framePanel, but it can be called anything you want. Then framePanel will be the only child of your frame and the other 2 panels will be children of framePanel.

    So the new constructor could look like this:

    MyFrame::MyFrame
    
        wxBoxSizer* MainSizer = new wxBoxSizer(wxHORIZONTAL);
        framePanel = new wxPanel(this, wxID_ANY);
        Info_Panel = new wxPanel(framePanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T(""));
        Initialize_Project_Info();
        Initialize_Draw_Input();
    
        Info_Panel->Fit();
        MainSizer->Add(Info_Panel,wxSizerFlags(0));
        MainSizer->Add(Draw_Panel,wxSizerFlags(1).Expand());
    
        framePanel->SetSizer(MainSizer);
    }
    

    And you should also change the defition for Draw_Panel to have the new parent like this:

    Draw_Panel = new wxPanel(framePanel, wxID_ANY);
    

    Finnally this gives this layout:

    enter image description here

    This is why the answer to your previous question included the part

    While you can place controls directly in a wxFrame, because of 'TAB traversal' and a OS's typical background features you better use a wxPanel.


    1. Based on the edit with the static box around the draw panel, you need to create the static box sizer first and then create the panel as a child of the static box:
    void MyFrame::Initialize_Draw_Input() {
    
        Draw_StaticBox = new wxStaticBoxSizer(wxHORIZONTAL, Final_Panel, _T("Cross Section"));
        Draw_Panel = new wxPanel(Draw_StaticBox->GetStaticBox(), wxID_ANY,wxDefaultPosition,wxSize(1000,1000),wxTAB_TRAVERSAL,"");
        Draw_Sizer = new wxBoxSizer(wxHORIZONTAL);
    
        Draw_Panel->Connect(wxEVT_PAINT, wxPaintEventHandler(MyFrame::OnPaint), NULL, this);
    
        wxStaticText* x = new wxStaticText(Draw_StaticBox->GetStaticBox(),wxID_ANY,"");
        Draw_Sizer->Add(x);
        Draw_StaticBox->Add(Draw_Panel,wxSizerFlags(1).Expand());
        Draw_StaticBox->Add(Draw_Sizer,wxSizerFlags().Expand());
    }
    

    Right now neither Draw_Sizer nor the empty static text x seem serve any purpose, but I assume they're place holders for something that will come later.

    Finally, you need to change the constructor to add Draw_StaticBox to Output_and_Draw_vSizer instead of Draw_Panel. ie change

    Output_and_Draw_vSizer->Add(Draw_Panel, wxSizerFlags(1).Expand().Border(wxLEFT| wxBOTTOM  | wxRIGHT));
    

    to

    Output_and_Draw_vSizer->Add(Draw_StaticBox, wxSizerFlags(1).Expand().Border(wxLEFT| wxBOTTOM  | wxRIGHT));