Page 1 of 1

wxTreeCtrl and multiply displayed items - Saving memory

Posted: Mon Apr 10, 2006 10:24 am
by Thelvyn
I have a tree control where I display thousands of items from a database.
Thing is objects can be displayed in multiple places at the same time depending on the objects attributes.

So I wrote this class, well two but one is negligable and only used as a base class for objects, could also rewrite it as a template but would still need to support two functions to work properly with this class.

Using boost::shared_ptr here so that I can share pointers to objects, this reduces overhead and simplifies the objects also for usage in std::vector.

I just put this together and while it works fine for me I guarantee nothing :)

First the base class for objects, could just as easily move the ctor/dtor to the header as they do nothing anyway.

As you can see WriteTree and GetName are requirements.

Code: Select all

/* TreeObjectBase.hpp
 *
 * Copyright (c) 2006
 * Richard V. Day
 * Email : richardvday@yahoo.com
 *
 * This material is provided "as is", with absolutely no warranty expressed
 * or implied. Any use is at your own risk.
 *
 * Permission to use or copy this software for any purpose is hereby granted 
 * without fee, provided the above notices are retained on all copies.
 * Permission to modify the code and to distribute modified code is granted,
 * provided the above notices are retained, and a notice that the code was
 * modified is included with the above copyright notice.
 *
 *
 */

#ifndef _CTreeObjectBase_hpp
#define _CTreeObjectBase_hpp

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CTreeObjectBase  
{
public:
  virtual bool WriteTree( wxTreeCtrl* tree, wxTreeItemId id, wxTreeItemId* newId ) = 0;
  virtual const wxString& GetName() const = 0;
  CTreeObjectBase();
  virtual ~CTreeObjectBase();
};

#endif // #ifndef _CTreeObjectBase_hpp

Code: Select all

/* TreeObjectBase.cpp
 *
 * Copyright (c) 2006
 * Richard V. Day
 * Email : richardvday@yahoo.com
 *
 * This material is provided "as is", with absolutely no warranty expressed
 * or implied. Any use is at your own risk.
 *
 * Permission to use or copy this software for any purpose is hereby granted 
 * without fee, provided the above notices are retained on all copies.
 * Permission to modify the code and to distribute modified code is granted,
 * provided the above notices are retained, and a notice that the code was
 * modified is included with the above copyright notice.
 *
 *
 */
#include "wxprec.h"
#include "TreeObjectBase.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CTreeObjectBase::CTreeObjectBase(){
}

CTreeObjectBase::~CTreeObjectBase(){
}
Now the real code where real work is done.

Code: Select all

/* TreeObjectDisplay.hpp
 *
 * Copyright (c) 2006
 * Richard V. Day
 * Email : richardvday@yahoo.com
 *
 * This material is provided "as is", with absolutely no warranty expressed
 * or implied. Any use is at your own risk.
 *
 * Permission to use or copy this software for any purpose is hereby granted 
 * without fee, provided the above notices are retained on all copies.
 * Permission to modify the code and to distribute modified code is granted,
 * provided the above notices are retained, and a notice that the code was
 * modified is included with the above copyright notice.
 *
 *
 */

#ifndef _CTreeObjectDisplay_hpp
#define _CTreeObjectDisplay_hpp

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CTreeObjectBase;
class CTreeObjectDisplay
{
public:
	size_t GetObjectSize() const;
	size_t GetCategorySize() const;
	CTreeObjectBase* GetObject( size_t Index );
	wxString& GetName();
	void AddCategory( boost::shared_ptr<CTreeObjectDisplay> NewCategory );
	void AddObject( boost::shared_ptr<CTreeObjectBase> NewObject );

