2010년 10월 27일 수요일

MFC CTreectrl Item Drag & Drop

출처 : http://maluchi.cafe24.com/xe/?mid=MyProgrammingTips&page=6&listStyle=list&document_srl=15989

[ 트리컨트롤(Tree Control) ]



1. 대화상자에 트리컨트롤을 붙이고 옵션을 다음과 같이 수정하자.






   Edit labels: 트리컨트롤에서 에디트 기능을 사용할때.

   Show selection always: 선택된 아이템을 표시할때.

2. 맴버 변수를 m_ctrTree라고 만들자(Control형 하나밖에 없다).

3. 아이템 추가하기
   TVINSERTSTRUCT  TI;
   TI.hParent  = TVI_ROOT;        // TVI_ROOT, NULL
                         // HTREEITEM값을 사용하면 해당하는 아이템의 자식으로 아이템이 추가된다.
   TI.hInsertAfter = TVI_LAST;    // TVI_FIRST, TVI_LAST, TVI_SORT
   TI.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
   TI.item.iImage = 0;                // Tree가 선택안되었을때 표시될 아이콘
   TI.item.iSelectedImage = 1;   // Tree가 선택되었을때 표시될 아이콘
   TI.item.pszText = "root";

   HTREEITEM hTreeItem = m_ctrTree.InsertItem(&TI); // 추가된 아이템의 HTREEITEM이 리턴된다.

4. 아이템 확장하기.
   m_ctrTree.Expand(hTreeItem, TVE_EXPAND);

5. 아이템 선택시 선택된 아이템 알아보기
   + TVN_SELCHANGED메시지를 사용한다.
   NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
   HTREEITEM hTreeItem = pNMTreeView->itemNew.hItem;   // 이 값이 선택된 아이템의 핸들이다.

6. 아이템 문자열 가져오기
   CString str = m_ctrTree.GetItemText(hTreeItem);

7. 아이템 개수 알아내기
   int nCount = m_ctrTree.GetCount();

8. 아이템 제거하기
   m_ctrTree.DeleteItem(hTreeItem);   // 핸들 아래단의 아이템들도 모두 제거된다.

9. 현재 선택된 아이템 알아내기
   HTREEITEM hTreeItem = m_ctrTree.GetSelectedItem();

10. 위치로 아이템 찾기
   CPoint  p;
   GetCursorPos(&p);
   ::ScreenToClient(m_ctrTree.m_hWnd, &p);
   HTREEITEM hItem = m_ctrTree.HitTest(p);

11. 아이템 확장 축소 감지
   + TVN_ITEMEXPANDED메시지를 사용한다.
   NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

   TVITEM  item;
   item.mask = TVIF_HANDLE;
   item.hItem = pNMTreeView->itemNew.hItem;
   m_ctrTree.GetItem(&item);            // 아이템 정보를 알아낸다.

   if(item.state & TVIS_EXPANDED)
   {
      // 확장
   }
   else
   {
      // 축소
   }

12. 아이템 아이콘 설정 변경
   m_ctrTree.SetItemImage(hTreeItem, 0, 1);

13. 아이템 에디트 입력중 포커스가 나갈때 입력중인 값 아이템에 적용하기
   + TVN_ENDLABELEDIT메시지를 사용한다.
   TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;

   CEdit *pEdit = m_ctrTree.GetEditControl();
   if(pEdit)
   {
      CString str;
      pEdit->GetWindowText(str);
      if(str.GetLength() > 0)
      {
         m_ctrTree.SetItemText(pTVDispInfo->item.hItem, str);
      }
   }

14. 이미지 리스트 설정
   + CImageList  m_Image;      // 32 x 16 아이콘 BITMAP 16 x 16 2개 짜리

   m_Image.m_hImageList = ImageList_LoadImage(
                                          (HINSTANCE) GetWindowLong(m_hWnd, GWL_HINSTANCE),
                                          MAKEINTRESOURCE(IDB_BITMAP_SMALL), 16, 2,
                                          RGB(255,255,255), IMAGE_BITMAP, LR_CREATEDIBSECTION);
   m_ctrTree.SetImageList(&m_Image, TVSIL_NORMAL);







- 드래그 앤 드롭 사용하기



1. 드래그 시작

   - 트리컨트롤의 TVN_BEGINDRAG메시지 사용

   CImageList  *m_pTreeDragImage = NULL; // 드래그시 생성된 이미지 사용
   HTREEITEM  m_hDragItem = NULL;           // 드래그시 처음 선택된 아이템 핸들 기억용



   void CDlg::OnBegindragTree(NMHDR* pNMHDR, LRESULT* pResult)
   {
      NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
      // TODO: Add your control notification handler code here

      // 드래그 이미지 생성
      if(m_pTreeDragImage) m_pTreeDragImage->DeleteImageList();
      m_pTreeDragImage = m_ctrTree.CreateDragImage(pNMTreeView->itemNew.hItem);



      // 드래그시 사용할 이미지 크기 계산
      RECT  rc;
      m_ctrTree.GetItemRect(pNMTreeView->itemNew.hItem, &rc, TRUE); // 아이콘을 포함하는 크기

      // 드래그를 시작
      m_pTreeDragImage->BeginDrag(0, CPoint(pNMTreeView->ptDrag.x-rc.left+16,

                                                       pNMTreeView->ptDrag.y-rc.top));
      // 드래그 이미지 표시
      m_pTreeDragImage->DragEnter(&m_ctrTree, pNMTreeView->ptDrag);
  
      // 마우스 메시지를 잡아두고
      SetCapture();


      // 현재 선택된 아이템 핸들을 기억
      m_hDragItem = pNMTreeView->itemNew.hItem;


      *pResult = 0;
   }



2. 이동

   - WM_MOUSEMOVE메시지 사용



   void CDlg::OnMouseMove(UINT nFlags, CPoint point)
   {
      // TODO: Add your message handler code here and/or call default
      // 드래그 중이라면
      if(m_pTreeDragImage)
      {

         // 트리컨트롤 기준으로 마우스 좌표 계산
         CPoint  p = point;
         ClientToScreen(&p);
         ::ScreenToClient(m_ctrTree.m_hWnd, &p);



         // 마우스가 위치한 아이템을 검사한다.항목이 트리 뷰 항목위에 있는지 확인하고 그렇다면 항목이 밝게 표시되도록한다.
         HTREEITEM hItem = m_ctrTree.HitTest(p);



         // 밝게 표시된 부분과 현재 선택된 아이템이 틀리다면

         if(hItem != m_ctrTree.GetDropHilightItem())
         {
            // 드래그 이미지 그리기 중지
            m_pTreeDragImage->DragLeave(&m_ctrTree);



            // 새로운 항목을 밝게 표시한다.
            m_ctrTree.SelectDropTarget(hItem);



            // 드래그 이미지를 다시 보여준다.
            m_pTreeDragImage->DragEnter(&m_ctrTree, p);
         }
         else
         {
            m_pTreeDragImage->DragMove(p);
         }
      }



      CDialog::OnMouseMove(nFlags, point);
   }



3. 드롭

   - WM_LBUTTONUP메시지 사용



   void CDlg::OnLButtonUp(UINT nFlags, CPoint point)
   {
      // TODO: Add your message handler code here and/or call default



     // 드래그 중이 었다면

      if(m_pTreeDragImage)
      {

         // 마우스 메시지 캡쳐 기능을 제거한다.

         ReleaseCapture();



         // 드래그 과정을 중단한다.
         m_pTreeDragImage->DragLeave(&m_ctrTree);
         m_pTreeDragImage->EndDrag();
         m_pTreeDragImage->DeleteImageList();
         m_pTreeDragImage = NULL;
 
         // 일단 마지막으로 밝게 표시되었던 항목을 찾는다.
         HTREEITEM hTargetItem = m_ctrTree.GetDropHilightItem();
 
         // 밝게 표시된 드롭 항목의 선택을 취소한다.
         m_ctrTree.SelectDropTarget(NULL);



         // 선택된 항목(아이템)이 있다면

         if(hTargetItem)
         {
            // 선택된 아이템과 이동될 곳의 아이템이 같다면 이동할 필요가 없다.
            if(m_hDragItem != hTargetItem)
            {

               // 현재 자식의 부모 아이템 핸들을 구한다.
               HTREEITEM hParentItem = m_ctrTree.GetNextItem(m_hDragItem,

                                                                                       TVGN_PARENT);



               // 이동하려는 곳이 자신이 직접속한 항목 이라면 이동할 필요가 없다.
               if(hParentItem != hTargetItem)
               {
                  // 트리의 내용을 이동하자.
                  MoveTreeItem(&m_ctrTree, m_hDragItem, hTargetItem);



                  // 이동된 곳의 트리를 확장하자.
                  m_ctrTree.Expand(hTargetItem, TVE_EXPAND);


                  // 이미지도 확장한걸로 바꾸자
                  m_ctrTree.SetItemImage(hTargetItem, 1, 1);
    
                  // 원본 트리의 모든 아이템이 사라졌다면 이미지 그림을 기본으로 바꾸자.
                  HTREEITEM hItem = m_ctrTree.GetChildItem(hParentItem);
                  if(!hItem)
                  {
                     m_ctrTree.SetItemImage(hParentItem, 0, 0);
                  }
               }
            }
         }

         m_hDragItem = NULL;
      }



      CDialog::OnLButtonUp(nFlags, point);

   }



