How to embed button in wxDataViewListCtrl

  |   Source

Before creating wxDataViewListCtrl, it's better to detect information about text size so that we can set the column width and height intelligently.

Here is the code:

void GuessRowSize(int* w, int* h) {
    //@see http://sourceforge.net/apps/trac/codelite/browser/trunk/LiteEditor/new_build_tab.cpp?rev=5804

// Determine the row height
    wxBitmap tmpBmp(1, 1);
    wxMemoryDC memDc;
    memDc.SelectObject(tmpBmp);
    wxFont f = wxSystemSettings::GetFont(wxSYS_ANSI_FIXED_FONT);
    int xx, yy;
    memDc.GetTextExtent(wxT("Tp"), &xx, &yy, NULL, NULL, &f);

    //enough height for ICON
    *h=yy<16? 16: yy;

    memDc.GetTextExtent("Wp", &xx, &yy, NULL, NULL, &f);
    *w=xx/2;

    return;
}
wxDataViewListCtrl* ctrl=new wxDataViewListCtrl(parent,-1);
ctrl->Create(parent,id,wxDefaultPosition,wxDefaultSize);
int w,h;
GuessRowSize(&w,&h);
//hard code width may not be good, may be can use w ,h
AppendTextColumn(_T("Column 1"),wxDATAVIEW_CELL_INERT,200 /*width*/);
//must be called after Create()
ctrl->SetRowHeight(h+6);


// well I need provide button render by myself
class wxDataViewMyButtonRenderer: public wxDataViewCustomRenderer, public wxTimer
{
public:
    wxDataViewMyButtonRenderer( const wxString &varianttype = wxT("wxString"),
                              int align = wxDVR_DEFAULT_ALIGNMENT );

    virtual bool SetValue( const wxVariant &value );
    virtual bool GetValue( wxVariant &value ) const;

    virtual bool Render( wxRect, wxDC*, int);
    virtual wxSize GetSize() const;
    virtual void Notify();
    void SetParent(GalleryListView*);
    // Implementation only, don't use nor override
    virtual bool ActivateCell(const wxRect& rect,
        wxDataViewModel *model,
        const wxDataViewItem& item,
        unsigned int col,
        const wxMouseEvent *mouseEvent);

private:
    wxString m_value;
    bool m_button_clicked;
    wxRect m_cell_rect;
    GalleryListView* m_parent;

protected:
    DECLARE_DYNAMIC_CLASS_NO_COPY(wxDataViewMyButtonRenderer)
};

When user click one row, the ActivateCell is called, we can use some rect detect algorithm to find if the button rect is clicked.

There is no mouse up event handler! So I have to hack, basically start a wxTimer when button clicked and draw the button up effect after about 1 second in Notify(). That's why I ask wxDataViewMyButtonRenderer to inherit from wxTimer.

To draw the button up effect, I need ask the parent wxDataViewListCtrl to refresh itself, so that the button's Render() method has a chance to be called. That's why we need SetParent().

Here is the part of implementation:

bool
wxDataViewMyButtonRenderer::ActivateCell(const wxRect& rect,
    wxDataViewModel *model,
    const wxDataViewItem& item,
    unsigned int col,
    const wxMouseEvent *mouseEvent)
{
    wxDataViewListStore* store=(wxDataViewListStore*) model;

    if ( mouseEvent ) {
        if ( !wxRect(GetSize()).Contains(mouseEvent->GetPosition()) ){
            return false;
        }
        wxVariant item_value_pdf;
        store->GetValueByRow(item_value_pdf , store->GetRow(item),0);
        wxVariant item_value_created;
        store->GetValueByRow(item_value_created , store->GetRow(item),1);

        m_button_clicked=true;
        m_cell_rect=rect;
    } else {
        wxLogDebug(_T("Sorry, I don't handle keyboard"));
    }

    return true;
}

bool
wxDataViewMyButtonRenderer::Render( wxRect rect, wxDC *dc, int state )
{
    wxLogDebug(_T("Render called"));
    // Ensure that the check boxes always have at least the minimal required
    // size, otherwise DrawCheckBox() doesn't really work well. If this size is
    // greater than the rect size, the checkbox will be truncated but this is a
    // lesser evil.
    wxSize size = rect.GetSize();
    size.IncTo(GetSize());
    rect.SetSize(size);

    // draw button
    if(m_button_clicked==true && rect.Intersects(m_cell_rect)){
        //draw the button when clicked
        dc->SetBrush(wxBrush(wxColour(65, 150, 65), wxBRUSHSTYLE_SOLID));
        dc->SetPen( *wxBLACK_PEN );
        dc->DrawRectangle(rect);

        // draw button push down effect. since we cannot detect mouse up event, we use
        // timer to draw it
        const int time_to_button_up=350;
        StartOnce(time_to_button_up);
    } else {
        // draw normal button
        dc->SetBrush(wxBrush(wxColour(84, 174, 84), wxBRUSHSTYLE_SOLID));
        dc->SetPen( *wxBLACK_PEN );
        dc->DrawRectangle(rect);

        //draw inner white border
        rect.Deflate(1);
        dc->SetPen( *wxWHITE_PEN );
        dc->DrawRectangle(rect);
    }

    dc->SetTextForeground(*wxWHITE);
    dc->DrawLabel(m_value,wxRect(dc->GetTextExtent(m_value)).CentreIn(rect));

    return true;
}
Comments powered by Disqus