	wxTreeItemId GetTreeId(); // return our id
	bool AppendAfter( wxTreeCtrl* tree, wxTreeItemId Parent );// tells us where to append ourselves
  // need more then add em later good for now
	
	
	CTreeObjectDisplay* ContainsId( wxTreeItemId Id );// is it contained ie a child of us
	bool IsId( wxTreeItemId Id ); // is this us ?
  CTreeObjectDisplay* operator [](size_t Index);
  bool operator==( wxTreeItemId Id );
  enum _state {
    expanded,
    collapsed
  };
	CTreeObjectDisplay();
  CTreeObjectDisplay( wxString DisplayName );
	virtual ~CTreeObjectDisplay();
  // overide these if you need different then default processing
  // return value is not used at this time
  bool Collapsing( wxTreeItemId Item );// find the id
  bool Collapsing(); // its this instance
  bool Expanding( wxTreeItemId Item ); // find the id
  bool Expanding(); // its this instance
protected:
  virtual void Init();
  _state m_state;
  wxString m_name;
  wxTreeCtrl* m_tree;
  wxTreeItemId m_treeId;// if we are collapsed this may not be valid
  wxTreeItemId m_parent;
  std::vector< boost::shared_ptr<CTreeObjectBase> > m_objects;
  std::vector< boost::shared_ptr<CTreeObjectDisplay> > m_categorys;
};

#endif // #ifndef _CTreeObjectDisplay_hpp
and the implementation

Code: Select all

/* TreeObjectDisplay.cpp
 *
 * Copyright (c) 2006
 * Richard V. Day
 * Email : richardvday@yahoo.com
 *
 * This material is provided "as is", with absolutely no warranty expressed
 * or implied. Any use is at your own risk.
 *
 * Permission to use or copy this software for any purpose is hereby granted 
 * without fee, provided the above notices are retained on all copies.
 * Permission to modify the code and to distribute modified code is granted,
 * provided the above notices are retained, and a notice that the code was
 * modified is included with the above copyright notice.
 *
 *
 */
#include "wxprec.h"
#include "TreeObjectDisplay.h"
#include "TreeObjectBase.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
#include <stdexcept>
#include <string>

CTreeObjectDisplay::CTreeObjectDisplay( wxString DisplayName ) {
  Init();
  m_name = DisplayName;
}

CTreeObjectDisplay::CTreeObjectDisplay() {
  Init();
}

CTreeObjectDisplay::~CTreeObjectDisplay() {
}

void CTreeObjectDisplay::Init() {
  m_name.Empty();
  m_treeId.Unset();//invalidates
  m_objects.clear();
  m_categorys.clear();
  m_state = collapsed;
  m_tree = NULL;
}

bool CTreeObjectDisplay::Collapsing() {
//  assert( m_state == expanded );
  
  assert( NULL != m_tree );
  if( NULL == m_tree ) {
    return false;
  }
  assert( m_treeId.IsOk() );
  if( !m_treeId.IsOk() ) {
    return false;
  }
  m_state = collapsed;

  // notify child items(Objects dont know about this so not them)
  // Objects know absolutely nothing about us nor should they
  int len = m_categorys.size();
  for( int index = 0; index < len; index++ ) {
    m_categorys[index]->Collapsing( m_categorys[index]->GetTreeId() );
  }
  m_tree->CollapseAndReset( m_treeId );
  if( m_categorys.size() || m_objects.size() ) {
    m_tree->SetItemHasChildren( m_treeId, true );
  }
  return true;
}

bool CTreeObjectDisplay::Collapsing( wxTreeItemId Item ) {
  assert( NULL != m_tree );
  if( NULL == m_tree ) {
    return false;
  }
  assert( m_treeId.IsOk() );
  if( !m_treeId.IsOk() ) {
    return false;
  }
  assert( Item.IsOk() );
  if( !Item.IsOk() ) {
    return false;
  } else if( m_treeId == Item ) {  // is it us ?
    return Collapsing();
  } else { // ok not us a child perhaps?
    CTreeObjectDisplay* rc = ContainsId( Item );
    if( NULL != rc ) {
      return rc->Collapsing( Item );
    }
  }
  // Objects dont count we dont collapse them by themselves
  // hence false is not necessarily an error
  return false;
}