4. 트리 항목(아이템) 이동 함수

   // 아이템 데이터 이동
   BOOL MoveTreeItem(CTreeCtrl *pTree, HTREEITEM hSrcItem, HTREEITEM hDestItem)
   {
      // 이동할 아이템의 정보를 알아내자.
      TVITEM    TV;
      char    str[256];
      ZeroMemory(str, sizeof(str));
      TV.hItem = hSrcItem;
      TV.mask  = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
      TV.pszText = str;
      TV.cchTextMax = sizeof(str);
      m_ctrTree.GetItem(&TV);
      DWORD dwData = pTree->GetItemData(hSrcItem);



      // 아이템을 추가 하자.
      TVINSERTSTRUCT  TI;
      TI.hParent        = hDestItem;
      TI.hInsertAfter   = TVI_LAST;
      TI.item.mask     = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
      TI.item.iImage   = TV.iImage;
      TI.item.iSelectedImage = TV.iSelectedImage;
      TI.item.pszText   = TV.pszText;
      HTREEITEM hItem  = pTree->InsertItem(&TI);
      pTree->SetItemData(hItem, dwData);



      // 현재 아이템에 자식 아이템이 있다면
      HTREEITEM hChildItem = pTree->GetChildItem(hSrcItem);
      if(hChildItem)
      {
         // 자식 아이템이 있다면 같이 이동하자.
         MoveChildTreeItem(pTree, hChildItem, hItem);
      }

      // 확장 여부를 알아서 똑같이 하자.
      TVITEM  item;
      item.mask = TVIF_HANDLE;
      item.hItem = hSrcItem;
      pTree->GetItem(&item);
      if(item.state & TVIS_EXPANDED)
      {
         pTree->Expand(hItem, TVE_EXPAND);
      }



      // 아이템을 선택하자.
      pTree->SelectItem(hItem);



      // 기존 아이템을 제거한다.
      pTree->DeleteItem(hSrcItem);



      return TRUE;
   }





   // 현재 트리의 모든 아이템 데이터 이동
   BOOL MoveChildTreeItem(CTreeCtrl *pTree, HTREEITEM hChildItem,
                                                                       HTREEITEM hDestItem)
   {
      HTREEITEM hSrcItem = hChildItem;



      while(hSrcItem)
      {
         // 이동할 아이템의 정보를 알아내자.
         TVITEM    TV;
         char    str[256];
         ZeroMemory(str, sizeof(str));
         TV.hItem     = hSrcItem;
         TV.mask     = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
         TV.pszText = str;
         TV.cchTextMax = sizeof(str);
         m_ctrTree.GetItem(&TV);
         DWORD dwData = pTree->GetItemData(hSrcItem);



         // 아이템을 추가 하자.
         TVINSERTSTRUCT  TI;
         TI.hParent       = hDestItem;
         TI.hInsertAfter  = TVI_LAST;
         TI.item.mask    = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
         TI.item.iImage   = TV.iImage;
         TI.item.iSelectedImage = TV.iSelectedImage;
         TI.item.pszText   = TV.pszText;
         HTREEITEM hItem  = pTree->InsertItem(&TI);
         pTree->SetItemData(hItem, dwData);



         // 현재 아이템에 자식 아이템이 있다면
         HTREEITEM hChildItem = pTree->GetChildItem(hSrcItem);

                                         // pTree->GetNextItem(hSrcItem, TVGN_CHILD);
         if(hChildItem)
         {
            MoveChildTreeItem(pTree, hChildItem, hItem);
         }



         // 확장 여부를 알아서 똑같이 하자.
         TVITEM  item;
         item.mask = TVIF_HANDLE;
         item.hItem = hSrcItem;
         pTree->GetItem(&item);
         if(item.state & TVIS_EXPANDED)
         {
            pTree->Expand(hItem, TVE_EXPAND);
         }



         // 다음 아이템을 알아보자.
         hSrcItem = pTree->GetNextItem(hSrcItem, TVGN_NEXT);
      }
 
      // 기존 아이템을 제거한다.
      pTree->DeleteItem(hChildItem);

      return TRUE;
   }

Metaclasses and Reflection in C++

출처 : http://www.vollmann.com/en/pubs/meta/meta/meta.html

Metaclasses and Reflection in C++

© Copyright 2000, Detlef Vollmann

Preface

Over the last two years I've held several tutorials on meta-classes and reflection in C++. I use the term reflection here in its original sense, "looking back to oneself", nowadays sometimes called introspection. The more general process to allow modifications at class level at run-time is the old task to provide a meta-level, but is today sometimes (mis-)called "behavioural reflection" or "structural reflection".

In some sense this article presents work in progress, though it's going on now for nearly ten years. It's not the definitive meta-object protocol for C++, but more a presentation of lesser-known C++ techniques to solve some specific design problems.

If you have comments on these techniques, or proposals how the problems could be solved completely differently, or if you find errors in this article, I really appreciate your feedback to dv@vollmann.ch.

This document can be found on the web at http://www.vollmann.com/en/pubs/meta/index.html.

Some source code to illustrate the implementation of the ideas of this article can be found at http://www.vollmann.com/download/mop/index.html.

The code in this article is not completely identical with that source code (due to typographical reasons and compiler restrictions). So, it could well be that some errors crept into the code here; if you find them, please mail me as well.

Introduction

C++ is a strongly typed compiler language. Though not as strongly typed as ADA, a C++ compiler will complain if you try to assign an object of one type to an object of another type (if there is no acceptable conversion). Obviously, this requires that the compiler knows all available types. More specifically, all classes must be known at compile-time1. But sometimes, it would be quite handy to add new classes at runtime. And in some application domains, this is absolutely necessary.

A simple story

Let's look at a simple example: Susan, the manager of a local bookstore, wants to expand into the Internet. So she asks you to write a simple program for an Internet bookshop. No problem for you. Part of your solution will probably look like the class model in Fig. 1.
Fig. 1: Simple Shop Model
Fig. 1: Simple Shop Model

The implementation of this in C++ is straightforward. Here is the Book class:
    class Book
    {
    public:
        Book(const string & author_,
             const string & title_,
             const string & publisher_,
             double price_,
             double weight_);
        string getName()
        {
            string name;
            name = author + ": " + title;
            return name.substr(0, 40);
        }
        double getPrice();
        double getWeight();
    private:
        string author, title, publisher;
        double price, weight;
    };
    
Your solution works, Susan is happy, and all is fine for a while...
But changes come on the Web in Internet time: the bookshop is a success and Susan decides to sell CDs as well. So you have to change your program. With object orientation, you can do this quite easily and your modified class model will look like Fig. 2.
Fig. 2: Product Model
Fig. 2: Product Model

As you probably guessed, this was only the beginning. Some time later, Susan wants to sell Pop music accessories like T-shirts, posters, etc. as well.
Now it is clear that it is not acceptable to modify the source code of your program every time a new product category is introduced. So you start to think about the actual requirements, and find that you need to provide different interfaces for your Product class (Fig. 3): A simple interface for ShoppingCart providing getName(), getPrice(), and getWeight(). This is what you already have. Then you need a different interface for a general search machine2, which must provide information like:
  • what is the actual class of the object
  • what attributes does that class have
  • what are the actual values of these attributes for the object.
This is a classic reflection interface that gives you information about the properties of classes and objects.
But you also need a third interface for product maintenance that allows you to define new product classes, specify the attributes for them, create instances of these classes, and set the attribute values of these instances. Such an interface is called a "Meta-Object Protocol (MOP)" and thoroughly discussed in [1]. The reflection protocol is a subset of such a MOP.
Fig. 3: A Better Model
Fig. 3: A Better Model

Meta Classes for C++

What is the meaning of "Meta-Object Protocol"? Well, meta information is information about something else seen from a level beyond -- a meta level. So, information about the attribute values of an object, say someBook.author, is information on the object level. But information about the properties of the object itself, about its attributes, its structure, etc. is meta information.
In C++, this information is captured in the class definition for the object, so the class is a meta-object. And in C++, you have all the functionality of a MOP at class level -- which is at development time. But that level is not available at runtime: You cannot manipulate classes like objects, you cannot add new classes at runtime. The idea of a MOP is to collapse the meta-level (classes) and the object level (objects); i.e. make the class definitions normal objects and the object properties are normal attribute values of the class definitions that can be manipulated at runtime.
While languages like CLOS or Smalltalk provide this combined level directly, C++ as strongly typed compiler language has no such features. So, what can you do about it? The typical solution is to provide a MOP yourself, as proposed e.g. in [2] or [3].

MOP Overview

For simplicity, we ignore methods for now, so our MOP must provide:
  • definition of new classes
  • adding attributes to classes
  • querying attributes of classes
  • creating objects
  • querying the class of an object
  • setting attribute values of an object
  • querying attribute values of an object
  • deleting objects
If you use an old rule of OO design, you take all the nouns of the above requirements and make classes out of them. When you think about the "attributes" and "values" you have to decide whether they are typed. As the underlying language C++ is typed this should be mirrored in your design.
Another question is about inheritance support. For our example with the Internet shop and a product hierarchy this would probably quite useful. So, a first class model is shown in Fig 4.
Fig. 4: MOP Class Model
Fig. 4: MOP Class Model

Type

While it is useful to go top-down for a general overview, it's easier to start with the simple basic things for the details. So we'll first look at Type.
The main purpose of Type is to distinguish different kind of Attributes. For this, a simple enum would suffice. But the idea of types is to have different kind of Values for different Types, so the Type should create new Values. So we put the enum inside the Type class, provide the newValue() method, and get the interface shown in Fig. 4.
Now for implementation. Though we don't look closer at Value for now, if we have different kind of values we probably need some base class for them. Let's call it "BaseValue", and newValue() can just return a pointer to BaseValue.
Now we know what to create, but how? While there are several patterns to implement polymorphic creation [4], the simplest one for our purpose is probably the Prototype, which can be easily implemented with a simple static vector3.
Now we have everything to implement the entire class:
    class Type
    {
    public:
        enum TypeT {stringT, intT, doubleT, unknownT};

        explicit Type(TypeT typeId_) : typeId(typeId_) {}

        BaseValue * newValue() const
        {
            return prototypes[typeId]->clone();
        }

        TypeT getType() const
        {
            return typeId;
        }

        static void init();
        {
            prototypes[stringT] = new Value<string>("");
            prototypes[intT] = new Value<int>(0);
            prototypes[doubleT] = new Value<double>(0);
        }

    private:
        TypeT typeId;

        static vector<BaseValue *> prototypes;
    };

    vector<BaseValue *> Type::prototypes(Type::unknownT);
    

Attribute

As we have decided to create new Values through Type, Attribute contains only a name and type, so here is its implementation:
    class Attribute
    {
    public:
        Attribute(const string & name_, Type::TypeT typeId)
         : name(name_), type_(typeId)
        {}

        const string & getName() const
        {
            return name;
        }
        Type getType() const
        {
            return type_;
        }

    private:
        string name;
        Type type_;
    };
    

Classes

