有时,需要创建一个自定义窗口类。通常,您通过AfxRegisterWindowClass来完成此操作,给窗口一个您选择的类名,然后在Create调用中使用这个类。这个类通常有一个与之关联的自定义MFC子类。左边的插图显示了一个带有自定义控件——罗盘的小应用程序。 一个典型的例子可能是希望创建一个具有自定义图形的简单控件。对于这个示例,我创建了compass控件,它的类将是CCompass,它将显示一个模拟的罗盘指针。它是“泛型CWnd”的子类。 要创建这个类,进入ClassWizard,选择“添加类”按钮,然后选择“New class”选项。键入您的类的名称,并在“基类”框中选择“generic CWnd”选项,它几乎出现在选项的底部。 当您单击OK时,您将得到两个文件,指南针.cpp和指南针。h,它实现你的类。 当你回到ClassWizard中时,这个类应该被选择为你想要的类。对于自定义图形类,通常需要添加WM_ERASEBKGND和WM_PAINT处理程序。为此,在窗口中选择类,选择WM_ERASEBKGND,单击添加函数,选择WM_PAINT,然后单击添加函数。您应该得到如下所示的结果: 此时,您可以进入并填充这两个函数。 但是,在对话框中使用这个类有一个问题。您必须首先在一个特定的类名下注册“窗口类”,这样对话框编辑器才能创建它。如果您想在cdialog派生类、cpropertypage派生类或cformview派生类中使用控件,这是必要的。这意味着您必须提供一个调用来注册类,并且必须在尝试创建包含控件的类之前执行此调用。 这是不方便的。为什么程序员必须记住这样做;如果不这样做,对话框就不会出现。 我决定,在编写客户想要使用的类时,他们不应该因为必须记住注册类或理解AfxRegisterClass调用的细节而感到不便。所以我决定创建一个自动注册类的机制。 该技术是创建类的静态成员变量并对其进行初始化。作为副作用,初始化将注册类。因为该变量是一个静态成员变量,所以它将在应用程序启动时被初始化。因此,类将自动注册。 因此,我向CCompass类添加了以下声明:复制Code
protected: static BOOL hasclass; static BOOL RegisterMe(); #define COMPASS_CLASS_NAME _T("Compass")
然后在CCompass.cpp文件中,我添加了:Hide 复制Code
BOOL CCompass::hasclass = CCompass::RegisterMe();
注意,因为这是一个静态初始化器,所以它将在系统启动时执行。这意味着由RegisterMe注册的类将在应用程序初始化时注册。然后,该类将可用于任何对话框、属性页或表单视图。 然而,有些方法是行不通的。例如,您不能使用AfxRegisterWndClass,因为它返回合成的类名的字符串,一个在执行时确定的名称,但是对话框模板要求您在构造模板时知道类名。确定AfxRegisterWndClass返回的字符串并指定它作为程序员应该使用的类名,这将是极其愚蠢的。 此外,您不能调用AfxGetInstanceHandle来获得注册类的实例句柄。这是因为AfxGetInstanceHandle使用的变量是在调用MFC的WinMain之后初始化的,而WinMain是在初始化静态成员变量之后。但是您可以使用低级API调用::GetModuleHandle。为了与16位Windows兼容,这将返回类型HMODULE而不是HINSTANCE,尽管这种区别在Win32中没有意义。但是,您必须进行显式强制转换,否则编译器会不高兴。 我还发现,如果你选择::DefWindowProc作为窗口过程,而不是NULL(当你子类化窗口时,这个将最终被AfxWndProc所取代),它会工作得更好。不要选择AfxWndProc! 在下面的代码中,我还做了一些随意的选择。例如,因为这将是一个子控件,所以它不需要图标,因此hIcon成员被设置为NULL。为了说明如何选择背景刷,如果你需要一个,我选择使用一个标准的背景颜色,对话框背景,COLOR_BTNFACE,和完全依照特殊要求的窗口类(它将有很大的意义,例如,不允许COLOR_color 0的整数指示器,但是这需要精心设计),我要的颜色加1。由于它是子控件,所以它没有菜单,因此lpszMenuName为NULL。关键参数是类名。这是程序员必须在对话框模板。隐藏,复制Code
BOOL CCompass::RegisterMe() { WNDCLASS wc; wc.style = 0; wc.lpfnWndProc = ::DefWindowProc; // must be this value wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = (HINSTANCE)::GetModuleHandle(NULL); wc.hIcon = NULL; // child window has no icon wc.hCursor = NULL; // we use OnSetCursor wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wc.lpszMenuName = NULL; // no menu wc.lpszClassName = COMPASS_CLASS_NAME; return AfxRegisterClass(&wc); }
要在对话框中放置控件,请打开对话框编辑器。对于步骤1,选择工具箱中的“自定义控件”图标,图标,并将控件放置在对话框所需的部分,如步骤2所示。然后打开属性框。在步骤3中,删除标题,在步骤4中,键入用作COMPASS_CLASS_NAME的类的名称。 不幸的是,ClassWizard相当原始;它不会承认这种控制的存在。为什么?问微软,我不知道为什么它会从它的控件列表中排除这个控件,你可以为它创建一个成员变量。但它确实。 所以你必须“手工”编辑你的对话框。好消息是,这很容易。 例如,在对话框的头文件中找到AFX_DATA部分。我的对话框类叫做CController,并且我已经使用ClassWizard为被跟踪对象的范围、速度和高度创建了成员变量。隐藏,复制Code
//{{AFX_DATA(CController) enum { IDD = IDD_CONTROLLER }; CStatic c_Range; CStatic c_Speed; CStatic c_Altitude; CCompass c_Compass; //}}AFX_DATA
这里需要注意的是,一旦你添加了变量,ClassWizard会很乐意处理它,只是它不会让你添加变量!奇怪! 现在进入对话框的实现文件,找到DoDataExchange方法。在AFX_DATA_MAP部分中,添加如下所示的行。注意,除了控制ID和变量名反映所需的映射之外,它在形式上与其他创建控制变量的行相同。同样,一旦完成了这些操作,ClassWizard就很乐意管理控件了。隐藏,复制Code
void CController::DoDataExchange(CDataExchange* pDX) { CFormView::DoDataExchange(pDX); //{{AFX_DATA_MAP(CController) DDX_Control(pDX, IDC_RANGE, c_Range); DDX_Control(pDX, IDC_SPEED, c_Speed); DDX_Control(pDX, IDC_ALTITUDE, c_Altitude); DDX_Control(pDX, IDC_COMPASS, c_Compass); //}}AFX_DATA_MAP }
此时,您可以自由实例化对话框了。请注意,当应用程序加载时,类是注册的,因此即使您要在SDI应用程序中使用CFormView,您也不需要进一步努力来使用该类。 从GDI的角度来看,这个控件有一些有趣的属性。例如,我想要一个圆形的罗盘在控件中,但我不想约束对话框的设计者来选择一个正方形对话框。我也不希望在背景重绘时指南针内出现任何恼人的闪光。 为此,我创建了一个圆形区域,它阻止默认的WM_ERASEBKGND处理程序触摸控件的内容。然后我使用这个限制剪辑的输出操作内的罗盘上升。这也可以用于点击测试,使用PtInRegion查看鼠标是否在圆形区域。 罗盘的禁用和启用模式如下所示。 CCompass: CreateClipRegionHide,复制Code
CRect CCompass::<A name=CreateClipRegion>CreateClipRegion</A>(CRgn & rgn) { CRect r; GetClientRect(&r); int radius = min(r.Width() / 2, r.Height() / 2); CPoint center(r.Width() / 2, r.Height() / 2); rgn.CreateEllipticRgn(center.x - radius, center.y - radius, center.x + radius, center.y + radius); return CRect(center.x - radius, center.y - radius, center.x + radius, center.y + radius); } // CCompass::CreateClipRegion
CCompass: OnEraseBkgndHide,复制Code
BOOL CCompass::<A name=OnEraseBkgnd>OnEraseBkgnd</A>(CDC* pDC) { CRgn rgn; CSaveDC sdc(pDC); <A href="#CreateClipRegion">CreateClipRegion</A>(rgn); pDC->SelectClipRgn(&rgn, RGN_DIFF); // remove circle from update area return CWnd::OnEraseBkgnd(pDC); }
CCompass: MapDC 由于我映射DC的频率不同,为此我创建了一个单独的方法。隐藏,复制Code
void CCompass::<A name=MapDC>MapDC</A>(CDC & dc) { dc.SetMapMode(MM_ISOTROPIC); CRect r; GetClientRect(&r); dc.SetWindowExt(r.Width(), r.Height()); dc.SetViewportExt(r.Width(), -r.Height()); CPoint center(r.left + r.Width() / 2, r.top + r.Height() / 2); dc.SetViewportOrg(center.x, center.y); } // CCompass::MapDC
CDoublePoint 这个类让我来表示分数角。事实证明,应用程序中的其他表示已经使用了双精度,因此在compass中使用它是一种自然的扩展。注意简单的CPoint转换,它截断而不是舍入;对于应用程序来说,这已经足够了。隐藏,复制Code
class <A name=CDoublePoint>CDoublePoint</A> { public: CDoublePoint(){} CDoublePoint(double ix, double iy) {x = ix; y = iy; } double x; double y; operator CPoint() { CPoint pt; pt.x = (int)x; pt.y = (int)y; return pt; } };
CCompass: OnLButtonDown 只有当鼠标在指南针区域时,才会响应按钮检测。注意,我向父节点发送了一条用户定义的消息,这在我的同伴文章中有描述。隐藏,复制Code
void CCompass::OnLButtonDown(UINT nFlags, CPoint point) { CRgn rgn; <A href="#CreateClipRegion">CreateClipRegion</A>(rgn); if(rgn.<A name=PtInRegion>PtInRegion</A>(point)) { /* in region */ CClientDC dc(this); <A href="#MapDC">MapDC</A>(dc); dc.DPtoLP(&point); GetParent()->SendMessage(CPM_CLICK, (WPARAM)point.x, (LPARAM)point.y); return; } /* in region */ CWnd::OnLButtonDown(nFlags, point); }
DegreesToRadians / GeographicToGeometric 我有一个实用函数,转换角度弧度,声明在一个单独的头文件。隐藏,复制Code
__inline double <A name=DegreesToRadians>DegreesToRadians</A>(double x) { return (((x)/360.0) * (2.0 * 3.1415926535)); }
法向几何坐标系的角度0.0指向原点的右侧,并随着角度的增加逆时针旋转。我们想从地理的角度来考虑度数,0.0是北,90.0是东,180.0是南,270.0是西。下面的内联方法对于从地理的自然坐标转换为math.h库所需的坐标非常有用。隐藏,复制Code
__inline double <A name=GeographicToGeometric>GeographicToGeometric</A>(double x) { return -(x - 90.0); }
CCompass: CCompass 构造函数加载坐标指示器表。隐藏,复制Code
CCompass::CCompass() { // Note: for optimal performance, sort monotonically by font size // Note: The first entry must be the largest display.Add(new displayinfo( 0.0, _T("N"), 100.0, TRUE)); display.Add(new displayinfo( 90.0, _T("E"), 90.0, FALSE)); display.Add(new displayinfo(180.0, _T("S"), 90.0, FALSE)); display.Add(new displayinfo(270.0, _T("W"), 90.0, FALSE)); display.Add(new displayinfo( 45.0, _T("NE"), 80.0, FALSE)); display.Add(new displayinfo(135.0, _T("SE"), 80.0, FALSE)); display.Add(new displayinfo(225.0, _T("SW"), 80.0, FALSE)); display.Add(new displayinfo(315.0, _T("NW"), 80.0, FALSE)); RegistryString compass(IDS_COMPASS); compass.load(); if(compass.value.GetLength() == 0 || !arrow.Read(compass.value)) arrow.Read(_T("Arrow.plt")); // use default angle = 0.0; // initialize at North ArrowVisible = FALSE; }
CCompass: OnPaintHide,收缩,复制Code
void CCompass::OnPaint() { CPaintDC dc(this); // device context for painting CBrush br(::GetSysColor(COLOR_INFOBK)); CRgn rgn; CRect r; r = <A href="#CreateClipRegion">CreateClipRegion</A>(rgn); #define BORDER_WIDTH 2 CPen border(PS_SOLID, BORDER_WIDTH, RGB(0,0,0)); CBrush needle(RGB(255, 0, 0)); #define ENABLED_COLOR RGB(0,0,0) #define DISABLED_COLOR RGB(128,128,128) CPen enabledPen(PS_SOLID, 0, ENABLED_COLOR); CPen disabledPen(PS_SOLID, 0, DISABLED_COLOR); //---------------------------------------------------------------- // GDI resources must be declared above this line //---------------------------------------------------------------- CSaveDC sdc(dc); dc.SelectClipRgn(&rgn); // clip to compass dc.FillRgn(&rgn, &br); // Convert the origin to the center of the circle CPoint center(r.left + r.Width() / 2, r.top + r.Height() / 2); // Renormalize the rectangle to the center of the circle r -= center; int radius = r.Width() / 2; dc.SetBkMode(TRANSPARENT); <A href="#MapDC">MapDC</A>(dc); // Draw the border { CSaveDC sdc2(dc); dc.SelectClipRgn(NULL); dc.SelectStockObject(HOLLOW_BRUSH); dc.SelectObject(&border); dc.Ellipse(-radius, -radius, radius, radius); r.InflateRect(-BORDER_WIDTH, -BORDER_WIDTH); radius = r.Width() / 2; } radius = r.Width() / 2; dc.SelectObject(IsWindowEnabled() ? &enabledPen : &disabledPen); // Draw N-S line dc.MoveTo(0, radius); dc.LineTo(0, -radius); // Draw E-W line dc.MoveTo(-radius, 0); dc.LineTo(radius, 0); // Draw SW-NE line dc.MoveTo((int)(radius * sin(<A href="#DegreesToRadians">DegreesToRadians</A>(<A href="#GeographicToGeometric">GeographicToGeometric</A>(225.0)))), (int)(radius * cos(<A href="#DegreesToRadians">DegreesToRadians</A>(<A href="#GeographicToGeometric">GeographicToGeometric</A>(225.0)))) ); dc.LineTo((int)(radius * sin(<A href="#DegreesToRadians">DegreesToRadians</A>(<A href="#GeographicToGeometric">GeographicToGeometric</A>( 45.0)))), (int)(radius * cos(<A href="#DegreesToRadians">DegreesToRadians</A>(<A href="#GeographicToGeometric">GeographicToGeometric</A>( 45.0)))) ); // Draw NW-SE line dc.MoveTo((int)(radius * sin(<A href="#DegreesToRadians">DegreesToRadians</A>(<A href="#GeographicToGeometric">GeographicToGeometric</A>(315.0)))), (int)(radius * cos(<A href="#DegreesToRadians">DegreesToRadians</A>(<A href="#GeographicToGeometric">GeographicToGeometric</A>(315.0)))) ); dc.LineTo((int)(radius * sin(<A href="#DegreesToRadians">DegreesToRadians</A>(<A href="#GeographicToGeometric">GeographicToGeometric</A>(135.0)))), (int)(radius * cos(<A href="#DegreesToRadians">DegreesToRadians</A>(<A href="#GeographicToGeometric">GeographicToGeometric</A>(135.0)))) ); // Now create the font elements // The symbols are placed along a circle which is inscribed // within the compass area // // +-----------------------------+ // / N <IMG height=120 src="/KB/dialog/selfregister/compassdisabled.gif" width=120 align=right border=0> // / NW | NE // / | // | | | // | W-----+-----E | // | | | // | / // SW | SE / // S / // +-----------------------------+ double size = 0.15 * (double)r.Width(); double CurrentFontSize = 0.0; // current font size CFont * f = NULL; dc.SetTextColor(IsWindowEnabled() ? ENABLED_COLOR : DISABLED_COLOR); for(int i = 0; i < display.GetSize(); i++) { /* draw points */ CSaveDC sdc2(dc); dc.SetBkMode(OPAQUE); dc.SetBkColor(::GetSysColor(COLOR_INFOBK)); if(display[i]->GetSize() != CurrentFontSize) { /* new font */ if(f != NULL) delete f; f = display[i]->CreateFont(size, _T("Times New Roman")); } /* new font */ dc.SelectObject(f); CurrentFontSize = display[i]->GetSize(); CString text = display[i]->GetText(); // // 4 | 1 // --+-- // 3 | 2 //------------------------------------------------------------------ // Ø qdant x y x-origin y-origin alignment // ---------------------------------------------------------------- // 0 4.1 0 >0 x-w/2 y TOP, LEFT // <90 1 >0 >0 x y TOP, RIGHT // 90 1.2 >0 0 x y-h/2 TOP, RIGHT // <180 2 >0 <0 x y BOTTOM, RIGHT // 180 2.3 0 <0 x-w/2 y BOTTOM, RIGHT // <270 3 <0 <0 x y BOTTOM, LEFT // 270 3.4 <0 0 x y-h/2 TOP, LEFT // <360 4 <0 >0 x y TOP, LEFT int x = (int)(radius * cos(<A href="#DegreesToRadians">DegreesToRadians</A>(<A href="#GeographicToGeometric">GeographicToGeometric</A>(display[i]->GetAngle())))); int y = (int)(radius * sin(<A href="#DegreesToRadians">DegreesToRadians</A>(<A href="#GeographicToGeometric">GeographicToGeometric</A>(display[i]->GetAngle())))); CSize textSize = dc.GetTextExtent(text); double theta = display[i]->GetAngle(); if(theta == 0.0) { /* 0 */ dc.SetTextAlign(TA_TOP | TA_LEFT); x -= textSize.cx / 2; } /* 0 */ else if(theta < 90.0) { /* < 90 */ dc.SetTextAlign(TA_TOP | TA_RIGHT); } /* < 90 */ else if(theta == 90.0) { /* 90 */ dc.SetTextAlign(TA_TOP | TA_RIGHT); y += textSize.cy / 2; } /* 90 */ else if(theta < 180.0) { /* < 180 */ dc.SetTextAlign(TA_BOTTOM | TA_RIGHT); } /* < 180 */ else if(theta == 180.0) { /* 180 */ dc.SetTextAlign(TA_BOTTOM | TA_LEFT); x -= textSize.cx / 2; } /* 180 */ else if(theta < 270.0) { /* < 270 */ dc.SetTextAlign(TA_BOTTOM | TA_LEFT); } /* < 270 */ else if(theta == 270) { /* 270 */ dc.SetTextAlign(TA_TOP | TA_LEFT); y += textSize.cy / 2; } /* 270 */ else { /* < 360 */ dc.SetTextAlign(TA_TOP | TA_LEFT); } /* < 360 */ dc.TextOut(x, y, text); } /* draw points */ if(f != NULL) delete f; // Draw the arrow if(IsWindowEnabled() && ArrowVisible) { /* draw arrow */ CRect bb = arrow.GetInputBB(); dc.SelectObject(&needle); arrow.Transform(angle, (double)abs(bb.Height()) / (2.0 * (double)radius)); arrow.Draw(dc, <A href="#CDoublePoint">CDoublePoint</A>(0.0, 0.0)); } /* draw arrow */ // Do not call CWnd::OnPaint() for painting messages }
本文转载于:http://www.diyabc.com/frontweb/news12152.html