Переопределение поведения стандартных компонентов. Делаем свой wxGrid

Александр (sandy) Илюшенко любезно предоставил статью о том, как настроить класc wxGrid под свои нужды:

Захотелось мне как-то, чтобы в гриде были не номера строк, а маркер.
К тому же очень хотелось, чтобы незаполненное пространство грида было не белым, а, примерно, как на рисунке ниже.
Делаем собственный wxGrid
Навеяно это было в основном аналогичными и другими классами, предоставляемыми MFC. Тут же и вспомнилось, что подобные классы также прдоставляют очень полезные методы для хранения дополнительных не отображаемых данных, такие как SetData() или GetData().

Вначеле даже была мысль, использовать для этого wxGrid::SetRowLabelValue(), с учетом того, что перерисовку лэйбла я собирался переопределить. Но, покурив исходники грида, я обнаружил, что в функциях bool InsertRows(int pos = 0, int numRows = 1, bool updateLabels = true) и bool DeleteRows(int pos = 0, int numRows = 1, bool updateLabels = true), третий аргумент не используется. А это значит, что при вставке или удалении строки у меня все посыпется. К тому же неизвестно, как в будущем этот аргумент будет использоваться.

Потому не нашел я ничего лучшего, как переопределить грид и снабдить его необходимыми функциями.
wxGridCtrl.h

#pragma once
#include < wx/wx.h >
#include < wx/grid.h >
#include < map >

class wxGridCtrl: public wxGrid {
    std::map< int, int > m_mRowData;
    DECLARE_DYNAMIC_CLASS(wxGridCtrl)
protected:
    virtual void DrawRowLabel(wxDC& dc, int row);
    virtual void OnLabelLeftClick(wxGridEvent& event);
    virtual void OnSelectCell(wxGridEvent& event);

    DECLARE_EVENT_TABLE();
public:
    wxGridCtrl();
    wxGridCtrl(wxWindow* parent, const long& id);
/// Override
    bool CreateGrid(int numRows, int numCols,
                    wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells);
    bool AppendRows(int numRows = 1, bool updateLabels = true);
    bool InsertRows(int pos = 0, int numRows = 1, bool updateLabels = true);
    bool DeleteRows(int pos = 0, int numRows = 1, bool updateLabels = true);
/// New
    void SetRowData(const int& row, const int& data);
    int GetRowData(const int& row);
};

Теперь, как это все реализовано:

  • DrawRowLabel – это функция, реализующая перерисовку лэйбла грида, а именно, рисующая маркер в лэйбле.
  • OnLabelLeftClick – усталавливает визуальный курсор в первую колонку, выделенной строки.
  • Это понадобилось для того, чтобы маркер не оставался на предыдущей строке, когда кликаем по следующей.
  • OnSelectCell – функция, без которой я не смог обойтись, чтобы все хорошо работало:)

Несколько слов о контейнере std::map m_mRowData:

Этот контейнер, имеющий ключем номер строки, и содержит значения, присваиваемые функцией SetRowData. И, чтобы данные соответствовали строками после создания грида, добаления, вставки и удаления записей, пришлось переопределить соответсвующие функции: CreateGrid, AppendRows, InsertRows, DeleteRows. Получить данные для соответствующей строки, как и сами догадываетесь, можно функцией GetRowData.
wxGridCtrl.cpp


#include "wxGridCtrl.h"
#include "cursor.xpm"

IMPLEMENT_DYNAMIC_CLASS(wxGridCtrl, wxGrid);

wxGridCtrl::wxGridCtrl()
{
}

wxGridCtrl::wxGridCtrl(wxWindow* parent, const long& id):
    wxGrid(parent,id,wxDefaultPosition,wxDefaultSize)
{
}

BEGIN_EVENT_TABLE(wxGridCtrl, wxGrid)
    EVT_GRID_LABEL_LEFT_CLICK(wxGridCtrl::OnLabelLeftClick)
    EVT_GRID_SELECT_CELL(wxGridCtrl::OnSelectCell)
END_EVENT_TABLE();

void wxGridCtrl::DrawRowLabel(wxDC& dc, int row)
{
    if (GetRowHeight(row)< =0 || m_rowLabelWidth<=0)
        return;
    wxRect rect;
#ifdef __WXGTK20__
    rect.SetX(1);
    rect.SetY(GetRowTop(row)+1);
    rect.SetWidth(m_rowLabelWidth-2);
    rect.SetHeight(GetRowHeight(row)-2);
    CalcScrolledPosition(0, rect.y, NULL, &rect.y);
    wxWindowDC *win_dc=(wxWindowDC*)&dc;
    wxRendererNative::Get().DrawHeaderButton(win_dc->m_owner, dc, rect, 0);
#else
    int rowTop=GetRowTop(row),
        rowBottom=GetRowBottom(row)-1;
    dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW), 1, wxSOLID));
    dc.DrawLine(m_rowLabelWidth - 1, rowTop, m_rowLabelWidth - 1, rowBottom);
    dc.DrawLine(0, rowTop, 0, rowBottom);
    dc.DrawLine(0, rowBottom, m_rowLabelWidth, rowBottom);
    dc.SetPen(*wxWHITE_PEN);
    dc.DrawLine(1, rowTop, 1, rowBottom);
    dc.DrawLine(1, rowTop, m_rowLabelWidth - 1, rowTop);