We can now finish the class (meta-) level of our model by looking at Class itself. As multiple inheritance is probably not an issue for our purposes, we have just one pointer to a base class (which can be 0). The more important question is the attribute list: Should it hold only the attributes defined for this class or should it include all inherited attributes? While for the actual object value access a complete list is more useful (and much faster), for class maintenance it might be important to know which attribute was defined in which class. So we just keep both. For the complete list, the order might be significant: should the own attributes come first or the inherited ones? In most illustrations the inherited attributes come first, so we keep this order as well.
What happens if the name of an attribute is the same as the name of an inherited attribute? As C++ allows it, we can allow it as well (saving us some extra effort to check this), but in this case we must guarantee that on lookup of an attribute by name we get the most derived attribute. So, findAttribute() must do a reverse search. What shall we return from findAttribute()? The STL way would be to return an iterator, but for applications with GUIs (to create new objects and assign values to its attributes based on selection lists) an index-based access to the attributes will be more appropriate. So findAttribute() returns an index and getAttribute() takes an index and returns an Attribute. So the Attribute lists need to be an indexed containers, so we choose vectors for them.
A major purpose of Class is to create Objects from it, so it has a method newObject() which returns a pointer to an Object. Do we need to keep a repository with references to all created objects? For a full reflection interface we should do this. But for actual applications this is nearly never useful, as objects of the same class are created for completely different purposes. But do we need the repository for internal use? It depends on what we want to do with objects after they were created. This leads directly to another important decision: What do we do with already existing objects if we add a new attribute to a class? One option is to add this attribute to all existing objects and assign it a default value. The other option is to leave these existing objects and add the new attribute only to new objects. This leads to differently structured objects of the same class at the same time, and then we must add some version information to the objects. But there is a third option: To forbid the modification of a class definition once an instance of that class was created. This is the easiest option, so we adopt it for our MOP and add a flag definitionFix. With that flag, we can skip the object repository.
A last design question is when to add the attributes: At creation time of a class definition (through the constructor) or later (with a member function)? For different applications both options might be useful, so we'll provide two constructors and addAttribute().
Now you can implement this4:
    class ClassDef
    {        //typedefs Container, Iterator for attributes
    public:
        ClassDef(ClassDef const * base, const string & name_)
         : baseClass(base), name(name_),
           definitionFix(false)
        {
            baseInit();
            effectiveAttributes.insert(effectiveAttributes.end(),
                                       ownAttributes.begin(),
                                       ownAttributes.end());
        }

        template <typename iterator>
        ClassDef(ClassDef const * base, const string & name_,
                 iterator attribBegin, iterator attribEnd)
         : baseClass(base), name(name_),
           ownAttributes(attribBegin, attribEnd),
           definitionFix(false)
        {
            baseInit();
            effectiveAttributes.insert(effectiveAttributes.end(),
                                       ownAttributes.begin(),
                                       ownAttributes.end());
        }

        string getName() const;
        Object * newObject() const
        {
            definitionFix = true;
            return new Object(this);
        }

        AttrIterator attribBegin() const;
        AttrIterator attribEnd() const;
        Attribute const & getAttribute(size_t idx) const;
        void addAttribute(const Attribute &);
        size_t getAttributeCount() const;

        size_t findAttribute(string const & name) const
        {
            // this does a reverse search to find the most derived
            AttributeContainer::const_reverse_iterator i;

            for (i = effectiveAttributes.rbegin();
                 i != effectiveAttributes.rend();
                 ++i)
            {
                if (i->getName() == name)
                {
                    return distance(i, effectiveAttributes.rend()) - 1;
                }
            }
            return getAttributeCount();
        }

    private:
        void baseInit()
        {
            if (baseClass)
            {
                baseClass->definitionFix = true;
                copy(baseClass->attribBegin(), baseClass->attribEnd(),
                     back_inserter<AttributeContainer>(effectiveAttributes));
            }
        }

        ClassDef const * const baseClass;
        string name;
        AttributeContainer ownAttributes, effectiveAttributes;
        mutable bool definitionFix;
    };
    

Values

Fig. 5: Value Model
Fig. 5: Value Model
Before we can design Object, we have to think about Value. We need a common interface to manage them, which we already called BaseValue. But what interface do we need? The whole idea of Value is to store values, so we need a set() function. What parameter? The only thing we have is BaseValue, so that's the parameter type. Pass by value, by reference, or by pointer? Definitely not by value, as BaseValue is only an interface. On the other hand, what you pass is a value, so the parameter passing should be by value to let you pass temporaries. So one option would be to pass by const reference. But though this helps for the problem at hand, it doesn't cure the fundamental problem: you should have a value, but all you have is a polymorphic interface. The real solution here is the pimpl idiom, also known as Cheshire Cat, Envelope/Letter, or more generally Handle/Body. So we add a handle class, name it Value, and look at it later again. For now we're still at BaseValue.
We now have set(Value), so what about get()? The return type of get() would be Value, and the implementation would look like:
    Value BaseValue::get()
    {
        return *this; // calls Value(BaseValue const &)
    }
    
But that we can do directly, so get() doesn't make much sense.
What other BaseValue functions do we need? Values must be copied, so we add clone().
That's all what we really need from a value, but we add asString() for convenience5.
    class BaseValue
    {
    public:
        virtual ~BaseValue(){}

        virtual BaseValue * clone() const = 0;

        virtual string asString() const = 0;
        // fromString()

        virtual void set(Value const & v) = 0;
        // no get()!
    private:
        // Type info
    };
    

RealValue

Now, as we have the interface, what about the implementation? We need values for int, double, string, .... And an int value must hold an int, a double value a double, etc. This looks like an opportunity for a template. So, let's define RealValue<T>, derive it from BaseValue, implement the inherited interface, and we're nearly done. But as RealValue<T> is just a wrapper around T with some additional functionality, but essentially still a T, we should provide conversion in both directions, by providing a converting constructor and a conversion operator.
    template <typename PlainT>
    class RealValue : public BaseValue
    {
    public:
        RealValue(PlainT v)
         : val(v) {}

        RealValue * clone() const
        {
            return new RealValue(*this);
        }

        string asString() const
        {
            ostringstream os;
            os << val;
            return os.str();
        }

        operator PlainT() const // conversion to plain type
        {
            return val;
        }

        RealValue<PlainT>::set(Value const & v)
        {
            val = v.get<PlainT>();
        }

    private:
        PlainT val;
    };
    
A note about RealValue: As we have conversion in both directions, we can use RealValue<T> like T:
    RealValue<int> i = 1;
    int j = i;
    RealValue<double> d = i + 5.2 / (i*2);
    cout << d << endl;
    
Nearly: the following doesn't work:
    RealValue<string> name, author = "Bjarne", title = "The C++ PL";
    name = author + ": " + title;
    cout << name << endl;
    
The reason is that the compiler only applies one user-defined conversion, but for string literals, you need two: from char const * to string, and from string to RealValue<string>. If you want to work with RealValue<string> outside the MOP, you should define a specialization:
    template <>
    class RealValue<string> : public BaseValue, public string
    {
    public:
        RealValue(string const & s) : string(s) {}
        RealValue(char const * s) : string(s) {}
        RealValue() {}

        RealValue * clone() const
        {
            return new RealValue(*this);
        }

        string asString() const
        {
            return static_cast<string>(*this);
        }

        // no operator string(), conversion to base automatically

        void set(Value const & v)
        {
            string::operator=(v.get<string>());
        }
    };
    
Note: Actually, its not really clean to derive RealValue<string> from std::string, but as long as you don't delete a RealValue<string> through a pointer to string, it will work.

Value handle

Now back to the handle class Value. As a handle class, it contains its body and cares for it. Its main job is to adopt/create and to delete its body. And it mirrors the interface of the body and forwards all messages. But it should also be a real value class, thus providing default and copy constructor and assignment. But how to implement the default constructor? As we don't know what type to create, we must create an empty handle without a body and check before forwarding if we actually have something to forward to. The assignment is essentially the set(), so we skip the set().
Now let's come back to the get(). Of course, to return a Value or BaseValue doesn't make sense. But what about returning the RealValue or even the wrapped underlying value? That would be really useful, but for that we have to tell get() what we want as return type. So get() becomes a member template and so can return whatever is inside the RealValue<>.
    class Value        // Value handle
    {
    public:
        Value(BaseValue const & bv)
         : v(bv.clone())
        {}

        Value(Value const & rhs)
         : v(rhs.v ? rhs.v->clone() : 0)
        {}

        explicit Value(BaseValue * bv = 0)
         : v(bv)
        {}

        ~Value()
        {
            delete v;
        }

        Value & operator=(const Value & rhs)
        {
            // this is not a typical pimpl assignment, but a set()
            if (v)
            {
                if (rhs.v)
                { // fine, all v's exist
                    v->set(rhs);
                }
                else
                { // the other v doesn't exist, so we must delete our own :-(
                    BaseValue * old = v;
                    v = 0;
                    delete old;
                }
            }
            else
            { // we don't have a v, so just copy the other
                v = (rhs.v ? rhs.v->clone() : 0);
            }

            return *this;
        }

        template <typename PlainT>
        PlainT get() const
        {
            if (v)
            {
                RealValue<PlainT> const & rv
                    = dynamic_cast<RealValue<PlainT> const &>(*v);
                return rv;        // uses conversion operator
            }
            else
            {
                return PlainT();
            }
        }

        std::string asString() const
        {
            if (v)
            {
                return v->asString();
            }
            else
            {
                return string();
            }
        }

    private:
        BaseValue * v;
    };
    

Object

Finally we come to Object. Now, as we have everything else, an Object is mainly a container for its attribute values. To ease implementation, we will structurally mirror the attribute container in the class definition, so we use a vector. As we have so much effort invested in our Value handle, it would make sense to store that in the vector. But for future extensions it will be easier to have the BaseValue pointers directly available.
The constructor will create the values through the types of the attributes, so the only constructor takes a ClassDef*.
To set and get the values for the attributes, we provide two options: to specify the attribute by name and also by index.
For reflection purposes (as well as for internal implementation) we need a pointer to the class definition, but then we have it all:
    class Object
    {
    public:
        explicit Object(ClassDef const * class_)
         : myClass(class_), values(class_->getAttributeCount())
        {
            buildValueList();
        }

        ClassDef const & instanceOf() const
        {
            return *myClass;
        }

        Value getValue(size_t attribIdx) const
        {
            return *values[attribIdx]; // calls Value(BaseValue &)
        }
        Value getValue(string const & attribName) const
        {
            size_t idx = instanceOf()->findAttribute(attribName);
            // should check for not found
            return getValue(idx);
        }

        void setValue(size_t idx, Value const & v)
        {
            values[idx]->set(v);
        }
        void setValue(string const & attribName, Value const &v)
        {
            size_t idx = instanceOf()->findAttribute(attribName);
            // should check for not found
            setValue(idx, v);
        }

    private:
        typedef vector<BaseValue *> ValueContainer;
        void buildValueList()
        {
            ClassDef::AttrIterator a;
            ValueContainer::iterator i = values.begin();
            for (a = instanceOf()->attribBegin();
                 a != instanceOf()->attribEnd();
                 ++a, ++i)
            {
                *i = a->getType().newValue();
            }
        }

        ClassDef const * const myClass;
        ValueContainer values;
    };
    
Now the MOP is complete. Let's use it:
Creating the Product class:
    ClassDef * product
         = new ClassDef(0, // no base class for Product
                        "Product"); // name of class
    
