Bài giảng Lập trình Windows - Bài 6: Cơ bản về lập trình với MFC

doc 42 trang phuongnguyen 4680
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Lập trình Windows - Bài 6: Cơ bản về lập trình với MFC", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • docbai_giang_lap_trinh_windows_bai_6_co_ban_ve_lap_trinh_voi_mf.doc

Nội dung text: Bài giảng Lập trình Windows - Bài 6: Cơ bản về lập trình với MFC

  1. Bài Đọc Thêm CƠ BẢN VỀ LẬP TRÌNH VỚI MFC Với các bài học vừa qua, chúng ta đã nắm được các đặc điểm cơ bản nhất về lập trình trên Windows, sử dụng các hàm API để viết các ứng dụng dạng Win32 Application. Đây là cách thức lập trình khá đơn giản, đồng thời giúp người học hiểu và vận dụng cụ thể các kiến thức về Windows để xử lý cho các ứng dụng, tuy nhiên nó lại không phải là cách mà các lập trình viên thường sử dụng để xây dựng các ứng dụng Windows-based bằng ngôn ngữ C. Kết hợp cơ chế hướng đối tượng (object oriented), các chuyên gia của Microsoft đã mở rộng cách lập trình, thao tác các hàm API một cách gián tiếp để tạo các ứng dụng trên Windows. Cách lập trình này sử dụng thư viện các lớp đối tượng nền tảng trên Windows đã được Microsoft tổ chức lại, gọi là MFC (Microsoft Foundation Class) theo khung ứng dụng (Application Framework) riêng. Bài học này được chúng tôi đưa thêm vào cuối giáo trình như phần đọc thêm của môn Lập trình Windows, với mong muốn cung cấp cho sinh viên những kiến thức cơ sở nhất về MFC để các bạn có thể tự tìm hiểu và phát triển khả năng lập trình của mình sau khi đã có một khối lượng kiến thức cơ bản về lập trình trực tiếp các hàm API ở các bài học trước. 6.1. MFC APPLICATION FRAMEWORK Mỗi một ứng dụng, khi lập trình, đều phải tuân theo một cấu trúc nhất định gắn liền với ngôn ngữ và trình biên dịch hỗ trợ
  2. Lập trình Windows nó. Chúng ta đã quen thuộc với việc thể hiện mọi thao tác của ứng dụng trong hàm main viết bằng C chuẩn trên DOS, hoặc là hàm WinMain của một ứng dụng Win32 Application khởi tạo cửa sổ rồi sau đó xử lý với hàm WndProc. Tương tự, để xây dựng một ứng dụng trên Windows sử dụng cơ chế hỗ trợ của MFC, chúng ta cũng cần thực hiện theo một định dạng khung ứng dụng cho trước, được gọi là MFC Application Framework. Khung ứng dụng này của MFC được định nghĩa như là “một bộ tích hợp các thành phần phần mềm hướng đối tượng với mục đích hỗ trợ tất cả những gì cần thiết cho lập trình viên tạo lập và quản lý code ứng dụng”. Giải thích đơn giản hơn, chúng ta chỉ cần hiểu đó là cấu trúc có thể kết hợp chặt chẽ các lớp đối tượng của thư viện MFC vào trong ứng dụng để quản lý (chứ không sử dụng riêng lẻ một đối tượng bất kỳ nào đó được định nghĩa chỉ trong ứng dụng theo khái niệm lập trình hướng đối tượng thuần tuý). Thật ra, ứng dụng MFC là một mở rộng cho dạng ứng dụng Win Application trên Windows, thế nên cũng tuân thủ cấu trúc xử lý của một ứng dụng Windows với các hàm API đã được định nghĩa trước, nghĩa là cũng cần có một đối tượng tiến trình (instance), cũng định nghĩa một cửa sổ chính (window), cũng bắt đầu ứng dụng bằng khởi tạo hàm WinMain, rồi xử lý theo cơ chế thông điệp, v.v . Và chỉ khác là tất cả những đặc điểm này được ánh xạ sang Application Framework mà thôi. Để dễ dàng nắm bắt vấn đề hơn, chúng ta cùng nhau xây dựng ứng dụng dạng MFC Application và cùng phân tích các đặc điểm vừa nêu thông qua ví dụ MFCExample. 2
  3. Bài 6. Cơ bản về lập trình với MFC 6.1.1. Tạo lập project MFC sử dụng AppWizard Tương tự việc tạo lập project Win32 Application, khi tạo lập một project MFC, ta cũng nên sử dụng công cụ AppWizard mà Microsoft Visual C++ 6.0 hỗ trợ để phát sinh các thành phần cơ bản nhất cho project theo các cấu trúc đã được định nghĩa trước. Thay vì chọn chọn project dạng Win32 Application, ta chọn project dạng MFC AppWizard (exe) rồi gõ tên project như đã biết – xem hình 6.1. Hình 6.1. Project MFCExample dạng MFC AppWizard (exe) Sau khi chọn OK, AppWizard chuyển sang bước 1 cho phép lập trình viên chọn dạng ứng dụng cho project này. Trên Windows, các ứng dụng thường thuộc một trong 3 dạng chính là: ứng dụng đơn tài liệu (single document), ví dụ như ứng 3
  4. Lập trình Windows dụng Microsoft Paint, chỉ thao tác một tài liệu tại một thời điểm; ứng dụng đa tài liệu (multiple documents), với phần mềm Microsoft Visual C++ 6.0 mà chúng ta đang dùng là một điển hình, cho phép ta có thể mở nhiều cửa sổ để soạn thảo các nội dung khác nhau; và ứng dụng dạng hộp thoại (dialog based) với giao diện chính là một hộp thoại, ví dụ như là ứng dụng Microsoft Calculator. Hình 6.2. Chọn project dạng Single document Ở đây chúng ta chọn project dạng Single document có hỗ trợ kiến trúc Document-View (xem ở phần 6.3). Sau đó qua các bước tiếp theo. Ở các bước từ 2 đến 5 ta chọn các thông tin mặc định mà AppWizard cung cấp. Cuối cùng, ở bước thứ 6, 4
  5. Bài 6. Cơ bản về lập trình với MFC Visual C++ 6.0 liệt kê 4 lớp đối tượng chính cấu thành project MFC AppWizard mà chúng ta cần phân tích, bao gồm lớp khung nhìn CMFCExampleView, lớp đối tượng ứng dụng CMFCExampleApp, lớp cửa sổ ứng dụng CMainFrame và lớp dữ liệu CMFCExampleDoc. Bình thường ta dùng luôn thông số các lớp cơ sở (base class) của chúng, tuy nhiên ở đây ta chọn lớp cơ sở về khung nhìn là CScrollView để minh họa một số thao tác khi trình bày một số vấn đề tiếp theo - xem hình 6.3. Hình 6.3. Các lớp đối tượng chính của project MFCExample Sau khi chọn Finish, MS Visual C ++ 6.0 thông báo kết quả tạo lập project với các thông số đã chọn. Chúng ta chọn OK để kết thúc và chuyển sang tìm hiểu code của chúng. 5
  6. Lập trình Windows 6.1.2. Đối tượng ứng dụng và cửa sổ với thư viện MFC Như đã trình bày ở trên, một project ứng dụng dạng MFC là một khung kết hợp các lớp đối tượng đã được Microsoft định nghĩa vào một cấu trúc chặt chẽ. Các lớp đối tượng này có thể kế thừa, tác động qua lại lẫn nhau dựa trên các đặc trưng hướng đối tượng của chúng. Hình 6.4 mô tả một phần bảng phân cấp các lớp trong thư viện MFC 7.0 giúp ta có một cái nhìn toàn vẹn hơn về chúng. Hình 6.4. Một phần bảng phân cấp các lớp của thư viện MFC 7.0 6
  7. Bài 6. Cơ bản về lập trình với MFC Một ứng dụng thông thường và cơ bản nhất dạng MFC phải có duy nhất một đối tượng ứng dụng - trong ví dụ vừa tạo lập là đối tượng CMFCExampleApp theApp - tương tự như đối tượng HINSTANCE hInst của ứng dụng dạng Win32 Application, và một cửa sổ - ở đây là CMainFrame - tương tự đối tượng HWND hWnd xác định giao diện chính của ứng dụng. Hai đối tượng này được cấu trúc và cài đặt dựa trên hai lớp cơ sở CWinApp và CFrameWnd của MFC. Để phân tích kỹ hơn về chúng, chúng ta cùng xem code trong project MFCExample được Visual C ++ 6.0 phát sinh. Tập tin header MFCExample.h định nghĩa cấu trúc lớp CMFCExampleApp như sau: 1 // MFCExample.h : main header file for the MFCEXAMPLE application 2 // 3 4 #if !defined(AFX_MFCEXAMPLE_H__9735298D__INCLUDED_) 5 #define AFX_MFCEXAMPLE_H__9735298D__INCLUDED_ 6 7 #if _MSC_VER > 1000 8 #pragma once 9 #endif // _MSC_VER > 1000 10 11 #ifndef __AFXWIN_H__ 12 #error include 'stdafx.h' before including this file for PCH 13 #endif 14 15 #include "resource.h" // main symbols 16 17 ///////////////////////////////////////////////////////////////////////////// 18 // CMFCExampleApp: 19 // See MFCExample.cpp for the implementation of this class 20 // 21 22 class CMFCExampleApp : public CWinApp 23 { 24 public: 25 CMFCExampleApp(); 26 27 // Overrides 7
  8. Lập trình Windows 28 // ClassWizard generated virtual function overrides 29 //{{AFX_VIRTUAL(CMFCExampleApp) 30 public: 31 virtual BOOL InitInstance(); 32 //}}AFX_VIRTUAL 33 34 // Implementation 35 //{{AFX_MSG(CMFCExampleApp) 36 afx_msg void OnAppAbout(); 37 // NOTE - the ClassWizard will add and remove member functions here. 38 // DO NOT EDIT what you see in these blocks of generated code ! 39 //}}AFX_MSG 40 DECLARE_MESSAGE_MAP() 41 }; 42 43 44 ///////////////////////////////////////////////////////////////////////////// 45 46 //{{AFX_INSERT_LOCATION}} 47 // Microsoft Visual C++ will insert additional declarations immediately before the 48 previous line. 49 50 #endif // !defined(AFX_MFCEXAMPLE_H__9735298D__INCLUDED_) Chi tiết cài đặt được thể hiện trong tập tin MFCExample.cpp. 1 // MFCExample.cpp : Defines the class behaviors for the application. 2 // 3 4 #include "stdafx.h" 5 #include "MFCExample.h" 6 7 #include "MainFrm.h" 8 #include "MFCExampleDoc.h" 9 #include "MFCExampleView.h" 10 11 #ifdef _DEBUG 12 #define new DEBUG_NEW 13 #undef THIS_FILE 14 static char THIS_FILE[] = __FILE__; 15 #endif 16 17 ///////////////////////////////////////////////////////////////////////////// 18 // CMFCExampleApp 19 20 BEGIN_MESSAGE_MAP(CMFCExampleApp, CWinApp) 21 //{{AFX_MSG_MAP(CMFCExampleApp) 8
  9. Bài 6. Cơ bản về lập trình với MFC 22 ON_COMMAND(ID_APP_ABOUT, OnAppAbout) 23 // NOTE - the ClassWizard will add and remove mapping macros here. 24 // DO NOT EDIT what you see in these blocks of generated code! 25 //}}AFX_MSG_MAP 26 // Standard file based document commands 27 ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) 28 ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen) 29 // Standard print setup command 30 ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup) 31 END_MESSAGE_MAP() 32 33 ///////////////////////////////////////////////////////////////////////////// 34 // CMFCExampleApp construction 35 36 CMFCExampleApp::CMFCExampleApp() 37 { 38 // TODO: add construction code here, 39 // Place all significant initialization in InitInstance 40 } 41 42 ///////////////////////////////////////////////////////////////////////////// 43 // The one and only CMFCExampleApp object 44 45 CMFCExampleApp theApp; 46 47 ///////////////////////////////////////////////////////////////////////////// 48 // CMFCExampleApp initialization 49 50 BOOL CMFCExampleApp::InitInstance() 51 { 52 AfxEnableControlContainer(); 53 54 // Standard initialization 55 // If you are not using these features and wish to reduce the size 56 // of your final executable, you should remove from the following 57 // the specific initialization routines you do not need. 58 59 #ifdef _AFXDLL 60 Enable3dControls(); // Call this when using MFC in a shared DLL 61 #else 62 Enable3dControlsStatic(); // Call this when linking to MFC statically 63 #endif 64 65 // Change the registry key under which our settings are stored. 66 // TODO: You should modify this string to be something appropriate 67 // such as the name of your company or organization. 68 SetRegistryKey(_T("Local AppWizard-Generated Applications")); 69 70 LoadStdProfileSettings(); // Load standard INI file options (including MRU) 9
  10. Lập trình Windows 71 72 // Register the application's document templates. Document templates 73 // serve as the connection between documents, frame windows and views. 74 75 CSingleDocTemplate* pDocTemplate; 76 pDocTemplate = new CSingleDocTemplate( 77 IDR_MAINFRAME, 78 RUNTIME_CLASS(CMFCExampleDoc), 79 RUNTIME_CLASS(CMainFrame), // main SDI frame window 80 RUNTIME_CLASS(CMFCExampleView)); 81 AddDocTemplate(pDocTemplate); 82 83 // Parse command line for standard shell commands, DDE, file open 84 CCommandLineInfo cmdInfo; 85 ParseCommandLine(cmdInfo); 86 87 // Dispatch commands specified on the command line 88 if (!ProcessShellCommand(cmdInfo)) 89 return FALSE; 90 91 // The one and only window has been initialized, so show and update it. 92 m_pMainWnd->ShowWindow(SW_SHOW); 93 m_pMainWnd->UpdateWindow(); 94 95 return TRUE; 96 } 97 98 99 ///////////////////////////////////////////////////////////////////////////// 100 // CAboutDlg dialog used for App About 101 102 class CAboutDlg : public CDialog 103 { 104 public: 105 CAboutDlg(); 106 107 // Dialog Data 108 //{{AFX_DATA(CAboutDlg) 109 enum { IDD = IDD_ABOUTBOX }; 100 //}}AFX_DATA 111 112 // ClassWizard generated virtual function overrides 113 //{{AFX_VIRTUAL(CAboutDlg) 114 protected: 115 virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support 116 //}}AFX_VIRTUAL 117 118 // Implementation 119 protected: 10
  11. Bài 6. Cơ bản về lập trình với MFC 120 //{{AFX_MSG(CAboutDlg) 121 // No message handlers 122 //}}AFX_MSG 123 DECLARE_MESSAGE_MAP() 124 }; 125 126 CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) 127 { 128 //{{AFX_DATA_INIT(CAboutDlg) 129 //}}AFX_DATA_INIT 130 } 131 132 void CAboutDlg::DoDataExchange(CDataExchange* pDX) 133 { 134 CDialog::DoDataExchange(pDX); 135 //{{AFX_DATA_MAP(CAboutDlg) 136 //}}AFX_DATA_MAP 137 } 138 139 BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) 140 //{{AFX_MSG_MAP(CAboutDlg) 141 // No message handlers 142 //}}AFX_MSG_MAP 143 END_MESSAGE_MAP() 144 145 // App command to run the dialog 146 void CMFCExampleApp::OnAppAbout() 147 { 148 CAboutDlg aboutDlg; 149 aboutDlg.DoModal(); 150 } 151 152 ///////////////////////////////////////////////////////////////////////////// 153 // CMFCExampleApp message handlers Biến đối tượng ứng dụng theApp được khai báo ở dòng 45 của tập tin cài đặt, và như đã nói, đây chính là đối tượng mà Windows dùng để quản lý ứng dụng khi thực thi. Để ý rằng tất cả các ứng dụng trên Windows đều được bắt đầu gọi thực thi từ hàm WinMain, vì thế ứng dụng MFC cũng có hàm WinMain, tuy nhiên Application Framework đã giấu hàm này, và chúng ta không thấy trong source code. Khi hàm WinMain được gọi, ứng dụng sẽ tìm đối tượng toàn cục trên và xử lý theo các thông tin 11
  12. Lập trình Windows định nghĩa bên trong hàm khởi tạo (construction) của nó (trong ứng dụng này, ta không thao tác gì bên trong hàm CMFCExampleApp::CMFCExampleApp() ). Tiếp đến ứng dụng gọi hàm ảo InitInstance để khởi tạo và hiển thị cửa sổ ứng dụng. Hàm InitInstance được viết lại (override) tuỳ theo dạng cửa sổ chính ứng dụng mà lập trình viên muốn thể hiện, bởi vì MFC không cài đặt hàm này trong lớp đối tượng cơ sở CWinApp do không xác định dạng khung cửa sổ mà chúng ta cần. Tiếp theo ứng dụng gọi hàm CWinApp::Run để chuyển các thông điệp gởi đến ứng dụng cho các cửa sổ tương ứng và giữ cho ứng dụng hoạt động – tương tự đoạn code vòng lặp nhận và chuyển thông điệp trong hàm WinMain của ứng dụng Win32 Application. Hàm Run cũng được giấu trong lớp cơ sở CWinApp của MFC. Trong tập tin MFCExample.cpp ở trên còn có code liên quan đến một đối tượng hộp thoại CAboutDlg cùng thao tác gọi hiển thị hộp thoại này từ menu ứng dụng, tuy nhiên chúng ta không phân tích điều này ở đây mà sẽ tìm hiểu ở phần 6.2 liên quan đến vấn đề ánh xạ thông điệp trên MFC nhờ ClassWizard. Tương tự như trên, tập tin header MainFrm.h định nghĩa cấu trúc lớp CMainFrame như sau: 1 // MainFrm.h : interface of the CMainFrame class 2 // 3 ///////////////////////////////////////////////////////////////////////////// 4 5 #if !defined(AFX_MAINFRM_H__A84CB291__INCLUDED_) 6 #define AFX_MAINFRM_H__A84CB291__INCLUDED_ 7 12
  13. Bài 6. Cơ bản về lập trình với MFC 8 #if _MSC_VER > 1000 9 #pragma once 10 #endif // _MSC_VER > 1000 11 12 class CMainFrame : public CFrameWnd 13 { 14 15 protected: // create from serialization only 16 CMainFrame(); 17 DECLARE_DYNCREATE(CMainFrame) 18 19 // Attributes 20 public: 21 22 // Operations 23 public: 24 25 // Overrides 26 // ClassWizard generated virtual function overrides 27 //{{AFX_VIRTUAL(CMainFrame) 28 virtual BOOL PreCreateWindow(CREATESTRUCT& cs); 29 //}}AFX_VIRTUAL 30 31 // Implementation 32 public: 33 virtual ~CMainFrame(); 34 #ifdef _DEBUG 35 virtual void AssertValid() const; 36 virtual void Dump(CDumpContext& dc) const; 37 #endif 38 39 protected: // control bar embedded members 40 CStatusBar m_wndStatusBar; 41 CToolBar m_wndToolBar; 42 43 // Generated message map functions 44 protected: 45 //{{AFX_MSG(CMainFrame) 46 afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); 47 // NOTE - the ClassWizard will add and remove member functions here. 48 // DO NOT EDIT what you see in these blocks of generated code! 49 //}}AFX_MSG 50 DECLARE_MESSAGE_MAP() 51 }; 52 53 ///////////////////////////////////////////////////////////////////////////// 54 55 //{{AFX_INSERT_LOCATION}} 56 // Microsoft Visual C++ will insert additional declarations immediately 13
  14. Lập trình Windows 57 // before the previous line. 58 59 #endif // !defined(AFX_MAINFRM_H__A84CB291__INCLUDED_) Chi tiết cài đặt được thể hiện trong tập tin MainFrm.cpp. 1 // MainFrm.cpp : implementation of the CMainFrame class 2 // 3 4 #include "stdafx.h" 5 #include "MFCExample.h" 6 7 #include "MainFrm.h" 8 9 #ifdef _DEBUG 10 #define new DEBUG_NEW 11 #undef THIS_FILE 12 static char THIS_FILE[] = __FILE__; 13 #endif 14 15 ///////////////////////////////////////////////////////////////////////////// 16 // CMainFrame 17 18 IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd) 19 20 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) 21 //{{AFX_MSG_MAP(CMainFrame) 22 // NOTE - the ClassWizard will add and remove mapping macros here. 23 // DO NOT EDIT what you see in these blocks of generated code ! 24 ON_WM_CREATE() 25 //}}AFX_MSG_MAP 26 END_MESSAGE_MAP() 27 28 static UINT indicators[] = 29 { 30 ID_SEPARATOR, // status line indicator 31 ID_INDICATOR_CAPS, 32 ID_INDICATOR_NUM, 33 ID_INDICATOR_SCRL, 34 }; 35 36 ///////////////////////////////////////////////////////////////////////////// 37 // CMainFrame construction/destruction 38 39 CMainFrame::CMainFrame() 40 { 41 // TODO: add member initialization code here 42 43 } 14
  15. Bài 6. Cơ bản về lập trình với MFC 44 45 CMainFrame::~CMainFrame() 46 { 47 } 48 49 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 50 { 51 if (CFrameWnd::OnCreate(lpCreateStruct) == -1) 52 return -1; 53 54 if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE 55 | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | 56 CBRS_SIZE_DYNAMIC) || 57 !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) 58 { 59 TRACE0("Failed to create toolbar\n"); 60 return -1; // fail to create 61 } 62 63 if (!m_wndStatusBar.Create(this) || 64 !m_wndStatusBar.SetIndicators(indicators, 65 sizeof(indicators)/sizeof(UINT))) 66 { 67 TRACE0("Failed to create status bar\n"); 68 return -1; // fail to create 69 } 70 71 // TODO: Delete these three lines if you don't want the toolbar to 72 // be dockable 73 m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); 74 EnableDocking(CBRS_ALIGN_ANY); 75 DockControlBar(&m_wndToolBar); 76 77 return 0; 78 } 79 80 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) 81 { 82 if( !CFrameWnd::PreCreateWindow(cs) ) 83 return FALSE; 84 // TODO: Modify the Window class or styles here by modifying 85 // the CREATESTRUCT cs 86 87 return TRUE; 88 } 89 90 ///////////////////////////////////////////////////////////////////////////// 91 // CMainFrame diagnostics 92 15
  16. Lập trình Windows 93 #ifdef _DEBUG 94 void CMainFrame::AssertValid() const 95 { 96 CFrameWnd::AssertValid(); 97 } 98 99 void CMainFrame::Dump(CDumpContext& dc) const 100 { 101 CFrameWnd::Dump(dc); 102 } 103 104 #endif //_DEBUG 105 106 ///////////////////////////////////////////////////////////////////////////// 107 // CMainFrame message handlers Đối tượng của lớp CMainFrame là thể hiện của cửa sổ chính ứng dụng. Đối với ứng dụng này thì cửa sổ có dạng Single Document và được dùng trong template của ứng dụng (dòng 79 tập tin CMFCExampleApp.cpp định nghĩa template này). Cửa sổ này được quản lý thông qua biến mặc định m_pMainWnd của ứng dụng. Khi khởi tạo cửa sổ, Windows gọi hàm PreCreateWindow để thiết lập một số thông số khởi tạo – trong ứng dụng này chúng ta không có thao tác gì – và gọi hàm OnCreate (tương ứng chặn thông điệp WM_CREATE của ứng dụng) để tạo lập cửa sổ thật sự. Sau khi tạo lập cửa sổ, ta cũng dùng hàm ShowWindow và UpdateWindow – tích hợp như là hàm bên trong lớp cửa sổ - để hiển thị (xem gọi thực thi ở dòng 92-93 của tập tin CMFCExampleApp.cpp). Như vậy, chúng ta dễ dàng thay đổi cấu trúc giao diện của cửa sổ khi nắm rõ lớp đối tượng của nó. Để ý kỹ lại chúng ta có thể nhận ra cửa sổ này có một thanh status bar được quản lý thông qua biến CStatusBar m_wndStatusBar và một tool bar CToolBar m_wndToolBar. Các đối tượng này cũng được khởi tạo trong hàm OnCreate. 16
  17. Bài 6. Cơ bản về lập trình với MFC Sau khi tạo lập cửa sổ, Windows sẽ dựa vào các thông điệp mà chúng ta cài đặt bên trong ứng dụng để xử lý. Ta đã biết các ứng dụng dạng Win32 Application sử dụng hàm DefWindowProc để xử lý mặc định các thông điệp; thì các ứng dụng MFC còn hơn thế nữa, mỗi lớp đối tượng cơ sở đều có các hàm tạo lập, xử lý, hủy bỏ mặc định; và do đó, nếu ta không viết gì thêm thì Windows sẽ xử lý mặc định, còn nếu muốn thực hiện theo cách riêng, ta chỉ việc override các hàm (thường gắn với các thông điệp nào đó) hoặc định nghĩa thêm các hàm (biến) thành phần cho lớp đối tượng tương ứng. Ứng dụng sẽ kết thúc khi người dùng thao tác đóng cửa sổ chính theo các chức năng mà hệ điều hành hỗ trợ (như là chọn nút Close hoặc chọn tổ hợp phím Alt+F4 của các ứng dụng thông thường). Thao tác này kích hoạt một loạt các thao tác như ngưng và kiểm tra rồi huỷ bỏ các đối tượng có trong ứng dụng, hủy cửa sổ chính, kết thúc hàm Run, kết thúc hàm WinMain và cuối cùng là huỷ đối tượng ứng dụng theApp. Như vậy, ta có thể tìm hiểu từng đối tượng trong thư viện MFC trên MSDN mà Microsoft đã hỗ trợ; tìm hiểu cách dùng các lớp đối tượng, các biến thuộc tính, các hàm thành phần của chúng và đưa vào các ứng dụng liên quan. Trong phần 6.2 tiếp theo, tôi sẽ đề cập tới một số vấn đề cần lưu ý khác khi lập trình bằng MFC – trong đó đáng chú ý nhất là kỹ thuật mà Microsoft hỗ trợ khi lập trình gọi là ClassWizard để thao tác trực quan các biến, hàm của các lớp trong ứng dụng. 6.2. CLASSWIZARD VÀ VẤN ĐỀ THÔNG ĐIỆP Như đã nói ở trên, việc xây dựng một ứng dụng MFC có thể được hiểu lại như là việc tổ chức các lớp đối tượng có liên quan 17
  18. Lập trình Windows trong project thông qua các biến, hàm, và dựa trên cơ chế thông điệp cũng như các hàm API của Windows. Do đó, khi lập trình, ta cũng có thể thực hiện soạn thảo theo cách thông thường, dựa trên các cú pháp về hàm, biến, để viết các dòng văn bản trên text editor. Tuy nhiên, nếu chỉ thao tác như vậy thì ta chưa tận dụng được hết đặc trưng giao diện trực quan của công cụ MS Visual C++, cũng như cơ chế liên kết các đối tượng của MFC Application Framework. Microsoft tích hợp trong công cụ Visual C++ của mình một chương trình, cài đặt dưới dạng DLL, cho phép người dùng truy cập từ menu View. Đó là bộ công cụ ClassWizard – xem hình 6.5. Hình 6.5. Công cụ MFC ClassWizard MFC ClassWizard giúp các lập trình viên dễ dàng kiểm soát các lớp MFC với các chức năng chính như tạo mới một lớp đối tượng, định nghĩa và ánh xạ các thông điệp liên quan của mỗi 18
  19. Bài 6. Cơ bản về lập trình với MFC lớp, override các thông điệp ảo của MFC, và quản lý dữ liệu các menu item, accelerator cũng như control của các hộp thoại, form view hoặc record view. Thông tin các lớp được ClassWizard quản lý được đăng ký trong tập tin dữ liệu ClassWizard (.clw). Phần quan trọng nhất của ClassWizard là hệ thống ánh xạ thông điệp (message map). Quả thật là như vậy, ai trong chúng ta cũng biết vấn đề quan trọng nhất khi lập trình trên Windows là phải hiểu và xử lý được các thông điệp liên quan đến ứng dụng. Hầu như tất cả các thao tác mà chúng ta tác động đến ứng dụng đều được Windows gởi về dưới dạng các thông điệp. Mặt khác, ta cũng biết là khi tổ chức ứng dụng dưới dạng hướng đối tượng thì hầu như các thao tác của chúng ta tác động lên ứng dụng, mà cụ thể là tác động lên các đối tượng trong ứng dụng, đều thường thể hiện dưới dạng các hàm của đối tượng đó. Chính vì lẽ đó, hệ thống ánh xạ thông điệp có nhiệm vụ chuyển các thông điệp tương ứng thành các hàm con cho mỗi lớp đối tượng mà chúng ta cài đặt. Khi chúng ta chọn chức năng về Message Maps, MFC ClassWizard liệt kê cho chúng ta tất cả các định danh đối tượng trong list box Object IDs, các thông điệp và hàm thành phần của đối tượng tương ứng trong list box Messages, và các hàm thành phần mà chúng ta muốn cài đặt thay đổi (overrid) hoặc thêm vào (addtional) trong list Member functions. Chúng ta chỉ việc chọn đối tượng, chọn thông điệp cần thao tác và ClassWizard có nhiệm vụ tạo ra ánh xạ thông điệp và hàm tương ứng, định nghĩa trong tập tin header của class và tạo phần code cơ bản nhất trong tập tin cài đặt (implementation). Ở ví dụ trong hình 6.5 ở trên, ta đang chọn lớp CMFCExampleApp của project MFCExample, với hàm ảo InitInstance và hàm 19
  20. Lập trình Windows OnAppAbout tương ứng ánh xạ thông điệp WM_COMMAND của menu item ID_APP_ABOUT. Đối chiếu lại phần code, MFC ClassWizard phát sinh cho chúng ta hàm ảo InitInstance ở dòng 31 và hàm thông điệp MFC (afx_msg) OnAppAbout tại dòng 36 của tập tin MFCExampleApp.h, cũng như đoạn code ánh xạ thông điệp ở WM_COMMAND cho menu item ID_APP_ABOUT ở dòng 22 của tập tin MFCExampleApp.cpp. Khi người dùng chọn Edit Code, ClassWizard sẽ chuyển đến đúng hàm để chúng ta viết code (ở đây hai hàm này đã được viết theo đúng cách thức thao tác thông thường). Tương tự như vậy, các bạn có thể kiểm tra lại các hàm PreCreateWindow và OnCreate ứng với thông điệp WM_CREATE của cửa sổ ứng dụng trong class CMainFrame. Như vậy, khi lập trình bằng MFC với sự hỗ trợ của MFC ClassWizard, chúng ta không cần phải nhớ chính xác từng thông điệp để viết code trực tiếp như với ứng dụng Win32 Application, mà chỉ cần hiểu các thông điệp để chọn thao tác mà thôi. Hơn nữa, việc ClassWizard cấu trúc các thông điệp và các hàm ảo gắn liền với từng dạng lớp đối tượng giúp người lập trình có thể quản lý project chặt chẽ và rõ ràng hơn nhiều. Ngoài ra, nếu các bạn tinh ý thì sẽ thấy là chương trình sẽ trở nên linh động hơn nhiều vì chúng ta không phải viết code cho tất cả các thông điệp chỉ trong một hàm (kiểu như hàm WndProc của ứng dụng Win32 Application) mà viết riêng từng hàm cho mỗi thông điệp. Chúng ta cũng không cần phải nhớ các thông số lParam và wParam đi kèm với mỗi thông điệp 20
  21. Bài 6. Cơ bản về lập trình với MFC nữa, vì các giá trị này đã được chuyển thành các biến theo dạng dữ liệu (data type) phù hợp nhất và chuyển vào cho hàm tương ứng – ví dụ với hàm OnCreate của cửa sổ chính ta có biến LPCREATESTRUCT lpCreateStruct tương ứng giá trị lParam của thông điệp WM_CREATE, hoặc nếu chọn thao tác về thiết bị chuột với hàm OnMouseMove thì các biến gởi vào là UINT nFlags, CPoint point ứng với giá trị wParam và lParam của thông điệp WM_MOUSEMOVE. Với phạm vi môn học, chúng tôi không thể trình bày thêm nhiều vấn đề khác. Tuy nhiên, trước khi kết thúc, chúng ta cùng nhau phân tích thêm một vấn đề khá quan trọng đối với các ứng dụng có quản lý và thể hiện giao diện dữ liệu, gọi là kiến trúc Tài liệu – Khung nhìn (Document – View Architecture). 6.3. KIẾN TRÚC DOCUMENT – VIEW Một ứng dụng thông thường trên Windows thường gồm một lớp đối tượng ứng dụng, một lớp khung cửa sổ - như đã phân tích ở trên - và thêm vào đó là hai lớp đối tượng khác đại diện cho “dữ liệu” và “khung nhìn”. Cấu trúc này được gọi là kiến trúc Document-View, được phát triển dựa trên nền tảng các lớp Model/View/Controller của Smalltalk. Theo nghĩa đơn giản, kiến trúc Document-View tách rời dữ liệu thực tế với cách nhìn của người dùng về dữ liệu. Lợi ích rõ ràng nhất của kiến trúc này là cho phép chúng ta thể hiện được nhiều khung nhìn khác nhau với cùng một dữ liệu. Giả dụ chúng ta có một tài liệu chứa các số liệu về giá chứng khoán (stock) được lưu trên đĩa. Và ta muốn hiển thị dữ liệu này dưới cả hai dạng thức là bảng biểu (table) và biểu đồ 21
  22. Lập trình Windows (chart) trên một ứng dụng. Người dùng có thể cập nhật các giá trị trong bảng biểu trên một khung nhìn, và họ cũng có thể xem kết quả trên dưới dạng biểu đồ trên một khung nhìn khác. Trong ứng dụng sử dụng thư viện MFC, các tài liệu và khung nhìn được thể hiện bằng các đại diện (instance) dưới dạng lớp C++. Hình 6.6 minh họa cho yêu cầu vừa nêu ra ở trên, chúng ta có thể thể hiện 3 đối tượng dữ liệu cho các công ty AT&T, IBM và GM dưới dạng lớp đối tượng CStockDoc. Cả 3 đối tượng tài liệu này đều có khung nhìn bảng biểu đi kèm, và ta cần thêm một khung nhìn dạng cột cho công ty AT&T. Ta có 4 đối tượng dưới dạng các lớp CStockTableView và CStockChartView. Hình 6.6. Mối liên hệ Document-View Lớp tài liệu cơ sở của ứng dụng cài đặt sẵn cơ chế hỗ trợ việc thao tác dữ liệu trên các tập tin. Chúng ta có thể kết hợp 22
  23. Bài 6. Cơ bản về lập trình với MFC các menu item File Open và File Save theo cấu trúc thông thường của các ứng dụng để gọi các hàm mở các hộp thoại tương ứng. Trong ứng dụng MFCExample, ta nhận thấy để mở các hộp thoại File Open và File Save, chúng ta sử dụng các hàm mặc định của các lớp mà không cần phải thao tác các hộp thoại thông dụng như đã tìm hiểu ở bài 3 của Win32 Application. Khi đó, với dữ liệu của lớp tài liệu (document class) có được, khi cần hiển thị dữ liệu lên một cửa sổ nào đó, ta chỉ việc gọi nhận dữ liệu này và hiển thị ra màn hình theo cơ chế mà cửa sổ đó hỗ trợ. Tất cả các thao tác giao tiếp giữa tài liệu, khung nhìn, cửa sổ và đối tượng ứng dụng đều được quản lý chặt chẽ thông qua các hàm ảo của thư viện MFC. Chúng ta cùng nhau tìm hiểu về kiến trúc Document-View thông qua ví dụ nạp ảnh bitmap từ tập tin (.bmp) và hiển thị lên cửa sổ ứng dụng MFCExample. Các bạn sẽ áp dụng hiểu biết của mình về thao tác đồ họa GDI cũng như thao tác bàn phím (keyboard) để cài đặt cho ứng dụng. Như đã trình bày ở bài 4, đối tượng ảnh bitmap trên Windows được phân thành 2 dạng là bitmap phụ thuộc thiết bị (DDB – Device Dependent Bitmap) và bitmap độc lập thiết bị (DIB – Device Independent Bitmap), và chúng ta đã làm quen thao tác nạp ảnh bitmap từ resource và tạo đối tượng DDB HBITMAP rồi hiển thị lên màn hình. Tương tự như vậy, ở đây chúng ta cũng thực hiện việc hiển thị ảnh bitmap, nhưng là ở dạng DIB và được đọc từ tập tin ảnh. Để làm điều này, chúng ta không chỉ đơn giản gọi hàm LoadBitmap, mà cần đọc ảnh theo cấu trúc định dạng ảnh ở trên tập tin. Và vì ảnh là đối tượng độc lập thiết bị, do đó ta cần 23
  24. Lập trình Windows sử dụng một số hàm được cài đặt riêng. Rất may là Microsoft đã cung cấp một số hàm trong các ví dụ ở trên MSDN, và chúng ta có thể kế thừa chúng cho ứng dụng minh họa của mình. 6.3.1. Thư viện DIBAPI của Microsoft Ta tạo mới trên project các tập tin DIBAPI.h, DIBAPI.cpp và MyFile.cpp, cài đặt các hàm liên quan tới việc thao tác dữ liệu tập tin ảnh cũng đối tượng ảnh trên vùng nhớ. Tập tin DIBAPI.h định nghĩa đối tượng ảnh HDIB và prototype của một số hàm thao tác thông tin ảnh (cài đặt trong tập tin DIBAPI.cpp) và thao tác tập tin ảnh (cài đặt trong tập tin MyFile.cpp). 1 // dibapi.h 2 // 3 // This is a part of the Microsoft Foundation Classes C++ library. 4 // Copyright (C) 1992-1998 Microsoft Corporation 5 // All rights reserved. 6 // 7 // This source code is only intended as a supplement to the 8 // Microsoft Foundation Classes Reference and related 9 // electronic documentation provided with the library. 10 // See these sources for detailed information regarding the 11 // Microsoft Foundation Classes product. 12 13 14 #ifndef _INC_DIBAPI 15 #define _INC_DIBAPI 16 17 /* Handle to a DIB */ 18 DECLARE_HANDLE(HDIB); 19 20 /* DIB constants */ 21 #define PALVERSION 0x300 22 23 /* DIB Macros*/ 24 25 #define IS_WIN30_DIB(lpbi) ((*(LPDWORD)(lpbi)) == sizeof(BITMAPINFOHEADER)) 26 #define RECTWIDTH(lpRect) ((lpRect)->right - (lpRect)->left) 24
  25. Bài 6. Cơ bản về lập trình với MFC 27 #define RECTHEIGHT(lpRect) ((lpRect)->bottom - (lpRect)->top) 28 29 // WIDTHBYTES performs DWORD-aligning of DIB scanlines. The "bits" 30 // parameter is the bit count for the scanline (biWidth * biBitCount), 31 // and this macro returns the number of DWORD-aligned bytes needed 32 // to hold those bits. 33 34 #define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4) 35 36 /* Function prototypes */ 37 BOOL WINAPI PaintDIB (HDC, LPRECT, HDIB, LPRECT, CPalette* pPal); 38 BOOL WINAPI CreateDIBPalette(HDIB hDIB, CPalette* cPal); 39 LPSTR WINAPI FindDIBBits (LPSTR lpbi); 40 DWORD WINAPI DIBWidth (LPSTR lpDIB); 41 DWORD WINAPI DIBHeight (LPSTR lpDIB); 42 WORD WINAPI PaletteSize (LPSTR lpbi); 43 WORD WINAPI DIBNumColors (LPSTR lpbi); 44 HGLOBAL WINAPI CopyHandle (HGLOBAL h); 45 46 BOOL WINAPI SaveDIB (HDIB hDib, CFile& file); 47 HDIB WINAPI ReadDIBFile(CFile& file); 48 49 #endif //!_INC_DIBAPI Với khuôn khổ giáo trình, chúng tôi không đưa nội dung các tập tin cài đặt ở đây. Tuy nhiên chúng tôi sẽ giải thích ngắn gọn các hàm này. Các cài đặt cụ thể xin các bạn xem trong project MFCExample trên đĩa CD. Tập tin MyFile.cpp cài đặt hàm đọc và ghi tập tin ảnh dạng bmp. Hàm đọc tập tin ảnh ReadDIBFile đọc nội dung tập tin file và trả về chỉ danh (handle) HDIB của đối tượng ảnh DIB nếu thành công. Ngược lại, giá trị trả về bằng NULL. Trong ứng dụng của chúng ta, khi người dùng chọn menu File Open mở đọc một tập tin, ta sẽ gọi hàm này để đọc nội dung ảnh. Hàm được dùng khi cài đặt mở tập tin ảnh trong lớp CMFCExampleDoc. 25
  26. Lập trình Windows HDIB WINAPI ReadDIBFile(CFile& file); Hàm lưu tập tin ảnh SaveDIB lưu nội dung ảnh HDIB vào tập tin file. Nếu thành công giá trị trả về là TRUE, ngược lại, là FALSE. Trong ứng dụng của chúng ta, khi người dùng chọn lưu tập tin (File Save), ta sẽ gọi hàm này để lưu nội dung ảnh. BOOL WINAPI SaveDIB(HDIB hDib, CFile& file); Tập tin DIBAPI.cpp cài đặt một số hàm thao tác dữ liệu ảnh. Hàm PaintDIB vẽ ảnh bitmap hDIB với kích thước lpDIBRect lên vùng hình chữ nhật lpDCRect trên Device Context hDC. Dữ liệu bảng màu của ảnh hDIB được xác định bởi pPal. Nếu ảnh được vẽ, giá trị trả về là TRUE, ngược lại là FALSE. BOOL WINAPI PaintDIB(HDC hDC, LPRECT lpDCRect, HDIB hDIB, LPRECT lpDIBRect, CPalette* pPal); Hàm CreateDIBPalette tạo bảng màu cho ảnh hDIB. Bảng màu của ảnh hDIB cần tạo được gởi qua con trỏ pPal. Nếu thành công hàm trả về giá trị TRUE, ngược lại là FALSE. BOOL WINAPI CreateDIBPalette(HDIB hDIB, CPalette* pPal); Hàm FindDIBBits lấy địa chỉ vùng nhớ dữ liệu ảnh. LPSTR WINAPI FindDIBBits(LPSTR lpbi); Đối với đối tượng ảnh, dữ liệu là một khối nhớ được cấp phát toàn cục trên Windows (xem lại hàm ReadDIBFile). Mặt khác, ta lại biết dữ liệu ảnh bitmap được tổ chức theo cấu trúc 26
  27. Bài 6. Cơ bản về lập trình với MFC phức tạp gồm 2 phần chính là Header và Data. Vì thế, nếu muốn thao tác đến chính xác dữ liệu nào đó thì ta phải "ép" (cast) vùng nhớ tương ứng về cấu trúc dữ liệu mà ta cần. Đối với hàm FindDIBBits này và các hàm tiếp theo, chúng ta sẽ thực hành và hiểu rõ cách thao tác để có dự liệu mong muốn. Hàm FindDIBBits có mục đích lấy được vùng dữ liệu ảnh. Các giải thuật trên ảnh mà chúng ta sẽ thao tác sau này chính thao tác trên vùng dữ liệu ảnh này. Hàm này chỉ đơn giản trả về địa chỉ vùng dữ liệu ảnh dưới dạng mảng các byte ảnh. Khi chúng ta thao tác ảnh 256 màu, một điểm ảnh trên ảnh thật chính là một byte trong mảng này sau khi tham chiếu giá trị màu trong bảng màu của ảnh. Tương tự, hàm DIBWidth và DIBHeight lấy chiều rộng và chiều cao của ảnh. Thông qua con trỏ trỏ đến vùng nhớ ảnh lpDIB và định nghĩa cấu trúc BITMAPINFOHEADER của ảnh bitmap trên Windows, ta dễ dàng xác định chiều rộng và chiều cao ảnh. DWORD WINAPI DIBWidth(LPSTR lpDIB); DWORD WINAPI DIBHeight(LPSTR lpDIB); Vì kích thước bảng màu của ảnh bitmap họ Windows 3.0 khác các loại ảnh DIB còn lại, ta dùng hàm PaletteSize để lấy chính xác kích thước palette ảnh. WORD WINAPI PaletteSize(LPSTR lpbi); Thông qua giá trị biBitCount của cấu trúc BITMAPINFOHEADER ta biết được ảnh là 2, 16, 256 hay 27
  28. Lập trình Windows true color, ta dùng hàm DIBNumColors để xác định số lượng màu trên ảnh bitmap. WORD WINAPI DIBNumColors (LPSTR lpbi); Cuối cùng, hàm CopyHandle copy một đối tượng DIB để tạo một đối tượng vùng nhớ có dữ liệu giống một vùng nhớ khác. Hàm được dùng với mục đích tạo lại một đối tượng DIB thông qua chỉ danh của nó khi cần thiết. HGLOBAL WINAPI CopyHandle (HGLOBAL h); 6.3.2. Đọc và hiển thị tập tin ảnh bmp trên ứng dụng Để quản lý đối tượng ảnh, chúng ta cần tạo lập các đối tượng dữ liệu trong lớp CMFCExampleDoc. Chúng ta cần một đối tượng ảnh HDIB m_hDIB, xác định thông qua kích thước CSize m_sizeDoc, có bảng màu CPalette* m_palDIB, và số màu ảnh DWORD m_NumColors. Các biến này được định nghĩa trong tập tin CMFCExampleDoc.h. 1 // MFCExampleDoc.h : interface of the CMFCExampleDoc class 2 // 3 ///////////////////////////////////////////////////////////////////////////// 4 5 #if !defined(AFX_MFCEXAMPLEDOC_H__INCLUDED_) 6 #define AFX_MFCEXAMPLEDOC_H__INCLUDED_ 7 8 #if _MSC_VER > 1000 9 #pragma once 10 #endif // _MSC_VER > 1000 11 12 #include"DIBAPI.h" 13 14 15 class CMFCExampleDoc : public CDocument 16 { 17 protected: // create from serialization only 18 CMFCExampleDoc(); 19 DECLARE_DYNCREATE(CMFCExampleDoc) 20 28
  29. Bài 6. Cơ bản về lập trình với MFC 21 // Attributes 22 public: 23 HDIB GetHDIB() const 24 { return m_hDIB; } 25 CPalette* GetDocPalette() const 26 { return m_palDIB; } 27 CSize GetDocSize() const 28 { return m_sizeDoc; } 29 30 // Operations 31 public: 32 void ReplaceHDIB(HDIB hDIB); 33 void InitDIBData(); 34 35 // Overrides 36 // ClassWizard generated virtual function overrides 37 //{{AFX_VIRTUAL(CMFCExampleDoc) 38 public: 39 virtual BOOL OnNewDocument(); 40 virtual void Serialize(CArchive& ar); 41 virtual BOOL OnOpenDocument(LPCTSTR lpszPathName); 42 virtual BOOL OnSaveDocument(LPCTSTR lpszPathName); 43 //}}AFX_VIRTUAL 44 45 // Implementation 46 public: 47 DWORD m_NumColors; 48 BOOLm_bModified; 49 virtual ~CMFCExampleDoc(); 50 #ifdef _DEBUG 51 virtual void AssertValid() const; 52 virtual void Dump(CDumpContext& dc) const; 53 #endif 54 55 protected: 56 HDIB m_hDIB; 57 CPalette* m_palDIB; 58 CSize m_sizeDoc; 59 60 // Generated message map functions 61 protected: 62 //{{AFX_MSG(CMFCExampleDoc) 63 // NOTE - the ClassWizard will add and remove member functions here. 64 // DO NOT EDIT what you see in these blocks of generated code ! 65 //}}AFX_MSG 66 DECLARE_MESSAGE_MAP() 67 }; 68 69 ///////////////////////////////////////////////////////////////////////////// 29
  30. Lập trình Windows 70 71 //{{AFX_INSERT_LOCATION}} 72 // Microsoft Visual C++ will insert additional declarations immediately 73 // before the previous line. 74 75 #endif // !defined(AFX_MFCEXAMPLEDOC_H__INCLUDED_) Trong tập tin trên, ta cũng có một số hàm thao tác tập tin cũng như dữ liệu thông tin ảnh, các hàm này được cài đặt trong tập tin CMFCExampleDoc.cpp. Để ý các hàm như OnOpenDocument, OnSaveDocument được tạo lập nhờ ClassWizard. 1 // MFCExampleDoc.cpp : implementation of the CMFCExampleDoc class 2 // 3 4 #include "stdafx.h" 5 #include "MFCExample.h" 6 7 #include "MFCExampleDoc.h" 8 9 #ifdef _DEBUG 10 #define new DEBUG_NEW 11 #undef THIS_FILE 12 static char THIS_FILE[] = __FILE__; 13 #endif 14 15 ///////////////////////////////////////////////////////////////////////////// 16 // CMFCExampleDoc 17 18 IMPLEMENT_DYNCREATE(CMFCExampleDoc, CDocument) 19 20 BEGIN_MESSAGE_MAP(CMFCExampleDoc, CDocument) 21 //{{AFX_MSG_MAP(CMFCExampleDoc) 22 // NOTE - the ClassWizard will add and remove mapping macros here. 23 // DO NOT EDIT what you see in these blocks of generated code! 24 //}}AFX_MSG_MAP 25 END_MESSAGE_MAP() 26 27 ///////////////////////////////////////////////////////////////////////////// 28 // CMFCExampleDoc construction/destruction 29 30 CMFCExampleDoc::CMFCExampleDoc() 31 { 32 m_hDIB = NULL; 33 m_palDIB = NULL; 30
  31. Bài 6. Cơ bản về lập trình với MFC 34 m_sizeDoc = CSize(1,1); // dummy value to make CScrollView happy 35 } 36 37 CMFCExampleDoc::~CMFCExampleDoc() 38 { 39 if (m_hDIB != NULL) 40 { 41 ::GlobalFree((HGLOBAL) m_hDIB); 42 } 43 if (m_palDIB != NULL) 44 { 45 delete m_palDIB; 46 } 47 } 48 49 BOOL CMFCExampleDoc::OnNewDocument() 50 { 51 if (!CDocument::OnNewDocument()) 52 return FALSE; 53 54 if (m_hDIB != NULL) 55 { 56 ::GlobalFree((HGLOBAL) m_hDIB); 57 } 58 if (m_palDIB != NULL) 59 { 60 delete m_palDIB; 61 } 62 m_hDIB = NULL; 63 m_palDIB = NULL; 64 m_NumColors = 0; 65 m_sizeDoc = CSize(1,1); // dummy value to make CScrollView happy 66 67 return TRUE; 68 } 69 70 ///////////////////////////////////////////////////////////////////////////// 71 // CMFCExampleDoc serialization 72 73 void CMFCExampleDoc::Serialize(CArchive& ar) 74 { 75 if (ar.IsStoring()) 76 { 77 // TODO: add storing code here 78 } 79 else 80 { 81 // TODO: add loading code here 82 } 31
  32. Lập trình Windows 83 } 84 85 ///////////////////////////////////////////////////////////////////////////// 86 // CMFCExampleDoc diagnostics 87 88 #ifdef _DEBUG 89 void CMFCExampleDoc::AssertValid() const 90 { 91 CDocument::AssertValid(); 92 } 93 94 void CMFCExampleDoc::Dump(CDumpContext& dc) const 95 { 96 CDocument::Dump(dc); 97 } 98 #endif //_DEBUG 99 100 ///////////////////////////////////////////////////////////////////////////// 101 // CMFCExampleDoc commands 102 103 BOOL CMFCExampleDoc::OnOpenDocument(LPCTSTR lpszPathName) 104 { 105 CFile file; 106 CFileException fe; 107 if (!file.Open(lpszPathName, CFile::modeRead | CFile::shareDenyWrite, &fe)) 108 { 109 ReportSaveLoadException(lpszPathName, &fe, 110 FALSE, AFX_IDP_FAILED_TO_OPEN_DOC); 111 return FALSE; 112 } 113 DeleteContents(); 114 BeginWaitCursor(); 115 116 // replace calls to Serialize with ReadDIBFile function 117 TRY 118 { 119 m_hDIB = ::ReadDIBFile(file); 120 } 121 CATCH (CFileException, eLoad) 122 { 123 file.Abort(); // will not throw an exception 124 EndWaitCursor(); 125 ReportSaveLoadException(lpszPathName, eLoad, 126 FALSE, AFX_IDP_FAILED_TO_OPEN_DOC); 127 m_hDIB = NULL; 128 return FALSE; 129 } 130 END_CATCH 131 32
  33. Bài 6. Cơ bản về lập trình với MFC 132 InitDIBData(); 133 EndWaitCursor(); 134 135 if (m_hDIB == NULL) 136 { 137 // may not be DIB format 138 MessageBox(NULL, "Can not read selected file.\nThis is not valid 139 bitmap file, or its format is not currently supported.", NULL, 140 MB_ICONEXCLAMATION|MB_OK); 141 return FALSE; 142 } 143 SetPathName(lpszPathName); 144 SetModifiedFlag(FALSE); // start off with unmodified 145 return TRUE; 146 } 147 148 BOOL CMFCExampleDoc::OnSaveDocument(LPCTSTR lpszPathName) 149 { 150 CFile file; 151 CFileException fe; 152 153 if (!file.Open(lpszPathName, CFile::modeCreate | 154 CFile::modeReadWrite | CFile::shareExclusive, &fe)) 155 { 156 ReportSaveLoadException(lpszPathName, &fe, 157 TRUE, AFX_IDP_INVALID_FILENAME); 158 return FALSE; 159 } 160 161 // replace calls to Serialize with SaveDIB function 162 BOOL bSuccess = FALSE; 163 TRY 164 { 165 BeginWaitCursor(); 166 bSuccess = ::SaveDIB(m_hDIB, file); 167 file.Close(); 168 } 169 CATCH (CException, eSave) 170 { 171 file.Abort(); // will not throw an exception 172 EndWaitCursor(); 173 ReportSaveLoadException(lpszPathName, eSave, 174 TRUE, AFX_IDP_FAILED_TO_SAVE_DOC); 175 return FALSE; 176 } 177 END_CATCH 178 179 EndWaitCursor(); 180 SetModifiedFlag(FALSE); // back to unmodified 33
  34. Lập trình Windows 181 m_bModified = FALSE; 182 183 if (!bSuccess) 184 { 185 // may be other-style DIB (load supported but not save) 186 // or other problem in SaveDIB 187 MessageBox(NULL, "Can not save current bitmap. Check file pathname 188 again!", NULL, MB_ICONEXCLAMATION | MB_OK); 189 } 190 191 return bSuccess; 192 } 193 194 void CMFCExampleDoc::InitDIBData() 195 { 196 if (m_palDIB != NULL) 197 { 198 delete m_palDIB; 199 m_palDIB = NULL; 200 } 201 if (m_hDIB == NULL) 202 { 203 return; 204 } 205 // Set up document size 206 LPSTR lpDIB = (LPSTR) ::GlobalLock((HGLOBAL) m_hDIB); 207 if (::DIBWidth(lpDIB) > INT_MAX ||::DIBHeight(lpDIB) > INT_MAX) 208 { 209 ::GlobalUnlock((HGLOBAL) m_hDIB); 210 ::GlobalFree((HGLOBAL) m_hDIB); 211 m_hDIB = NULL; 212 MessageBox(NULL, "Bitmap too big!", NULL, MB_ICONEXCLAMATION | 213 MB_OK); 214 return; 215 } 216 m_NumColors = ::DIBNumColors(lpDIB); 217 m_sizeDoc = CSize((int) ::DIBWidth(lpDIB), (int) ::DIBHeight(lpDIB)); 218 ::GlobalUnlock((HGLOBAL) m_hDIB); 219 // Create copy of palette 220 m_palDIB = new CPalette; 221 if (m_palDIB == NULL) 222 { 223 // we must be really low on memory 224 ::GlobalFree((HGLOBAL) m_hDIB); 225 m_hDIB = NULL; 226 return; 227 } 228 if (::CreateDIBPalette(m_hDIB, m_palDIB) == NULL) 229 { 34
  35. Bài 6. Cơ bản về lập trình với MFC 230 // DIB may not have a palette 231 delete m_palDIB; 232 m_palDIB = NULL; 233 return; 234 } 235 } 236 237 void CMFCExampleDoc::ReplaceHDIB(HDIB hDIB) 238 { 239 if (m_hDIB != NULL) 240 { 241 ::GlobalFree((HGLOBAL) m_hDIB); 242 } 243 m_hDIB = hDIB; 244 } Với tập tin ảnh đã được nạp vào biến m_hDIB của lớp CMFCExampleDoc, ta chỉ việc dùng hàm OnDraw của lớp CMFCExampleView để xuất lên cửa sổ ứng dụng. Ngoài ra ta cũng chặn thông điệp WM_KEYDOWN ứng hàm OnKeyDown để thao tác scroll ảnh bằng phím. Nội dung tập tin CMFCExampleView.h như sau: 1 // MFCExampleView.h : interface of the CMFCExampleView class 2 // 3 ///////////////////////////////////////////////////////////////////////////// 4 5 #if !defined(AFX_MFCEXAMPLEVIEW_H __INCLUDED_) 6 #define AFX_MFCEXAMPLEVIEW_H __INCLUDED_ 7 8 #if _MSC_VER > 1000 9 #pragma once 10 #endif // _MSC_VER > 1000 11 12 13 class CMFCExampleView : public CScrollView 14 { 15 protected: // create from serialization only 16 CMFCExampleView(); 17 DECLARE_DYNCREATE(CMFCExampleView) 18 19 // Attributes 20 public: 21 CMFCExampleDoc* GetDocument(); 35
  36. Lập trình Windows 22 23 // Operations 24 public: 25 26 // Overrides 27 // ClassWizard generated virtual function overrides 28 //{{AFX_VIRTUAL(CMFCExampleView) 29 public: 30 virtual void OnDraw(CDC* pDC); // overridden to draw this view 31 virtual BOOL PreCreateWindow(CREATESTRUCT& cs); 32 protected: 33 virtual void OnInitialUpdate(); // called first time after construct 34 virtual BOOL OnPreparePrinting(CPrintInfo* pInfo); 35 virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo); 36 virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo); 37 //}}AFX_VIRTUAL 38 39 // Implementation 40 public: 41 virtual ~CMFCExampleView(); 42 #ifdef _DEBUG 43 virtual void AssertValid() const; 44 virtual void Dump(CDumpContext& dc) const; 45 #endif 46 47 protected: 48 49 // Generated message map functions 50 protected: 51 //{{AFX_MSG(CMFCExampleView) 52 afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); 53 //}}AFX_MSG 54 DECLARE_MESSAGE_MAP() 55 }; 56 57 #ifndef _DEBUG // debug version in MFCExampleView.cpp 58 inline CMFCExampleDoc* CMFCExampleView::GetDocument() 59 { return (CMFCExampleDoc*)m_pDocument; } 60 #endif 61 62 ///////////////////////////////////////////////////////////////////////////// 63 64 //{{AFX_INSERT_LOCATION}} 65 // Microsoft Visual C++ will insert additional declarations immediately before the 66 previous line. 67 68 #endif // !defined(AFX_MFCEXAMPLEVIEW_H __INCLUDED_) 36
  37. Bài 6. Cơ bản về lập trình với MFC Sau khi cài đặt lại hàm OnDraw, OnInitialUpdate và hàm OnKeyDown, khi thao tác mở tập tin ảnh bitmap Elephant.bmp, ta sẽ có kết quả như ở hình 6.7. Hình 6.7. Hiển thị ảnh Elephant.bmp trên ứng dụng MFCExample Nội dung tập tin CMFCExampleView.cpp: 1 // MFCExampleView.cpp : implementation of the CMFCExampleView class 2 // 3 4 #include "stdafx.h" 5 #include "MFCExample.h" 6 7 #include "MFCExampleDoc.h" 8 #include "MFCExampleView.h" 9 37
  38. Lập trình Windows 10 #ifdef _DEBUG 11 #define new DEBUG_NEW 12 #undef THIS_FILE 13 static char THIS_FILE[] = __FILE__; 14 #endif 15 16 ///////////////////////////////////////////////////////////////////////////// 17 // CMFCExampleView 18 19 IMPLEMENT_DYNCREATE(CMFCExampleView, CScrollView) 20 21 BEGIN_MESSAGE_MAP(CMFCExampleView, CScrollView) 22 //{{AFX_MSG_MAP(CMFCExampleView) 23 ON_WM_KEYDOWN() 24 //}}AFX_MSG_MAP 25 // Standard printing commands 26 ON_COMMAND(ID_FILE_PRINT, CScrollView::OnFilePrint) 27 ON_COMMAND(ID_FILE_PRINT_DIRECT, CScrollView::OnFilePrint) 28 ON_COMMAND(ID_FILE_PRINT_PREVIEW, CScrollView::OnFilePrintPreview) 29 END_MESSAGE_MAP() 30 31 ///////////////////////////////////////////////////////////////////////////// 32 // CMFCExampleView construction/destruction 33 34 CMFCExampleView::CMFCExampleView() 35 { 36 // TODO: add construction code here 37 38 } 39 40 CMFCExampleView::~CMFCExampleView() 41 { 42 } 43 44 BOOL CMFCExampleView::PreCreateWindow(CREATESTRUCT& cs) 45 { 46 // TODO: Modify the Window class or styles here by modifying 47 // the CREATESTRUCT cs 48 49 return CScrollView::PreCreateWindow(cs); 50 } 51 52 ///////////////////////////////////////////////////////////////////////////// 53 // CMFCExampleView drawing 54 55 void CMFCExampleView::OnDraw(CDC* pDC) 56 { 57 CMFCExampleDoc* pDoc = GetDocument(); 58 38
  39. Bài 6. Cơ bản về lập trình với MFC 59 HDIB hDIB = pDoc->GetHDIB(); 60 if (hDIB != NULL) 61 { 62 LPSTR lpDIB = (LPSTR) ::GlobalLock((HGLOBAL) hDIB); 63 int cxDIB = (int) ::DIBWidth(lpDIB); // Size of DIB - x 64 int cyDIB = (int) ::DIBHeight(lpDIB); // Size of DIB - y 65 ::GlobalUnlock((HGLOBAL) hDIB); 66 CRect rCDIP; 67 rCDIP.top = rCDIP.left = 0; 68 rCDIP.right = cxDIB; 69 rCDIP.bottom = cyDIB; 70 CRect rcDest; 71 if (pDC->IsPrinting()) // printer DC 72 { 73 // get size of printer page (in pixels) 74 int cxPage = pDC->GetDeviceCaps(HORZRES); 75 int cyPage = pDC->GetDeviceCaps(VERTRES); 76 // get printer pixels per inch 77 int cxInch = pDC->GetDeviceCaps(LOGPIXELSX); 78 int cyInch = pDC->GetDeviceCaps(LOGPIXELSY); 79 80 // 81 // Best Fit case create a rectangle which preserves 82 // the DIB's aspect ratio, and fills the page horizontally. 83 // 84 // The formula in the "->bottom" field below calculates the Y 85 // position of the printed bitmap, based on the size of the 86 // bitmap, the width of the page, and the relative size of 87 // a printed pixel (cyInch / cxInch). 88 // 89 rcDest.top = rcDest.left = 0; 90 rcDest.bottom = (int)(((double)cyDIB * cxPage * cyInch) 91 / ((double)cxDIB * cxInch)); 92 rcDest.right = cxPage; 93 } 94 else // not printer DC 95 rcDest = rCDIP; 96 ::PaintDIB(pDC->m_hDC, &rcDest, pDoc->GetHDIB(), 97 &rCDIP, pDoc->GetDocPalette()); 98 } 99 } 100 101 void CMFCExampleView::OnInitialUpdate() 102 { 103 CScrollView::OnInitialUpdate(); 104 ASSERT(GetDocument() != NULL); 105 106 SetScrollSizes(MM_TEXT, GetDocument()->GetDocSize()); 107 } 39
  40. Lập trình Windows 108 109 ///////////////////////////////////////////////////////////////////////////// 110 // CMFCExampleView printing 111 112 BOOL CMFCExampleView::OnPreparePrinting(CPrintInfo* pInfo) 113 { 114 // default preparation 115 return DoPreparePrinting(pInfo); 116 } 117 118 void CMFCExampleView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/) 119 { 120 // TODO: add extra initialization before printing 121 } 122 123 void CMFCExampleView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/) 124 { 125 // TODO: add cleanup after printing 126 } 127 128 ///////////////////////////////////////////////////////////////////////////// 129 // CMFCExampleView diagnostics 130 131 #ifdef _DEBUG 132 void CMFCExampleView::AssertValid() const 133 { 134 CScrollView::AssertValid(); 135 } 136 137 void CMFCExampleView::Dump(CDumpContext& dc) const 138 { 139 CScrollView::Dump(dc); 140 } 141 142 CMFCExampleDoc* CMFCExampleView::GetDocument() // non-debug version is 143 inline 144 { 145 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMFCExampleDoc))); 146 return (CMFCExampleDoc*)m_pDocument; 147 } 148 #endif //_DEBUG 149 150 ///////////////////////////////////////////////////////////////////////////// 151 // CMFCExampleView message handlers 152 153 void CMFCExampleView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 154 { 155 switch(nChar) 156 { 40
  41. Bài 6. Cơ bản về lập trình với MFC 157 case VK_HOME: 158 OnVScroll(SB_TOP, 0, NULL); 159 OnHScroll(SB_LEFT, 0, NULL); 160 break; 161 case VK_END: 162 OnVScroll(SB_BOTTOM, 0, NULL); 163 OnHScroll(SB_RIGHT, 0, NULL); 164 break; 165 case VK_UP: 166 OnVScroll(SB_LINEUP, 0, NULL); 167 break; 168 case VK_DOWN: 169 OnVScroll(SB_LINEDOWN, 0, NULL); 170 break; 171 case VK_PRIOR: 172 OnVScroll(SB_PAGEUP, 0, NULL); 173 break; 174 case VK_NEXT: 175 OnVScroll(SB_PAGEDOWN, 0, NULL); 176 break; 177 case VK_LEFT: 178 OnHScroll(SB_LINELEFT, 0, NULL); 179 break; 180 case VK_RIGHT: 181 OnHScroll(SB_LINERIGHT, 0, NULL); 182 break; 183 default: 184 CScrollView::OnKeyDown(nChar, nRepCnt, nFlags); 185 } 186 } 6.4. THAY LỜI KẾT Qua bài học này, chúng ta đã nắm được cơ chế xây dựng các ứng dụng bằng MFC trên Windows. Với sự quản lý chặt chẽ của Application Framework – xem phần 6.1 – và sự hỗ trợ trực quan của công cụ ClassWizard – xem phần 6.2, chúng ta dễ dàng lập trình thao tác xử lý cho từng ứng dụng. Bằng việc tìm hiểu cơ bản về các lớp CDocument và CView – xem phần 6.3, chúng ta đã bước đầu quản lý được dữ liệu và thể hiện được giao diện của một ứng dụng dạng SDI. 41
  42. Lập trình Windows Và như thế, chúng ta đã có thể hiểu tại sao MFC được sử dụng để lập trình hơn là cách chúng ta lập trình trực tiếp với các hàm API của Win32 Application, đồng thời cũng hiểu tại sao khi học về lập trình Windows chúng ta cũng chỉ cần học thông qua minh họa với Win32 Application trước. Từ đây, các bạn có thể dễ dàng tìm hiểu kỹ các lớp đối tượng MFC trên MSDN, cũng như những vấn đề mở rộng khác để phát triển khả năng lập trình của mình và có thể xây dựng các ứng dụng phức tạp và chuyên nghiệp trên Windows. 42