Организация работы с древовидными структурами в wxWidgets

 

Источник:  "wxWidgets programming" by t_rex

В этот раз я расскажу, как в wxWidgets организована работа древовидными структурами и списками на примере wxTreeCtrl (типа Tree View) и wxListView. Ясное дело, я покажу только основы, но, думаю, этого будет достаточно, чтобы разобраться, что к чему.

Итак, начнем…

Файл Application.h
Здесь всё как обычно: декларация класса приложения и функции OnInit.
<!--c1-->

<!--ec1-->
#ifndef _APPLICATION_H
#define _APPLICATION_H

#include <wx/wx.h>

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

#endif
<!--c2-->
<!--ec2-->

Файл Application.cpp
<!--c1-->
<!--ec1-->
#include "Application.h"
#include "MainFrame.h"
#include <wx/image.h>

IMPLEMENT_APP(TreeListTestApp)

bool TreeListTestApp::OnInit()
{
    wxImage::AddHandler(new wxXPMHandler());
    wxImage::AddHandler(new wxPNGHandler());
    MainFrame * frame = new MainFrame(NULL);
    SetTopWindow(frame);
    frame->Show();
    return TRUE;
}
<!--c2-->
<!--ec2-->
Строки
<!--c1-->
<!--ec1-->
wxImage::AddHandler(new wxXPMHandler());
wxImage::AddHandler(new wxPNGHandler());
<!--c2-->
<!--ec2-->
Обеспечивают поддержку изображений PNG и XPM (крайне рекомендую просмотреть файл .XPM в текстовом редакторе. Узнаете много нового wink.gif)

Файл MainFrame.h
<!--c1-->
<!--ec1-->
#ifndef _MAIN_FRAME_H
#define _MAIN_FRAME_H

#include <wx/wx.h>
/// нужен для работы с wxTreeCtrl
#include <wx/treectrl.h>
/// нужен для работы с wxListView
#include <wx/listctrl.h>
/// нужен для работы с wxSplitterWindow
#include <wx/splitter.h>
/// Этот класс необходим для хранения данных, ассоциированных с ячейкой (node) дерева
class MyTreeItemData : public wxTreeItemData
{
    /// Каждая ячейка дерева будет хранить какой-нибудь цвет
    wxColour m_Colour;
public:    
    MyTreeItemData(wxColour & colour) : m_Colour(colour) {}
    void SetColour(wxColour & colour) {m_Colour = colour;}
    const wxColour & GetColour() {return m_Colour;}
};

class MainFrame : public wxFrame
{        
    /// Вот, это, собственно, наше дерево
    wxTreeCtrl * m_TreeCtrl;
    /// А это наш список
    wxListView * m_ListView;
    /// А это сплиттер (окно-контейнер с разделителем, который позволяет менять размеры находящихся в нем компонентов с помощью мыши)
    wxSplitterWindow * m_SplitterWindow;
    /// Эта панелька будет менять цвет фона
    wxPanel * m_ResultPanel;
    /// ImageList – набор иконок для TreeCtrl и ListView
    wxImageList m_ImageList;
public:
    MainFrame(wxWindow * parent);
    ~MainFrame();
    DECLARE_EVENT_TABLE()
    /// Обработчик команды wxID_EXIT
    void OnExit(wxCommandEvent & event);    
    /// Скрывает/показывает строку состояния
    void OnToggleStatusBar(wxCommandEvent & event);    
    /// Добавляет новый элемент в дерево и в список
    void OnAddColour(wxCommandEvent & event);
    /// Очищает дерево и список
    void OnClearColours(wxCommadEvent & event);
    /// Обработчик события, возникающего при выделении ячейки дерева
    void OnTreeCtrlSelectionChanged(wxTreeEvent & event);
    /// Обработчик события, возникающего при выделении элемента списка
    void OnListViewItemSelected(wxListEvent & event);
};

#endif

<!--c2-->
<!--ec2-->