Adding attributes:
    product->addAttribute(Attribute("Product Number", Type::intT));
    product->addAttribute(Attribute("Name", Type::stringT));
    product->addAttribute(Attribute("Price", Type::doubleT));
    product->addAttribute(Attribute("Weight", Type::doubleT));
    
Creating the Book class with an attribute list:
    list<Attribute> attrL;
    attrL.push_back(Attribute("Author", Type::stringT));
    attrL.push_back(Attribute("Title", Type::stringT));
    attrL.push_back(Attribute("ISBN", Type::intT));

    ClassDef * book
     = new ClassDef(product, // base class
                    "Book",
                    attrL.begin(), attrL.end());
    
Creating an object:
    Object * bscpp(book->newObject());
    

Setting the values for the objects:

Set an int value by index (don't forget that index 0 is ProductNo):
    bscpp->setValue(0, RealValue<int>(12345));
    
Same for a string value:
    bscpp->setValue(4, RealValue<string>("Bjarne Stroustrup"));
    
Better way: set value by name this gives the most derived attribute:
    bscpp->setValue("Title",
                    RealValue<string>("The C++ Programming Language"));
    bscpp->setValue("Weight", Value<double>(370));
    

Getting the values:

Display a book:
        ClassDef::AttrIterator a;
        size_t idx;
        for (a = book->attribBegin(), idx = 0;
             a != book->attribEnd();
             ++a, ++idx)
        {
            cout << a->getName() << ": "
                 << bscpp->getValue(idx).asString() << endl;
        }
    
and we get:
    Product Number: 12345
    Name:
    Price:
    Weight: 370
    Author: Bjarne Stroustrup
    Title: The C++ Programming Language
    ISBN:
    
So, our MOP is complete. For our sample application, you have to add a class repository, some nice GUI to define classes and objects, creating the index for the search machine, provide an interface for ShoppingCart, but then you're done, and Susan is happy as she now can create her own new product categories at runtime.

 

Reflection for existing C++ classes

If you provide the interface for the ShoppingCart in our example, you'll find that it isn't so easy. If all classes are dynamic classes, all access must go through the MOP:
A getName() for Book:
    string bookGetName(Object const * book)
    {
        if (book->instanceOf().getName() != "Book")
        {
            /* throw some exception */
        }
        string name;
        //  name = book->author + ": " + book->title;  it was so easy...

        string author = book->getValue("Author").get<string>();
        string title = book->getValue("Title").get<string>();
        name = author + ": " + title;

        return name.substr(0, 40);
    }
    
For a lot of applications, it would be useful to provide some classes of a hierarchy as C++ classes, e.g. Product, but still let the user add classes of the same hierarchy at runtime, e.g. TShirt. So, let's look at this. If we want access through our MOP to C++ classes, we need a getValue() to which we can give the attribute we want to access at runtime. So, here it is:
    Value getValue(Object *o, MemberPointer mp)
    {
        return o->*mp;
    }
    
The magic lies in '->*': This is the pointer-to-member selector of C++.

Pointer to member

You can imaging a pointer-to-member in C++ as an offset6 from the base address of an object to a specific member. If you apply that offset to such a base address, you get a reference to the member (Fig. 6). But as a pointer-to-member is a normal data type in C++, you can store them in containers, pass them to functions, etc. Thus, you can write the function above, building the fundamental base for our combination of C++-classes and runtime-classes.
Fig. 6: Pointer to Member
Fig. 6: Pointer to Member
Let's look at some details of pointer-to-members. As an example, we use the following class:
    class Product
    {
    // ...
    protected:
        RealValue<double> price;
    };

    class Book : public Product
    {
    public:
        // ...
    private:
        RealValue<string> author, title;
        RealValue<double> weight;
    };

    Book b, *bp;
    
A pointer-to-member is a type that is derived from two other types: The type of the base object (Book in our example) and the type of the member (RealValue<>). The type decorator for a pointer-to-member is '::*', so let's define two variables with initialization:
RealValue<string> Book::* bookStringMemPtr = &Book::author;
RealValue<double> Book::* bookDoubleMemPtr = &Book::weight; The pointer-to-member selector comes in two variations: as '.*' you can apply it to references of the class and as '->*' it takes a pointer. It is a binary operand, as left operand it takes a reference (or pointer) to an object and as right operand a pointer-to-member. So, with the above definitions, you can do things like
    b.*bookStringMemPtr = "Bjarne Stroustrup"; // assigns b.author

    bookStringMemPtr = &Book::title;

    bp->*bookStringMemPtr = "The C++ Programming Language"; // assigns b.author
    
Of course, as title is a private member of Book, the assignment of the pointer-to-member must be at the scope of that class. But the pointer-to-members themselves can be used even if you don't have access privileges to the members (as long as you have access to the pointer-to-member).

pointer-to-member Types

A word about the types: RealValue<double> Book::*, RealValue<double> Product::*, and BaseValue Product::* are different types. But are there conversions? The C++ standard provides a conversion from a pointer-to-member of a base class to a pointer-to-member of a derived class. That makes sense: You can apply an offset to a member of a base class to the base address of a derived object as well, as the base is a part of the derived object7. So you can assign
bookDoubleMemPtr = &Product::price; as the price is part of each Book instance.
The other way around it obviously doesn't work, you couldn't initialize
RealValue<double> Product::* &Book::author; as author is not a member of each instance of type Product.
But the standard does not provide a conversion from RealValue<double> Book::* to BaseValue Book::*, though it would be save: If the result type of the pointer-to-member selector is a reference to a derived class, it can be safely converted to a reference of a respective base class, so it would also be safe to let the compiler do the conversion automatically and therefore also convert the pointer-to-members themselves. As already mentioned, the standard doesn't provide (implicit) and even doesn't allow (explicit through static_cast) that conversion, probably because the committee didn't see any use for pointer-to-data-members at all (see [5]), and for pointer-to-member-functions that conversion really doesn't make sense.
The problem for us is: we need that conversion. We want to keep pointer-to-members to all members of a class in one common container, but what could be the type of that container's elements? One option would be to force the conversion through a reinterpret_cast, but the only thing you can safely do with a reinterpret_casted thing is to reinterpret_cast it back, and for that you have to store the original type as well. So we use another option: we just define the conversion! But as C++ doesn't allow you to define your own conversions to compiler-provided types (and in this sense the pointer-to-members are compiler defined, though the involved single types like Book or BaseValue are user-defined), we have to define wrapper classes around them.
Here's the implementation:
    template <typename BaseType, typename BaseTargetType>
    class MemPtrBase
    {
    public:
        virtual BaseTargetType & value(BaseType & obj) const = 0;
        virtual BaseTargetType const & value(BaseType const & obj) const = 0;

    protected:
        MemPtrBase() {}
        virtual ~MemPtrBase() {};

    private:
        MemPtrBase(MemPtrBase const &);
        MemPtrBase & operator=(MemPtrBase const &);
    };

    template <typename BaseType, typename BaseTargetType, typename TargetType>
    class TypedMemPtr : public MemPtrBase<BaseType, BaseTargetType>
    {
    public:
        TypedMemPtr(TargetType BaseType::* ptr)
         : p(ptr)
        {}

        BaseTargetType & value(BaseType & obj) const
        {
            return obj.*p;
        }

        BaseTargetType const & value(BaseType const & obj) const
        {
            return obj.*p;
        }

    private:
        TargetType BaseType::* p;
    };

    template <typename BaseType, typename BaseTargetType>
    class MemPtr // this is a handle only
    {
    public:
        template <typename BaseType2, typename TargetType>
        explicit MemPtr(TargetType BaseType2::* ptr)
         : p(new TypedMemPtr<BaseType, BaseTargetType,
            TargetType>(static_cast<TargetType BaseType::*>(ptr)))
        {}

        ~MemPtr()
        {
            delete p;
        }

        BaseTargetType & value(BaseType & obj) const
        {
            return p->value(obj);
        }

        BaseTargetType const & value(BaseType const & obj) const
        {
            return p->value(obj);
        }

    private:
        MemPtrBase<BaseType, BaseTargetType> * p;
    };
    
Some notes to the code: BaseType is used for the class to which a pointer to member is applied (e.g. Book), TargetType is the result type to which a pointer-to-member points (RealValue<double>), and BaseTargetType is the base class of TargetType (BaseValue). MemPtrBase<> is the common base class as we need it (e.g. MemPtrBase<Book, BaseValue>, which stands for BaseValue Book::*), TypedMemPtr<> hold an actual C++ pointer-to-member (TypedMemPtr<Book, RealValue<double> >), and MemPtr<> is a handle class around MemPtrBase<> to store them in a container. Here, the actual access function is the value() member function. If you want, you can add a global operator '->*' (as template function), but you can't provide the operator by a member function (as the left operand is not the class instance), and you can't overload '.*' (this is one of the few non-overloadable operators).
The MemPtr constructor is a member template with two template parameters: a BaseType2 and the TargetType. The second one is clear as it defines the actual TypedMemPtr to be constructed, but the BaseType2 is not so obvious. If we omit the BaseType2, so only having
    template <typename BaseType, typename BaseTargetType>
    class MemPtr // this is a handle only
    {
    public:
        template <typename TargetType>
        explicit MemPtr(TargetType BaseType::* ptr)
         : p(new TypedMemPtr<BaseType, BaseTargetType, TargetType>(ptr))
        {}
        // ...
    }
    
and then we try to create a
    MemPtr<Book, BaseValue> mp2(&Product::price);
    
some compilers give an error, as they cannot fiddle out the correct conversion. This would be to convert RealValue<double> Product::* to RealValue<double> Book::*, which should be done automatically, and then to instantiate MemPtr's constructor with RealValue<double> as TargetType.
One way to solve this is to explicitly cast the pointer-to-member:
    MemPtr<Book, BaseValue>
        mp2(static_cast<RealValue<double> Book::*>(&Product::price));
    
but that's quite a lot to type. It's actually much easier to move that explicit conversion into the constructor itself and just provide an additional template parameter, as shown in the implementation above. The compiler checks the conversion anyway, so you will get a compile time error if that conversion is not allowed (e.g. if you try to convert a RealValue<double> Book::* to RealValue<double> Cd::*.

C++ classes

The MemPtrs allow you to access the attribute values of an ordinary C++ object. This helps for one part of the MOP. But what about the attributes themselves? The compiler has the necessary knowledge, but unfortunately there is no standard way to access that knowledge at runtime. So we must provide it and define an interface for it. To allow a smooth integration with our existing ClassDef, we provide an Attribute iterator as interface. For now, we provide the information about the attributes manually, but in a following article we'll explore the use of a pre-processor for that.
So, for our class Book we provide the following functions:
    class Book : public Product
    {
        typedef MemPtr<Book, FinalValue> MemberPtr;
    public:
        // as before
        static size_t ownAttribCount()
        {
            return 5;
        }
        static Attribute * ownAttribBegin()
        {
            static Attribute a[]
                = {Attribute("Author", Type::stringT),
                   Attribute("Title", Type::stringT),
                   Attribute("Publisher", Type::stringT),
                   Attribute("Price", Type::doubleT),
                   Attribute("Weight", Type::doubleT)
                  };
            return a;
        }
        static Attribute * ownAttribEnd()
        {
            return ownAttribBegin() + ownAttribCount();
        }
        static MemberPtr * memberBegin()
        {
            static MemberPtr m[]
                = {MemberPtr(&Book::productNo),
                   MemberPtr(&Product::weight),
                   MemberPtr(&Book::author),
                   MemberPtr(&Book::title),
                   MemberPtr(&Book::publisher),
                   MemberPtr(&Book::price),
                   MemberPtr(&Book::weight)
                  };

            return m;
        }
        static MemberPtr * memberEnd()
        {
            return memberBegin() + 7;
        }

    private:
        RealValue<string> author, title, publisher;
        RealValue<double> price, weight;
    };
    
Please note the difference between ownAttribBegin() and memberBegin(): the first provides information only about the own attributes, while the latter provides access also to base class members. This separation makes sense: while on object level all data members build together one object, on the class level the base class is a different entity and should be available as common base class for the meta object protocol as well. But this separation has consequences: we can't derive a C++ class from a MOP class (but this is definitely no real restriction) and the C++ base class must be also made known to the MOP (but that's useful anyway).
We have no function that provides information about the base classes, as the baseClass in ClassDef must be made a ClassDef instance as well, as noted above. The C++ base classes are not of much use for our application.
With these functions, we can build a ClassDef from a C++ class; it's so easy that we can even provide a helper function for that:
    template <typename CppClass>
    ClassDef makeClass(ClassDef const * base, string const & name)
    {
        return ClassDef(base, name,
                        CppClass::ownAttribBegin(), CppClass::ownAttribEnd());
    }
    
Now, we can build our ClassDefs for Product and Book and create instances from them:
    ClassDef base(makeClass<DynaProduct>(0, "Product"));
    ClassDef book(makeClass<Book>(base, "Book"));
    book.newObject();
    
But stop -- though this works, it's not what we want: now, the instances are not genuine C++ objects, but MOP objects, and all access must still go through the MOP.

C++ objects

What we want are real C++ objects that we can also access through the MOP, i.e. through the Object interface. The OO way to do that is to derive our Product from Object, but though other OO languages like Smalltalk do that, I think there's a better, less intrusive option: we provide an adaptor.
From [4] we learn that there are two options for the adaptor pattern: to design the adaptor class as forwarding wrapper class derived only from Object and containing a Product member or using multiple inheritance and derive the adaptor from Object and Product. For simplicity, we will use the first option, but real world applications often benefit from the second approach. So we provide a wrapper class CppObject that is derived from Object and that holds the original C++ object. It implements the interface of Object (getValue() and setValue()) through our MemPtrs:
    template <typename OrigClass>
    class CppObject : public Object
    {
        typedef MemPtr<OrigClass, BaseValue> MemberPtr;
    public:
        CppObject(ClassDef const * myClass)
         : Object(myClass), myObject(), members(OrigClass::memberBegin())
        {}

        virtual Object * clone() const
        {
            return new CppObject(*this);
        }

        using Object::getValue; // importing getValue(name)
        using Object::setValue; // importing setValue(name)

        virtual Value getValue(size_t idx) const
        {
            return members[idx].value(myObject);
        }

        virtual void setValue(size_t idx, Value const & v)
        {
            BaseValue * p = &(members[idx].value(myObject));
            p->set(v);
        }

    private:
        MemberPtr * members;
        OrigClass myObject;
    };
    
A useful rule of OO design says that only leaf classes should be concrete, so let's define Object as abstract base class and create a new class DynaObject that resembles our former Object for real MOP class instances (Fig. 7).
Fig. 7: Object Hierarchy
Fig. 7: Object Hierarchy
If we now do a prod.newObject(), we still get it wrong: we now get a DynaObject, but we want a CppObject<Product>. To solve that, we must provide the ClassDef with a means to create the correct kind of object, and the simplest way to do that is a factory method: we provide a static creation function in CppObject<> and DynaObject, give a pointer to that function to the ClassDef's constructor, store it and use that function in ClassDef::newObject():
Creation functions for DynaObject and CppObject:
    Object *
    DynaObject::newObject(ClassDef const * myClass)
    {
        return new DynaObject(myClass);
    }

    template <typename OrigClass>
    Object *
    CppObject<OrigClass>::newObject(ClassDef const * myClass)
    {
        return new CppObject(myClass);
    }
    
Changes to ClassDef:
    class ClassDef
    {
    public:
        typedef Object * (*CreateObjFunc)(ClassDef const *);

        template <typename Iterator>
        ClassDef(ClassDef const *, string const &,
                 CreateObjFunc objFunc,
                 Iterator, Iterator)
         : // ...
           createObj(objFunc)
        {
            // ...
        }

        ClassDef(ClassDef const *, string & const name_,
                 CreateObjFunc objFunc)
         : // ...
           createObj(objFunc)
        {
            // ...
        }

        Object * newObject() const
        {
            definitionFix = true;
            return (*createObj)(this);
        }

        // ... as before

    private:
        const CreateObjFunc createObj;
        // ... as before
    };
    
And a simple change to makeClass:
    template <typename CppClass>
    ClassDef makeClass(ClassDef const * base, string const & name)
    {
        return ClassDef(base, name,
                        CppObject<CppClass>::newObject,
                        CppClass::ownAttribBegin(),
                        CppClass::ownAttribEnd());
    }
    
Now, everything works. Well -- nearly. If we now try to create a ClassDef for Product with makeClass the compiler complains about creating an abstract class: makeClass gives the ClassDef constructor a pointer to CppObject<Product>::newObject(), and that creates a Product instance as part of CppObject<Product>. This is easily fixed: just call the ClassDef constructor directly with a null-pointer for the creation function, thus prohibiting the creation of a Product instance through the MOP.

Usage

The MOP, as you have it now, allows you to define the C++ classes as MOP classes as before:
    ClassDef base(0, "Product", 0,
                  Product::ownAttribBegin(),
                  Product::ownAttribEnd());
    ClassDef book(makeClass<Book>(&base, "Book"));
    
You can create instances of them
book.newObject();
You can define new classes derived from Product
    ClassDef * tShirt
        = new ClassDef(&base, "T-Shirt",
              DynaObject::newObject);

    tShirt->addAttribute(Attribute("Size", Type::stringT));
    tShirt->addAttribute(Attribute("Color", Type::stringT));
    tShirt->addAttribute(Attribute("Name", Type::stringT));
    tShirt->addAttribute(Attribute("Price", Type::doubleT));

    classReg.registerClass(tShirt);
    
and manipulate instances of existing classes and new classes through the MOP:
A C++ object:
    Object * ecpp(book.newObject());

    ecpp->setValue(5, RealValue<double>(22.50));
    ecpp->setValue(0, RealValue<int>(23456));
    ecpp->setValue(2, RealValue<string>("Scott Meyers"));
    ecpp->setValue("Title", RealValue<string>("Effective C++"));
    ecpp->setValue(6, RealValue<double>(280));
    size_t idx;

    cout << "ecpp:" << endl;
    for (a = book.attribBegin(), idx = 0;
         a != book.attribEnd();
         ++a, ++idx)
    {
        cout << a->getName() << ": "
             << ecpp->getValue(idx).asString() << endl;
    }

    cout << ecpp->getValue("Author").asString() << endl;
    
And a dynamic object:
    Object * ts(tShirt.newObject());
    ts->setValue(0, RealValue<int>(87654));
    ts->setValue(2, RealValue<string>("XXL"));
    ts->setValue("Color", RealValue<string>("red"));
    ts->setValue("Price", RealValue<double>(25.95));
    ts->setValue("Weight", RealValue<double>(387));

    for (size_t idx = 0; idx != 4; ++idx)
    {
        cout << ts->getValue(idx).asString() << endl;
    }
    

C++ Interface

You can't access the instances created by the MOP through the Product interface. For instances of C++ classes you can modify the CppObject<T> to derive from T as we have discussed before, or better, to provide a member function in CppObject that returns a pointer to myObject. And for instances of MOP classes you can define a wrapper around an Object that implements the Product interface:
    class DynaProduct : public Product
    {
    public:
        DynaProduct(Object const * o) : obj(o) {}
        virtual std::string getName() const
        {
            Value v = obj->getValue("Name");
            return v.get<std::string>();
        }
        virtual double getPrice() const
        {
            Value v = obj->getValue("Price");
            return v.get<double>();
        }

        virtual double getWeight() const
        {
            Value v = obj->getValue("Weight");
            return v.get<double>();
        }

    private:
        Object const * const obj;
    };
    
And you can't access normal C++ instances of Book through the MOP; to solve this, you could add another constructor that adopts the C++ instance by copying (and consequently you should delete the original one to avoid an object that exists twice) or you could modify the wrapper to hold only a pointer to the C++ object and add a member function to return the controlled C++ object on request: Here, we use the first approach:
        template <typename OrigClass>
        CppObject<OrigClass>::CppObject(ClassDef const * myClass,
                                        OrigClass const & obj)
         : Object(myClass),
           myObject(obj), // calls the copy-ctor of OrigClass,
                             which must be accessible
           members(OrigClass::memberBegin())
        {}
    
And now, you can do this:
    Book b("Bjarne Stroustrup", "The C++ Programming Language",
           "Addison-Wesley", 27.50, 370);
    CppObject<Book> mb(*book, b);
    Object * ob = &mb;
    cout << "C++ object through MOP" << endl;
    for (a = ob->instanceOf()->attribBegin(), idx = 0;
         a != ob->instanceOf()->attribEnd();
         ++a, ++idx)
    {
        cout << a->getName() << ": "
             << ob->getValue(idx).asString() << endl;
    }
    
But in general, it's just important that you can define basic classes in C++ at programming time but allow the user to derive own classes from these base classes at runtime.

Applications

Is such a MOP approach for C++ actually useful? The pure reflection mechanism based on pointers-to-members is quite useful for persistence libraries -- on relational databases or file formats like XML. This was the application when I first used pointers-to-members in C++, which were just the C++ replacement of the old C offsetOf macro that is still in widespread use for that purpose.
But I also came across quite a lot of applications where a handful of pre-defined entities provided 98% of the requirements of the users of the system, but the remaining 2% were so different for different users that a common solution for all was not adequate. For these special cases, a full meta-object protocol approach was really a quite simple and elegant solution that provided all the flexibility the users requested.

Two final remarks

Could the same kind of reflection be achieved with get/set functions instead of pointers-to-members? Perhaps, yes. Some component mechanisms use that approach (e.g. Borland's VCL). In that case, a get() and set() function for each attribute and a specialized TypedMemPtr<> for each attribute for each class is required. That's a lot of work, but with a respective pre-processor or compiler support that's not a point. But it's still much more intrusive to add all the getters and setters; and if they are public, they break encapsulation. Though pointers-to-members allow direct access to the data, that encapsulation leak can be much better controlled.

The second remark relates to RealValue<>. Are they really necessary for true reflection purpose? Actually not. In that case, you could remove BaseTargetType from all MemPtr templates, return the TargetType in TypedMemPtr's getter function, and make the getter function of the MemPtr handle a member template function analogous to the getter function of Value. In the source code for this article ( http://www.vollmann.com/download/mop/index.html) you'll find a sample implementation for that.
Though this seems to be a major advantage for pure reflection applications like persistency libraries, in fact I found it in most cases quite useful to have a base class like DbValue for all persistent attributes to provide additional functionality like dirty flags, type conversion specifics, etc.

Coming articles

This article provided reflection and meta-class facilities for data attributes only. A following article will show the application of a MOP for the integration of a scripting language. And that will then allow to extend the MOP with member functions as well.
Another article will look into the capabilities of preprocessors to provide the reflection information.
And yet another article will look at real applications for reflection and meta-classes, like DB libraries.

References

[1] Gregor Kiczales, Jim des Rivières, Daniel G. Bobrow: "The Art of the Metaobject Protocol", MIT Press 1991, ISBN 0-262-61074-4

[2] James O. Coplien: "Advanced C++ Programming Styles And Idioms", Chapters 8-10, Addison-Wesley, 1992, ISBN 0-201-54855-0

[3] Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad, Michael Stal: "Pattern-Oriented Software Architecture: A System of Patterns", Wiley 1996, ISBN 0-471-95869-7

[4] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: "Design Patterns", Addison-Wesley 1994, ISBN 0-201-63361-2

[5] Michael S. Ball, Stephen D. Clamage: "Pointers-to-Members", C++ Report 11(10):8-12, Nov/Dec 1999

Notes

1 I will not elaborate on differences between type and class.
2 The idea of the search machine is to have a batch process that queries all objects about its attribute values and create an own internal index from that information.
3 actually, a simple array would suffice, as its size is fix and known at compile time. But for future extensions a vector is more flexible.
4 As class is a reserved word in C++, and I don't like identifiers to differ only in case from others I have chosen ClassDef here.
5 fromString() would also be useful, but we omit it here.
6 In fact, a pointer-to-member is not as easy as an offset. Especially if multiple inheritance comes in, things become more complicated.
7 It might be necessary to adjust that offset, but the compiler cares for that.

2010년 10월 14일 목요일

툴팁과 타이틀팁(Tooltip & Titletip)

출처 : http://www.devpia.com/maeul/contents/de ··· 3D587080 (새 창으로 열기)

9. 툴팁과 타이틀팁(Tooltip & Titletip)    

9.1 헤더를 위한 툴팁                      

헤더컨트롤을 위한 툴팁을 추가하는것은 매우 간단합니다.

CListCtrl 파생 클래스에 CToolTipCtrl 타입의 멤버변수를 선언합니다.

    CToolTipCtrl    m_tooltip;

CListCtrl 파생 클래스에서 PreSubclassWindow() 함수를 오버라이드 합니다. 베이스클래스의 PreSubclassWindow()를 호출한후, 툴팁객체를 만듭니다. OnCreate()대신에 PreSubclassWindow() 를 오버라이드한 이유는 컨트롤은 보통 다이얼로그 리소스로부터 생성됨으로써 이미 만들어진후에 C++ 객체에 붙기(Attach) 때문에, OnCreate가 객체에 대해 전혀 호출이 되지 않기때문입니다.

void CMyListCtrl::PreSubclassWindow()
{
        CListCtrl::PreSubclassWindow();

        // Add initialization code
        m_tooltip.Create( this );
        m_tooltip.AddTool( GetDlgItem(0), "Right click for context menu" );
}

PreTranslateMessage()함수를 오버라이드해서 CToolTip 객체의 RelayEvents()함수를 호출합니다.

BOOL CMyListCtrl::PreTranslateMessage(MSG* pMsg)
{
        m_tooltip.RelayEvent( pMsg );
        return CListCtrl::PreTranslateMessage(pMsg);
}

9.2 각각의 컬럼 헤더를 위한 툴팁

컬럼헤더에 툴팁을 제공하는것은 여러 용도가 있습니다. 헤더툴팁이 정말 유용하다고 느낀 한가지 경우는 컬럼의 넓이가 제한되어 있을때 입니다. 툴팁은 컬럼헤더가 제한된 넓이때문에 전달하지 못하는 것을 전달할 수 있습니다. 코드를 Modular하게 하기 위해 우리는 CListCtrl 파생클래스에 툴팁기능을 구현할것입니다.

CListCtrl 파생 클래스에 CToolTipCtrl 타입의 멤버변수를 선언합니다.

    CToolTipCtrl    m_tooltip;

CListCtrl 파생 클래스에서 PreSubclassWindow() 함수를 오버라이드 합니다. 베이스클래스의 PreSubclassWindow()를 호출한후, 툴팁객체를 만듭니다.

void CMyListCtrl::PreSubclassWindow()
{
        CListCtrl::PreSubclassWindow();
        // Add initialization code
        m_tooltip.Create( this );
}

PreTranslateMessage()함수를 오버라이드해서 CToolTip 객체의 RelayEvents()함수를 호출합니다. RelayEvents() 함수를 호출하는것은 툴팁이 마우스가 툴영역 어디에 들어왔는지는 알 수 있는 기회를 제공합니다. 비록 리스트뷰컨트롤이 받는 모든 메시지를 패스하지만, 툴팁컨트롤은 WM_?BUTTONDOWN,WM_?BUTTONUP, 그리고 WM_MOUSEMOVE 메시지 만을 처리합니다.

BOOL CMyListCtrl::PreTranslateMessage(MSG* pMsg)
{
        m_tooltip.RelayEvent( pMsg );
        return CListCtrl::PreTranslateMessage(pMsg);
}

툴팁을 추가하기위한 방법을 제공합니다. 하나의 툴팁컨트롤은 여러개의 툴을 처리할수 있습니다. AddHeaderToolTip() 헬퍼 함수는 툴팁컨트롤에 새로운 툴을 추가합니다

// AddHeaderToolTip     -       컬럼헤더에 대한 툴팁을 추가합니다.
//                                         컨트롤은 자세히보기(LVS_REPORT) 모드여야 합니다.
// Returns                    -       성공시 TRUE 리턴
// nCol                         -       컬럼 인덱스
// sTip                         -       툴팁텍스트

BOOL CMyListCtrl::AddHeaderToolTip(int nCol, LPCTSTR sTip /*= NULL*/)
{
        const int TOOLTIP_LENGTH = 80;
        char buf[TOOLTIP_LENGTH+1];
       
        CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
        int nColumnCount = pHeader->GetItemCount();
        if( nCol >= nColumnCount)       return FALSE;
        if( (GetStyle() & LVS_TYPEMASK) != LVS_REPORT)  return FALSE;
       
        // 헤더의 높이를 구합니다.
        RECT rect;
        pHeader->GetClientRect( &rect );
        int height = rect.bottom;

        RECT rctooltip;
        rctooltip.top = 0;
        rctooltip.bottom = rect.bottom;

        // 컬럼의 좌우 테두리를 구합니다.
        rctooltip.left = 0 - GetScrollPos( SB_HORZ );
        for( int i = 0; i < nCol; i++ ) rctooltip.left += GetColumnWidth( i );
        rctooltip.right = rctooltip.left + GetColumnWidth( nCol );

        if( sTip == NULL )      {
                // 컬럼 헤딩 문자열을 가져옵니다.
                LV_COLUMN       lvcolumn;
                lvcolumn.mask           = LVCF_TEXT;
                lvcolumn.pszText                = buf;
                lvcolumn.cchTextMax     = TOOLTIP_LENGTH;
                if( !GetColumn( nCol, &lvcolumn ) )     return FALSE;
    }
        m_tooltip.AddTool( GetDlgItem(0), sTip ? sTip : buf, &rctooltip, nCol+1 );    return TRUE;
}

OnNotify()를 오버라이드하여 컬럼 넓이에 대한 변동사항을 추적합니다.만약 사용자가 컬럼크기를 조정했을때 툴팁정보를 갱신하지 않는다면 툴팁은 정확한 컬럼을 보여주지 않을것입니다.

BOOL CMyListCtrl::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
        HD_NOTIFY   *pHDN = (HD_NOTIFY*) lParam;

        if((pHDN->hdr.code == HDN_ENDTRACKA || pHDN->hdr.code == HDN_ENDTRACKW))   {
                // 툴팁의 정보를 갱신합니다.
                CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
                int nColumnCount = pHeader->GetItemCount();
                CToolInfo toolinfo;
                toolinfo.cbSize = sizeof( toolinfo );

                // 영향을 받은 각 컬럼을 tooltipinfo 를 통해 순환(Cycle)합니니다.
                for( int i = pHDN->iItem; i <= nColumnCount; i++ )        {
                        m_tooltip.GetToolInfo( toolinfo, pHeader, i + 1 );
                        int dx;                 // 넓이의 변경사항을 저장합니다.
                        if( i == pHDN->iItem )  
                               dx = pHDN->pitem->cxy - toolinfo.rect.right;
                        else                                            
                               toolinfo.rect.left += dx;
                        toolinfo.rect.right += dx;
                        m_tooltip.SetToolInfo( &toolinfo );
                }
        }
        return CListCtrl::OnNotify(wParam, lParam, pResult);
}


9.3 각 셀을 위한 툴팁

툴팁은 컬럼넓이가 제한된 화면 사이즈때문에 제한될때 매우 유용합니다. 이들은 단축되서 보여지는 컬럼의 문자열들을 확장하는데 사용될 수도 있습니다. 이 일을위해 MFC 에 의해 제공되는 툴팁기능을 사용할것입니다. 아래코드는 셀의 문자열을 툴팁에 보여줍니다만, 이것은 쉽게 이미 셀에 보여주고 있는것을 보이기보다 좀 다른것을 보여주는 것으로 수정될 수 있습니다.

각 셀에 대해 툴팁을 추가하는 것은 상당히 쉽습니다. 어쨌든 문서는 별로 도움이되지 못하며, 인식해야할 몇가지 사항이 있습니다. NT 4.0 과 Win95 의 리스트뷰 컨트롤은 몇가지 중요한 차이점을 가지고 있습니다. 첫째로 , 리스트 뷰 컨트롤과툴팁 컨트롤은 Windows 95 상에서는 ANSI 컨트롤입니다. 이것이 의미하는 것은 Windows 95 상에서는 메시지들이 ANSI 버젼이라는 것입니다. 프로젝트 세팅에 따라서 메시지 상수들(A 나 W 접미어가 없는것)이 맞는값으로 변환되는것에 의존하지마십시오. 예를 들어 UNICODE 프로그램을 개발한다면 TTN_NEEDTEXT는 TTN_NEEDTEXTW로 번역되어야 합니다. 하지만 Windows 95 에서는 실제 메시지는 TTN_NEEDTEXTA 입니다. 이것은 또한 구조체와 문자열에도 적용됩니다. Windows 95 에서는 컨트롤에전달되는 모든 문자열은 ANSI 문자열이어야 합니다. NT 4.0에선 컨트롤들이 모두UNICODE 컨트롤입니다.

둘째로, NT 4.0 에서는 리스트뷰 컨트롤이 자동으로 툴팁컨트롤을 만듭니다. 이 내장 툴팁 컨트롤은 마우스가 리스트뷰 컨트롤위에 올라와 잠시 움직이지 않을 때 자동적으로 TTN_NEEDTEXTW 통지를 보내게 됩니다. 아래코드는 이 내장 툴팁 컨트롤로부터의 통지를 무시합니다.

PreSubclassWindow()를 오버라이드하고 베이스 클래스의 PreSubclassWindow()를 호출한후 EnableToolTips(TRUE)를 호출합니다. EnableToolTips()함수는 CWnd 클래스의멤버 함수이고, 따라서 모든 윈도우와 컨트롤에 사용이 가능합니다.

void CMyListCtrl::PreSubclassWindow()
{
        CListCtrl::PreSubclassWindow();
        // Add initialization code
        EnableToolTips(TRUE);
}

OnToolHitTest()함수를 오버라이드 합니다. OnToolHitTest()는 CWnd클래스에 정의된 가상함수이고 프레임워크에 의해서 마우스포인터가 어떤툴위에 있는지 결정하기위해호출됩니다. 툴은 컨트롤 윈도우이거나 또는 윈도우안의 사각형 영역일수도 있습니다우리의 목적을 위해선 각 셀의 영역이 툴로 처리되어야 합니다.

OnToolHitTest() 문서(Documentation)는 툴을 찾았다면 1을 못찾았다면 -1을 리턴하라고 암시합니다.하지만 실제로 프레임워크는 리턴값을 툴이 바뀌었는지 결정하는데 사용합니다. 프레임워크는 툴이 바뀌었을때만 툴팁을 갱신하므로, OnToolHitTest()는 지정된 점의 셀이 값(문자열)이 바뀌어었을때 다른 값을 리턴해야 합니다.

int CMyListCtrl::OnToolHitTest(CPoint point, TOOLINFO * pTI) const
{
        int             row, col;
        RECT    cellrect;
        row = CellRectFromPoint(point, &cellrect, &col );
        if ( row == -1 )        return -1;

        pTI->hwnd               = m_hWnd;
        pTI->uId                = (UINT)((row<<10)+(col&0x3ff)+1);
        pTI->lpszText   = LPSTR_TEXTCALLBACK;
        pTI->rect               = cellrect;
        return  pTI->uId;
}

이 함수는 처음에 CellRectFromPoint() 함수를 호출하여 로우와 컬럼, 그리고 셀의 경계 사각형을 알아냅니다.
CellRectFromPoint() 는 아래에서 살펴봅니다. 함수는그리고 TOOLINFO 구조체를 설정합니다. 'uId' 는 줄,열의 값을 결합한 값을 지정합니다. 로우와 컬럼의 결합방법은 4194303 개의 로우와 1023개의 컬럼을 허용합니다. 또한 결과에 1이 더해진것을 주의하십시오. 이렇게 하는 이유는 0이 아닌값만을 생성하기위해서 입니다. 우리는 NT4.0 에서 자동적으로 만들어진 툴팁에서 보낸 통지와 구별하기 위해서 0이 아닌값을 필요로 합니다. 앞에서 언급했듯이 NT 4.0 에서 만들어진 리스트뷰 컨트롤은 자동적으로 툴팁을 생성하며 이 툴팁의 ID 는 0입니다.

다음 우리는 OnToolHitTest() 에서 사용된 CellRectFromPoint() 함수를 정의합니다. 이 함수는 #1 - 4.4 에서 다루어진 HitTestEx() 함수와 매우 비슷합니다. 점 위치의 로우 와 컬럼값을 알아내는것에 더해서 이 함수는 점아래 셀의 경계 사각형도 알아냅니다.

// CellRectFromPoint    - 셀의 로우,컬럼,경계사각형을 알아냅니다.
// Returns                   - 성공시 로우 인덱스, 아니면 -1
// point                       - 검사될 점(현재 마우스 포인터)
// cellrect                    - 경계사각형을 저장할 변수
// col                          - 컬럼값을 저장할 변수

int CMyListCtrl::CellRectFromPoint(CPoint & point, RECT * cellrect, int * col)    const
{
        int colnum;

        // 리스트뷰가 LVS_REPORT 모드에있는지 확인
        if( (GetWindowLong(m_hWnd, GWL_STYLE) & LVS_TYPEMASK) != LVS_REPORT )
             return -1;

        // 현재 화면에 보이는처음과 끝 Row 를 알아내기
        int row = GetTopIndex();
        int bottom = row + GetCountPerPage();
        if( bottom > GetItemCount() )   bottom = GetItemCount();
   
        // 컬럼갯수 알아내기
        CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
        int nColumnCount = pHeader->GetItemCount();

        // 현재보이는 Row 들간에 루프 돌기
        for( ;row <=bottom;row++)               {
                // 아이템의 경계 사각형을 가져오고, 점이 포함되는지 체크        
                CRect rect;
                GetItemRect( row, &rect, LVIR_BOUNDS );
                if( rect.PtInRect(point) )              {
                        // 컬럼찾기
                        for( colnum = 0; colnum < nColumnCount; colnum++ )  {
                                int colwidth = GetColumnWidth(colnum);                
                                if( point.x >= rect.left && point.x <= (rect.left + colwidth) ) {
                                        RECT rectClient;
                                        GetClientRect( &rectClient );
                                        if( col ) *col = colnum;
                                        rect.right = rect.left + colwidth;
       
                                        // 오른쪽 끝이 클라이언트 영역을 벗어나지 않도록 확인
                                        if( rect.right > rectClient.right )            
                                                 rect.right = rectClient.right;
                                        *cellrect = rect;

                                        return row;
                                }
                                rect.left += colwidth;
                        }
                }
        }
        return -1;
}

OnToolTipText()함수를 정의합니다. 이것은 툴팁으로부터의 TTN_NEEDTEXT통지에 대한 핸들러입니다. 실제로 OnToolTipText()는 TTN_NEEDTEXTA와 TTN_NEEDTEXTW 통지 양쪽을 처리하며 프로그램자신이 ANSI든 UNICODE든 관계없이 전자를 위해 ANSI 스트링을사용하며 후자를 위해 UNICODE 스트링을 사용합니다.

BOOL CMyListCtrl::OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT * pResult )
{
        // ANSI 와 UNICODE 양쪽버젼의 메시지를 처리해야함
        TOOLTIPTEXTA*   pTTTA   = (TOOLTIPTEXTA*)pNMHDR;
        TOOLTIPTEXTW*   pTTTW   = (TOOLTIPTEXTW*)pNMHDR;
        CString         strTipText;
        UINT    nID = pNMHDR->idFrom;

        if( nID == 0 )                  // NT 에서의 자동생성 툴팁으로부터의 통지
                return FALSE;       // 그냥 빠져나간다.

        int row     = ((nID-1) >> 10) & 0x3fffff ;
        int col     = (nID-1) & 0x3ff;

        strTipText = GetItemText( row, col );

#ifndef _UNICODE
        if (pNMHDR->code == TTN_NEEDTEXTA)      
               lstrcpyn(pTTTA->szText, strTipText, 80);
        else                                                                            
               _mbstowcsz(pTTTW->szText, strTipText, 80);
#else
        if (pNMHDR->code == TTN_NEEDTEXTA)      
               _wcstombsz(pTTTA->szText, strTipText, 80);
        else                                                                            
               lstrcpyn(pTTTW->szText, strTipText, 80);
#endif
        *pResult = 0;
        return TRUE;            // 메시지가 처리되었다.
}

