MFC CListCtrl 和 CListView关系

来源:互联网 发布:八达岭长城 知乎 编辑:程序博客网 时间:2024/06/08 00:11

下面这篇文章转自微软的网站,可以发现其中一个很聪明的技巧,借此技巧也可以告诉我们如何实现自己的CtrlView

 
  Q
    I made a custom control derived fromCWnd, and now I want to use it as a view. My first solution was toembed the control into the view and handle OnSize in the view toposition the control over the client area. The problem is that mousemessages go to the control and cannot be overridden in the view. Thekeystroke messages go to the view and must be manually forwarded to thecontrol.
    I read about CCtrlView as a base class for commoncontrols. I've even managed to write the view around it (I believe thatyou wrote about this in an issue of MSJ), but I could not get it towork with my CWnd-based control. Can this be done, and how?
                        Mateo Anderson
A
    CCtrlView is a trick that MFC uses toconvert control classes to view classes. For example, from CTreeCtrl toCTreeView and from CListCtrl to CListView. The comment in thedocumentation for CCtrlView says, "CCtrlView allows almost any controlto be a view." Unfortunately, the "almost" is a bit of an exaggeration,unless by "any control" the author was thinking about any of thebuilt-in Windows? controls like CEdit, CTreeCtrl, and so on. CCtrlViewuses a trick that works only in special circumstances.
    Tounderstand how CCtrlView works, let's take a look at CTreeView, whichis derived from CCtrlView. There are three important functions toconsider: 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 incommctrl.h) is the name of the (Windows) tree control class: namely,SysTreeView32. CCtrlView stores this name in a data member for lateruse.
 
CCtrlView::CCtrlView(LPCTSTR lpszClass,
  DWORD dwStyle)
{
  m_strClass = lpszClass;
  m_dwDefaultStyle = dwStyle;
}
 
    The next function that comes into play isPreCreateWindow, which CTreeCtrl inherits from CCtrlView.CCtrlView::PreCreateWindow uses m_strClass to set the class name in theCREATESTRUCT 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 desiredclass—in this case, SysTreeView32. So far, so good. But if CTreeCtrl isderived from CCtrlView, which is derived from CView, how can it also bederived from CTreeCtrl, the MFC class that wraps the tree control?CTreeView and CTreeCtrl are completely independent, with differentinheritance chains. CTreeCtrl is derived from CWnd directly, whereasCTreeView 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 aCTreeCtrl. But wait a minute—how on earth can this work? The twoclasses are entirely different, with different data members and virtualfunction tables—you can't just cast one class to another and expect itto work!
    The answer is that CTreeCtrl has no virtual functionsand no member data. You could call it a pure wrapper class. CTreeCtrldoesn't add anything (data or virtual functions) to its base class,CWnd; all it adds is a bunch of wrapper functions, concrete functionsthat send messages to the underlying HWND. For example:
 
HTREEITEM CTreeCtrl::InsertItem(...)
{
  return (HTREEITEM)::SendMessage(m_hWnd,
    TVM_INSERTITEM, ...);
}
 
    The only data member that InsertItem accessesis m_hWnd, which all CWnd-derived classes have. InsertItem and all theother wrapper functions simply pass their arguments to the underlyingHWND, converting C++-style
member functions to Windows-styleSendMessage calls. The object itself ("this" pointer) could be aninstance of any CWnd-derived class, as long as m_hWnd is in the rightplace (that is, the first data member of the class) and the HWND is, infact, 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 aCWnd, not a CEdit: because CEdit is also a pure wrapper class, with noextra data or virtual functions beyond what it inherits from CWnd.
   So the "almost any" in the statement "CCtrlView allows almost anycontrol to be a view" means specifically any control that adds nomember data and no virtual functions to CWnd, what I am calling a "purewrapper class." If your control class has its own data or virtualfunctions, you can't use CCtrlView because the extra data/virtualfunctions won't exist in CCtrlView/CView.
    For example, the firstvirtual function in CView is CView::IsSelected. If your control classhas some other virtual function, then things will certainly bomb whenyou cast CCtrlView to your CFooCtrl and try to call that virtualfunction. The function simply doesn't exist. Likewise, the first datamember in CView is m_pDocument. If your control class expects someother data member, your code will bite the bag when it tries to accessit, if the object called is really a CCtrlView, not a CFooCtrl. Toobad, so sad.
    In short, the only time you can use the CCtrlViewtrick is when your CWnd-derived control class has no virtual functionsand no member data of its own. C'est la vie.
    If you want to useyour control in a doc/view app, what can you do—throw your head on thetable and weep? Of course not! Your first approach was dandy: createyour control as a child of the view and use OnSize to position itexactly 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 easilyovercome. Consider the mouse. If you want to let the parent view handlemouse messages sent to your control, the thing to do is abstract themessages into higher-level events. That's a highfalutin way of sayingsomething familiar to us all.
    Consider, for example, a button.When the user clicks a button, the button notifies its parent with aBN_CLICKED event. It does not send WM_LBUTTONDOWN; it sends aWM_COMMAND message with subcode = BN_CLICKED.
    The button istelling its parent window: the user clicked me. Likewise, list controlsdon't broadcast WM_LBUTTONDOWN; they do a little processing and notifytheir parents with LBN_SELCHANGE. (In the case of a double-click, listcontrols do propagate LBN_DBLCLK, which is little more thanWM_LBUTTONDBLCK.) In general, the idea is that controls convert rawevents into higher-level events that are meaningful in the context ofthe control.
    If you're doing this at home, you should probablyuse the more modern way, which is WM_NOTIFY, instead of WM_COMMAND.WM_NOTIFY lets you pass a whole struct of information instead of tryingto squish everything into half a DWORD. You can decide which mousemessages 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 orAlt-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. Donot pass view. I told you it was easy! This is the age-old Windows wayof doing things, but with all those frameworks doing so much for younowadays, it's easy to miss the basics.
    The upshot is this: ifyour 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 itas 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 ortwo—this example illustrates one of the drawbacks of the MFC objectmodel, which doesn't allow multiple inheritance. You can't say, "myclass is a view and a foo control," which is really what you want todo. It also shows why some programmers may choose to implement customcontrols using C only, and not C++. It is possible, you know. All youhave to do is register your own window class (in the Windows sense),allocate a little memory block to hold your window's instance data, andimplement all your functions as messages—WMFOO_GETTHIS andWMFOO_SETTHAT. This was the only way to implement custom controlsbefore C++ came along, and it still has many benefits.
    Forexample, if you do it this way, some other C++ programmer could comealong and write a C++ pure wrapper class for your window, with simplewrapper functions CFooCtrl::GetThis and CFooCtrl::SetThat, which merelypassed the parameters to and fro using the proper WMFOO_XXX messages,and then such a programmer could in fact use CCtrlView to convert yourcontrol to a view! Or, to put it differently, one way to use CCtrlViewis to reimplement your custom control using pure C and the Windows APIwith messages and subclassing instead of MFC! This would require a bitmore typing and type-casting (for all those WPARAMs and LPARAMs), butwould leave you feeling satisfied and pure.
原创粉丝点击