// insert this obj after Parent
bool CTreeObjectDisplay::AppendAfter( wxTreeCtrl *tree, wxTreeItemId Parent ) {
  assert( NULL != tree );
  if( NULL == tree ) {
    return false;
  }
  wxTreeItemId Found;
  m_tree = tree;
  // EDIT: Removed call to external utility function not really necessary here anyway
  m_treeId = m_tree->AppendItem( Parent, m_name );
  if( m_tree->IsExpanded( m_treeId ) ) {
    m_state = expanded;
  } else {
    m_state = collapsed;
    if( m_categorys.size() || m_objects.size() ) {
      m_tree->SetItemHasChildren( m_treeId, true );
    }
  }
  return m_treeId.IsOk();
}

// we MUST be a valid tree item when this is called
bool CTreeObjectDisplay::Expanding() {
  assert( m_state == collapsed );
  assert( NULL != m_tree );
  if( NULL == m_tree ) {
    return false;
  }
  assert( m_treeId.IsOk() );
  if( !m_treeId.IsOk() ) {
    return false;
  }
  m_state = expanded;
  // notify children items(Objects dont know about this so not them)
  // Objects know absolutely nothing about us
  int len = m_categorys.size();
  for( int index = 0; index < len; index++ ) {
    // they do get expanded by default
    // this MAY change in future
    m_categorys[index]->AppendAfter( m_tree, m_treeId );
  }
  // now objects
  len = m_objects.size();
  for( index = 0; index < len; index++ ) {
    wxTreeItemId newId; // we dont care about this at this time
    if( ! m_objects[index]->WriteTree( m_tree, m_treeId, &newId ) ) {
      // if it fails to do what?
      assert(0);
    }
  }
  return true;
}

bool CTreeObjectDisplay::Expanding( wxTreeItemId Item ) {
  assert( NULL != m_tree );
  if( NULL == m_tree ) {
    return false;
  }
  assert( m_treeId.IsOk() );
  if( !m_treeId.IsOk() ) {
    return false;
  }
  assert( Item.IsOk() );
  if( !Item.IsOk() ) {
    return false;
  } else if( m_treeId == Item ) {  // is it us ?
    return Expanding();
  } else { // ok not us a child perhaps?
    CTreeObjectDisplay* rc = ContainsId( Item );
    if( NULL != rc ) {
      return rc->Expanding( Item );
    }
  }
  return false;
}

// NOT recursive
bool CTreeObjectDisplay::IsId( wxTreeItemId Id ) {
  return Id == m_treeId;
}

bool CTreeObjectDisplay::operator==( wxTreeItemId Id ) {
  return IsId( Id );
}

// IS recursive
CTreeObjectDisplay* CTreeObjectDisplay::ContainsId( wxTreeItemId Id ) {
  CTreeObjectDisplay* rc = NULL;
  if( IsId( Id ) ) { // is it us ?
    return this;
  }
  int len = m_categorys.size();
  for( int index = 0; index < len; index++ ) {
    rc = m_categorys[index]->ContainsId( Id );
    if( NULL != rc ) {
      return rc;
    }
  }
  return rc;
}

wxTreeItemId CTreeObjectDisplay::GetTreeId() {
  return m_treeId;
}

void CTreeObjectDisplay::AddObject( boost::shared_ptr<CTreeObjectBase> NewObject ) {
  if( m_state == expanded ) {
    assert( NULL != m_tree );
    if( NULL != m_tree ) {
      assert( m_treeId.IsOk() );
      wxTreeItemId newId; // we dont care about this at this time
      if( ! NewObject->WriteTree( m_tree, m_treeId, &newId ) ) {
        // if it fails to do what?
        assert(0);
      }
      assert( newId.IsOk() );
    }
  }
  m_objects.push_back( NewObject );
  m_tree->SetItemHasChildren( m_treeId, true );
}

