wxTreeCtrl and multiply displayed items - Saving memory

If you have a cool piece of software to share, but you are not hosting it officially yet, please dump it in here. If you have code snippets that are useful, please donate!
Post Reply
Thelvyn
Earned a small fee
Earned a small fee
Posts: 23
Joined: Sun Feb 26, 2006 8:03 pm
Location: Linwood Pa USA
Contact:

wxTreeCtrl and multiply displayed items - Saving memory

Post by Thelvyn » Mon Apr 10, 2006 10:24 am

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.

Thelvyn
Earned a small fee
Earned a small fee
Posts: 23
Joined: Sun Feb 26, 2006 8:03 pm
Location: Linwood Pa USA
Contact:

Post by Thelvyn » Tue Apr 11, 2006 2:09 am

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

Post Reply