Which files I need to modify to customize wxOwnerDrawnComboBox? Topic is solved

Are you writing your own components and need help with how to set them up or have questions about the components you are deriving from ? Ask them here.
Tapsa
Earned some good credits
Earned some good credits
Posts: 144
Joined: Tue Dec 06, 2011 5:52 pm
Location: Helsinki

Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by Tapsa » Mon Oct 03, 2016 3:35 pm

Hi

I am making for me a version of wxOwnerDrawnComboBox whose m_strings member is std::shared_ptr<wxArrayString>.
I located files odcombo.h and odcombo.cpp, but what for is odcombocmn.cpp? Do I need to copy that as well?
I think I only need to copy those odcombo files into my project and adjust them for a bit, leaving no need to recompile whole wxWidgets.

I'm getting hundreds of "redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]" warnings. How do I get rid of them?

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2057
Joined: Sun Jan 03, 2010 5:45 pm

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by PB » Mon Oct 03, 2016 5:29 pm

Hi,

I assume that the original real issue is not excessive memory consumption caused by duplicating the strings for each control but (as already reported here) the long time it on MSW takes to create a huge number of comboboxes containing many items? If so, you may consider a different approach. The code below creates 50 regular wxComboCtrls and 50 default wxOwnerDrawnComboBoxes, each having the same 100 strings, see the difference:
cbx.png
cbx.png (20.14 KiB) Viewed 3104 times
On my PC, according to wxStopWatch, creating a single wxComboBox (with passing wxArray with 100 strings to its constructor) takes about 50 ms while creating the default owner drawn combobox the exactly same way takes about 2 ms.

The code, please test on your PC if you get a similar difference between the regular and default owner drawn combobox:

Code: Select all

#include <wx/wx.h>
#include <wx/odcombo.h>

class MyFrame : public wxFrame
{
public:
    MyFrame()
        : wxFrame(NULL, wxID_ANY, _("Test"), wxDefaultPosition, wxSize(600, 800))
    {                              
        const size_t stringCount = 100;
        wxArrayString strings;

        strings.reserve(stringCount);        
        for ( size_t i = 0; i < stringCount; i++ )            
        {
            wxString s;
            s.Printf(_T("%d ab cdeXYZ fghijk lmnop %d"), rand(), rand());
            s.Shrink();
            strings.push_back(s);
        }
                
        wxPanel* panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
        wxBoxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL);
        wxBoxSizer* cbSizer = new wxBoxSizer(wxVERTICAL);
        wxBoxSizer* odSizer = new wxBoxSizer(wxVERTICAL);
        wxStopWatch watch;        
        
        const size_t comboCount = 50;                         
        
        long cbTime;        
        
        watch.Start();

        for (size_t i = 0; i < comboCount; i++) {
            wxStopWatch watchControl;
            wxComboBox* comboBox = new wxComboBox(panel, wxID_ANY, _T("Combo!"), wxDefaultPosition, wxDefaultSize, strings, wxCB_READONLY);
            wxLogDebug("wxComboBox #%zu created in %ld ms.", i + 1, watchControl.Time());
            comboBox->Select(0);
            cbSizer->Add(comboBox, 0, wxALL, 5);            
        }

        cbTime = watch.Time();        
         
        long odTime;        
        
        watch.Start();
        for (size_t i = 0; i < comboCount; i++) {
            wxStopWatch watchControl;                       
            wxOwnerDrawnComboBox* odComboBox = new wxOwnerDrawnComboBox(panel, wxID_ANY, _T("ODCombo!"), wxDefaultPosition, wxDefaultSize, strings, wxCB_READONLY | wxODCB_STD_CONTROL_PAINT);                         
            wxLogDebug("wxOwnerDrawnComboBox #%zu created in %ld ms.", i + 1, watchControl.Time());
            odComboBox->Select(0);            
            odSizer->Add(odComboBox, 0, wxALL, 5);
        }
        odTime = watch.Time();

