自动记录式窗口类

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

原文地址:https://www.cnblogs.com/Dincat/p/13473608.html