void CTreeObjectDisplay::AddCategory( boost::shared_ptr<CTreeObjectDisplay> NewCategory ) {
  if( m_state == expanded ) {
    assert( NULL != m_tree );
    if( NULL != m_tree ) {
      assert( m_treeId.IsOk() );
      if( m_treeId.IsOk() ) {
        wxTreeItemId nItem = m_tree->AppendItem( m_treeId, NewCategory->GetName() );
        assert( nItem.IsOk() );
      }
    }
  }
  m_tree->SetItemHasChildren( m_treeId, true );
  m_categorys.push_back( NewCategory );
}

wxString& CTreeObjectDisplay::GetName() {
  return m_name;
}

CTreeObjectBase* CTreeObjectDisplay::GetObject(size_t Index) {
  size_t len = m_objects.size();
  if( Index >= len ) {
    std::string msg = "CTreeObjectDisplay::GetObject Index out of range!";
    throw new std::range_error( msg );
  }
  return m_objects[Index].get();
}

CTreeObjectDisplay* CTreeObjectDisplay::operator [](size_t Index) {
  size_t len = m_categorys.size();
  if( Index >= len ) {
    std::string msg = "CTreeObjectDisplay::operator[] Index out of range!";
    throw new std::range_error( msg );
  }
  return m_categorys[Index].get();
}

size_t CTreeObjectDisplay::GetCategorySize() const {
  return m_categorys.size();
}

size_t CTreeObjectDisplay::GetObjectSize() const {
  return m_objects.size();
}

Now the categorys need to get events to be of any use.
I call from my frame handlers as an example

Code: Select all

void C2ObjectsFrame::OnTreeItemExpanding(wxTreeEvent &event) {
  m_categorys[0]->Expanding( event.GetItem() );
}

void C2ObjectsFrame::OnTreeItemCollapsed(wxTreeEvent &event) {
  m_categorys[0]->Collapsing( event.GetItem() );
}
m_categorys[0] is the root of my tree but could just as easily be a child.
To further reduce memory usage you could of course automagically collapse branches if they are not visible(I do but its not relevant to what I am discussing here)

Well I hope someone may find this of use :)

Richard V. Day
richardvday@yahoo.com
Edited : Removed extraneous call to global utility function that was no longer needed.

Posted: Tue Apr 11, 2006 2:09 am
by Thelvyn
Here is the revised template version.
Also I was thinking of making it match wxTreeCtrl's interface more closely
so that for all intents you could use this directly assuming that you have appended at least 1 item so it has the tree control pointer of course.

If there is any interest let me know and I will post that when complete otherwise I will not post any more changes :)

Code: Select all

/* TCTreeObjectDisplay.hpp
 *
 * Copyright (c) 2006
 * Richard V. Day
 * Email : richardvday@yahoo.com
 *
 * This material is provided "as is", with absolutely no warranty expressed
 * or implied. Any use is at your own risk.
 *
 * Permission to use or copy this software for any purpose is hereby granted 
 * without fee, provided the above notices are retained on all copies.
 * Permission to modify the code and to distribute modified code is granted,
 * provided the above notices are retained, and a notice that the code was
 * modified is included with the above copyright notice.
 *
 *
 *
 *  requires two interface functions
 *
 *  Onjects handle there own writing to the tree, could be very complex or simple we dont know or care
 *  newId is the id of the base of the object - where it is inserted at
 *  currently I do not use that but this is subject to change
 *  virtual bool WriteTree( wxTreeCtrl* tree, wxTreeItemId id, wxTreeItemId* newId );
 *
 *  this is the text that is displayed in the tree
 *  I may remove the necessity of providing this
 *  virtual const wxString& GetName() const { return m_name; }
 *
 */

