wxTreeCtrl

Introduction

The wxTreeCtrl (wxWidgets: wxTreeCtrl Class Reference) control displays data in the form of a tree. The nodes of the tree can be expanded or collapsed.

Features

wxWidgets' wxTreeCtrl supports the following features:

  • Context menus can be displayed when the user right-clicks on a node as shown in this example.
  • Labels can be edited by the user if the wxTR_EDIT_LABELS style is specified as shown in this example.

Simple Tree Example

The following example shows a simple use of a wxTreeCtrl control. The example is a simple modification of the MinimalApp2 example we presented in the minimal application tutorial. The full source for this example is available from our GitHub repository: wxWidgetsTutorials/TreeAndListControls/WxTreeCtrl1.

We add a wxTreeCtrl control to the frame with the following steps:

  1. A new instance of the wxTreeCtrl class is created.
  2. The AddRoot method is used to set the root of the tree. This method returns a handle that can be used to identify the new node in the tree. The type of the handles is wxTreeItemId.
  3. More nodes are added to the tree by calling the AppendItem method. This method takes as argument the handle of the node that will become the parent of the new node.
  4. By default the nodes will be collapsed, the ExpandAll method is used to expand all the nodes of the tree.
 File: TreeAndListControls/WxTreeCtrl1/src/WxTreeCtrl1Frame.cpp
#include "WxTreeCtrl1Frame.h"
#include <wx/panel.h>
#include <wx/treectrl.h>
#include <wx/sizer.h>

WxTreeCtrl1Frame::WxTreeCtrl1Frame(const wxString& title)
    : wxFrame(NULL, wxID_ANY, title)
{
    // Create a top-level panel to hold all the contents of the frame
    wxPanel* panel = new wxPanel(this, wxID_ANY);

    // Create a wxTreeCtrl control and add a few nodes to it
    wxTreeCtrl* treeCtrl = new wxTreeCtrl(panel, wxID_ANY, 
        wxDefaultPosition, wxSize(250,200));
    wxTreeItemId rootId = treeCtrl->AddRoot("Root");
    treeCtrl->AppendItem(rootId, "Node 1");
    wxTreeItemId child2Id = treeCtrl->AppendItem(rootId, "Node 2");
    treeCtrl->AppendItem(child2Id, "Child of node 2");
    treeCtrl->AppendItem(rootId, "Node 3");

    // Expand all the nodes
    treeCtrl->ExpandAll();

    // Set up the sizer for the panel
    wxBoxSizer* panelSizer = new wxBoxSizer(wxHORIZONTAL);
    panelSizer->Add(treeCtrl, 1, wxEXPAND);
    panel->SetSizer(panelSizer);

    // Set up the sizer for the frame and resize the frame
    // according to its contents
    wxBoxSizer* topSizer = new wxBoxSizer(wxHORIZONTAL);
    topSizer->Add(panel, 1, wxEXPAND);
    SetSizerAndFit(topSizer);
}

Figure 1 shows the tree created by the code.

Figure 1: The WxTreeCtrl1 Application

The rest of the source files are shown below. They don't differ significantly from the MinimalApp2 code.

 File: TreeAndListControls/WxTreeCtrl1/src/WxTreeCtrl1Frame.h
#ifndef _TUTORIALS_WXWIDGETS_WXTREECTRL1FRAME_H_
#define _TUTORIALS_WXWIDGETS_WXTREECTRL1FRAME_H_

#include <wx/frame.h>

class WxTreeCtrl1Frame : public wxFrame
{
public:
    WxTreeCtrl1Frame(const wxString& title);
};

#endif
 File: TreeAndListControls/WxTreeCtrl1/src/WxTreeCtrl1App.h
#ifndef _TUTORIALS_WXWIDGETS_WXTREECTRL1APP_H_
#define _TUTORIALS_WXWIDGETS_WXTREECTRL1APP_H_

#include <wx/app.h>

class WxTreeCtrl1App : public wxApp
{
public:
    virtual bool OnInit();
};

#endif
 File: TreeAndListControls/WxTreeCtrl1/src/WxTreeCtrl1App.cpp
