Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

Shadow Relations: How to Register the Changes :)

4.00/5 (1 vote)
12 Feb 2010CPOL1 min read 7.1K  
http://rapidsh...
http://rapidshare.com/files/349564969/ChZoneEXE.zip.html[^]
http://rapidshare.com/files/349565255/ChZoneSLN.zip.html[^]



Introduction


Hello.
It is sometime necessary to control the changes of an object to its initial state, to register and may be reset they rapidly. Here I would try to show a possible way for the scanning of an object with the simple members (their types support the operator ==). And... I would begin from the end and describe the possible usage firstly... :)

Using of Code


Step 1. Declare your object as changeable and mark the members to scan
C++
...
#include "ChangeableObject.h"
...
// CChZoneDlg dialog
class CChZoneDlg : public CDialog
{
  DECLARE_CHANGEABLE_OBJECT()

protected:
  Changeable<int>     m_iChoice1;
  Changeable<int>     m_iChoice2;
  Changeable<int>     m_iChoice3;
  Changeable<int>     m_iChoice4;
  Changeable<int>     m_iState;
  Changeable<CString> m_cszEntry;
  
// Construction
public:
    CChZoneDlg(CWnd* pParent = NULL);    // standard constructor
...

Step 2. Implement your changeable object and insert its marked members in to the scanning engine
C++
// CChZoneDlg dialog
IMPLEMENT_CHANGEABLE_OBJECT(CChZoneDlg)

CChZoneDlg::CChZoneDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CChZoneDlg::IDD, pParent)
  , m_iChoice1(0)
  , m_iChoice2(1)
  , m_iChoice3(2)
  , m_iChoice4(3)
  , m_cszEntry(_T("Hello world"))
  , m_iState(1)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

  ADD_CHAHNGEABLE_CHECK(m_iChoice1)
  ADD_CHAHNGEABLE_CHECK(m_iChoice2)
  ADD_CHAHNGEABLE_CHECK(m_iChoice3)
  ADD_CHAHNGEABLE_CHECK(m_iChoice4)
  ADD_CHAHNGEABLE_CHECK(m_cszEntry)
  ADD_CHAHNGEABLE_CHECK(m_iState)

  RefreshShadow();
}

Step 3. Using of changeable objects interface

That is all :) Now your object has the following levers:
- void RefreshShadow() - to remember the actual state of its members (see example above)
- bool IsChanged() - to determinate the fact of the changes:
C++
/*virtual*/ void CChZoneDlg::OnOK()
{
  UpdateData(TRUE);

  int iAnswer(IDYES);

  if (IsChanged()) {
    iAnswer = MessageBox(_T("Do you want to save your changes ?"),
                         _T("Changes have been found..."),
                         MB_YESNOCANCEL | MB_ICONQUESTION);
    if (IDYES == iAnswer) {
      // Save the changes...
    }
  }

  if (IDCANCEL != iAnswer) {
    CDialog::OnOK();
  }
}

- void BackToShadow() - to reset the changes and take the last marked (by RefreshShadow()) state:
C++
void CChZoneDlg::OnReset()
{
  BackToShadow();

  UpdateData(FALSE);
}




Implementation


Woult you still like to ask how does it work ? :)
Sorry if it was not useful for you...
...and elsewise - well, we could observe the implementations code:
C++
// ChangeableObject.h

#pragma once
#include "afxcoll.h"

// An abstraction for all changeable members
// to allow the iteration inside their collection
class CAbstractChangeable
{
public:
  virtual void BackToShadow()   = 0; // take the saved state as actual
  virtual void RefreshShadow()  = 0; // save the actual state as shadow
  virtual bool IsChanged()      = 0; // is there a difference of the states
};

// Data holding object
// of any type supporting the operator ==
template <typename T, class Abstraction = CAbstractChangeable>
class Changeable : public Abstraction
{
  T m_original,                      // member's value reference
    m_shadow;                        // member's value shadow

public:
  Changeable()                  {};
  Changeable(const T& init)     { m_original = m_shadow = init; };
  operator T&()                 { return m_original; };
  T* operator &()               { return &m_original; };

  virtual void BackToShadow()   { m_original = m_shadow; };
  virtual void RefreshShadow()  { m_shadow = m_original; };
  virtual bool IsChanged()      { return m_shadow != m_original; };

  Changeable* GetThis()         { return this; };
};

// Collection of the changeable members
class CChangeableArray : public CPtrArray
{
public:
  CChangeableArray();
  virtual ~CChangeableArray();

  void MakeSame(bool bForwards);     // synchronization of the states
  bool IsChanged();                  // is there a difference here
};

// Declaration of the collection and interface
// for an object with changeable members
#define DECLARE_CHANGEABLE_OBJECT()   \
private:                              \
  CChangeableArray m_cChangeableArray;\
  void BackToShadow();                \
  void RefreshShadow();               \
public:                               \
  bool IsChanged();

// Implementation of the object's interface
#define IMPLEMENT_CHANGEABLE_OBJECT(Class)                              \
  void Class##::BackToShadow()  { m_cChangeableArray.MakeSame(false); } \
  void Class##::RefreshShadow() { m_cChangeableArray.MakeSame(true);  } \
  bool Class##::IsChanged()     { return m_cChangeableArray.IsChanged(); }

// Inserting of a changeable member
// in to the object's collection
#define ADD_CHAHNGEABLE_CHECK(Member) \
  m_cChangeableArray.Add(Member.GetThis());

C++
// ChangaebleObject.cpp

#include "StdAfx.h"
#include "ChangeableObject.h"

CChangeableArray::CChangeableArray()
{
}

CChangeableArray::~CChangeableArray()
{
}

void CChangeableArray::MakeSame(bool bForwards)
{
  int iCount(GetCount());
  while (iCount--) {
    CAbstractChangeable* pChangeable = (CAbstractChangeable*) GetAt(iCount);
    if (pChangeable) {
      if (bForwards) {
        pChangeable->RefreshShadow(); // remember the actual state
      } else {
        pChangeable->BackToShadow();  // reset the changes
      }
    }
  }
}

bool CChangeableArray::IsChanged()
{
  int iCount(GetCount());
  while (iCount--) {
    CAbstractChangeable* pChangeable = (CAbstractChangeable*) GetAt(iCount);
    if (pChangeable &&
        pChangeable->IsChanged()) {
      return true;                    // at least a member was modified
    }
  }
  return false;
}

Limitations


Please don't use the technique with the pointer-members because only the pointer (and not their contents) will be compared (and may be reseted...) ! Another solution (pointers case) could be a object's memory dump by its Serialize(..) method - to (re)set the states and on-dump... memcmp(..) - to compare them... :)

Points of Interest


Is there a way to iterate the C++ class members addresses directly ?

Thank you ! :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)