1. 程式人生 > 實用技巧 >自動記錄式視窗類

自動記錄式視窗類

有時,需要建立一個自定義視窗類。通常,您通過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