#include "WxTreeCtrl1App.h"
#include "WxTreeCtrl1Frame.h"

wxIMPLEMENT_APP(WxTreeCtrl1App);

bool WxTreeCtrl1App::OnInit()
{
    WxTreeCtrl1Frame* frame = new WxTreeCtrl1Frame("WxTreeCtrl1");
    frame->Show(true);
    return true;
}

Tree Events

Trees trigger several events that can be useful to handle. In this example we will show how to handle these events by handling the node selection event. The full source for this example is available from our GitHub repository: wxWidgetsTutorials/TreeAndListControls/WxTreeCtrl2.

We modify the previous example as follows:

  1. We add a wxTextCtrl that contains the name of the selected node. This is a simple way to see the event handler in action.
  2. We add a method to handle the event, OnTreeSelectionChanged, and associate it with the wxTreeCtrl instance by using the EVT_TREE_SEL_CHANGED macro in the event table.
  3. In the event handler we use the wxTreeEvent::GetItem() method to get the id of the selected node, and then update the wxTextCtrl with the text of the selected node.

The changes are highlighted in the files below.

 File: TreeAndListControls/WxTreeCtrl2/src/WxTreeCtrl2Frame.h
#ifndef _TUTORIALS_WXWIDGETS_WXTREECTRL2FRAME_H_
#define _TUTORIALS_WXWIDGETS_WXTREECTRL2FRAME_H_

#include <wx/frame.h>
#include <wx/treectrl.h>
#include <wx/textctrl.h>

class WxTreeCtrl2Frame : public wxFrame
{
public:
    WxTreeCtrl2Frame(const wxString& title);

private:
    void OnTreeSelectionChanged(wxTreeEvent& evt);

private:
    wxTreeCtrl* m_treeCtrl;
    wxTextCtrl* m_textCtrl;

    wxDECLARE_EVENT_TABLE();
};

#endif
 File: TreeAndListControls/WxTreeCtrl2/src/WxTreeCtrl2Frame.cpp
#include "WxTreeCtrl2Frame.h"
#include "WindowIDs.h"
#include <wx/panel.h>
#include <wx/sizer.h>

WxTreeCtrl2Frame::WxTreeCtrl2Frame(const wxString& title)
    : wxFrame(NULL, wxID_ANY, title), m_treeCtrl(0), m_textCtrl(0)
{
    // Create a top-level panel to hold all the contents of the frame
    wxPanel* panel = new wxPanel(this, wxID_ANY);

    // Create a wxTreeCtrl control and add a few nodes to it
    m_treeCtrl = new wxTreeCtrl(panel, TreeCtrlID,
        wxDefaultPosition, wxSize(250, 200));
    wxTreeItemId rootId = m_treeCtrl->AddRoot("Root");
    m_treeCtrl->AppendItem(rootId, "Node 1");
    wxTreeItemId child2Id = m_treeCtrl->AppendItem(rootId, "Node 2");
    m_treeCtrl->AppendItem(child2Id, "Child of node 2");
    m_treeCtrl->AppendItem(rootId, "Node 3");

    // Expand all the nodes
    m_treeCtrl->ExpandAll();

    // Create a wxTextCtrl that will be updated when
    // nodes of the tree are selected
    m_textCtrl = new wxTextCtrl(panel, wxID_ANY);

    // Set up the sizer for the panel
    wxBoxSizer* panelSizer = new wxBoxSizer(wxHORIZONTAL);
    panelSizer->Add(m_treeCtrl, 1, wxEXPAND);
    panelSizer->Add(m_textCtrl, 1, wxEXPAND);
    panel->SetSizer(panelSizer);

    // Set up the sizer for the frame and resize the frame
    // according to its contents
    wxBoxSizer* topSizer = new wxBoxSizer(wxHORIZONTAL);
    topSizer->Add(panel, 1, wxEXPAND);
    SetSizerAndFit(topSizer);
}