Так… что же у нас делает пример.
По нажатию кнопки «Добавить цвет» появляется диалог выбора цвета. Выбираем цвет, жмем ОК, после этого в дерево добавляется новая ячейка с текстом, отображающим параметры выбранного цвета (RGB). Аналогичный элемент добавляется в список.
По нажатию на ячейке дерева или на элементе списка, панелька справа внизу меняет цвет.

Файл MainFrame.cpp
<!--c1-->
<!--ec1-->
#include "MainFrame.h"
/// Необходим для работы с окном выбора цвета
#include <wx/colordlg.h>

enum
{
    ID_TOGGLE_STATUSBAR = 10001,
    ID_SPLITTER_WINDOW,
    ID_TREE_CTRL,
    ID_LIST_VIEW,
    ID_RESULT_PANEL,
    ID_ADD_COLOUR,
    ID_CLEAR_COLOURS,

    // Unused
    ID_UNUSED
};

BEGIN_EVENT_TABLE(MainFrame, wxFrame)
EVT_MENU(wxID_EXIT, MainFrame::OnExit)
EVT_MENU(ID_TOGGLE_STATUSBAR, MainFrame::OnToggleStatusBar)
EVT_MENU(ID_ADD_COLOUR, MainFrame::OnAddColour)
EVT_MENU(ID_CLEAR_COLOURS, MainFrame::OnClearColours)
/// Обращаем внимание… !!! Обработчик события от дерева
EVT_TREE_SEL_CHANGED(ID_TREE_CTRL, MainFrame::OnTreeCtrlSelectionChanged)
/// Обращаем внимание… !!! Обработчик события от списка
EVT_LIST_ITEM_SELECTED(ID_LIST_VIEW, MainFrame::OnListViewItemSelected)
END_EVENT_TABLE()

MainFrame::MainFrame(wxWindow * parent)
: wxFrame(parent, -1, _("Test"), wxDefaultPosition, wxSize(640,480))
{        
    /// Создаем наш ImageList, указываем размер изображений в списке
    m_ImageList.Create(16, 16);
    /// Подгружаем иконку
    /// !!! !!! Все нужные иконки находятся в папке $(WXWIN)/art !!! !!!
/// Копируем их оттуда руками в папку с приложением и пользуемся на здоровье
    m_ImageList.Add(wxBitmap(wxT("wxwin16x16.png"), wxBITMAP_TYPE_PNG));
    /// Наша строка меню... создаем
    wxMenuBar * menuBar = new wxMenuBar();
    /// Ассоциируем строку меню с формой
    SetMenuBar(menuBar);
    
    wxMenu * fileMenu = new wxMenu;
    fileMenu->Append(wxID_EXIT, _("Exit\tAlt+F4"));

    wxMenu * viewMenu = new wxMenu;
    viewMenu->AppendCheckItem(ID_TOGGLE_STATUSBAR, _("StatusBar"), _("Shows/hides StatusBar"));
    viewMenu->Check(ID_TOGGLE_STATUSBAR, true);

    menuBar->Append(fileMenu, _("File"));
    menuBar->Append(viewMenu, _("View"));

    /// Создаем панельинструментов. В wxWidgets с формой может быть ассоциирована только одна панель инструментов. Если возникает желание поюзать несколько панелей инструментов, то все остальные панели размещаются руками или с помощью Sizer’ов
    wxToolBar * toolBar = CreateToolBar(wxNO_BORDER|wxTB_HORIZONTAL|wxTB_FLAT);
/// Добавляем кнопки на панель инструментов
    toolBar->AddTool(ID_ADD_COLOUR, _("Добавить цвет"),
        wxBitmap(wxT("new.xpm"), wxBITMAP_TYPE_XPM), _("Добавялет новый цвет в список"));
    toolBar->AddTool(ID_CLEAR_COLOURS, _("Очистить список цветов"),
        wxBitmap(wxT("delete.xpm"), wxBITMAP_TYPE_XPM), _("Удаляет все цвета из списка"));
    /// Обновляем панель инструментов. Функция Realize выстраивает компоненты на панели. Ее нужно вызывать ОБЯЗАТЕЛЬНО
    toolBar->Realize();

    wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL);
    SetSizer(sizer);
    /// Создаем наш сплиттер
    m_SplitterWindow = new wxSplitterWindow(this, ID_SPLITTER_WINDOW);
    /// Говорим, что сплиттер может иметь минимальный размер (600,400)
    /// меньше этого размера его нельзя будет сжать
    m_SplitterWindow->SetMinSize(wxSize(600, 400));
    /// Устанавливаем минимальный размер компонента на сплиттере
    m_SplitterWindow->SetMinimumPaneSize(50);
    sizer->Add(m_SplitterWindow, 1, wxEXPAND, 0);
    /// Создаем дерево. Указываем в свойствах что нужно отрисовывать линии и плюсики для сворачивания/разворачивания ветвей дерева
    m_TreeCtrl = new wxTreeCtrl(m_SplitterWindow, ID_TREE_CTRL,
        wxDefaultPosition, wxDefaultSize, wxNO_BORDER|wxTR_LINES_AT_ROOT|wxTR_HAS_BUTTONS);
