ListView视图中无法使用派生的CListCtrl类的原因

来源:互联网 发布:一元抢购淘宝 编辑:程序博客网 时间:2024/05/21 17:48

本文转自:http://blogold.chinaunix.net/u2/75914/showart_2010259.html

 

我想在以ListView视图中,在指定的列中设定背景颜色和文本颜色,在网上整整查了两个星期,查到的结果都是在窗口中的ListView控件中设置的,它们的方法基本上是采用派生CListCtrl类,在该类中编写设置颜色的方法,然后将ListCtrl控件与这个派生的类相关联。此方法一个比较简单的例子-> 。
        我曾在BBS中多次问过这个问题,有人告诉我说使用类化,于是乎,查看了大量的关于类化方面的资料,后来尝试着测试了很多,以下是一些测试的代码。
void CAaaaaView::OnInitialUpdate()//程序名为Aaaaa
{
    CListView::OnInitialUpdate();
    
    BOOL Result=TRUE;
    CListCtrl &lc=GetListCtrl();

//1.    Result=pMyList.SubclassDlgItem(lc.GetDlgCtrlID(),this);
//2.    Result=pMyList.SubclassDlgItem(lc.GetDlgCtrlID(),this->GetParent());
//3.    Result=pMyList.SubclassWindow(lc.m_hWnd);
//4.    Result=pMyList.SubclassWindow(this->GetSafeHwnd());
//5.    Result=pMyList.SubclassWindow(this->m_hWnd);
//6.    CWnd *pWnd=GetParent();
//         HWND hWnd=::GetDlgItem(GetParent()->m_hWnd,lc.GetDlgCtrlID());
//        pMyList.SubclassWindow(hWnd);
...
}
这些程序编译都能通过,但运行到类化的时候,即SubClassWindow或SubClassDlgItem时,就会出现错误无法继续运行。
          后来,在微软网站上看到一篇文章好像说ListView和ListCtrl是一个控件的两种表达形式,我们可以从CListCtrl &GetListCtrl()的定义中看出来,它的定义是这样的。
_AFXCVIEW_INLINE CListCtrl& CListView::GetListCtrl() const
    { return *(CListCtrl*)this; }
        看起来好奇怪,怎么能是一个东西呢?
        我们一起来看一下微软的解释:

  Q 
    I made a custom control derived from CWnd, and now I want to use it as a view. My first solution was to embed the control into the view and handle OnSize in the view to position the control over the client area. The problem is that mouse messages go to the control and cannot be overridden in the view. The keystroke messages go to the view and must be manually forwarded to the control.
    I read about CCtrlView as a base class for common controls. I've even managed to write the view around it (I believe that you wrote about this in an issue of MSJ), but I could not get it to work with my CWnd-based control. Can this be done, and how?
                        Mateo Anderson

 

  A 
    CCtrlView is a trick that MFC uses to convert control classes to view classes. For example, from CTreeCtrl to CTreeView and from CListCtrl to CListView. The comment in the documentation for CCtrlView says, "CCtrlView allows almost any control to be a view." Unfortunately, the "almost" is a bit of an exaggeration, unless by "any control" the author was thinking about any of the built-in Windows? controls like CEdit, CTreeCtrl, and so on. CCtrlView uses a trick that works only in special circumstances.
    To understand how CCtrlView works, let's take a look at CTreeView, which is derived from CCtrlView. There are three important functions to consider: the constructor, PreCreateWindow, and GetTreeCtrl.
    The constructor tells CCtrlView which kind of Windows control to create.

 