// This will be called when the selected node of the tree changes.
// It will copy the selected node text into the wxTextCtrl
void WxTreeCtrl2Frame::OnTreeSelectionChanged(wxTreeEvent& evt)
{
    if (m_treeCtrl && m_textCtrl)
    {
        wxTreeItemId selectedNode = evt.GetItem();
        wxString nodeText = m_treeCtrl->GetItemText(selectedNode);
        m_textCtrl->SetValue(nodeText);
    }

    // Just in case something else is interested in
    // this event
    evt.Skip();
}

// The event table associate the wxTreeCtrl with the event handler
wxBEGIN_EVENT_TABLE(WxTreeCtrl2Frame, wxFrame)
    EVT_TREE_SEL_CHANGED(TreeCtrlID, WxTreeCtrl2Frame::OnTreeSelectionChanged)
wxEND_EVENT_TABLE()

Figure 2 shows the application.

Figure 2: The WxTreeCtrl2 Application

The rest of the files don't have any significant changes but are shown here for completeness.

 File: TreeAndListControls/WxTreeCtrl2/src/WindowIDs.h
#ifndef _TUTORIALS_WXWIDGETS_WINDOWIDS_H_
#define _TUTORIALS_WXWIDGETS_WINDOWIDS_H_

#include <wx/defs.h>

const int TreeCtrlID = wxID_HIGHEST + 1;

#endif
 File: TreeAndListControls/WxTreeCtrl2/src/WxTreeCtrl2App.h
#ifndef _TUTORIALS_WXWIDGETS_WXTREECTRL2APP_H_
#define _TUTORIALS_WXWIDGETS_WXTREECTRL2APP_H_

#include <wx/app.h>

class WxTreeCtrl2App : public wxApp
{
public:
    virtual bool OnInit();
};

#endif
 File: TreeAndListControls/WxTreeCtrl2/src/WxTreeCtrl2App.cpp
#include "WxTreeCtrl2App.h"
#include "WxTreeCtrl2Frame.h"

wxIMPLEMENT_APP(WxTreeCtrl2App);

bool WxTreeCtrl2App::OnInit()
{
    WxTreeCtrl2Frame* frame = new WxTreeCtrl2Frame("WxTreeCtrl2");
    frame->Show(true);
    return true;
}

Context Menu Example

We have previously seen how to handle a tree event. There is a specific tree event designed to facilitate the display of a context menu when right-clicking on a tree node. We will show how to use this event in our next example where we will add a context menu that allows the user to display the node label in a message box. The full source for this example is available from our GitHub repository: wxWidgetsTutorials/TreeAndListControls/WxTreeCtrl3.

We modify the WxTreeCtrl1 example as follows:

  1. We add an event handler, OnTreeItemMenu, to handle right-click events on a node of the tree. We associate this handler with the event by using the EVT_TREE_ITEM_MENU macro in the event table.
  2. The OnTreeItemMenu method creates a menu and displays it. The menu has just one item: "Show node text".
  3. We create an event handler, OnMenuItem, that is called when the user select the "Show node text" option. The handler will display a message box with node label.
  4. We associate the menu handler with the menu item by using the wxEventHandler::Connect method when we create the menu in the OnTreeItemMenu method. This explained in more details next.

Usually we would associate the menu item with its event handler in the event table. This is not possible here because we need to pass the information about the node that was right-clicked to the OnMenuItem handler. By using the wxEventHandler::Connect method instead of the event table it is possible to pass extra information to the event handler. To pass the node information we create a class, UserData, to hold the information we need. We then use the Connect function to associate the handler with the menu item. When doing so we also pass an instance of the UserData class that contains the id of the node that was right-clicked.

 File: TreeAndListControls/WxTreeCtrl3/src/WxTreeCtrl3Frame.h
#ifndef _TUTORIALS_WXWIDGETS_WXTREECTRL3FRAME_H_
#define _TUTORIALS_WXWIDGETS_WXTREECTRL3FRAME_H_

#include <wx/frame.h>
#include <wx/treectrl.h>

class WxTreeCtrl3Frame : public wxFrame
{
public:
    WxTreeCtrl3Frame(const wxString& title);

private:
    void OnTreeItemMenu(wxTreeEvent& evt);
    void OnMenuItem(wxCommandEvent& evt);

private:
    wxTreeCtrl* m_treeCtrl;