        wxLogMessage("Created %zu comboboxes in %ld ms.\n Created %zu wxOwnerDrawnComboBoxes in %ld ms.\n", 
            comboCount, cbTime, comboCount, odTime);

        mainSizer->Add(cbSizer, 0, wxALL, 5);
        mainSizer->Add(odSizer, 0, wxALL, 5);
        panel->SetSizerAndFit(mainSizer);
        Layout();
    }   
};

class MyApp : public wxApp
{
public:     
    virtual bool OnInit()
    {        
        (new MyFrame())->Show();
        return true;
    }
};
wxIMPLEMENT_APP(MyApp);
Last edited by PB on Mon Oct 03, 2016 6:09 pm, edited 1 time in total.

Tapsa
Earned some good credits
Earned some good credits
Posts: 144
Joined: Tue Dec 06, 2011 5:52 pm
Location: Helsinki

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by Tapsa » Mon Oct 03, 2016 6:09 pm

I'm already using wxOwnerDrawnComboBoxes :)
Believe it or not, the strings take half of the memory my program uses.

In any case, I seem to need odcombo.h, odcombo.cpp and odcombocmn.cpp to customize wxOwnerDrawnComboBox to anything I want.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2057
Joined: Sun Jan 03, 2010 5:45 pm

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by PB » Mon Oct 03, 2016 6:14 pm

Tapsa wrote:I'm already using wxOwnerDrawnComboBoxes :)
I know, I meant simply using the provided implementation without additional trouble.

TBH, I personally wouldn't bother with saving a megabyte, two, or three in a rich GUI desktop app in this age, however wasteful the implementation may seem, and I learned to program on a PC with a 1 MB RAM in total... But I'm old, if I were young I can imagine being so wasteful would bother me. ;)

Tapsa
Earned some good credits
Earned some good credits
Posts: 144
Joined: Tue Dec 06, 2011 5:52 pm
Location: Helsinki

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by Tapsa » Tue Oct 04, 2016 11:38 am

We are talking about 40 to 160 MB of wasted memory. Qt also has this kind of functionality (model-view system) implemented long ago.

New Pagodi
Super wx Problem Solver
Super wx Problem Solver
Posts: 307
Joined: Tue Jun 20, 2006 6:47 pm
Contact:

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by New Pagodi » Wed Oct 05, 2016 3:48 am

Tapsa wrote:We are talking about 40 to 160 MB of wasted memory. Qt also has this kind of functionality (model-view system) implemented long ago.
Would it help to create a custom popup that uses a virtual listctrl? That way, each combobox won't need to store a copy of the set of strings.

I'm not sure I understand what you're trying to do, so sorry if that's a useless suggestion.

Tapsa
Earned some good credits
Earned some good credits
Posts: 144
Joined: Tue Dec 06, 2011 5:52 pm
Location: Helsinki

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by Tapsa » Wed Oct 05, 2016 1:51 pm

Yes indeed! I am currently copying and editing wx codes to achieve "virtual" popup list.

User avatar
doublemax
Moderator
Moderator
Posts: 14098
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by doublemax » Wed Oct 05, 2016 2:05 pm

wxOwnerDrawnComboBox uses a wxVListBox internally which is a "virtual" control. It's just not exposed in the public API.
Use the source, Luke!

Tapsa
Earned some good credits
Earned some good credits
Posts: 144
Joined: Tue Dec 06, 2011 5:52 pm
Location: Helsinki

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by Tapsa » Wed Oct 05, 2016 2:42 pm

Can I already use it as such via some undocumented functions?

I tried to set the item count in my wxODCB subclassed constructor along with custom OnDrawItem function, but the program segmentation faults at vlbox.cpp file on line 116, which makes no sense since there aren't even any pointers there.

Code: Select all