함수는 먼저 NT에서의 내장툴팁 통지인지 체크하고 맞다면 바로 리턴합니다. 그리고 id로부터 로우와 컬럼정보를 해독하고 셀안의 정보로 TOOLTIPTEXT구조체를 채웁니다.

메시지맵에 OnToolTipText()를 연결하십시오. ON_NOTIFY_EX 와 ON_NOTIFY_EX_RANGE매크로를 사용하는 것이 좋습니다. 이것은 필요하다면 더이상의 메시지 처리를 위해 통지를 전달하는 것을 가능하게 합니다.

BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
        //{{AFX_MSG_MAP(CMyListCtrl)
        :
        // other entries
        :
        //}}AFX_MSG_MAP
        ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
        ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
END_MESSAGE_MAP()

우리가 단순한 ON_NOTIFY 매크로를 사용하지 않는다는것에 주의하십시오. 실제로 당신이 ON_NOTIFY(TTN_NEEDTEXT, 0, OnToolTipText) 같은 메시지맵 엔트리를 사용한다면 몇개의 큰 문제점을 가지게 됩니다. 첫째로 TTN_NEEDTEXT 통지는 ANSI 빌드버젼에선 NT 4.0 상에서는 절대로 받을수 없는 TTN_NEEDTEXTA 로 변환됩니다. 둘째로, 우리는 일반적인 경우가 아닌 ID 0 이외의 값에 관심이 있다는 것입니다.