CTreeView::CTreeView() : 
  CCtrlView(WC_TREEVIEW, dwStyle)
{
}

 

    In this case, WC_TREEVIEW (#defined in commctrl.h) is the name of the (Windows) tree control class: namely, SysTreeView32. CCtrlView stores this name in a data member for later use.

 

CCtrlView::CCtrlView(LPCTSTR lpszClass, 
  DWORD dwStyle)
{
  m_strClass = lpszClass;
  m_dwDefaultStyle = dwStyle;
}

 

    The next function that comes into play is PreCreateWindow, which CTreeCtrl inherits from CCtrlView. CCtrlView::PreCreateWindow uses m_strClass to set the class name in the CREATESTRUCT just before the window is created.

 

// CCtrlView uses stored class name
BOOL CCtrlView::PreCreateWindow(CREATESTRUCT& cs)
{
  cs.lpszClass = m_strClass;
  ???
  return CView::PreCreateWindow(cs);
}

 

    Now the window created is of the desired class—in this case, SysTreeView32. So far, so good. But if CTreeCtrl is derived from CCtrlView, which is derived from CView, how can it also be derived from CTreeCtrl, the MFC class that wraps the tree control? CTreeView and CTreeCtrl are completely independent, with different inheritance chains. CTreeCtrl is derived from CWnd directly, whereas CTreeView is derived from 
CCtrlView/CView! This is where the trick comes in.
    To manipulate the tree view as a tree control, CTreeView provides a special function, GetTreeCtrl, to get the tree control.

 

CTreeCtrl& CTreeView::GetTreeCtrl() const

  return *(CTreeCtrl*)this; 
}

 

    GetTreeCtrl simply casts the CTreeView to a CTreeCtrl. But wait a minute—how on earth can this work? The two classes are entirely different, with different data members and virtual function tables—you can't just cast one class to another and expect it to work!
    The answer is that CTreeCtrl has no virtual functions and no member data. You could call it a pure wrapper class. CTreeCtrl doesn't add anything (data or virtual functions) to its base class, CWnd; all it adds is a bunch of wrapper functions, concrete functions that send messages to the underlying HWND. For example:

 

HTREEITEM CTreeCtrl::InsertItem(...)

  return (HTREEITEM)::SendMessage(m_hWnd, 
    TVM_INSERTITEM, ...);
}

 

    The only data member that InsertItem accesses is m_hWnd, which all CWnd-derived classes have. InsertItem and all the other wrapper functions simply pass their arguments to the underlying HWND, converting C++-style 
member functions to Windows-style SendMessage calls. The object itself ("this" pointer) could be an instance of any CWnd-derived class, as long as m_hWnd is in the right place (that is, the first data member of the class) and the HWND is, in fact, a handle to a tree control. It's the same reason you can write

 

pEdit = (CEdit*)GetDlgItem(ID_FOO);

 

    even though GetDlgItem returns a pointer to a CWnd, not a CEdit: because CEdit is also a pure wrapper class, with no extra data or virtual functions beyond what it inherits from CWnd.
    So the "almost any" in the statement "CCtrlView allows almost any control to be a view" means specifically any control that adds no member data and no virtual functions to CWnd, what I am calling a "pure wrapper class." If your control class has its own data or virtual functions, you can't use CCtrlView because the extra data/virtual functions won't exist in CCtrlView/CView.
    For example, the first virtual function in CView is CView::IsSelected. If your control class has some other virtual function, then things will certainly bomb when you cast CCtrlView to your CFooCtrl and try to call that virtual function. The function simply doesn't exist. Likewise, the first data member in CView is m_pDocument. If your control class expects some other data member, your code will bite the bag when it tries to access it, if the object called is really a CCtrlView, not a CFooCtrl. Too bad, so sad.
    In short, the only time you can use the CCtrlView trick is when your CWnd-derived control class has no virtual functions and no member data of its own. C'est la vie.
    If you want to use your control in a doc/view app, what can you do—throw your head on the table and weep? Of course not! Your first approach was dandy: create your control as a child of the view and use OnSize to position it exactly over the view's client area.

 

CFooView::OnSize(..., cx, cy)
{
  m_wndFooCtrl.SetWindowPos(NULL,
    0,0,cx,cy,SWP_NOZORDER);
}

 

    Those input problems you encountered are easily overcome. Consider the mouse. If you want to let the parent view handle mouse messages sent to your control, the thing to do is abstract the messages into higher-level events. That's a highfalutin way of saying something familiar to us all. 
    Consider, for example, a button. When the user clicks a button, the button notifies its parent with a BN_CLICKED event. It does not send WM_LBUTTONDOWN; it sends a WM_COMMAND message with subcode = BN_CLICKED. 
    The button is telling its parent window: the user clicked me. Likewise, list controls don't broadcast WM_LBUTTONDOWN; they do a little processing and notify their parents with LBN_SELCHANGE. (In the case of a double-click, list controls do propagate LBN_DBLCLK, which is little more than WM_LBUTTONDBLCK.) In general, the idea is that controls convert raw events into higher-level events that are meaningful in the context of the control.
    If you're doing this at home, you should probably use the more modern way, which is WM_NOTIFY, instead of WM_COMMAND. WM_NOTIFY lets you pass a whole struct of information instead of trying to squish everything into half a DWORD. You can decide which mouse messages your control should propagate. 
    For example, buttons don't normally send BN_DOUBLECLICKED unless they have the BS_NOTIFY style.
    So much for mousing. Now, what about the keyboard? That's even easier. When the user activates your app by clicking on the caption or Alt-TABing to it, Windows normally gives focus to the main frame. MFC, in turn, passes focus to your view:

 