GetVListBoxComboPopup()->wxVListBox::SetItemCount(10);
Program received signal SIGSEGV, Segmentation fault.
0x65915674 in wxVListBox::SetItemCount (this=0x0, count=10)
at ../../src/generic/vlbox.cpp:116
116 if ( m_current != wxNOT_FOUND && (size_t)m_current >= count )
(gdb) bt
#0 0x65915674 in wxVListBox::SetItemCount (this=0x0, count=10)
at ../../src/generic/vlbox.cpp:116
Does this mean that SetItemCount is operating on NULL object?

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2057
Joined: Sun Jan 03, 2010 5:45 pm

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by PB » Wed Oct 05, 2016 5:12 pm

Can't you just use a custom pop-up (e.g. a wxVListBox or wxListView with wxLC_REPORT and wxLC_VIRTUAL) for a wxComboCtrl, as shown in the comboctrl sample? It may need some tweaking to match the native appearance and the behaviour of the default combo pop-up but may still be worth a try.

Tapsa
Earned some good credits
Earned some good credits
Posts: 144
Joined: Tue Dec 06, 2011 5:52 pm
Location: Helsinki

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by Tapsa » Thu Oct 06, 2016 3:45 pm

I have tried to do that with wxVListBox for one full day, but I can no longer even get any drop box appear.

New Pagodi
Super wx Problem Solver
Super wx Problem Solver
Posts: 307
Joined: Tue Jun 20, 2006 6:47 pm
Contact:

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by New Pagodi » Thu Oct 06, 2016 6:14 pm

Tapsa wrote:I have tried to do that with wxVListBox for one full day, but I can no longer even get any drop box appear.
Here's a sample I threw together which uses a VListBox as a custom popup for a ComboCtrl. You would obviously need different overrides for getting and setting the strings.

Code: Select all

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include <wx/vlbox.h>
#include <wx/combo.h>

class wxVListComboPopup : public wxVListBox, public wxComboPopup
{
    public:
        //wxComboPopup virtual methods
        // Initialize member variables
        virtual void Init()
        {
            m_textHt=GetTextExtent("item 0").GetHeight();
        }

        // Create popup control
        virtual bool Create(wxWindow* parent)
        {
            SetItemCount(20);
            Bind(wxEVT_LEFT_UP,&wxVListComboPopup::OnMouseClick,this);
            Bind(wxEVT_MOTION,&wxVListComboPopup::OnMouseMove,this);
            return wxVListBox::Create(parent);
        }

        // Return pointer to the created control
        virtual wxWindow *GetControl() { return this; }

        // Translate string into a list selection
        virtual void SetStringValue(const wxString& s)
        {
            wxString temp=s;
            temp.Replace("item ","");
            long tlong;
            temp.ToLong(&tlong);

            SetSelection(tlong);
        }

        // Get list selection as a string
        virtual wxString GetStringValue() const
        {
            int i = GetSelection();
            if(i==wxNOT_FOUND)
            {
                return wxEmptyString;
            }
            else
            {
                return GetString(i);
            }
        }

        //wxVListBox virtual methods
        virtual wxCoord OnMeasureItem (size_t n) const
        {
            return m_textHt;
        }

        virtual void OnDrawItem (wxDC &dc, const wxRect &rect, size_t n) const
        {
            dc.DrawText(GetString(n),rect.GetLeft(),rect.GetTop());
        }

        //MouseHelpers
        // Do mouse hot-tracking (which is typical in list popups)
        void OnMouseMove(wxMouseEvent& event)
        {
            int i=VirtualHitTest(event.GetY());
            if(i!=wxNOT_FOUND)
            {
                SetSelection(i);
            }
        }

        // On mouse left up, set the value and close the popup
        void OnMouseClick(wxMouseEvent& WXUNUSED(event))
        {
            // TODO: Send event as well
            Dismiss();
        }

    private:
        wxString GetString(int n) const {return wxString::Format("item %d",n);}
        int m_textHt;
};

