查看MFC中与CListCtrl和CListView相关的源码时发现,CListView派生自CWnd->CView->CCtrlView,而CListCtrl则直接派生自CWnd类,而CListView::GetListCtrl函数居然仅仅用了一句 return *(CListCtrl*)this; 将CListView活生生的转换成了CListCtrl,百思不得其解,后在CSDN论坛中向众高手求助,终于明白了个中原由。
CSDN论坛求助帖地址:http://community.csdn.net/Expert/topic/3542/3542830.xml?temp=.3308374
原帖内容如下:
主题: MFC中的代码,不敢相信自己会C++了........
作者: qrlvls (AIResearcher)
信 誉 值: 110
所属论坛: VC/MFC 界面
问题点数: 200
回复次数: 25
发表时间: 2004-11-11 11:44:52
_AFXCVIEW_INLINE CListCtrl& CListView::GetListCtrl() const
{ return *(CListCtrl*)this; }
从上面代码来看,CListView的指针是可以强制转换成CListCtrl指针的
但是看过MFC中代码后发现,CListView派生自CWnd->CView->CCtrlView
而CListCtrl直接派生自CWnd,CWnd中没有GetListCtrl这样的函数,而且CListView的实现中也没有重载CListCtrl强制转换操作符
现在郁闷中.....求解!
回复人: happyparrot(快乐鹦鹉) ( ) 信誉:155 2004-11-11 11:57:29 得分:0
指针的强制转换不需要有什么强制转换函数。
只需要两个指针对象的空间顺序兼容就可以了。
回复人: DentistryDoctor(雅克医生<改行做程序员了>) ( ) 信誉:162 2004-11-11 11:58:08 得分:0
BOOL CCtrlView::PreCreateWindow(CREATESTRUCT& cs)
{
ASSERT(cs.lpszClass == NULL);
cs.lpszClass = m_strClass;
// initialize common controls
VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG));
AfxDeferRegisterClass(AFX_WNDCOMMCTLSNEW_REG);
// map default CView style to default style
// WS_BORDER is insignificant
if ((cs.style | WS_BORDER) == AFX_WS_DEFAULT_VIEW)
cs.style = m_dwDefaultStyle & (cs.style | ~WS_BORDER);
return CView::PreCreateWindow(cs);
}
实际上 CCtrlView 直接以 C_LISTVIEW (对于CTreeViewCtrl就是WC_TREE) 创建的, 所以可以这样认为CListView实际上也是CListCtrl的派生类,所以那样转换就没有任何问题。
这是MFC为了避免多继续而采用的变通方式。
class AFX_NOVTABLE CCtrlView : public CView
可能与CCtrlView没有虚函数表有关吧,因为一般的成函数并不占其实例的内存空间,并且CListView汉有任务成员变量,所以这样应该是安全的。
回复人: roger_ding(海天一色) ( ) 信誉:103 2004-11-11 12:49:22 得分:0
先说下,CListCtrl的类中只继承了两个虚函数,如下
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
virtual BOOL OnChildNotify(UINT, WPARAM, LPARAM, LRESULT*);
而这两个都是由FrameWork来调用的,一般程序中不需要直接调用这两个函数
因为CListView的m_hWnd其实就是"ListCtrl window",所以当强行转换成CListCtrl后,m_hWnd是正确的,但也仅需要该成员变量正确而已,因为看看CListCtrl的所有非虚成员函数,基本上都是通过m_hWnd对listctrl window消息的封装,而非虚成员函数在exe文件中其实等同于全局函数或者静态函数,只是一段代码,当调用时需传入this指针而已
因此虽然CListView和CListCtrl对象的内存内容不一样,但是因为m_hWnd的内存地址是一样的(都是继承于CWnd),所以正确的使用转换后的CListCtrl指针
回复人: verybigbug() ( ) 信誉:98 2004-11-11 13:17:30 得分:30
其实,CButton, CListCtrl, CEdit,CTreeCtrl都是假的,所有函数都是通过SendMessage实现的。
注意,这些class中都没有成员变量。
所以,MFC对窗口的包装都是假的。
回复人: camelyi(C++打字员) ( ) 信誉:99 2004-11-11 13:32:53 得分:100
众所周知,C++的多继承是个极其讨厌的东西,稍有不慎就会带来灾难
尤其是钻石型多继承:
A
↑ ↑
B C
↑ ↑
D
(这个图有点难看,不好意思)
查看MFC源码,多继承的类非常少(嗯,M$那帮程序员看来也害怕多继承),但是却有很多类间的关系带有明显的多继承特征:
1)CEdit继承自CWnd
2)CEditView继承自CCtrlView,CCtrlView继承自CWnd
实际使用中显然可以发现CEditView有很多CEdit的行为,难道CEditView重复了CEdit的代码?其实不然
下面这篇文章转自微软的网站,可以发现其中一个很聪明的技巧,借此技巧也可以告诉我们如何实现自己的CtrlView
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 functions CFooCtrl::GetThis and CFooCtrl::SetThat, which merely passed the parameters to and fro using the proper WMFOO_XXX messages, and then such a programmer could in fact use CCtrlView to convert your control to a view! Or, to put it differently, one way to use CCtrlView is to reimplement your custom control using pure C and the Windows API with messages and subclassing instead of MFC! This would require a bit more typing and type-casting (for all those WPARAMs and LPARAMs), but would leave you feeling satisfied and pure.
回复人: DentistryDoctor(雅克医生<改行做程序员了>) ( ) 信誉:162 2004-11-11 13:39:06 得分:50
打字员给的内容,在MSDN的在线杂志上有,很精典很全面,这只是MFC玩的小猫腻。
回复人: bluebohe(薄荷) ( ) 信誉:135 2004-11-11 13:44:16 得分:10
这个问题以前bcpl回答过
怀念bcpl一把
具体什么原因楼上已经说清楚了:)
回复人: dhbo(沉默的石头) ( ) 信誉:98 2004-11-11 13:51:56 得分:10
我觉得没什么不可以啊,就象
class A
{
public:
A(){
a1=0;
a2=0 ;}
~A(){}
protected:
private:
int a1,a2 ;
};
class B
{
public:
B() {b1=1 ; b2=1 ; b3=1 ;}
~B(){}
protected:
private:
int b1,b2,b3 ;
};
int main(int argc, char* argv[])
{
printf("Hello World!\n");
A a,*pa ;
pa = &a ;
B b,*pb ;
pb = &b ;
pa =(A *)(void *)pb ;
return 0;
}
可以得到两个不相干得类的信息,但是你自己要明白那个内存区是你要取得的内存区 。<可以把, {a1,a2}和{b1,b2} 看作是CListCtrl>
以上个人理解
回复人: qrlvls(AIResearcher) ( ) 信誉:110 2004-11-11 13:52:36 得分:0
感谢大家的关注,还引来了so多高手,结帐
结帖后,悠客发来如下内容,在此谢过!
class wnd
{
public :
void display()
{
printf("wnd\n");
}
};
class _list //:public wnd
{
public:
_list()
{
i=1;
j=3;
}
void display()
{
printf("list\n");
}
virtual fun()
{
printf("virtual _list\n");
}
void hello()
{
printf("_list say hello\n");
}
int i;
int j;
};
class _view //:public wnd
{
public:
_view()
{
i=2;
}
void display()
{
printf("view\n");
}
_list &getlist()
{
//return *((_list *)this);
return (_list &)*this;
}
virtual fun()
{
printf("virtual _view\n");
}
int i;
};
int main(int argc, char* argv[])
{
_view v;
v.getlist().display();
printf("%d\n",v.getlist().i);
v.getlist().fun();
v.getlist().hello(); // 并没构造_list,但仍可调用_list::hello
printf("%d\n",v.getlist().j);
return 0;
}
结果:
list
2
virtual _view
_list say hello
1245120 这里是个随机值,并不是预想中的3
由上可以得出
1、对"指针"或"引用"的强制转换可以是任意的,编译器不会检查
如果改成 _list l = (_list)v; 就编译不过去
2、调用某类的公共成员函数并不需要借助类的具体实实例
比如要调用_list::hello,按c++的语法,必须先构造_list实例;
但实际上hello函数是独立于_list之外的,即使没构造_list,它也存在
你可以把它当作是一个全局函数。
例如:
假设_list::hello在内存在地址是0x40100
v.getlist().hello(); 这行代码编译器直接把它编译成对call 0x40100
3、v.getlist() 这行代码里面有没有构造 _list 实例
没有,确实是_view实例指针,由
printf("%d\n",v.getlist().i); 显示 2
v.getlist().fun(); 显示_list say hello
可以看出,类实例里面只包含成员变量和虚函数表,这是大家都知道的
而这行printf("%d\n",v.getlist().j); 显示1245120更说明这点
v.getlist().j的访问实际已超出了_view实例的内存范围
后来为了验证函数与数据的存储写了个测试程序:
#include "stdafx.h"
class NewClass
{
public:
void hello() {printf("%d\n", a);};
protected:
private:
int a;
};
int main(int argc, char* argv[])
{
NewClass *p = NULL;
p->hello();
printf("Hello World!\n");
return 0;
}
注意,这时程序是会报错的,因为数据成员a根本不存在,访问了野指针,而如果将hello()函数改为一个空函数,则一切运行正常,因为对hello函数的寻址并非通过p的偏移量去查询的(即hello函数并不处于对象的内存结构中,估计在处理调用成员函数时,编译器是将对象的this指针作为一个默认的参数传递给了函数),而对数据p->a的寻址则是通过偏移量去找的,这样一来,NewClass对象的内存结构也就清晰了,呵呵。
如果有什么不正确的地方希望大家帮忙指点指点,我的邮箱:qrao@163.com