#ifndef _TCTreeObjectDisplay_hpp
#define _TCTreeObjectDisplay_hpp

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

template< class T >
class TCTreeObjectDisplay  
{
public:
  enum _state { // default state is collapsed
    expanded,
    collapsed
  };
  TCTreeObjectDisplay( wxString DisplayName ) {
    Init();
    m_name = DisplayName;
  }
  TCTreeObjectDisplay() {
    Init();
  }
  
  ~TCTreeObjectDisplay() {// does nothing for now at least, prob never will
  }
  void Init() {// reset to known empty state
    m_name.Empty();
    m_treeId.Unset();//invalidates
    m_objects.clear();
    m_categorys.clear();
    m_state = collapsed;
    m_tree = NULL;
  }
  bool Collapsing() { // this item is collapsing, notify children if any, objects are not told at this point
    //  assert( m_state == expanded );
    assert( NULL != m_tree );
    if( NULL == m_tree ) {
      return false;
    }
    assert( m_treeId.IsOk() );
    if( !m_treeId.IsOk() ) {
      return false;
    }
    m_state = collapsed;
    // notify child items(Objects dont know about this so not them)
    // Objects know absolutely nothing about us nor should they
    int len = m_categorys.size();
    for( int index = 0; index < len; index++ ) {
      m_categorys[index]->Collapsing( m_categorys[index]->GetTreeId() );
    }
    m_tree->CollapseAndReset( m_treeId );
    if( m_categorys.size() || m_objects.size() ) {
      m_tree->SetItemHasChildren( m_treeId, true );
    }
    return true;
  }
  bool Collapsing( wxTreeItemId Item ) { // some item is collapsing,if its us handle it, if its a child notify it
    assert( NULL != m_tree );
    if( NULL == m_tree ) {
      return false;
    }
    assert( m_treeId.IsOk() );
    if( !m_treeId.IsOk() ) {
      return false;
    }
    assert( Item.IsOk() );
    if( !Item.IsOk() ) {
      return false;
    } else if( m_treeId == Item ) {  // is it us ?
      return Collapsing();
    } else { // ok not us a child perhaps?
      TCTreeObjectDisplay<T>* rc = ContainsId( Item );
      if( NULL != rc ) {
        return rc->Collapsing( Item );
      }
    }
    // Objects dont count we dont collapse them by themselves
    // hence false is not necessarily an error
    return false;
  }
  bool AppendAfter( wxTreeCtrl *tree, wxTreeItemId Parent ) {// insert this obj as child of Parent item
    assert( NULL != tree );
    if( NULL == tree ) {
      return false;
    }
    wxTreeItemId Found;
    m_tree = tree;
    m_treeId = m_tree->AppendItem( Parent, m_name );
    if( m_tree->IsExpanded( m_treeId ) ) {
      m_state = expanded;
    } else {
      m_state = collapsed;
      if( m_categorys.size() || m_objects.size() ) {
        m_tree->SetItemHasChildren( m_treeId, true );
      }
    }
    return m_treeId.IsOk();
  }
  // we MUST be a valid tree item when this is called
  bool Expanding() {
    assert( m_state == collapsed );
    assert( NULL != m_tree );
    if( NULL == m_tree ) {
      return false;
    }
    assert( m_treeId.IsOk() );
    if( !m_treeId.IsOk() ) {
      return false;
    }
    m_state = expanded;
    // notify children items(Objects dont know about this so not them)
    // Objects know absolutely nothing about us
    int len = m_categorys.size();
    for( int index = 0; index < len; index++ ) {
      // they do NOT get expanded by default
      // this MAY change in future
      m_categorys[index]->AppendAfter( m_tree, m_treeId );
    }
    // now objects
    len = m_objects.size();
    for( index = 0; index < len; index++ ) {
      wxTreeItemId newId; // we dont care about this at this time
      if( ! m_objects[index]->WriteTree( m_tree, m_treeId, &newId ) ) {
        // if it fails to do what?
        assert(0);
      }
    }
    return true;
  }
  bool Expanding( wxTreeItemId Item ) {
    assert( NULL != m_tree );
    if( NULL == m_tree ) {
      return false;
    }
    assert( m_treeId.IsOk() );
    if( !m_treeId.IsOk() ) {
      return false;
    }
    assert( Item.IsOk() );
    if( !Item.IsOk() ) {
      return false;
    } else if( m_treeId == Item ) {  // is it us ?
      return Expanding();
    } else { // ok not us a child perhaps?
      TCTreeObjectDisplay<T>* rc = ContainsId( Item );
      if( NULL != rc ) {
        return rc->Expanding( Item );
      }
    }
    return false;
  }
  // does not check children
  bool IsId( wxTreeItemId Id ) {
    return Id == m_treeId;
  }
  bool operator==( wxTreeItemId Id ) {
    return IsId( Id );
  }
  // DOES check children
  TCTreeObjectDisplay<T>* ContainsId( wxTreeItemId Id ) {
    TCTreeObjectDisplay<T>* rc = NULL;
    if( IsId( Id ) ) { // is it us ?
      return this;
    }
    int len = m_categorys.size();
    for( int index = 0; index < len; index++ ) {
      rc = m_categorys[index]->ContainsId( Id );
      if( NULL != rc ) {
        return rc;
      }
    }
    return rc;
  }
  wxTreeItemId GetTreeId() {
    return m_treeId;
  }
  void AddObject( boost::shared_ptr<T> NewObject ) {
    if( m_state == expanded ) {
      assert( NULL != m_tree );
      if( NULL != m_tree ) {
        assert( m_treeId.IsOk() );
        wxTreeItemId newId; // we dont care about this at this time
        if( ! NewObject->WriteTree( m_tree, m_treeId, &newId ) ) {
          // if it fails to do what?
          assert(0);
        }
        assert( newId.IsOk() );
      }
    }
    m_objects.push_back( NewObject );
    m_tree->SetItemHasChildren( m_treeId, true );
  }
  void AddCategory( boost::shared_ptr< TCTreeObjectDisplay<T> > NewCategory ) {
    if( m_state == expanded ) {
      assert( NULL != m_tree );
      if( NULL != m_tree ) {
        assert( m_treeId.IsOk() );
        if( m_treeId.IsOk() ) {
          wxTreeItemId nItem = m_tree->AppendItem( m_treeId, NewCategory->GetName() );
          assert( nItem.IsOk() );
        }
      }
    }
    m_tree->SetItemHasChildren( m_treeId, true );
    m_categorys.push_back( NewCategory );
  }
  wxString& GetName() {
    return m_name;
  }
  T* GetObject(size_t Index) {
    size_t len = m_objects.size();
    if( Index >= len ) {
      std::string msg = "CTreeObjectDisplay::GetObject Index out of range!";
      throw new std::range_error( msg );
    }
    return m_objects[Index].get();
  }
  TCTreeObjectDisplay<T>* operator [](size_t Index) {
    size_t len = m_categorys.size();
    if( Index >= len ) {
      std::string msg = "CTreeObjectDisplay::operator[] Index out of range!";
      throw new std::range_error( msg );
    }
    return m_categorys[Index].get();
  }
  size_t GetCategorySize() const {
    return m_categorys.size();
  }
  size_t GetObjectSize() const {
    return m_objects.size();
  }
  protected:
    _state m_state;
    wxString m_name;
    wxTreeCtrl* m_tree;
    wxTreeItemId m_treeId;// if we are collapsed this may not be valid
    wxTreeItemId m_parent;
    std::vector< boost::shared_ptr< T > > m_objects;
    std::vector< boost::shared_ptr< TCTreeObjectDisplay<T> > > m_categorys;
};

#endif // #ifndef _TCTreeObjectDisplay_hpp