/// Устанавливаем ImageList
    m_TreeCtrl->SetImageList(&m_ImageList);
    wxPanel * rightpanel = new wxPanel(m_SplitterWindow, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
    wxBoxSizer * rightpanelsizer = new wxBoxSizer(wxVERTICAL);
    rightpanel->SetSizer(rightpanelsizer);
/// Создаем список. Указываем ему стиль REPORT
    m_ListView = new wxListView(rightpanel, ID_LIST_VIEW, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER|wxLC_REPORT);
/// Устанавливаем ImageList
    m_ListView->SetImageList(&m_ImageList, wxIMAGE_LIST_NORMAL|wxIMAGE_LIST_SMALL);
    m_ResultPanel = new wxPanel(rightpanel, ID_RESULT_PANEL, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER);
    m_ResultPanel->SetSize(300, 150);
    rightpanelsizer->Add(m_ListView, 1, wxEXPAND, 0);
    rightpanelsizer->Add(m_ResultPanel, 0, wxALIGN_CENTER|wxALL, 5);
    /// Добавляем столбцы в список
    m_ListView->InsertColumn(0, _("ID"), wxLIST_FORMAT_LEFT, 70);
    m_ListView->InsertColumn(1, _("Name"), wxLIST_FORMAT_LEFT, 300);
    /// Устанавливаем иконку для столбца
    m_ListView->SetColumnImage(0, 0);    
    /// Создаем корневой элемент дерева
    m_TreeCtrl->AddRoot(_("TreeCtrl sample"));
    /// Располагаем компоненты на сплиттере
    m_SplitterWindow->SplitVertically(m_TreeCtrl, rightpanel, 150);
    /// Создаем строку состояния
    /// Создаем строку состояния
    CreateStatusBar();
    /// Подогнать размер формы до размеров, занимаемых компонентами (чтобы все компоненты были видны)
    sizer->Fit(this);
    /// Центрируем форму на экране
    Centre();
}

MainFrame::~MainFrame()
{
}

void MainFrame::OnToggleStatusBar(wxCommandEvent & event)
{
    /// Получаем указатель на строку состояния
    wxStatusBar * statusBar = GetStatusBar();
    /// Если у окна есть строка состояния, то...
    if(statusBar)
    {
        /// Говорим что ее нет...
        SetStatusBar(NULL);
        /// Удаляем ту, что была...
        statusBar->Destroy();
    }
    /// А если строки состояния не было...
    else
    {
        /// Пересоздаем...
        CreateStatusBar();
    }
    /// Говорим окну что необходимо обновить размещение дочерних контролов
    Layout();
}

void MainFrame::OnExit(wxCommandEvent & event)
{
    Close();
}