#endif
    if (row==GetGridCursorRow()) {
        dc.DrawBitmap(wxBitmap(cursor_xpm),0,GetRowTop(row),true);
    }
}

void wxGridCtrl::OnLabelLeftClick(wxGridEvent& event)
{
    if (event.GetRow() != -1) {
        SetGridCursor(event.GetRow(),0);
    }
    event.Skip();
}

void wxGridCtrl::OnSelectCell(wxGridEvent& event)
{
    GetGridRowLabelWindow()->Refresh();
    event.Skip();
}

/// Override
bool wxGridCtrl::CreateGrid(int numRows, int numCols,
                            wxGrid::wxGridSelectionModes selmode)
{
    bool bCreate=wxGrid::CreateGrid(numRows, numCols, selmode);
    if (bCreate) 
    {
        for (int i=0; i < numRows; i++) 
        {
            m_mRowData[i]=0;
        }
    }
    return bCreate;
}

bool wxGridCtrl::AppendRows(int numRows, bool updateLabels)
{
    int nRow = GetNumberRows();
    bool bAdd = wxGrid::AppendRows(numRows, updateLabels);
    if (bAdd) 
    {
        while(nRow < GetNumberRows())
        {
            m_mRowData[nRow]=0;
            nRow++;
        }
    }
    return bAdd;
}

bool wxGridCtrl::InsertRows(int pos, int numRows, bool updateLabels)
{
    std::map< int, int > mRow;
    for (int i=0; i < GetNumberRows(); i++)
    {
        mRow[i]=GetRowData(i);
    }
    bool bIns=wxGrid::InsertRows(pos,numRows,updateLabels);
    if (bIns)
    {
        for (int i = pos + numRows; i < GetNumberRows(); i++)
        {
            m_mRowData[i]=mRow[i-numRows];
        }
        for (int i=pos; i < pos + numRows; i++)
        {
            m_mRowData[i]=0;
        }
    }
    return bIns;
}

bool wxGridCtrl::DeleteRows(int pos, int numRows, bool updateLabels)
{
    std::map< int, int > mRow;
    for (int i = 0; i < GetNumberRows(); i++)
    {
        mRow[i] = GetRowData(i);
    }
    bool bDel = wxGrid::DeleteRows(pos,numRows,updateLabels);
    if (bDel)
    {
        for (int i = pos; i < GetNumberRows(); i++)
        {
            m_mRowData[i] = mRow[i + numRows];
        }
    }
    return bDel;
}

/// New
void wxGridCtrl::SetRowData(const int& row, const int& data)
{
    if (row < GetNumberRows())
    {
        m_mRowData[row] = data;
    }
}

int wxGridCtrl::GetRowData(const int& row)
{
    int data = 0;
    if (row < GetNumberRows() && m_mRowData.find(row) != m_mRowData.end())
    {
        data = m_mRowData[row];
    }
    return data;
}


Что касается цветов, это я не включил в класс. Мало ли как кому нужно будет?

Пример использования примерно следующий:

Exmple.cpp


#include "wxGridCtrl.h"
/* ... */
    m_pGrid = new wxGridCtrl(this, wxID_ANY);
    m_pGrid->CreateGrid(0,0);
    m_pGrid->SetRowLabelSize(20);
    m_pGrid->SetColLabelSize(20);
    m_pGrid->DisableDragRowSize();
    m_pGrid->SetDefaultCellBackgroundColour(wxColour(128,128,128));

    m_pGrid->AppendCols(3);
    m_pGrid->SetColLabelValue(0,wxT("A"));
    m_pGrid->SetColLabelValue(1,wxT("B"));
    m_pGrid->SetColLabelValue(2,wxT("C"));

    wxGridCellAttr* pAttr = new wxGridCellAttr;
    pAttr->SetBackgroundColour(wxColour(255,255,255));

    wxGridCellAttr* pAttrBool = new wxGridCellAttr;
    pAttrBool->SetBackgroundColour(wxColour(255,255,255));
    pAttrBool->SetRenderer(new wxGridCellBoolRenderer());
    pAttrBool->SetEditor(new wxGridCellBoolEditor());
    pAttrBool->SetAlignment(wxALIGN_CENTRE,wxALIGN_CENTRE);

    m_pGrid->SetColAttr(0,pAttr);
    m_pGrid->SetColAttr(1,pAttr);
    m_pGrid->SetColAttr(2,pAttrBool);
/* ... */

Leave a Reply

Your email address will not be published. Required fields are marked *

Please leave these two fields as-is: