自动记录式窗口类
有时,需要创建一个自定义窗口类。通常,您通过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

浙公网安备 33010602011771号