void CFrameWnd::OnSetFocus(...)
{
  if (m_pViewActive != NULL)
    m_pViewActive->SetFocus();
  else
    CWnd::OnSetFocus(...);
}

 

    All you have to do is pass the focus, in turn, to your control:

 

CFooView::OnSetFocus(...)
{
  m_wndFooCtrl.SetFocus();
}

 

    Now keystrokes go directly to your control. Do not pass view. I told you it was easy! This is the age-old Windows way of doing things, but with all those frameworks doing so much for you nowadays, it's easy to miss the basics.
    The upshot is this: if your custom control view class is not a pure wrapper function, that is, if it has so much as one data member or virtual function of its own, then the way to convert your control into a view is to instantiate it as a child window of the view and integrate it in three simple steps.
    Handle WM_SIZE in the view to position your control exactly over the view's client area.
    Convert mouse messages in the control to higher-level parent WM_NOTIFY notifications.
    Handle WM_FOCUS in the view to set focus to your control.
    Incidentally—if I may be permitted to muse for just a paragraph or two—this example illustrates one of the drawbacks of the MFC object model, which doesn't allow multiple inheritance. You can't say, "my class is a view and a foo control," which is really what you want to do. It also shows why some programmers may choose to implement custom controls using C only, and not C++. It is possible, you know. All you have to do is register your own window class (in the Windows sense), allocate a little memory block to hold your window's instance data, and implement all your functions as messages—WMFOO_GETTHIS and WMFOO_SETTHAT. This was the only way to implement custom controls before C++ came along, and it still has many benefits.
    For example, if you do it this way, some other C++ programmer could come along and write a C++ pure wrapper class for your window, with simple wrapper func 不好意思,后面的我也打不开,看到这里,相信你已经了解到ListView和ListCtrl对象的关系了。

         我们可以继续查看这方面的资料,接下来的例子中就是对本标题的答案。

How do I use a derived CListCtrl with a CListView?


The short answer is - you don't. The long answer is that you derive from CListView instead and add the same functionality to the CListView derived class. The reason for this is that MFC is designed in such a way that only one C++ object can be associated with one window or control. When you use a CListView, the list view control is already associated with the view class so you cannot have another C++ object associated with the control.

The call to GetListCtrl() actually returns a pointer to the CListView object after casting it to a pointer to CListCtrl. So the return value from GetListCtrl() doesn't really point to a CListCtrl but instead it points to the ClistView.

The workaround is that you derive from CListView and add the same functions and method handlers that you have for the CListCtrl. The class wizard supports all the same window message for ClistView derived class that it does for the CListCtrl class. In this scenario, we use the old form of code reuse, we copy and paste the code.

Let's assume that one of the messages that the derived CListCtrl handles is the LVN_ENDLABELEDIT notification. To add this functionality to the CListView derived class, simply use the class wizard to add the message handler, copy the code from the CListCtrl derived class and finally precede all calls to CListCtrl methods with 'GetListCtrl()->'. Here's an example to illustrate the point.

void CMyListCtrl::OnEndLabelEdit(LPNMHDR pnmhdr, LRESULT *pLResult){LV_DISPINFO *plvDispInfo = (LV_DISPINFO *)pnmhdr; LV_ITEM*plvItem = &plvDispInfo->item;if (plvItem->pszText != NULL)SetItemText(plvItem->iItem, plvItem->iSubItem, plvItem->pszText);}void CMyListView::OnEndLabelEdit(LPNMHDR pnmhdr, LRESULT *pLResult){LV_DISPINFO *plvDispInfo = (LV_DISPINFO *)pnmhdr; LV_ITEM*plvItem = &plvDispInfo->item;if (plvItem->pszText != NULL)GetListCtrl().SetItemText(plvItem->iItem, plvItem->iSubItem, plvItem->pszText);}


看来想在视图中使用ListCtrl的派生类是不太可能了,我至目前为止还没找到更好的方法,如果你有什么好的方法,请给予于回复,非常感谢你的建议。
注:本帖中引用的内容出处现已无法查出,如果存在版权问题,请告知,我会尽快处理。
原创粉丝点击