Страничка программиста

Захотелось мне как-то, чтобы в гриде были не номера строк, а маркер. К тому же очень хотелось, чтобы незаполненное пространство грида было не белым, а, примерно, как на рисунке ниже.

Навеяно это было в основном аналогичными и другими классами, предоставляемыми 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<int,int> 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);
/* ... */

Пишите автору




wxGridCtrl