    wxDECLARE_EVENT_TABLE();

private:
    class UserData : public wxObject
    {
    public:
        std::string m_data;
    };
};

#endif
 File: TreeAndListControls/WxTreeCtrl3/src/WxTreeCtrl3Frame.cpp
#include "WxTreeCtrl3Frame.h"
#include "WindowIDs.h"
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/menu.h>
#include <wx/msgdlg.h>

WxTreeCtrl3Frame::WxTreeCtrl3Frame(const wxString& title)
    : wxFrame(NULL, wxID_ANY, title), m_treeCtrl(0)
{
    // Create a top-level panel to hold all the contents of the frame
    wxPanel* panel = new wxPanel(this, wxID_ANY);

    // Create a wxTreeCtrl control and add a few nodes to it
    m_treeCtrl = new wxTreeCtrl(panel, TreeCtrlID,
        wxDefaultPosition, wxSize(250,200));
    wxTreeItemId rootId = m_treeCtrl->AddRoot("Root");
    m_treeCtrl->AppendItem(rootId, "Node 1");
    wxTreeItemId child2Id = m_treeCtrl->AppendItem(rootId, "Node 2");
    m_treeCtrl->AppendItem(child2Id, "Child of node 2");
    m_treeCtrl->AppendItem(rootId, "Node 3");

    // Expand all the nodes
    m_treeCtrl->ExpandAll();

    // Set up the sizer for the panel
    wxBoxSizer* panelSizer = new wxBoxSizer(wxHORIZONTAL);
    panelSizer->Add(m_treeCtrl, 1, wxEXPAND);
    panel->SetSizer(panelSizer);

    // Set up the sizer for the frame and resize the frame
    // according to its contents
    wxBoxSizer* topSizer = new wxBoxSizer(wxHORIZONTAL);
    topSizer->Add(panel, 1, wxEXPAND);
    SetSizerAndFit(topSizer);
}

void WxTreeCtrl3Frame::OnTreeItemMenu(wxTreeEvent& evt)
{
    // This event handler creates a new menu and displays
    // it as a popup menu with the wxWindow::PopupMenu function

    wxMenu menu;
    menu.Append(MenuItemID, "Show node text");

    // We need to pass some data (the text of the node) to
    // the menu event handler. We create a class that holds
    // the needed data and use the Connect function to
    // associate our event handler with the menu.
    UserData* userData = new UserData();
    userData->m_data = m_treeCtrl->GetItemText(evt.GetItem());
    menu.Connect(MenuItemID, wxEVT_MENU, 
        (wxObjectEventFunction)&WxTreeCtrl3Frame::OnMenuItem,
    userData, this);

    // Display the menu as a popup menu
    PopupMenu(&menu);

    evt.Skip();
}

void WxTreeCtrl3Frame::OnMenuItem(wxCommandEvent& evt)
{
    // Display a message with the text of the node
    // the user right-clicked on
    UserData* userData = dynamic_cast<UserData*>(evt.GetEventUserData());
    if (userData)
    {
        wxMessageBox("You clicked on item: " + userData->m_data);
    }
}

wxBEGIN_EVENT_TABLE(WxTreeCtrl3Frame, wxFrame)
    EVT_TREE_ITEM_MENU(TreeCtrlID, WxTreeCtrl3Frame::OnTreeItemMenu)
wxEND_EVENT_TABLE()

Figure 3 shows the application when the user right-clicks on a node of the tree. Figure 4 shows the application after the user selects the "Show text node" option.

Figure 3: The WxTreeCtrl3 application when the user right-clicks on a node
Figure 4: The WxTreeCtrl3 application when the user selects "Show node text"

The rest of the files don't have any significant changes but are shown here for completeness.

 File: TreeAndListControls/WxTreeCtrl3/src/WindowIDs.h
#ifndef _TUTORIALS_WXWIDGETS_WINDOWIDS_H_
#define _TUTORIALS_WXWIDGETS_WINDOWIDS_H_

#include <wx/defs.h>

const int TreeCtrlID = wxID_HIGHEST + 1;
const int MenuItemID = wxID_HIGHEST + 2;