9.4 각 셀을 위한 타이틀팁

타이틀은 다소 툴팁과 비슷합니다. 리스트뷰 컨트롤에 대해서, 타이틀팁은 텍스트를 다 보여줄수 있을만큼 넓지 못할 때 사용됩니다. 타이틀팁은 마우스가 셀의 위에 위치하지마자 보여지게 됩니다.

우리는 타이틀팁을 위해 커스텀클래스를 정의합니다. 타이틀팁은 WM_MOUSEMOVE 핸들러에서 생성됩니다. 타이틀 팁은 마우스가 아이템 밖으로 나가거나 응용프로그램이 포커스를 잃을때 자기 자신을 파괴하게 됩니다.

OnMouseMove() 코드는 CellRectFromPoint() 함수를 로우,컬럼인덱스와 하부아이템의 경계영역 사각형을 알아내기 위해 사용합니다. 그리고 타이틀팁 객체에 사각형과 아이템 텍스트의 정보를 넘기게 됩니다.  타이틀팁객체가 화면에 보여질지를 결정하게됩니다.

void CMyListCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
        if( nFlags == 0 )               {       // 타이틀팁 객체 Enable 하기 위해서
                int row, col;
                RECT cellrect;
                row = CellRectFromPoint(point, &cellrect, &col );
                if( row != -1 )         {
                        int offset = 5; // 컬럼 왼쪽 경계에서부터의 오프셋은 보통 5픽셀
                        // 첫번째 컬럼에 대해선 오프셋은 그림을 고려해야 합니다.
                        // 아래오프셋은 자기자신의 프로그램에 맞는값을 사용하십시오            
                        if( col == 0 ) offset+=19;
                        m_titletip.Show( cellrect, GetItemText( row, col ), offset );        
                }
        }
        CListCtrl::OnMouseMove(nFlags, point);
}