class comboctrlFrame : public wxFrame
{
    public:

        comboctrlFrame( wxWindow* parent, wxWindowID id = wxID_ANY,
                 const wxString& title = wxEmptyString,
                 const wxPoint& pos = wxDefaultPosition,
                 const wxSize& size = wxSize( 481,466 ),
                 long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );

    private:
        wxComboCtrl* comboCtrl;

};

comboctrlFrame::comboctrlFrame( wxWindow* parent, wxWindowID id,
      const wxString& title,const wxPoint& pos, const wxSize& size, long style)
      :wxFrame(parent,id,title)
{
    comboCtrl = new wxComboCtrl(this, wxID_ANY, wxEmptyString);
    comboCtrl->SetPopupControl(new wxVListComboPopup());

    wxBoxSizer* bSizer1 = new wxBoxSizer( wxVERTICAL );
    bSizer1->Add( comboCtrl , 0, wxALL, 5 );
    SetSizer(bSizer1);
}

class comboctrlApp : public wxApp
{
    public:
        virtual bool OnInit()
        {
            comboctrlFrame* frame = new comboctrlFrame(0L);
            frame->Show();

            return true;
        }
};

wxIMPLEMENT_APP(comboctrlApp);
I hope that helps.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2057
Joined: Sun Jan 03, 2010 5:45 pm

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by PB » Thu Oct 06, 2016 6:58 pm