#endif
 File: TreeAndListControls/WxTreeCtrl3/src/WxTreeCtrl3App.h
#ifndef _TUTORIALS_WXWIDGETS_WXTREECTRL3APP_H_
#define _TUTORIALS_WXWIDGETS_WXTREECTRL3APP_H_

#include <wx/app.h>

class WxTreeCtrl3App : public wxApp
{
public:
    virtual bool OnInit();
};

#endif
 File: TreeAndListControls/WxTreeCtrl3/src/WxTreeCtrl3App.cpp
#include "WxTreeCtrl3App.h"
#include "WxTreeCtrl3Frame.h"

wxIMPLEMENT_APP(WxTreeCtrl3App);

bool WxTreeCtrl3App::OnInit()
{
    WxTreeCtrl3Frame* frame = new WxTreeCtrl3Frame("WxTreeCtrl3");
    frame->Show(true);
    return true;
}

Label Editing Example

In this section we will show how to create a wxTreeCtrl control that allows the user to edit some of the labels but not others. It will also validate the new label entered by the user. The full source for this example is available from our GitHub repository: wxWidgetsTutorials/TreeAndListControls/WxTreeCtrl4.

By default a wxTreeCtrl doesn't allow its labels to be edited. This feature can be enabled by requesting the wxTR_EDIT_LABELS style at construction time. This will make the labels of all nodes of the tree editable.

Events associated with node editing allow to control which nodes can be edited or not and also to validate the new labels.

  • The EVT_TREE_BEGIN_LABEL_EDIT macro allows to specify an event handler that gets called when the user begins editing a node. Vetoing the event will basically make the node uneditable.
  • The EVT_TREE_END_LABEL_EDIT macro allows to specifify an event handler that gets called when the user finishes editing the node but before the change is actually made to the tree. Vetoing this event will cancel the change.

To create an application where some of the nodes are editable we modify the WxTreeCtrl1 example as follows:

  1. We add the wxTR_EDIT_LABELS style to the tree when we construct it.
  2. We add an event handler, OnBeginLabelEdit, and associate it with the wxTreeCtrl by using the EVT_TREE_BEGIN_LABEL_EDIT macro in the event table. OnBeginLabelEdit uses the wxTreeEvent::GetLabel method to get the current node label and vetoes editing of any node that has a label of "Node 1" or "Node 2".
  3. We add an event handler, OnEndLabelEdit, and associate it with the wxTreeCtrl by using the EVT_TREE_END_LABEL_EDIT macro in the event table. OnEndLabelEdit uses the wxTreeEvent::GetLabel method to get the new label entered by the user and rejects the change if the proposed label is "Node 1" or "Node 2".
 File: TreeAndListControls/WxTreeCtrl4/src/WxTreeCtrl4Frame.h
#ifndef _TUTORIALS_WXWIDGETS_WXTREECTRL4FRAME_H_
#define _TUTORIALS_WXWIDGETS_WXTREECTRL4FRAME_H_

#include <wx/frame.h>
#include <wx/treectrl.h>

class WxTreeCtrl4Frame : public wxFrame
{
public:
    WxTreeCtrl4Frame(const wxString& title);

private:
    void OnBeginLabelEdit(wxTreeEvent& evt);
    void OnEndLabelEdit(wxTreeEvent& evt);

    wxDECLARE_EVENT_TABLE();
};

#endif
 File: TreeAndListControls/WxTreeCtrl4/src/WxTreeCtrl4Frame.cpp
#include "WxTreeCtrl4Frame.h"
#include "WindowIDs.h"
#include <wx/panel.h>
#include <wx/sizer.h>