void MainFrame::OnAddColour(wxCommandEvent & event)
{
/// Создаем диалог выбора цвета. В мурзилке сказано что диалоги и прочие большие объекты желательно создавать как указатели, а не как статические объекты. Поэтому делаем все правильно.
    wxColourDialog * dialog = new wxColourDialog(this);
/// Размещаем диалог в центре формы
    dialog->Centre();
    if(dialog->ShowModal() == wxID_OK)
    {
        /// Если мы выбрали цвет
        wxTreeItemId root = m_TreeCtrl->GetRootItem();
        wxColour colour = dialog->GetColourData().GetColour();
        /// Создаем лэйбочку для элемента списка и ячейки дерева
        wxString label = wxString::Format(_("Colour (%i, %i, %i)"), colour.Red(), colour.Green(), colour.Blue());
        /// Добавляем новый элемент в список
        m_ListView->InsertItem(m_ListView->GetItemCount(), wxString::Format(_("%u"), m_ListView->GetItemCount()));
        /// Устанавливаем текст во второй колонке списка для нового элемента
        m_ListView->SetItem(m_ListView->GetItemCount()-1, 1, label);    
        /// Создаем объект, который будет хранить информацию о цвете
        MyTreeItemData * data = new MyTreeItemData(colour);
        /// Создаем новую ячейку дерева и ассоциируем объект, хранящий цвет, с этой ячейкой
        /// Что самое приятное, ОБЪЕКТЫ АССОЦИИРОВАННЫЕ С ЯЧЕЙКАМИ ДЕРЕВА НЕ НАДО УДАЛЯТЬ РУКАМИ !!! ПРИ УДАЛЕНИИ ЯЧЕЙКИ, УДАЛЯЮТСЯ И ОБЪЕКТЫ С ДАННЫМИ. НО ДЛЯ ЭТОГО НУЖНО ЧТОБЫ ОБЪЕКТОВ С ДАННЫМИ БЫЛ ПРОИЗВОДНЫМ от wxTreeItemData
        wxTreeItemId item = m_TreeCtrl->AppendItem(root, label, -1, -1, data);
        /// Ассоциируем элемент списка с объектом, хранящим цвет
        m_ListView->SetItemData(m_ListView->GetItemCount()-1, (long)data);
        /// Раскрываем корневой элемент в дереве
        m_TreeCtrl->Expand(root);
        /// Выделяем новую ячейку дерева (не забываем, у нас еще есть обработчик выделения)
        m_TreeCtrl->SelectItem(item);
    }
    /// Удаляем диалог !!!! Не с помощью delete, а с помощью Destroy
    dialog->Destroy();
}

void MainFrame::OnTreeCtrlSelectionChanged(wxTreeEvent & event)
{    
/// если выделили корневой элемент, то ничего не делать. Корневой элемент не хранит у нас ни каких данных
    if(event.GetItem() == m_TreeCtrl->GetRootItem()) return;
    /// Получаем объект данных выделенного элемента
    MyTreeItemData * data = (MyTreeItemData *)m_TreeCtrl->GetItemData(event.GetItem());
/// Если не пустой указатель…
    if(data)
    {
        /// Устанавливаем цвет панельке и обновляем ее
        m_ResultPanel->SetBackgroundColour(data->GetColour());
        m_ResultPanel->Refresh();
    }
}

void MainFrame::OnClearColours(wxCommandEvent & event)
{
    /// Удаляем все элементы списка и все дочерние ячейки у корневой ячейки дерева
    m_ListView->DeleteAllItems();
    m_TreeCtrl->DeleteChildren(m_TreeCtrl->GetRootItem());
}

void MainFrame::OnListViewItemSelected(wxListEvent & event)
{
    /// Получаем объект данных, ассоциированных с выделенным элементом списка
    MyTreeItemData * data = (MyTreeItemData *)event.GetItem().GetData();
    if(data)
    {
        /// Меняем цвет панельки и обновляем ее
        m_ResultPanel->SetBackgroundColour(data->GetColour());
        m_ResultPanel->Refresh();
    }
}


Опубликовал admin
23 Ноя, Среда 2005г.



Программирование для чайников.