And here is another approach (I created it before I noticed NewPagodi's post), using wxListView in virtual report mode, based on simple modification of the comboctrl sample. It certainly needs a polishing but I think it could be used as a base. OTOH, there may be an issue hard to work around, difficult to say without using the control in the real application...

Code: Select all

#include <wx/wx.h>
#include <wx/combo.h>
#include <wx/listctrl.h>

#include <memory>

typedef std::shared_ptr<wxArrayString> wxSharedArrayString;


class wxMyComboPopup : public wxListView, public wxComboPopup
{
public:                
    virtual void Init() wxOVERRIDE
    {
        m_value = -1;
        m_itemHere = -1; // hot item in list

        Bind(wxEVT_MOTION, &wxMyComboPopup::OnMouseMove, this);
        Bind(wxEVT_LEFT_DOWN, &wxMyComboPopup::OnMouseClick, this);
        Bind(wxEVT_CHAR, &wxMyComboPopup::OnChar, this);
    }
    
    virtual bool Create(wxWindow* parent) wxOVERRIDE
    {        
/*      This would create a pop-up with strings in columns  
        return wxListView::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 
            wxLC_LIST | wxLC_VIRTUAL | wxLC_SINGLE_SEL | wxSIMPLE_BORDER);        
*/
       if ( wxListView::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 
                wxLC_REPORT | wxLC_VIRTUAL | wxLC_SINGLE_SEL | wxLC_NO_HEADER | wxSIMPLE_BORDER) )
       {
            AppendColumn(("Dummy"));
            SetColumnWidth(0, GetClientSize().GetWidth());
            return true;
       }
       return false;
    }

    void SetStrings(const wxSharedArrayString& strings)
    {
        wxASSERT( strings.get() && !strings->empty() );
        
        m_strings = strings;
        SetItemCount(m_strings->size());        
    }

    virtual void OnPopup() wxOVERRIDE
    {        
        if ( m_value != wxNOT_FOUND )    
            EnsureVisible(m_value);        

        wxComboPopup::OnPopup();
    }

    // Return pointer to the created control
    virtual wxWindow *GetControl()  wxOVERRIDE { return this; }

    virtual wxString OnGetItemText(long item, long column) const wxOVERRIDE
    {                        
        wxCHECK( (size_t)item < m_strings->size() && column == 0, wxEmptyString );

        return (*m_strings)[item];
    }
    
    // Translate string into a list selection
    virtual void SetStringValue(const wxString& s) wxOVERRIDE
    {        
        int n = wxListView::FindItem(-1, s);
        if ( n != wxNOT_FOUND && n < wxListView::GetItemCount() )
        {
            m_value = n;
            wxListView::Select(n);
        }
    }
    
    // Get list selection as a string
    virtual wxString GetStringValue() const wxOVERRIDE
    {                
        if ( m_value >= 0 )  
        { 
            wxASSERT( (size_t)m_value < m_strings->size() );
            return (*m_strings)[(size_t)m_value];
        }
            
        return wxEmptyString;
    }
protected:
    wxSharedArrayString m_strings;

    int m_value; // current item index
    int m_itemHere; // hot item in popup 

        
    // Do mouse hot-tracking (which is typical in list popups)
    void OnMouseMove(wxMouseEvent& event)
    {
        // Move selection to cursor if it is inside the popup
        int resFlags = wxLIST_HITTEST_ONITEM;
        int itemHere = HitTest(event.GetPosition(), resFlags);
        if ( itemHere != wxNOT_FOUND )
        {
            wxListView::SetItemState(itemHere, 
                wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
            m_itemHere = itemHere;
        }
        event.Skip();
    }
    
    // On mouse left up, set the value and close the popup
    void OnMouseClick(wxMouseEvent&)
    {        
        ClosePopup(m_itemHere);    
    }

    void OnChar(wxKeyEvent& event)
    {
        if ( event.GetKeyCode() == WXK_RETURN )                    
            ClosePopup(GetNextItem(0, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED));        
        else
            event.Skip();
    } 

    void ClosePopup(int value)
    {
        // TODO: Send event as well
        if ( value != wxNOT_FOUND )
            m_value = value;
        Dismiss();
    }
};

class MyFrame : public wxFrame
{
public:
    MyFrame()
        : wxFrame(NULL, wxID_ANY, _("Test"), wxDefaultPosition, wxSize(1200, 800))
    {                              
        const size_t stringCount = 100 * 1000;
        wxSharedArrayString strings(new wxArrayString());

        strings->reserve(stringCount);        
        for ( size_t i = 0; i < stringCount; i++ )                                
            strings->push_back(wxString::Format("Item %zu: This is just a rest of the string", i+1));        
                
        const size_t colCount = 8;
        const size_t comboCount = colCount * 25;

        wxPanel* panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);        
        wxGridSizer* cbSizer = new wxGridSizer(colCount, 5, 5);
              
        for ( size_t i = 0; i < comboCount; i++ )
        { 
           wxComboCtrl* cc = new wxComboCtrl(panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCB_READONLY);        
            cc->UseAltPopupWindow();
            cc->SetPopupMinWidth(300);

            wxMyComboPopup* pp = new wxMyComboPopup();                
            cc->SetPopupControl(pp); 
            pp->SetStrings(strings);

            cc->SetValue((*strings)[0]);

            cbSizer->Add(cc);
        }        

        panel->SetSizer(cbSizer);
    }   
};

class MyApp : public wxApp
{
public:     
    virtual bool OnInit()
    {        
        (new MyFrame())->Show();
        return true;
    }
};
wxIMPLEMENT_APP(MyApp);

Tapsa
Earned some good credits
Earned some good credits
Posts: 144
Joined: Tue Dec 06, 2011 5:52 pm
Location: Helsinki

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by Tapsa » Fri Oct 07, 2016 12:29 pm

Thanks guys. I'm getting forward now :)

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2057
Joined: Sun Jan 03, 2010 5:45 pm

Re: Which files I need to modify to customize wxOwnerDrawnComboBox?

Post by PB » Fri Oct 07, 2016 8:25 pm

I looked into it a bit more and solutions using a custom pop-up with wxComboCtrl compared to wxComboBox lacks so many features from both the user and programmer point of view....

I believe your original approach was better, i.e., to copy and modify the code of wxOwnerDrawnComboBox.

Post Reply