進入MFC講壇的前言(一)

在這裏,我想談談自己學習MFC的一些體會。我是從1997年纔開始在Window下編寫程序的。在這之前,我編寫過一些DOS程序,包括一個簡單的全屏幕編輯器和一個帶函數的表達式解釋器,都是一些小的程序。Window 3.1流行後,我開始在它下面編寫程序。

   從編寫DOS程序到編寫Window程序,需要從編程思想上作一個比較大的調整。在DOS下編寫程序,程序的總體流程完全由應用程序自己控制;但在 Window下,程序的總體流程是由操作系統控制的,這一點對在DOS下“胡作非爲”的DOS程序員而然,特別不習慣,思想上一時很難轉過彎來,總覺得操 作系統所控制的應用程序流程能夠滿足我們所提出的任意要求嗎?萬一某個應用程序所需要的流程同它相牴觸,那該怎麼樣?

  但後來隨着學習的深入,我覺得這種擔心是完全多餘的,就我個人而然在還沒有碰到上面的問題。

   另外一個轉變就是,在Window下,程序是由事件(或消息)驅動的,程序員在程序中主要是提供事件處理程序的代碼,然後由操作系統來調用這些代碼,從 程序員的角度看,就是操作系統在“回調”他或她所寫的代碼。這一點也很不習慣,因爲在DOS下,都是應用程序調用操作系統的代碼(API),現在一下反過 來了,角色變化了,受不了!不過,隨作編程量的增加,這一點也慢慢淡化了。

  剛開始,我是用SDK編程的,使用了半年後,我受不了了, 太麻煩了,編寫一個簡單的顯示”hello, world!”的程序就得上百行代碼,再加上討厭的make文件和.def文件(那時我使用的是Borland C++ 3.1,而且也不知道有OWL這個東西)。後來聽人說,現在在Window下編寫C或C++程序用的都是MFC,MFC的功能很強大!於是,我到圖書館去 借了兩本講VC的書,照着書上的內容,折騰了一個禮拜。

  說實在話,那一個禮拜是把我搞得最迷糊的一個禮拜,MFC把我給嚇壞了。是 的,用MFC編寫一個“hello, world!”程序只需自己編寫一行代碼,但我不知道我所編寫的那一行代碼是什麼時候執行的,我不知道MFC在背後幹了什麼。這些倒不是最主要的,更讓我 難以接受的是,我覺的我所有的編程行動都在MFC的控制之下,而且控制得更“死”了,我的思想鑽進上面所提到的“死衚衕”中去了。後來我想,如果那時候我 看了一些有關構件(Framework)的文章或書,我想,這個“死衚衕”對我而然,應該是不存在的。

  其實,所有這些都是由於對 MFC不熟悉所造成的,MFC是一個框架(Framework)式類庫,框架式類庫同一般的類庫的不同之處在於,庫中的各個類之間是有聯繫的,它們是按照 框架所定義的模式去協作完成任務的。所以,要學習MFC,首先就要了解各個類之間是如何協作的以及它們的接口。

  另外,我覺得,如果熟悉SDK的話,對理解MFC和使用MFC編寫程序是有很大幫助的,因此在後面的講解中,我會根據需要穿插一些SDK方面的知識,以助理解。

  最後,必須具有一定的C++知識,完全不知道C++爲何物而去使用MFC,我實在難以想象其最後的結果,最好掌握C++的基本知識。
MFC應用程序的控制流程

  一般的Window應用程序基本流程

  WinMain()函數

   任何一個應用程序都有一個入口函數,在Window下,程序的入口函數根據應用程序的類型,有兩種選擇:控制檯程序的入口函數是main(),一般的 Window界面程序的入口函數是WinMain()。這裏只探討同我們下面的討論有關的WinMain()函數。下面是該函數的原型:(Visuall C++中)

  int APIENTRY WinMain(

    HINSTANCE hInstance,

    HINSTANCE hPrevInstance,

    LPSTR lpCmdLine,

    int nCmdShow)

  其中:

  hInstance是標識當前進程的實例,它實際上是進程所佔據的地址空間的首地址,在很多Window API中,都要將它作爲一個參數傳進去,所以,應用程序一般都會將它保存在一個全局量中。

  hPreInstance是應用程序前一個實例的實例句柄。這是16位Window的殘留物,在Win32應用程序中,這個參數始終爲NULL。所以,某些從16爲移植到32位的應用程序,如果使用了hPreInstance,就應該對代碼作相應的修改。

  lpCmdLine是命令行參數,這同main()中的argv[]類似。

  nCmdShow用來指明應用程序的主窗口的顯示方式(最大化顯示,最小化顯示,一般化顯示)。

  一個實例

  下面是一個顯示”Hello, world”的程序的代碼,它體現了一般的Window應用程序的基本流程。

  int APIENTRY WinMain(HINSTANCE hInstance,

      HINSTANCE hPrevInstance,

      LPSTR lpCmdLine,

      int nCmdShow)

  {

   MSG msg;

   file://註冊窗口類

   WNDCLASSEX wcex;

   wcex.cbSize = sizeof(WNDCLASSEX);

   wcex.style = CS_HREDRAW | CS_VREDRAW;

   wcex.lpfnWndProc = (WNDPROC)WndProc;

   wcex.cbClsExtra = 0;

   wcex.cbWndExtra = 0;

   wcex.hInstance = hInstance;

   wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_HELLOWORLD);

   wcex.hCursor = LoadCursor(NULL, IDC_ARROW);

   wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

   wcex.lpszMenuName = (LPCSTR)IDC_HELLOWORLD;

   wcex.lpszClassName = szWindowClass;

   wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);

   RegisterClassEx(&wcex);

   file://創建一個該類型的窗口

   HWND hWnd;

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,

   CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd) return FALSE;

    file://一nCmdShow所指定的方式顯示窗口

    ShowWindow(hWnd, nCmdShow);

    UpdateWindow(hWnd);

    file://啓動消息循環,將消息發送給相應的窗口函數

    while (GetMessage(&msg, NULL, 0, 0))

     {

      TranslateMessage(&msg);

      DispatchMessage(&msg);

      }

    return msg.wParam;

   }

   file://窗口函數

  LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

  {

   PAINTSTRUCT ps;

   HDC hdc;

   char* szHello = “Hello, world!”;

    switch (message)

    {

     case WM_PAINT:

      hdc = BeginPaint(hWnd, &ps);

      RECT rt;

      GetClientRect(hWnd, &rt);

      DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);

      EndPaint(hWnd, &ps);

      break;

     case WM_DESTROY:

      PostQuitMessage(0);

      break;

     default:

      return DefWindowProc(hWnd, message, wParam, lParam);

     }

    return 0;

    }

  上面程序的執行過程如下:

  1、註冊一個窗口類

  這是爲後面的創建窗口作準備,在使用CreateWindwo()和CreateWindowEx()創建窗口時,都必須提供一個標識窗口類的字符串。創建窗口類的主要意圖是向操作系統提供窗口處理函數。

  2、創建窗口

     啓動消息循環,分發並處理消息。

     其中的關鍵部分是消息循環:

     while (GetMessage(&msg, NULL, 0, 0))

     {

      TranslateMessage(&msg);

      DispatchMessage(&msg);

     }

調用GetMessage()從線程的消息隊列中取出一條消息,將消息翻譯後,再調用

DispatchMessage()將該消息分發至相應的窗口過程。(實際上DispatchMessage()是將該消息作爲參數調用對應的窗口的窗口函數,這就是分發的實質),在後面我們會詳細討論MFC的消息環同上面的消息環的區別。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章