• 自动记录式窗口类


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

  • 相关阅读:
    linux 查找最后几条数据
    O(n) 取得数组中每个元素右边最后一个比它大的元素
    O(n) 取得数组中每个元素右边第一个比它大的元素
    位运算实现整数运算
    随手练——P1141 01迷宫
    迷宫寻路问题全解
    N皇后问题 各种优化
    八数码问题(三种解决办法)
    随手练——Uva-11584 划分成回文串(区间DP)
    【2016蓝桥杯省赛】试题C++ B组试题
  • 原文地址:https://www.cnblogs.com/Dincat/p/13473608.html
Copyright © 2020-2023  润新知