WxTreeCtrl4Frame::WxTreeCtrl4Frame(const wxString& title)
    : wxFrame(NULL, wxID_ANY, title)
{
    // Create a top-level panel to hold all the contents of the frame
    wxPanel* panel = new wxPanel(this, wxID_ANY);

    // Create a wxTreeCtrl control and add a few nodes to it
    // Give the tree the wxTR_EDIT_LABELS style in addition to the
    // default style to allow node labels to be edited by the user
    wxTreeCtrl* treeCtrl = new wxTreeCtrl(panel, TreeCtrlID,
        wxDefaultPosition, wxSize(250, 200), wxTR_DEFAULT_STYLE | wxTR_EDIT_LABELS);
    wxTreeItemId rootId = treeCtrl->AddRoot("Root");
    treeCtrl->AppendItem(rootId, "Node 1");
    wxTreeItemId child2Id = treeCtrl->AppendItem(rootId, "Node 2");
    treeCtrl->AppendItem(child2Id, "Child of node 2");
    treeCtrl->AppendItem(rootId, "Node 3");

    // Expand all the nodes
    treeCtrl->ExpandAll();

    // Set up the sizer for the panel
    wxBoxSizer* panelSizer = new wxBoxSizer(wxHORIZONTAL);
    panelSizer->Add(treeCtrl, 1, wxEXPAND);
    panel->SetSizer(panelSizer);

    // Set up the sizer for the frame and resize the frame
    // according to its contents
    wxBoxSizer* topSizer = new wxBoxSizer(wxHORIZONTAL);
    topSizer->Add(panel, 1, wxEXPAND);
    SetSizerAndFit(topSizer);
}

void WxTreeCtrl4Frame::OnBeginLabelEdit(wxTreeEvent& evt)
{
    // We use the EVT_TREE_BEGIN_LABEL_EDIT event handler
    // to restrict the nodes the user can edit, here
    // we arbitrarily decided that "Node 1" and "Node 2"
    // can't be edited
    wxString text = evt.GetLabel();
    if ((text == "Node 1") || (text == "Node 2"))
    {
        evt.Veto();
    }
}

void WxTreeCtrl4Frame::OnEndLabelEdit(wxTreeEvent& evt)
{
    // We use the EVT_TREE_END_LABEL_EDIT event handler
    // to validate the new label text the user has entered.
    // Here we arbitrarily decided that the user can't
    // rename nodes to "Node 1" and "Node 2"
    wxString text = evt.GetLabel();
    if ((text == "Node 1") || (text == "Node 2"))
    {
        evt.Veto();
    }
}

wxBEGIN_EVENT_TABLE(WxTreeCtrl4Frame, wxFrame)
    EVT_TREE_BEGIN_LABEL_EDIT(TreeCtrlID, WxTreeCtrl4Frame::OnBeginLabelEdit)
    EVT_TREE_END_LABEL_EDIT(TreeCtrlID, WxTreeCtrl4Frame::OnEndLabelEdit)
wxEND_EVENT_TABLE()

Figure 5 shows the application when the user edits a node. If you launch the application you will notice that "Node 1" and "Node 2" can't be edited and also that any attempt to change a label to "Node 1" or "Node 2" is cancelled.

Figure 5: The WxTreeCtrl4 application when the user edits a node

The rest of the files don't have any significant changes but are shown here for completeness.

 File: TreeAndListControls/WxTreeCtrl4/src/WindowIDs.h
#ifndef _TUTORIALS_WXWIDGETS_WINDOWIDS_H_
#define _TUTORIALS_WXWIDGETS_WINDOWIDS_H_

#include <wx/defs.h>

const int TreeCtrlID = wxID_HIGHEST + 1;

#endif
 File: TreeAndListControls/WxTreeCtrl4/src/WxTreeCtrl4App.h
#ifndef _TUTORIALS_WXWIDGETS_WXTREECTRL4APP_H_
#define _TUTORIALS_WXWIDGETS_WXTREECTRL4APP_H_

#include <wx/app.h>

class WxTreeCtrl4App : public wxApp
{
public:
    virtual bool OnInit();
};

#endif
 File: TreeAndListControls/WxTreeCtrl4/src/WxTreeCtrl4App.cpp
#include "WxTreeCtrl4App.h"
#include "WxTreeCtrl4Frame.h"

wxIMPLEMENT_APP(WxTreeCtrl4App);

bool WxTreeCtrl4App::OnInit()
{
    WxTreeCtrl4Frame* frame = new WxTreeCtrl4Frame("WxTreeCtrl4");
    frame->Show(true);
    return true;
}