PreSubclassWindow() 함수가 오버라이드 되어있지 않다면, 해야합니다. 이 함수에서타이틀팁 객체를 생성할것입니다.

void CMyListCtrl::PreSubclassWindow()
{
        CListCtrl::PreSubclassWindow();
        // Add initialization code
        m_titletip.Create( this );
}

헤더파일과 구현파일이 아래 있습니다.

#if !defined(   AFX_TITLETIP_H__FB05F243_E98F_11D0_82A3_20933B000000__INCLUDED_  )
#define                 AFX_TITLETIP_H__FB05F243_E98F_11D0_82A3_20933B000000__INCLUDED_

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

// TitleTip.h : header file
// CTitleTip    window

#define TITLETIP_CLASSNAME      _T("ZTitleTip")

class CTitleTip : public CWnd
{
        public: // Construction
        CTitleTip();
        public: // Attributes
        public: // Operations
       
        //Overrides
    // ClassWizard generated virtual function overrides
        //{{AFX_VIRTUAL(CTitleTip)
    public:
        virtual BOOL PreTranslateMessage(MSG* pMsg);
    //}}AFX_VIRTUAL

        public:         // Implementation
                void Show( CRect rectTitle, LPCTSTR lpszTitleText, int xoffset = 0);
                virtual BOOL Create( CWnd *pParentWnd);
                virtual ~CTitleTip();
        protected:
                CWnd *m_pParentWnd;
                CRect m_rectTitle;

               
        protected:              // Generated message map functions
    //{{AFX_MSG(CTitleTip)
        afx_msg void OnMouseMove(UINT nFlags, CPoint point);    
        //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately// before the previous line.
#endif // !defined(AFX_TITLETIP_H__FB05F243_E98F_11D0_82A3_20933B000000__INCLUDED_)

CTitleTip 의 생성자에서 이 프로그램의 다른 인스턴스에서 윈도우의 클래스를 등록하지 않았다면 등록하게 됩니다. 클래스에 대한 배경 브러쉬는 COLOR_INFOBK를사용하게 되어 툴팁의 색상과 같게 됩니다.

Create() 함수에서는 주목해서 볼것이 윈도우 스타입니다. WS_BORDER 스타일이 타이틀팁 윈도우 주위에 경계선을 그리게 합니다. WS_POPUP스타일은 타이틀팁이 리스트뷰 컨트롤의 경계를 벗어날수도 있게 하기위해 필요합니다. WS_EX_TOOLWINDOW스타일은 윈도우가 태스크바에 나타나지 않도록 해줍니다. WS_EX_TOPMOST 스타일은 타이틀팁이 보이도록 해줍니다.

Show() 함수는 텍스트의 크기가 셀의 크기보다 클때 타이틀팁을 보여주게 됩니다. 경계 사각형은 변형되고 나중에 언제 타이틀팁이 숨겨져야 할때를 결정하기 위해 저장됩니다.

WM_MOUSEMOVE 에 대한 핸들러 OnMouseMove() 는 타이틀팁이 보일 셀영역에 마우스가있는지를 검사합니다. 이 영역은 타이틀팁 윈도우의 클라이언트 영역 사각형보다 작습니다. 만약 마우스가 영역밖으로 나간다면 타이틀이 숨겨지고 적절한 윈도우에 WM_MOUSEMOVE 메시지가 전달됩니다.

타이틀팁은 또한 사용자가 키나 마우스버튼을 눌렀을때 없어질 필요가 있습니다. 우리는 이메시지들에 대해 살펴보기 위해 PreTranslateMessage()를 오버라이드 합니다. 만약 이 메시지들중에 어떤것이라도 받는다면 타이틀팁은 없어지고 리스트뷰 컨트롤에 메시지가 전달됩니다.

// TitleTip.cpp : implementation file
#include "stdafx.h"
#include "TitleTip.h"
#ifdef _DEBUG
        #define new     DEBUG_NEW
        #undef          THIS_FILE
        static char     THIS_FILE[] = __FILE__;
#endif

// CTitleTip

CTitleTip::~CTitleTip()         {       }

CTitleTip::CTitleTip()
{
        // 만약 이미 등록되지 않았다면 윈도우 클래스를 등록한다.
        WNDCLASS wndcls;
        HINSTANCE hInst = AfxGetInstanceHandle();
        if(!(::GetClassInfo(hInst, TITLETIP_CLASSNAME, &wndcls)))    {
                // 새로운 클래스 등록
                wndcls.style                    = CS_SAVEBITS ;
                wndcls.lpfnWndProc      = ::DefWindowProc;
                wndcls.cbClsExtra               = wndcls.cbWndExtra = 0;
                wndcls.hInstance                = hInst;
                wndcls.hIcon                    = NULL;
                wndcls.hCursor          = LoadCursor( hInst, IDC_ARROW );
                wndcls.hbrBackground    = (HBRUSH)(COLOR_INFOBK + 1);
                wndcls.lpszMenuName     = NULL;
        wndcls.lpszClassName    = TITLETIP_CLASSNAME;

                if (!AfxRegisterClass(&wndcls)) AfxThrowResourceException();
        }
}

BEGIN_MESSAGE_MAP(CTitleTip, CWnd)
        //{{AFX_MSG_MAP(CTitleTip)
        ON_WM_MOUSEMOVE()
        //}}AFX_MSG_MAP
END_MESSAGE_MAP()

// CTitleTip message handlers

BOOL CTitleTip::Create(CWnd * pParentWnd)
{
        ASSERT_VALID(pParentWnd);

        DWORD dwStyle = WS_BORDER | WS_POPUP;  
        DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
        m_pParentWnd = pParentWnd;
    return CreateEx( dwExStyle, TITLETIP_CLASSNAME, NULL, dwStyle, 0, 0, 0, 0, NULL, NULL, NULL );
}

void CTitleTip::Show(CRect rectTitle,LPCTSTR lpszTitleText,int xoffset /*=0*/)
{
        ASSERT( ::IsWindow( m_hWnd ) );
        ASSERT( !rectTitle.IsRectEmpty() );

    if( IsWindowVisible() )             return;
       
        m_rectTitle.top = -1;
        m_rectTitle.left        = -xoffset;
        m_rectTitle.right       = rectTitle.Width()-xoffset;
        m_rectTitle.bottom      = rectTitle.Height();
        m_pParentWnd->ClientToScreen( rectTitle );

        CClientDC dc(this);
        CString strTitle(lpszTitleText);
        CFont *pFont = m_pParentWnd->GetFont();
        CFont *pFontDC = dc.SelectObject( pFont );
        CRect rectDisplay = rectTitle;
        CSizesize = dc.GetTextExtent( strTitle );

        rectDisplay.left += xoffset;
        rectDisplay.right = rectDisplay.left + size.cx + 5;
        // 만약 텍스트가 공간에 맞다면 보이지 않는다.

        if( rectDisplay.right <= rectTitle.right-xoffset )              return;

        SetWindowPos( &wndTop, rectDisplay.left, rectDisplay.top,
               rectDisplay.Width(), rectDisplay.Height(),
               SWP_SHOWWINDOW|SWP_NOACTIVATE );

        dc.SetBkMode( TRANSPARENT );
        dc.TextOut( 0, -1, strTitle );        
        dc.SelectObject( pFontDC );
        SetCapture();
}

void CTitleTip::OnMouseMove(UINT nFlags, CPoint point)
{
        if( !m_rectTitle.PtInRect( point ) )            {
                ReleaseCapture();
                ShowWindow( SW_HIDE );

                // 메시지를 전달한다.
                ClientToScreen( &point );
                CWnd *pWnd = WindowFromPoint( point );
                if( pWnd == this ) pWnd = m_pParentWnd;
                pWnd->ScreenToClient( &point );
                pWnd->PostMessage(WM_MOUSEMOVE, nFlags, MAKELONG( point.x, point.y ));
        }
}

BOOL CTitleTip::PreTranslateMessage(MSG* pMsg)
{
        CWnd *pWnd;
        switch( pMsg->message )         {
                case WM_LBUTTONDOWN:
                case WM_RBUTTONDOWN:
                case WM_MBUTTONDOWN:
                        POINTS pts = MAKEPOINTS( pMsg->lParam );
                        POINT  point;
                        point.x = pts.x;
                        point.y = pts.y;
                        ClientToScreen( &point );
                        pWnd = WindowFromPoint( point );
                        if( pWnd == this ) pWnd = m_pParentWnd;
                        pWnd->ScreenToClient( &point );
                        pMsg->lParam = MAKELONG( point.x, point.y );        
                        // 그냥 밑으로 가게 합니다.
                case WM_KEYDOWN:
                case WM_SYSKEYDOWN:
                        ReleaseCapture( );
                        ShowWindow( SW_HIDE );
                        m_pParentWnd->PostMessage(pMsg->message, pMsg->wParam, pMsg->lParam);
                        return TRUE;
        }
        if( GetFocus() == NULL )        {
                ReleaseCapture();
                ShowWindow( SW_HIDE );
                return TRUE;
        }
        return CWnd::PreTranslateMessage(pMsg);
}