孫鑫老師的教學筆記(轉)

 
 第一課
1.MFC生成的C++源文件中都有StdAfx.h,此文件包含了常用的AFX函數的聲明,其中有afxwin.h,此文件包含了CRECT,CPoint,CWnd等許多類及其方法的聲明。
2.Project->Setting->Debug可以加入命令行參數。
3.在SDK中要加入"windows.h"和stdio.h。因爲LoadCursor,MessageBox等函數的聲明在這個文件中。
4.創建一個完整的窗口的四個步驟SDK,1設計窗口類,2註冊窗口類,3創建窗口,4顯示窗口
5.函數名可以代表函數代碼的首地址,即可作爲函數指針。
6.要查看VC數據類型,可以在MSDN中輸入“BOOL”然後選擇“DATA TYPE”。
7.atof將字符串轉化爲float,atoi將字符串轉化爲int型。
8.所有從CWnd類派生的類都有m_hWnd句柄。
9.變量的生命週期:可以認爲出了包含它的大括號,這個變量的生命週期結束。所以全局變量的聲明位於所有大括號之外。但是用new聲明的變量和用static聲明的變量除外。
10.SDK示範程序,見下面。
11.sprintf格式化字符,其頭文件爲stdio.h,在MFC中格式化字符用CString.Format
12.GetDC()與ReleaseDC()要成對使用,否則會內存泄漏。同樣,BeginPaint()與EndPaint()。
13.GetStockObject()得到畫筆、畫刷、字體、調色板的句柄,使用時必須用類型轉換。
14.什麼時候用NULL,什麼時候用0.答,對指針賦值時用NULL,對變量賦值時用0.
15.什麼是野指針?答:將指針指向的變量的內存釋放後,此指針即變成野指針!如何避免野指針?答:將此指針指向NULL即可。p=NULL;
16.SDK代碼流程:
#i nclude "windows.h"//包含頭文件LoadCursor,TextOut等函數
#i nclude "stdio.h"//包含sprintf,printf等函數
LRESULT CALLBACK MyProc(...);//聲明回調函數
int WINAPI WinMain()
{
WNDCLASS wndcls;//設計窗口類
wndcls.hcursor=LoadCursor();//初始化
....
RegisterClass(&wndcls);//註冊窗口類
hwnd=CreateWindow(...);//創建窗口
ShowWindow(..);//顯示窗口
UpdateWindow(..);
MSG msg;//定義消息結構體
while(GetMessage(...))//消息循環
{
...
}
return 0;
}
LRESULT CALLBACK MyProc(...)//實現回調函數
{
switch(uMsg)
{
case WM_CHAR:
break;
...
}
}
 
第2課
1.定義結構體和類時別忘記在最後加入";"號!例如Class Point{int x;int y;};
2.#i nclude <xxx.h>與#i nclude "xxx.h"的區別:<>不查找運行時目錄,""查找運行時目錄!
3.類的定義中,如果未指明成員類型,則缺省爲private.而結構體中則缺省爲public.
4.引用:引用經常用在函數的傳參上。另外數值交換函數也經常用引用。例
change(int &x,int &y){int temp;temp=x;x=y;y=x}調用時即可以用 int a=3;int b=4;change(a,b);一般不用指針來作爲參數進行數值交換。因爲會引起歧義。
5.通常將類的定義放.h文件,而將其實現放在cpp文件中,別忘記了在cpp文件中#i nclude "xxx.h"
6.如何防止類的重複定義?
用#inndef Point_H_H
#define Point_H_H
class Point{};
#endif來防止
7.源文件cpp文件單獨編譯成obj文件。最後由鏈接器將與將要使用到的C++標準庫類鏈接成exe文件,頭文件不參加編譯。所以在cpp文件中別忘記了加入#i nclude "xxx.h"
8.函數的覆蓋,在子類中重寫父類的函數,此時採用早期綁定的方法。如果加入了virtual,則將採用遲綁定的技術,在運行時根據對象的類型確定調用哪一個函數。此遲綁定技術是MFC的類的繼承的精髓。
9.強制類型轉換。如果CFish從CAnimal派生而來。則可以將魚的對象轉換爲CAnimal的對象,而反之則不行。從現實中理解也是正常的,魚可以是動物,而動物卻不是魚。再如int可以強制轉換成char型。而反之則出錯。
 
第3課
1.在main或WinMain之前,全局變量已經被分配內存並初始化了。
2.在MFC中在WinMain之前有個theApp全局變量先被構造並被初始化,而由於子類構造函數執行前,其父類的構造函數先被執行,所以CTestApp的父類CWinAPP的構造函數先執行。產生了theApp對象後,在WinMain()中的指針*pThread和*pApp就有了內容。
3.MFC大致流程:
CTestApp theApp;//構造全局對象
WinMain()
{
AfxWinMain();//調用下面的函數
}
AfxWinMain()
{
pThread->Initinstance();//初始化工作和註冊窗口類,窗口顯示和更新
pThread->Run();//消息循環
}
而在BOOL CTestApp::InitInstance()中的代碼
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
  IDR_MAINFRAME,
  RUNTIME_CLASS(CTestDoc),
  RUNTIME_CLASS(CMainFrame),       // main SDI frame window
  RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);
完成了將這三個類關聯起來的工作。
4.如何在單文檔文件中顯示一個CButton的對象?
在CMainFrame::OnCreate()中定義一個CButton的對象btn;然後調用btn.Create("維新",WS_DISABLED   |WS_CHILD | WS_VISIBLE | BS_AUTO3STATE,
  CRect(0,0,300,100),/*GetParent(),*/this,123);
注意點:
     (1).此處btn不能是局部變量,否則它的生命週期太短,將不能顯示。
     (2).在create函數的第二個參數中加入WS_VISIBLE 參數纔行。否則必須調用ShowWindow
也可以在view的OnCreate消息響應函數中加入
     (3).CButton類的定義頭文件在afxwin.h中,而stdafx.h包含了afxwin.h,所以可以直接使用。因爲MFC中的每一個類中都有#i nclude "stdafx.h"的聲明。
 
 第4課
1.在單文檔中view擋在MainFrame的前面。此時如果編寫針對MainFrame的mouseClick事件,將不會有反應。
2.消息響應會在3處修改代碼,1處是在頭文件中,
//{{AFX_MSG(CDrawView)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
另一處是cpp文件的begin MessageMap和End MessageMap之間,
BEGIN_MESSAGE_MAP(CDrawView, CView)
//{{AFX_MSG_MAP(CDrawView)
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
最後是要有函數實現的代碼。
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TOD Add your message handler code here and/or call default
m_ptOrigin=m_ptOld=point;
m_bDraw=TRUE;
CView::OnLButtonDown(nFlags, point);
}
3.畫線:定義一個成員變量保存mouseDown的點m_Point
  1)API函數方法畫線用HDC
  2)用CDC類成員函數畫線。此時別忘記ReleaseDC
  3)用CClientDC
  4)用CWindowDC,用它甚至可以整個屏幕區域畫線。
下面是上面4種方法的代碼
/*HDC hdc;
hdc=::GetDC(m_hWnd);
MoveToEx(hdc,m_ptOrigin.x,m_ptOrigin.y,NULL);
LineTo(hdc,point.x,point.y);
::ReleaseDC(m_hWnd,hdc);必須成對使用。*/
/*CDC *pDC=GetDC();
pDC->MoveTo(m_ptOrigin);
pDC->LineTo(point);
ReleaseDC(pDC);必須成對使用。*/
//CClientDC dc(this);
/*CClientDC dc(GetParent());
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
此處不需要ReleaseDC,因爲CClientDC會自動釋放DC*/
//CWindowDC dc(this);
//CWindowDC dc(GetParent());
/*CWindowDC dc(GetDesktopWindow());//
此時可以在整個屏幕上畫線。
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);*/
/*CPen pen(PS_DOT,1,RGB(0,255,0));
CClientDC dc(this);
CPen *pOldPen=dc.SelectObject(&pen);
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
dc.SelectObject(pOldPen);*/
  5)用Bitmap填充所畫的矩形。
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP1);
CBrush brush(&bitmap);
CClientDC dc(this);
dc.FillRect(CRect(m_ptOrigin,point),&brush);
//CBRUSH::FromHandle是靜態成員函數,所以可以用下面的方法調用。
CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
CBrush *pOldBrush=dc.SelectObject(pBrush);
dc.Rectangle(CRect(m_ptOrigin,point));
dc.SelectObject(pOldBrush);
m_bDraw=FALSE;
  6)用其它顏色畫線
CClientDC dc(this);
CPen pen(PS_SOLID,1,RGB(255,0,0));
CPen *pOldPen=dc.SelectObject(&pen);//選中紅色畫筆
if(m_bDraw==TRUE)
{
  dc.SetROP2(R2_BLACK);//設置繪畫模式
  dc.MoveTo(m_ptOrigin);
  //dc.LineTo(point);
  dc.LineTo(m_ptOld);
  //dc.MoveTo(m_ptOrigin);
  dc.MoveTo(m_ptOld);
  dc.LineTo(point);
  //m_ptOrigin=point;
  m_ptOld=point;
}
dc.SelectObject(pOldPen);
4.MFC中隱式的包含了windows.h。爲什麼?
因爲在AFXV_W32.h文件中:
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1998 Microsoft Corporation
// All rights reserved.
在AFXWIN.h中
// Note: WINDOWS.H already included from AFXV_W32.H
5.如何從句柄獲得對象的指針?
答FromHandle
6.類的靜態成員函數可以由類名直接調用,也可以由對象調用。可以認爲靜態成員函數並不屬於某個對象,它屬於類本身。程序運行伊始,即使沒有實例化類的對象,靜態成員函數和靜態成員變量已然有其內存空間。靜態成員函數不能訪問非靜態成員變量!靜態成員變量必須在類的外部初始化。當然如果並不打算用到靜態成員變量,此時你可以不初始它。
7.理解代碼區,數據區,堆,棧!
請見下面的簡介:
http://www.downcode.com/server/j_server/J_1010.Html
對於一個進程的內存空間而言,可以在邏輯上分成3個部份:代碼區,靜態數據區和動態數據區。動態數據區一般就是“堆棧”。“棧(stack)”和“堆(heap)”是兩種不同的動態數據區,棧是一種線性結構,堆是一種鏈式結構。進程的每個線程都有私有的“棧”,所以每個線程雖然代碼一樣,但本地變量的數據都是互不干擾。一個堆棧可以通過“基地址”和“棧頂”地址來描述。全局變量和靜態變量分配在靜態數據區,本地變量分配在動態數據區,即堆棧中。程序通過堆棧的基地址和偏移量來訪問本地變量。
 
第5課
1.CWnd::CreateSolidCaret創建插入符,ShowCaret()顯示插入符。GetTextMetrics(),獲得當前字體的一些信息。CWnd::CreateCaret()創建圖象插入符
bitmap.LoadBitmap(IDB_BITMAP1);//此處的bitmap爲成員變量!!!
CreateCaret(&bitmap);
ShowCaret();
TEXTMETRIC tm;//字體結構體
dc.GetTextMetrics(&tm);//
m_ptOrigin.y+=tm.tmHeight;//獲得字體高度。
2.VC中CString::LoadString(ID號),比較方便。
3.路徑層的概念:有兩種方法創建路徑層:
  (1)
pDC->BeginPath();
pDC->Rectangle(50,50,50+sz.cx,50+sz.cy);
pDC->EndPath();
pDC->SelectClipPath(RGN_DIFF);
   (2)
        CSize sz=pDC->GetTextExtent(str);
        CRgn rn;
        rn.CreateRectRgn(0,50,sz.cx,sz.cy);
        pDC->SelectClipRgn(&rn,RGN_DIFF);
路徑層有什麼作用?可以保護我們先前的文本或者圖像不被後來畫的覆蓋。
4.在View上輸入文字的步驟。
CFont font;//創建字體對象
font.CreatePointFont(300,"華文行楷",NULL);//設置
CFont *pOldFont=dc.SelectObject(&font);//將字體選擇到DC中
TEXTMETRIC tm;//創建字體信息對象
dc.GetTextMetrics(&tm);//獲得當前字體信息
if(0x0d==nChar)//處理回車鍵
{
  m_strLine.Empty();
  m_ptOrigin.y+=tm.tmHeight;
}
else if(0x08==nChar)//處理退格鍵
{
  COLORREF clr=dc.SetTextColor(dc.GetBkColor());
  dc.TextOut(m_ptOrigin.x,m_ptOrigin.y,m_strLine);
  m_strLine=m_strLine.Left(m_strLine.GetLength()-1);
  dc.SetTextColor(clr);
}
else
{
  m_strLine+=nChar;
}
CSize sz=dc.GetTextExtent(m_strLine);
CPoint pt;//處理光標的位置
pt.x=m_ptOrigin.x+sz.cx;
pt.y=m_ptOrigin.y;
SetCaretPos(pt);
dc.TextOut(m_ptOrigin.x,m_ptOrigin.y,m_strLine);//
輸出字體
dc.SelectObject(pOldFont);//將原先的字體選擇回去。
5.模擬卡啦OK變色的步驟。
   (1)設置定時器
   (2)在定時器中加入如下代碼
//DEL  m_nWidth+=5;//此爲view的成員變量,初始值爲0
//DEL
//DEL
//DEL  CClientDC dc(this);
//DEL  TEXTMETRIC tm;
//DEL  dc.GetTextMetrics(&tm);
//DEL  CRect rect;
//DEL  rect.left=0;
//DEL  rect.top=200;
//DEL  rect.right=m_nWidth;
//DEL  rect.bottom=rect.top+tm.tmHeight;//此長方形的長度隨着定時器的觸發,逐漸增大
//DEL
//DEL  dc.SetTextColor(RGB(255,0,0));
//DEL  CString str;
//DEL  str.LoadString(IDS_WEIXIN);
//DEL  dc.DrawText(str,rect,DT_LEFT);此函數的作用是將字符串輸出到長方形中,但如果字符串的長度超過長方形的長度,多餘的字符將被截斷
//DEL
//DEL  rect.top=150;
//DEL  rect.bottom=rect.top+tm.tmHeight;
//DEL  dc.DrawText(str,rect,DT_RIGHT);
//DEL
//DEL  CSize sz=dc.GetTextExtent(str);獲得字符串的長度
//DEL  if(m_nWidth>sz.cx)當長方形的長度大於字符串的長度後,將其重新歸0
//DEL  {
//DEL   m_nWidth=0;
//DEL   dc.SetTextColor(RGB(0,255,0));
//DEL   dc.TextOut(0,200,str);
//DEL  }
//DEL
//DEL  CView::OnTimer(nIDEvent);
6.SetTimer也可以用回調函數來操作,但並不方便。以下是步驟
  (1) 在View的OnCreate消息響應函數中:SetTimer(1,1000,Timer2Proc);
  (2) 回調函數的實現:
void CALLBACK EXPORT Timer2Proc(
   HWND hWnd,      // handle of CWnd that called SetTimer
   UINT nMsg,      // WM_TIMER
   UINT nIDEvent,   // timer identification
   DWORD dwTime    // system time
)
{
//  MessageBox((((CMainFrame *)AfxGetMainWnd())->m_hWnd),"ddfaf","weixin",0);
;
CMainFrame *pMain=(CMainFrame *)AfxGetApp()->m_pMainWnd;//獲得MainFrame的指針
CTextView *pView=(CTextView *)pMain->GetActiveView();//獲得view的指針
CClientDC dc(pView);//構造DC
  dc.TextOut(333,222,"hello world");
}//我們可以看出,使用回調函數時要獲得窗口或者APP的指針,給我們的操作帶來麻煩。並不方便。
 
第6課
1.當對某菜單添加消息響應函數時,4個類的消息響應優先次序分別是:1.View;2.CDOC;3.CMainFrame.4.CWinAPP.爲什麼?請參閱《深入淺出》
2.消息分類:a;標準消息(以WM_開頭的消息,但不包括ON_COMMAND);b;命令消息 ON_COMMAND(IDM_PHONE1, OnPhone1),菜單和工具欄的消息。c.通告消息:按鈕,列表框發出的消息。
CCmdTarget只能接受命令消息。而從CCmdTarget派生的CWnd可以接收命令消息,也可以接受標準消息。
3.確定菜單的索引號,注意從0開始,分隔符也算數。什麼叫彈出菜單(Popup Menu)?一個子菜單隻能有一個缺省菜單。 //GetMenu()->GetSubMenu(0)->SetDefaultItem(5,TRUE);
str.Format("x=%d,y=%d",GetSystemMetrics(SM_CXMENUCHECK),
   GetSystemMetrics(SM_CYMENUCHECK));//獲得系統的菜單的位圖的大小。
/* SetMenu(NULL);//移除菜單
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME);
SetMenu(&menu);
menu.Detach();*/
增加菜單,此處detach(),如果是局部變量。
4.
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)
{
if(2==pCmdUI->m_nIndex)
  pCmdUI->Enable();//當此菜單顯示時,設爲可用。
}
5.右鍵彈出菜單功能的實現方法有兩個:
  a.Project->Add to Project->component and controls->文件夾VC components->Popup Menu OK
  b.用TrackPopupMenu()實現。
CMenu menu;
menu.LoadMenu(IDR_MENU1);
CMenu *pPopup=menu.GetSubMenu(0);
ClientToScreen(&point);
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,
   GetParent());
6.動態創建菜單的方法:
CMenu menu;
menu.CreatePopupMenu();
// GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"WinSun");
GetMenu()->InsertMenu(2,MF_BYPOSITION | MF_POPUP,(UINT)menu.m_hMenu,"WinSun");
menu.AppendMenu(MF_STRING,IDM_HELLO,"Hello");
menu.AppendMenu(MF_STRING,112,"Weixin");
menu.AppendMenu(MF_STRING,113,"Mybole");
menu.Detach();
GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,114,"Welcome");
GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN,
   MF_BYCOMMAND | MF_STRING,115,"
維新");
// GetMenu()->DeleteMenu(1,MF_BYPOSITION);
// GetMenu()->GetSubMenu(0)->DeleteMenu(2,MF_BYPOSITION);
7.爲動態創建的菜單增加消息響應的步驟
  a.在resource.h中增加#define IDM_HELLO 123
  b.在MainFrm.h中加入afx_msg void OnHello();
  c.MainFrm.cpp中加入ON_COMMAND(IDM_HELLO,OnHello)
  d.最後加入
void CMainFrame::OnHello()
{
MessageBox("Hello!");
}
8.動態增加電話號碼本步驟
  a.處理WM_Char消息。如果回車,則清空字符串,窗口重繪invalidate,將人名加入到菜單中,將字符串保存集合類CStringArray中,用的是成員函數Add方法。
  b.取出動態創建的菜單的數據的方法。
    1)創建一個彈出菜單,彈出菜單下面有4個子菜單。將子菜單的ID號連續。
    2)在resource.h中添加#define IDM_PHONE1 123....
    3)添加其消息響應函數。注意註釋中的文字
BEGIN_MESSAGE_MAP(CMenu2View, CView)
//{{AFX_MSG_MAP(CMenu2View)
ON_WM_CHAR()
ON_COMMAND(ID_EDIT_COPY, OnEditCopy)//下面的4句代碼原來在此處。
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(IDM_PHONE1, OnPhone1)//一定要這4句代碼移到此處。
ON_COMMAND(IDM_PHONE2, OnPhone2)
ON_COMMAND(IDM_PHONE3, OnPhone3)
ON_COMMAND(IDM_PHONE4, OnPhone4)
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
    4)填寫代碼
9.如何在MainFrame中攔截OnCommand消息?答,在它增加OnCommand的消息處理函數即可。
10.錯誤調試方法:Missing ";" before "*"
CMenu2Doc* GetDocument();//因爲CMenu2Doc是個不認識的變量,將其頭文件包含進即可。
 
第7課
1.如果在SDI中要調用對話框
  a.先插入一個對話框資源;
  b.然後在ClassWizards中爲其創建一個類。其目的是比較方便爲添加按紐和消息響應函數。
  c.然後實例化它。在實例化時,必須將其頭文件包含進去。
2.創建非模態對話框,注意它不能是局部變量。當 點擊非模態對話框的OnOK按紐時,它並沒有關閉,而是隱藏了。需要調用destroyWindow().
3.一個對象只能一個按紐。爲什麼?因爲在Wincore.cpp的628行有代碼 ASSERT(pWnd->m_hWnd == NULL);   // only do once而創建後它的m_hWnd就不爲0了。此處ASSERT的用法是如果括號裏面不爲真,則程序崩潰。
4.如何爲靜態文本框增加消息響應?首先將IDC_STATIC改名。同時還需要將Notify特性複選中。
5.完成加法功能。
  a.GetDlgItem();
  b.GetDlgItemText();
  c.GetDlgItemInt();
  d.將IDC_EDIT1關聯CEDIT類型變量
  e.將IDC_EDIT1關聯int型變量。注意調用 UpdateData();
  f. //::SendMessage(GetDlgItem(IDC_EDIT1)->m_hWnd,WM_GETTEXT,10,(LPARAM)ch1);
//::SendMessage(m_edit1.m_hWnd,WM_GETTEXT,10,(LPARAM)ch1);
//GetDlgItem(IDC_EDIT1)->SendMessage(WM_GETTEXT,10,(LPARAM)ch1);
m_edit1.SendMessage(WM_GETTEXT,10,(LPARAM)ch1);
  g. SendDlgItemMessage(IDC_EDIT1,WM_GETTEXT,10,(LPARAM)ch1);
SendDlgItemMessage(IDC_EDIT2,WM_GETTEXT,10,(LPARAM)ch2);
6.點擊按紐改變窗口尺寸
   if(GetDlgItemText(IDC_BUTTON2,str),str=="收縮<<")
{
  SetDlgItemText(IDC_BUTTON2,"擴展>>");
static CRect rectLarge;
static CRect rectSmall;
if(rectLarge.IsRectNull())
{
  CRect rectSeparator;
  GetWindowRect(&rectLarge);
  GetDlgItem(IDC_SEPARATOR)->GetWindowRect(&rectSeparator);
  rectSmall.left=rectLarge.left;
  rectSmall.top=rectLarge.top;
  rectSmall.right=rectLarge.right;
  rectSmall.bottom=rectSeparator.bottom;
}
if(str=="
收縮<<")
{
  SetWindowPos(NULL,0,0,rectSmall.Width(),rectSmall.Height(),
   SWP_NOMOVE | SWP_NOZORDER);
}
else
{
  SetWindowPos(NULL,0,0,rectLarge.Width(),rectLarge.Height(),
   SWP_NOMOVE | SWP_NOZORDER);
}
7.回車時將輸入焦點移動到下一個控件
SetWindowLong()改變窗口的屬性。
方法1:
改變控件的回調函數,注意IDC_EDIT1的MultiLine要複選上。
WNDPROC prevProc;
LRESULT CALLBACK WinSunProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
)
{
if(uMsg==WM_CHAR && wParam==0x0d)
{
  //::SetFocus(::GetNextWindow(hwnd,GW_HWNDNEXT));
  //SetFocus(::GetWindow(hwnd,GW_HWNDNEXT));
  CString str;
  str.Format("%d",hwnd);
  AfxMessageBox(str);//, UINT nType = MB_OK, UINT nIDHelp = 0 );
//  AfxGetApp()->
  SetFocus(::GetNextDlgTabItem(::GetParent(hwnd),hwnd,FALSE));
  return 1;
}
else
{
  return prevProc(hwnd,uMsg,wParam,lParam);
}
}
BOOL CTestDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// TOD Add extra initialization here
prevProc=(WNDPROC)SetWindowLong(GetDlgItem(IDC_EDIT1)->m_hWnd,GWL_WNDPROC,
  (LONG)WinSunProc);//
設置回調函數
return TRUE;  // return TRUE unless you set the focus to a control
               // EXCEPTION: OCX Property Pages should return FALSE
}
方法2:
在OnOK響應函數中加入代碼
//GetDlgItem(IDC_EDIT1)->GetNextWindow()->SetFocus();
//GetFocus()->GetNextWindow()->SetFocus();
//GetFocus()->GetWindow(GW_HWNDNEXT)->SetFocus();
GetNextDlgTabItem(GetFocus())->SetFocus();
 
第8課 對話框
1.如何改變按紐的字體?在對話框的屬性中改變字體的屬性即可
2.逃跑按紐的實現
  1.從CButton派生一個類,CWeixinBtn
  2.將IDC_EDIT1關聯成員變量m_btn1,類型爲CWeixinBtn,注意要包含頭文件。
  3.在CWeixinBtn中加一個指針成員變量CWeixinBtn *pWeixinBtn,然後將其地址初始化。
  4.在新類中增加鼠標移動的消息處理。
3.屬性表單
  1.插入屬性頁資源。Insert->new Resource->Dialog
  2.當選擇Classwizard菜單時,系統提示是否爲創建新的類,我們將其從CPropertyPage派生!這樣可以爲
方便爲其增加消息響應函數。
  3.插入新的從CPropertySheet派生的類,在類中增加3個CPropertyPage的實例。
  4.在view中增加菜單項,當點擊時顯示屬性表單,出現中文亂碼,修改CPropertyPage屬性爲中文,另外將
其字體設爲宋體。
  5.在CPropertyPage中設置SetWizardButtons可將其屬性改爲上一步、完成!
  6.爲IDC_RADIO1關聯成員變量,需要先設置Group屬性纔行。另外別忘記調用UpdateData().
  7.爲CPropertyPage增加虛函數,OnWizardNext,如果用戶點擊下一步時,不想讓他進入下一步,剛返回-1!
  8.將用戶的選擇輸出到屏幕上,此時可以在View中增加幾個成員變量,用來接收用戶選擇的數據。
4.memset()的用法! memset(m_bLike,0,sizeof(m_bLike));
 

第9課
1.修改外觀和圖標可以在MainFrm中進行,而修改背景和光標只能在View中進行。爲什麼?因爲view的顯示擋
在了MainFrame的前面。
  a.在MainFrame中
      PreCreateWindow()中,在窗口創建之前,用重新註冊窗口類的方法,比較麻煩。在PreCreateWindow
()中修改
      也可以用簡單的方法,用全局函數
//cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,0,0,
// LoadIcon(NULL,IDI_WARNING));
     在窗口創建之後,在OnCreate()中修改
//SetWindowLong(m_hWnd,GWL_STYLE,WS_OVERLAPPEDWINDOW);
//SetWindowLong(m_hWnd,GWL_STYLE,GetWindowLong(m_hWnd,GWL_STYLE) & ~WS_MAXIMIZEBOX);
// SetClassLong(m_hWnd,GCL_HICON,(LONG)LoadIcon(NULL,IDI_ERROR));
  b.在View中
    PreCreateWindow()中
//cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
// LoadCursor(NULL,IDC_CROSS),(HBRUSH)GetStockObject(BLACK_BRUSH),NULL);
cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW);
    OnCreate()中
SetClassLong(m_hWnd,GCL_HBRBACKGROUND,(LONG)GetStockObject(BLACK_BRUSH));
SetClassLong(m_hWnd,GCL_HCURSOR,(LONG)LoadCursor(NULL,IDC_HELP));
2.創建一個不斷變化的圖標。用定時器和SetClassLong完成
  a.準備三個圖標文件,放在RES文件夾,Insert->Resource-三個圖標,
  b.在CMainFrame中增加圖標句柄數組,m_hIcons[3]
m_hIcons[0]=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE
(IDI_ICON1));//MAKEINTRESOURCE是一個宏,它將整數轉化爲Win32的資源類型,簡單的說它是一個類型轉換
#define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))
m_hIcons[1]=LoadIcon(theApp.m_hInstance,MAKEINTRESOURCE(IDI_ICON2));//
此處需要用到
theAPP對象,故要在文件中聲明extern CStyleApp theApp;
m_hIcons[2]=LoadIcon(AfxGetApp()->m_hInstance,MAKEINTRESOURCE(IDI_ICON3));
然後將其初始化
  c.然後在定時器中實現
3.工具欄的編程
  a.加入分隔符的方法,向右拖動即可;
  b.刪除按紐的方法,拖出即可。
4.創建一個新的工具欄的方法
  a.插入一個工具欄,畫出其圖形。
  b.在頭文件中,定義CToolBar m_newToolBar
  c.在MainFrm.cpp的OnCreate()中調用
if (!m_newToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_RIGHT
  | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
  !m_newToolBar.LoadToolBar(IDR_TOOLBAR1))
{
  TRACE0("Failed to create toolbar/n");
  return -1;      // fail to create
}  
  d.點擊“新的工具欄”菜單時,隱藏工具欄。兩種方法
  第一種/*if(m_newToolBar.IsWindowVisible())
{
  m_newToolBar.ShowWindow(SW_HIDE);
}
else
{
  m_newToolBar.ShowWindow(SW_SHOW);
}
RecalcLayout();
DockControlBar(&m_newToolBar);*/
  第二種ShowControlBar(&m_newToolBar,!m_newToolBar.IsWindowVisible(),FALSE);
  e.將菜單增加複選標記。在OnUpdateUI中加入代碼
    pCmdUI->SetCheck(m_newToolBar.IsWindowVisible());
5.狀態欄編程
  a.Indicator[]數組中有狀態欄的信息
  如果要增加,可以在String Table中加入一個IDS_Timer,然後將其加入到[]中。
  b.在時間欄顯示時間,代碼略,比較簡單
6.進度欄
  a.增加成員變量,CProgressCtrl m_progress
  b.OnCreate中 m_progress.Create(WS_CHILD | WS_VISIBLE,// | PBS_VERTICAL,
  rect,&m_wndStatusBar,123);
m_progress.SetPos(50);*/
  c.將其創建到狀態欄的方法!如果在OnCreate()中創建,則不成立,因爲獲取矩形大小時失敗。
    解決辦法,用自定義消息:
    在MainFrm.h中#define UM_PROGRESS  WM_USER+1
afx_msg void OnProgress();
    在MainFrm.cpp中
ON_MESSAGE(UM_PROGRESS,OnProgress)
然後實現這個函數
void CMainFrame::OnProgress()
{
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect);
m_progress.Create(WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
  rect,&m_wndStatusBar,123);
m_progress.SetPos(50);
}
     最後在OnCreate中調用 PostMessage(UM_PROGRESS);//不能用SendMessage()
   d.解決重繪時進度欄改變的問題。在OnPain()中重寫代碼
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect);
m_progress.Create(WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
  rect,&m_wndStatusBar,123);
m_progress.SetPos(50);
然後在定時器消息處理函數中加入
m_progress.StepIt();
   e.顯示鼠標位置。在View中增加OnMouseMove()處理函數
CString str;
str.Format("x=%d,y=%d",point.x,point.y);
//((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str);
//((CMainFrame*)GetParent())->SetMessageText(str);
//((CMainFrame*)GetParent())->GetMessageBar()->SetWindowText(str);
GetParent()->GetDescendantWindow(AFX_IDW_STATUS_BAR)->SetWindowText(str);
7.加入啓動畫面
  Project-Component and ->Visual C++ Components->SplashScreen->插入
 

第10課
1.畫圖:
   a.創建四個菜單,爲其添加消息響應;
   b.在View中添加m_DrawType,保存繪畫類型;
   c.增加成員變量,m_PtOrigin,當按下鼠標左鍵時,保存此點;
   d.在OnLButtonUp中畫點,線,矩形,橢圓,別忘記設置成透明畫刷
2.爲其添加一個設置對話框(線型和線寬)
   a.創建對話框,爲其創建一個新類關聯它;
   b.爲其中的線寬關聯成員變量;
   c.在View中增加一個菜單,響應新的對話框;
   d.添加線型選項設置,將其Group屬性選中,併爲單選按紐關聯成員變量。在view中增加一個線型變量m_nLineStyle
3.添加一個顏色對話框
   a.實例化一個CColorDialog
   b.調用DoModal方法
4.添加字體對話框,將選擇的字體在View中顯示出來。
   a.實例化一個對象;
   b.爲View添加一個字體成員變量,得到用戶選擇的字體。
   c.調用Invadate()發出重繪消息;
   d.再次注意一個對象只能創建一次,故要再次創建,必須將原告的刪除!
5.爲設置對話框增加示例功能。
   a.當控件內容改變時,發出En_change消息。而Radio按紐則爲Clicked。需先UpdateData()。另外還需要ScreenToClient(&rect)
6.改變對話框的背景色和控件顏色。
  每個控件被繪製時都發出WM_CTlColor消息,
7.如何改變OK按紐的字體和背景?
  OK按紐
  a.創建一個新類,CTestBtn,基類爲CButton
  b.在類中增加虛函數,DrawItem,添加代碼。
  c.將OK按紐關聯成員變量。類型爲CTestBtn,注意將OK按紐的OwnerDraw特性選中。
  Cancel按紐
  用新類來改變。
  a.加入新文件。
  b.爲Cancel關聯一個成員變量,類型爲CSXBtn;
  c.調用CSXBtn的方法。
  Cancel2按紐
  a.方法同上。
8.在窗口中貼圖,4個步驟
  1、創建位圖
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP1);
2、創建兼容DC
CDC dcCompatible;
dcCompatible.CreateCompatibleDC(pDC);
3、將位圖選到兼容DC中
dcCompatible.SelectObject(&bitmap);
4、將兼容DC中的位圖貼到當前DC中。在WM_EraseBkgnd()中調用,但不能再調用基類的擦除背景函數。也可以在OnDraw函數中完成,但效率低,圖像會閃爍,因爲它先擦除背景,慢。
pDC->BitBlt(rect.left,rect.top,rect.Width(),
rect.Height(),&dcCompatible,0,0,SRCCOPY);
 
第11課
1.創建4個菜單,爲其添加消息響應,用成員變量保存繪畫類型。添加LButtonDown和Up消息。
2.當窗口重繪時,如果想再顯示原先畫的數據,則需要保存數據。爲此創建一個新類來記錄繪畫類型和兩個點。
class CGraph 
{
public:
CPoint m_ptOrigin;//起點
CPoint m_ptEnd;//終點
UINT m_nDrawType;//繪畫類型
CGraph();
CGraph(UINT m_nDrawType,CPoint m_ptOrigin,CPoint m_ptEnd);//此爲構造函數。
virtual ~CGraph();
};
  
然後在void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)中加入如下代碼
//CGraph graph(m_nDrawType,m_ptOrigin,point);//不能用局部變量
//m_ptrArray.Add(&graph);//加入這種指針數組中
/* OnPrepareDC(&dc);//這個函數中可以重新設置窗口原點,對於滾動條中,保存數據前要調用此函數
dc.DPtoLP(&m_ptOrigin);//將設備座標轉換爲邏輯座標
dc.DPtoLP(&point);//
CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);//在堆中創建新的對象
m_ptrArray.Add(pGraph);*///加入到指針數組中
在GraphicView.h中有如下代碼
CPtrArray m_ptrArray;
   在OnDraw中重畫時調出數據
for(int i=0;i<m_ptrArray.GetSize();i++)
3.在CView::OnPaint()調用了OnDraw(),但在void CGraphicView::OnPaint()中MFC的Wizard沒有調用OnDraw(),要注意這個區別。如果你此時想調用,必須手動添加代碼。 OnDraw(&dc);
4.讓窗口具有滾動條的功能。
   第1.將CGraphicView的頭文件中的CView全部替換成CSrollView
   第2.添加如下的代碼
void CGraphicView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
// TOD Add your specialized code here and/or call the base class
SetScrollSizes(MM_TEXT,CSize(800,600));//
設置映射模式,設定窗口大小。OK!
}
5.座標系的轉換,此處不再詳細介紹,需要時請查閱相關資料。
6.解決重繪時線跑到上面的問題。爲什麼會錯位?因爲邏輯座標和設備座標沒有對應起來。
解決方法:
  在OnLButtonDown畫完圖後,保存之前。調用
/* OnPrepareDC(&dc);//重新設置邏輯座標的原點!!!
dc.DPtoLP(&m_ptOrigin);//設備座標轉化爲邏輯座標
dc.DPtoLP(&point);
CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);
m_ptrArray.Add(pGraph);*/
7.另外兩種方法來保存數據。
  一種是用CMetaFileDC
  另一種是利用兼容DC,重繪時利用 pDC->BitBlt(0,0,rect.Width(),rect.Height(),&m_dcCompatible,0,0,SRCCOPY);
將兼容DC的圖拷貝到屏幕DC上去。
此處不再詳細介紹這兩種方法,因爲介紹多了容易搞暈。呵呵
 
第12課 文件操作
1.常量指針與指針常量的區分
  char ch[5]="lisi";
  const char *pStr=ch;//const在*之前,表明指針指向的內容爲常量,即爲常量指針
  char * const pStr=ch;//const在*之後,表明指針的地址不能改變,即爲指針常量
  明白?
2.對文件讀寫的三種方法
  1.C中
    FILE *pFile=fopen("1.txt","w");
fwrite("http://www.sunxin.org",1,strlen("http://www.sunxin.org"),pFile);
//fseek(pFile,0,SEEK_SET);
//fwrite("ftp:",1,strlen("ftp:"),pFile);
//fwrite("http://www.sunxin.org",1,strlen("http://www.sunxin.org"),pFile);
fclose(pFile);*/
//fflush(pFile);
  2.C++中
/* ofstream ofs("4.txt");
ofs.write("http://www.sunxin.org",strlen("http://www.sunxin.org"));
ofs.close();*/
         要包括頭文件 "fstream.h"
  3.MFC中 用CFile類,哈哈!簡單好用
CFileDialog fileDlg(FALSE);
fileDlg.m_ofn.lpstrTitle="我的文件保存對話框";
fileDlg.m_ofn.lpstrFilter="Text Files(*.txt)/0*.txt/0All Files(*.*)/0*.*/0/0";
fileDlg.m_ofn.lpstrDefExt="txt";
if(IDOK==fileDlg.DoModal())
{
  CFile file(fileDlg.GetFileName(),CFile::modeCreate | CFile::modeWrite);
  file.Write("http://www.sunxin.org",strlen("http://www.sunxin.org"));
  file.Close();
}
  4.利用win32 API函數 CreateFile(),及WriteFile()
4.註冊表讀寫
  1.對win.ini的讀寫
//::WriteProfileString("http://www.sunxin.org","admin","zhangsan");
/* CString str;
::GetProfileString("http://www.sunxin.org","admin","lisi",
  str.GetBuffer(100),100);
AfxMessageBox(str);*/
  2.註冊表的讀寫
HKEY hKey;
DWORD dwAge=30;
RegCreateKey(HKEY_LOCAL_MACHINE,"Software//http://www.sunxin.org//admin",&hKey);
RegSetValue(hKey,NULL,REG_SZ,"zhangsan",strlen("zhangsan"));
RegSetValueEx(hKey,"age",0,REG_DWORD,(CONST BYTE*)&dwAge,4);
RegCloseKey(hKey);以上是寫入
代碼比較簡單,不再詳細介紹。本筆記也不是爲介紹函數而存在的。嘿嘿
 

第13課 文檔與串行化
1.CArchive在菜單打開保存時的代碼
CFile file("1.txt",CFile::modeCreate | CFile::modeWrite);
CArchive ar(&file,CArchive::store);
int i=4;
char ch='a';
float f=1.3f;
CString str("http://www.sunxin.org");
ar<<i<<ch<<f<<str;以上是保存,打開略
2.文檔-視類結構簡介
   OnNewDocument在程序啓動時被調用,此時可設置文檔標題,也可以在String Table的IDR_MAINFRAME的第二個"/"後改變文檔的標題。須瞭解的7個字符串的用途,見PPT。
   在WinAPP的InitInstance()中完成DOC,View,MainFrame的歸一。
   當點擊系統的打開和新建菜單時,有一系列的步驟,孫鑫老師給我們跟蹤了代碼的調用過程,此段跟蹤我們略過。但我們要牢記住:CWinAPP負責管理文檔管理器,文檔管理器有一個指針鏈表,且來保存文檔模板的指針,文檔模板指針管理三個類DOC,VIEW,MAINFRAME,使其爲某文件對象服務。
3.利用CArchive來保存一個類的對象,此類必須支持串行化,需要5個步驟。
  a.讓類從CObject派生;
  b.覆蓋Serialize()函數,在其中完成保存和讀取功能;
  c.在.h中加入 DECLARE_SERIAL(CGraph);
  d.在。cpp中加入IMPLEMENT_SERIAL(CGraph, CObject, 1 );
  e.定義一個不帶參數的構造函數。
保存繪畫數據到文件的簡單過程
  a.在CGraph中增加一個畫圖的成員函數,其實不增加也行。可以在View中完成相應功能。
  b.增加四個畫圖菜單,菜單可以從11課的代碼中拷貝。
  c.在View中增加LButtonDown和UP的響應,在UP中畫圖,在DOWN中保存點
  d.利用CObArray集合類來保存繪畫數據
  e.在CGraphicDOC::Serialize()中保存和讀取數據
  f.然後在OnDraw中重繪。
4.新建和打開文檔時,要注意銷燬原來的數據。在DOC的DeleteContents虛函數中是好時機。代碼如下
int nCount;
nCount=m_obArray.GetSize();
/*for(int i=0;i<nCount;i++)
{
  delete m_obArray.GetAt(i);//釋放指針指向的內存空間
  //m_obArray.RemoveAt(i);//移除鏈表中的元素。嘿嘿,別搞錯了。但在此處不能這樣用,會導致非法操作。要用下面的方法沙
}
m_obArray.RemoveAll();*/
while(nCount--)
{
  delete m_obArray.GetAt(nCount);
  m_obArray.RemoveAt(nCount);
}
 

第14課 網絡編程
1.TCP流式套接字的編程步驟
在使用之前須鏈接庫函數:工程->設置->Link->輸入ws2_32.lib,OK!
服務器端程序:
1、加載套接字庫
2、創建套接字(socket)。
3、將套接字綁定到一個本地地址和端口上(bind)。
4、將套接字設爲監聽模式,準備接收客戶請求(listen)。
5、等待客戶請求到來;當請求到來後,接受連接請求,返回一個新的對應於此次連接的套接字(accept)。
6、用返回的套接字和客戶端進行通信(send/recv)。
7、返回,等待另一客戶請求。
8、關閉套接字。
客戶端程序:
1、加載套接字庫
2、創建套接字(socket)。
3、向服務器發出連接請求(connect)。
4、和服務器端進行通信(send/recv)。
5、關閉套接字。
服務器端代碼如下:
#i nclude <Winsock2.h>//加裁頭文件
#i nclude <stdio.h>//加載標準輸入輸出頭文件
void main()
{
WORD wVersionRequested;//
版本號
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );//1.1版本的套接字
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
  return;
}//
加載套接字庫,加裁失敗則返回

if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
  WSACleanup( );
  return;
}//
如果不是1.1的則退出
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);//創建套接字(socket)。
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//
轉換Unsigned short爲網絡字節序的格式
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
//
將套接字綁定到一個本地地址和端口上(bind)
listen(sockSrv,5);//將套接字設爲監聽模式,準備接收客戶請求(listen)。
SOCKADDR_IN addrClient;//定義地址族
int len=sizeof(SOCKADDR);//初始化這個參數,這個參數必須被初始化
while(1)
{
  SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);accept
的第三個參數一定要有初始值。
//等待客戶請求到來;當請求到來後,接受連接請求,返回一個新的對應於此次連接的套接字(accept)。
//此時程序在此發生阻塞
  char sendBuf[100];
  sprintf(sendBuf,"Welcome %s to http://www.sunxin.org",
   inet_ntoa(addrClient.sin_addr));
//用返回的套接字和客戶端進行通信(send/recv)。
  send(sockConn,sendBuf,strlen(sendBuf)+1,0);
  char recvBuf[100];
  recv(sockConn,recvBuf,100,0);
  printf("%s/n",recvBuf);
  closesocket(sockConn);//關閉套接字。等待另一個用戶請求
}
}
客戶端代碼如下:
#i nclude <Winsock2.h>
#i nclude <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );加載套接字庫
if ( err != 0 ) {
  return;
}

if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
  WSACleanup( );
  return;
}
SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);
創建套接字(socket)。
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
向服務器發出連接請求(connect)。
char recvBuf[100];和服務器端進行通信(send/recv)。
recv(sockClient,recvBuf,100,0);
printf("%s/n",recvBuf);
send(sockClient,"This is lisi",strlen("This is lisi")+1,0);
closesocket(sockClient);關閉套接字。
WSACleanup();//必須調用這個函數清除參數
}
2.UDP型套接字。
服務器端(接收端)程序:
1、創建套接字(socket)。
2、將套接字綁定到一個本地地址和端口上(bind)。
3、等待接收數據(recvfrom)。
4、關閉套接字。
客戶端(發送端)程序:
1、創建套接字(socket)。
2、向服務器發送數據(sendto)。
3、關閉套接字。
服務器端代碼:
#i nclude <Winsock2.h>
#i nclude <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
  return;
}

if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
  WSACleanup( );
  return;
}
SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
char recvBuf[100];
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
printf("%s/n",recvBuf);
closesocket(sockSrv);
WSACleanup();
}
客戶端代碼:
#i nclude <Winsock2.h>
#i nclude <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
  return;
}

if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
  WSACleanup( );
  return;
}
SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
sendto(sockClient,"Hello",strlen("Hello")+1,0,
  (SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
closesocket(sockClient);
WSACleanup();
}
UDP
的不再加註釋了。因爲它比TCP的簡單多了。
3.基於字符界面的聊天程序,用的是UDP式套接字。代碼略。
4.如何添加新的工程?
首先選擇中Build工具欄,然後在工程管理器上點擊右鍵,選擇增加新的工程即可。
 

第15課多線程與網絡編程
1.多線程介紹,略
2.一個簡單的多線程程序
MSND中參數[in]和[out]的含義要注意
#i nclude <windows.h>
#i nclude <iostream.h>
DWORD WINAPI Fun1Proc(
  LPVOID lpParameter   // thread data
);
DWORD WINAPI Fun2Proc(
  LPVOID lpParameter   // thread data
);
int index=0;
int tickets=100;
HANDLE hMutex;
互斥對象的句柄
void main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);創建線程1
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);創建線程2
CloseHandle(hThread1);關閉線程的句柄,爲什麼要關閉?它將線程的使用計數減1
CloseHandle(hThread2);這樣當線程結束時,線程內核對象被釋放,否則只有當進程結束,才釋放線程的內核對象
/*while(index++<1000)
  cout<<"main thread is running"<<endl;*/
//hMutex=CreateMutex(NULL,TRUE,NULL);將第二個參數設爲true後,互斥對象的計數加1
hMutex=CreateMutex(NULL,TRUE,"tickets");此段代碼可以讓系統只一份實例在運行!
if(hMutex)
{
  if(ERROR_ALREADY_EXISTS==GetLastError())
  {
   cout<<"only instance can run!"<<endl;
   return;
  }
}
WaitForSingleObject(hMutex,INFINITE);此代碼也將互斥對象的計數加1
ReleaseMutex(hMutex);所以要釋放兩次互斥對象
ReleaseMutex(hMutex);
Sleep(4000);睡眠4000毫秒
// Sleep(10);
}
DWORD WINAPI Fun1Proc(
  LPVOID lpParameter   // thread data
)
{
/*while(index++<1000)
  cout<<"thread1 is running"<<endl;*/
/*while(TRUE)
{
  //ReleaseMutex(hMutex);
  WaitForSingleObject(hMutex,INFINITE);
等待互斥對象的到來,到來後將互斥對象的計數加1
  if(tickets>0)
  {
   Sleep(1);
   cout<<"thread1 sell ticket : "<<tickets--<<endl;
  }
  else
   break;
  ReleaseMutex(hMutex);釋放互斥對象,將其計數減1,這樣可以保證,這兩句話之間的代碼!的執行連續性!
}*/
WaitForSingleObject(hMutex,INFINITE);
cout<<"thread1 is running"<<endl;
return 0;
}
DWORD WINAPI Fun2Proc(
  LPVOID lpParameter   // thread data
)
{
/*while(TRUE)
{
  //ReleaseMutex(hMutex);
  WaitForSingleObject(hMutex,INFINITE);
  if(tickets>0)
  {
   Sleep(1);
   cout<<"thread2 sell ticket : "<<tickets--<<endl;
  }
  else
   break;
  ReleaseMutex(hMutex);
}*/
WaitForSingleObject(hMutex,INFINITE);
cout<<"thread2 is running"<<endl;
return 0;
}
3.多線程聊天程序
  1.加載套接字庫在InitInstance()中,調用AfxSocketInit(),此時可以不加載庫文件,但要加入Afxsock.h"頭文件
  2.在CChatDlg中創建成員變量m_socket,然後增加一個成員函數,IniSocket(),在其中完成m_socket的初始化和綁定。在OnInitDialog中調用InitSocket完成初始化工作。
  3.定義一個結構體,包含兩個參數,sock和hwnd,在OnInitDialog()中初始化這個結構體的對象。
  4.創建一個線程,CreateThread(),須將線程函數RecvProc定義爲靜態的或者全局函數。
    ::PostMessage()完成將收到的數據發送給對話框。用自定義的消息,自定義的消息如何寫?以前說過,參考下面的代碼。注意要將EDitBox的MultiLine屬性選上。 
    在ChatDlg.h中#define WM_RECVDATA  WM_USER+1
afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);
    在ChatDlg.cpp中
ON_MESSAGE(WM_RECVDATA,OnRecvData)
然後實現這個函數
void CChatDlg::OnRecvData(WPARAM wParam,LPARAM lParam)
{
CString str=(char*)lParam;
CString strTemp;
GetDlgItemText(IDC_EDIT_RECV,strTemp);
str+="/r/n";
str+=strTemp;
SetDlgItemText(IDC_EDIT_RECV,str);
}
     最後在DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
中調用 ::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)tempBuf);
//不能用SendMessage()
    4.對發送按紐的響應代碼:
void CChatDlg::OnBtnSend()
{
// TOD Add your control notification handler code here
DWORD dwIP;
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
SOCKADDR_IN addrTo;
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(6000);
addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
CString strSend;
GetDlgItemText(IDC_EDIT_SEND,strSend);
sendto(m_socket,strSend,strSend.GetLength()+1,0,
  (SOCKADDR*)&addrTo,sizeof(SOCKADDR));
SetDlgItemText(IDC_EDIT_SEND,"");
}
 

第16課
1.事件對象:來實現線程的同步。與互斥對象一樣均屬於內核對象。
            當人工重置有信號時,所有線程均得到信號,所以不能設爲人工重置。代碼就不貼了。
            通過創建匿名的事件對象,也可以讓一個程序只能運行一個實例。 
2.關鍵代碼段實現線程的同步:類似公用電話亭,只有當電話亭裏面沒人了,其它人才可以再進去打電話。用了4個函數,這種方法比較簡單!但缺點是如果使用了多少關鍵代碼碼,容易贊成線程的死鎖
3.線程死鎖,用關鍵代碼示例,用了兩個臨界區對象,實戰中要注意避免這種錯誤!
4.使用異步套接字編寫網絡聊天室
  1)加載套接字庫,進行版本協商,包含頭文件,鏈接庫文件,這次請示的是2.2版本!
  2)在類CChatDlg中增加一個成員變量m_socket,在析構函數中釋放這個變量
  3)利用WSASocket()創建套接字(數據報類型的UDP型的)
  4)然後調用WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ)爲網絡事件定義消息!此時如果發生FD_READ消息,系統會發送UM_SOCK消息給應用程序!程序並不會阻塞在這兒了!
  以上是在BOOL CChatDlg::OnInitDialog()完成
  5)然後完成消息響應!
  頭文件中:#define UM_SOCK  WM_USER+1
afx_msg void OnSock(WPARAM,LPARAM);
   源文件中:
    ON_MESSAGE(UM_SOCK,OnSock)
    實現消息響應函數:void CChatDlg::OnSock(WPARAM wParam,LPARAM lParam)
{
switch(LOWORD(lParam))
{
case FD_READ:
  WSABUF wsabuf;
  wsabuf.buf=new char[200];
  wsabuf.len=200;
  DWORD dwRead;
  DWORD dwFlag=0;
  SOCKADDR_IN addrFrom;
  int len=sizeof(SOCKADDR);
  CString str;
  CString strTemp;
  HOSTENT *pHost;
  if(SOCKET_ERROR==WSARecvFrom(m_socket,&wsabuf,1,&dwRead,&dwFlag,
      (SOCKADDR*)&addrFrom,&len,NULL,NULL))
  {
   MessageBox("接收數據失敗!");
   return;
  }
  pHost=gethostbyaddr((char*)&addrFrom.sin_addr.S_un.S_addr,4,AF_INET);
  //str.Format("%s說 :%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);
  str.Format("%s說 :%s",pHost->h_name,wsabuf.buf);
  str+="/r/n";
  GetDlgItemText(IDC_EDIT_RECV,strTemp);
  str+=strTemp;
  SetDlgItemText(IDC_EDIT_RECV,str);
  break;
}
}
OK!
      6)完成數據發送的功能!
      void CChatDlg::OnBtnSend()
{
// TOD Add your control notification handler code here
DWORD dwIP;
CString strSend;
WSABUF wsabuf;
DWORD dwSend;
int len;
CString strHostName;
SOCKADDR_IN addrTo;
HOSTENT* pHost;
if(GetDlgItemText(IDC_EDIT_HOSTNAME,strHostName),strHostName=="")
{
  ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
  addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
}
else
{
  pHost=gethostbyname(strHostName);
  addrTo.sin_addr.S_un.S_addr=*((DWORD*)pHost->h_addr_list[0]);
}
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(6000);
GetDlgItemText(IDC_EDIT_SEND,strSend);
len=strSend.GetLength();
wsabuf.buf=strSend.GetBuffer(len);
wsabuf.len=len+1;
SetDlgItemText(IDC_EDIT_SEND,"");
if(SOCKET_ERROR==WSASendTo(m_socket,&wsabuf,1,&dwSend,0,
   (SOCKADDR*)&addrTo,sizeof(SOCKADDR),NULL,NULL))
{
  MessageBox("
發送數據失敗!");
  return;
}
}
      7)完成將主機名轉換爲IP地址的功能,以前將IP地址轉換爲主機名的功能
嘿嘿,單線程的聊天室創建完畢!性能並且非常出色!
 

第17課 進程間通信
有四種方法
1.剪貼板
  a.創建個ClipBoard的對話框應用程序,加兩EditBox和兩個Button發送接收。
  b.具體代碼:
    發送端代碼:
if(OpenClipboard())
{
  CString str;
  HANDLE hClip;
  char *pBuf;
  EmptyClipboard();
  GetDlgItemText(IDC_EDIT_SEND,str);
  hClip=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1);
  pBuf=(char*)GlobalLock(hClip);將句柄轉換爲指針!
  strcpy(pBuf,str);
  GlobalUnlock(hClip);
  SetClipboardData(CF_TEXT,hClip);
  CloseClipboard();
}
     接收端代碼:
if(OpenClipboard())
{
  if(IsClipboardFormatAvailable(CF_TEXT))
  {
   HANDLE hClip;
   char *pBuf;
   hClip=GetClipboardData(CF_TEXT);
   pBuf=(char*)GlobalLock(hClip);
   GlobalUnlock(hClip);
   SetDlgItemText(IDC_EDIT_RECV,pBuf);
   CloseClipboard();
  }
}
2.匿名管道:只能在父子進程之間進行通信
  a.先建一個Parent的單文檔應用程序,增加“創建管道”“讀取數據”“寫入數據”三個菜單
  b.增加成員變量HANDLE類型的hRead,hWrite,初始化變量,並在析構函數中釋放句柄
  c.響應菜單代碼:
void CParentView::OnPipeCreate() 菜單“創建管道”代碼
{
// TOD Add your command handler code here
SECURITY_ATTRIBUTES sa;
sa.bInheritHandle=TRUE;
sa.lpSecurityDescriptor=NULL;
sa.nLength=sizeof(SECURITY_ATTRIBUTES);
if(!CreatePipe(&hRead,&hWrite,&sa,0))
{
  MessageBox("創建匿名管道失敗!");
  return;
}
STARTUPINFO sui;
PROCESS_INFORMATION pi;
ZeroMemory(&sui,sizeof(STARTUPINFO));將數據清0!
sui.cb=sizeof(STARTUPINFO);
sui.dwFlags=STARTF_USESTDHANDLES;
sui.hStdInput=hRead;
sui.hStdOutput=hWrite;
sui.hStdError=GetStdHandle(STD_ERROR_HANDLE);
if(!CreateProcess("..//Child//Debug//Child.exe",NULL,NULL,NULL,
   TRUE,0,NULL,NULL,&sui,&pi))
創建子進程
{
  CloseHandle(hRead);
  CloseHandle(hWrite);關閉句柄,將內核對象的使用計數減少1,這樣當操作系統發現內核對象的使用計數爲0時,將清除內核對象。
  hRead=NULL;
  hWrite=NULL;
  MessageBox("創建子進程失敗!");
  return;
}
else
{
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
}
}
 
void CParentView::OnPipeRead() 菜單“讀取數據”代碼
{
// TOD Add your command handler code here
char buf[100];
DWORD dwRead;
if(!ReadFile(hRead,buf,100,&dwRead,NULL))
{
  MessageBox("讀取數據失敗!");
  return;
}
MessageBox(buf);
}
void CParentView::OnPipeWrite() 菜單“寫入數據”代碼
{
// TOD Add your command handler code here
char buf[]="http://www.sunxin.org";
DWORD dwWrite;
if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))
{
  MessageBox("寫入數據失敗!");
  return;
}
}
     d.再建一個Child的單文檔,在View中增加兩個成員hRead和hWrite.在OnInitialUpdate()中得到句柄的值。
void CChildView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TOD Add your specialized code here and/or call the base class
hRead=GetStdHandle(STD_INPUT_HANDLE);
注意這句代碼!
hWrite=GetStdHandle(STD_OUTPUT_HANDLE);
}
     e.加菜單“讀取數據”“寫入數據”其代碼如下:
void CChildView::OnPipeRead()
{
// TOD Add your command handler code here
char buf[100];
DWORD dwRead;
if(!ReadFile(hRead,buf,100,&dwRead,NULL))
{
  MessageBox("讀取數據失敗!");
  return;
}
MessageBox(buf);
}
void CChildView::OnPipeWrite()
{
// TOD Add your command handler code here
char buf[]="
匿名管道測試程序";
DWORD dwWrite;
if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))
{
  MessageBox("寫入數據失敗!");
  return;
}
}
3.命名管道:還可以跨網絡通信,服務器只能在win2000和NT下運行!而客戶端可以在95下運行。關鍵函數CreateNamedPipe
  a.先建一個NamedPipeSRV單文檔應用程序,加菜單“創建管道”“讀取數據”“寫入數據”
  b.在View中增加Handle變量hPipe,注意在析構函數中釋放它!
  c.響應菜單,創建命名管道
void CNamedPipeSrvView::OnPipeCreate()
{
// TOD Add your command handler code here
hPipe=CreateNamedPipe("////.//pipe//MyPipe",
  PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
  0,1,1024,1024,0,NULL);
if(INVALID_HANDLE_VALUE==hPipe)
{
  MessageBox("創建命名管道失敗!");
  hPipe=NULL;
  return;
}
HANDLE hEvent;
hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
if(!hEvent)
{
  MessageBox("創建事件對象失敗!");
  CloseHandle(hPipe);
  hPipe=NULL;
  return;
}
OVERLAPPED ovlap;
ZeroMemory(&ovlap,sizeof(OVERLAPPED));
ovlap.hEvent=hEvent;
if(!ConnectNamedPipe(hPipe,&ovlap))
{
  if(ERROR_IO_PENDING!=GetLastError())
  {
   MessageBox("等待客戶端連接失敗!");
   CloseHandle(hPipe);
   CloseHandle(hEvent);
   hPipe=NULL;
   return;
  }
}
if(WAIT_FAILED==WaitForSingleObject(hEvent,INFINITE))
{
  MessageBox("等待對象失敗!");
  CloseHandle(hPipe);
  CloseHandle(hEvent);
  hPipe=NULL;
  return;
}
CloseHandle(hEvent);
}
void CNamedPipeSrvView::OnPipeRead()
{
// TOD Add your command handler code here
char buf[100];
DWORD dwRead;
if(!ReadFile(hPipe,buf,100,&dwRead,NULL))
{
  MessageBox("
讀取數據失敗!");
  return;
}
MessageBox(buf);
}
void CNamedPipeSrvView::OnPipeWrite()
{
// TOD Add your command handler code here
char buf[]="http://www.sunxin.org";
DWORD dwWrite;
if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
  MessageBox("
寫入數據失敗!");
  return;
}
}
      d.再建一個NamedPipeCLT單文檔工程,加菜單“連接管道”“讀取數據”“寫入數據”,當然別忘記成員變量hPipe的定義和初始化
      e.響應菜單代碼
void CNamedPipeCltView::OnPipeConnect() 連接管道
{
// TOD Add your command handler code here
if(!WaitNamedPipe("////.//pipe//MyPipe",NMPWAIT_WAIT_FOREVER))
{
  MessageBox("當前沒有可利用的命名管道實例!");
  return;
}
hPipe=CreateFile("////.//pipe//MyPipe",GENERIC_READ | GENERIC_WRITE,
  0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(INVALID_HANDLE_VALUE==hPipe)
{
  MessageBox("打開命名管道失敗!");
  hPipe=NULL;
  return;
}
}
void CNamedPipeCltView::OnPipeRead() 讀取數據
{
// TOD Add your command handler code here
char buf[100];
DWORD dwRead;
if(!ReadFile(hPipe,buf,100,&dwRead,NULL))
{
  MessageBox("讀取數據失敗!");
  return;
}
MessageBox(buf);
}
void CNamedPipeCltView::OnPipeWrite() 寫入數據
{
// TOD Add your command handler code here
char buf[]="命名管道測試程序";
DWORD dwWrite;
if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
  MessageBox("寫入數據失敗!");
  return;
}
}
 
老孫的教學筆記續(轉)     -|落魂 發表於 2006-4-25 18:28:00
 
4.郵槽,使用時應將消息長度限制在424字節以下,關鍵函數CreateMailSlot()
  a.先建一個MailSlotSRV工程,加菜單“接收數據”
  b.消息響應代碼:
void CMailslotSrvView::OnMailslotRecv() 菜單“接收數據”的代碼
{
// TOD Add your command handler code here
HANDLE hMailslot;
hMailslot=CreateMailslot("////.//mailslot//MyMailslot",0,
  MAILSLOT_WAIT_FOREVER,NULL);
if(INVALID_HANDLE_VALUE==hMailslot)
{
  MessageBox("創建油槽失敗!");
  return;
}
char buf[100];
DWORD dwRead;
if(!ReadFile(hMailslot,buf,100,&dwRead,NULL))
{
  MessageBox("讀取數據失敗!");
  CloseHandle(hMailslot);
  return;
}
MessageBox(buf);
CloseHandle(hMailslot);
}
    c.加工程MailSlotCLT,加菜單“發送數據”
    d.加消息響應,添加代碼,客戶端也比較簡單。
void CMailslotCltView::OnMailslotSend() 菜單“發送數據”的代碼
{
// TOD Add your command handler code here
HANDLE hMailslot;
hMailslot=CreateFile("////.//mailslot//MyMailslot",GENERIC_WRITE,
  FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(INVALID_HANDLE_VALUE==hMailslot)
{
  MessageBox("打開油槽失敗!");
  return;
}
char buf[]="http://www.sunxin.org";
DWORD dwWrite;
if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,NULL))
{
  MessageBox("寫入數據失敗!");
  CloseHandle(hMailslot);
  return;
}
CloseHandle(hMailslot);
}
5.以上4種方法各有優缺點:剪貼板比較簡單。郵槽是基於廣播的,可以一對多發送。但只能一個發送,一個接收,要想同時發送接收,須寫兩次代碼。
命名管道和郵槽可以進行網絡通信。
 
第18課 ActiveX編程(下面X均爲ActiveX簡稱)
1.在VB中調用X控件,添加方法 project->Add components。另外可以用Object Browser來查看控件
2.在VC中創建X控件
  1.新建一個X工程名爲Clock,注意一個文件中可以包含多個控件。
  2.保持缺省設置,完成。注意它生成的三個類,以及相關的接口。
  3.運行它。選擇TSTCON32.exe作爲容器。
  4.選擇Insert Control,此時我們可以看到,它畫了一個橢圓。也可以在VB中測試。
  5.刪除註冊信息。用regsvr32 /u +文件名。也可以在菜單選擇反註冊命令。
  6.重寫代碼。在CClockCtrl::OnDraw()中畫了一個橢圓,此時我們在其中得到系統時間,並顯示它。爲此我們在OnCreate()設置了一個定時器,每隔一定時間發出一個Invalidate()消息,使窗口重繪。
  7.如何改變控件的背景色和前景色?ClassWizard->AutoMation->Add Property->BackColor,還需要在OnDraw()中加上相應的代碼
   CBrush brush(TranslateColor(GetBackColor()));
pdc->FillRect(rcBounds, &brush);
pdc->SetBkMode(TRANSPARENT);
pdc->SetTextColor(TranslateColor(GetForeColor()));
  8.增加屬性頁。在
  BEGIN_PROPPAGEIDS(CClockCtrl, 2)此時數目也得改成相應的數目
PROPPAGEID(CClockPropPage::guid)
PROPPAGEID(CLSID_CColorPropPage)
  END_PROPPAGEIDS(CClockCtrl)  OK~
  9.增加自定義屬性:ClassWizard->AutoMation->Add Property加上一個變量m_interval,類型爲short,對應外部變量爲Interval。在CClockCtrl中增加OnIntervalChanged方法。添加如下代碼:
   if(m_interval<0 || m_interval>6000)
{
  m_interval=1000;
}
else
{
  m_interval=m_interval/1000*1000;
  KillTimer(1);
  SetTimer(1,m_interval,NULL);
  BoundPropertyChanged(0x1);
}
   10.測試:Control->Invoke Methods
   11.將時間間隔加到屬性頁中,在資源視圖中加入一文本框和編輯框。爲EditBox關聯成員變量,加入屬性interval。
   12.增加方法:ClassWizard->AutoMation->Add Method->Hello加入代碼 OK!在VB中可以調用此方法!
   void CClockCtrl::Hello()
{
// TOD Add your dispatch handler code here
MessageBox("Hello world!");
}
   13.增加事件:ClassWizard->AutoMation->Add Events->Click
   14.增加自定義事件:ClassWizard->AutoMation->Add Events->NewMinute
      在新的一分鐘到達時發出這個通知,在OnDraw()中寫代碼:
       CTime time=CTime::GetCurrentTime();
if(0==time.GetSecond())
{
  FireNewMinute();
}
   15.讓Interval屬性具有持久性。在CClockCtrl::DoPropExchange()中調用PX_short()方法,OK!
    PX_Short(pPX,"Interval",m_interval,1000);
   16.讓Property Page和Property屬性中的interval保持一致的方法:在OnIntervalChanged()中調用BoundPropertyChanged(0x1);
   17.希望控件在設計時間內不走動的方法:在OnTimer()中, if(AmbientUserMode())InvalidateControl();巧妙!
3.在VC中調用X控件
  1.新建ClockTest對話框應用程序
  2.點擊右鍵->插入X控件->時鐘控件
  3.Project->Add Component會生成CClock類。
  4.在CCLockTestDlg中增加CClock類的成員變量m_clock,然後可以動態創建一個這樣的東東!
  5.試驗Click(),NewMinute(),SetBkColor(),SetForeColor()方法和屬性
  6.如何爲動態創建的控件做事件響應呢?首先你得知道它的ID號,然後參考非動態的控件事件代碼,呵。
 

第19課 DLL編程
1.DLL簡介,動態庫,靜態庫。動態庫節約磁盤空間,靜態庫體積大。可以用多種語言編寫DLL文件。動態庫有兩種加載方式:隱式調用和動態加裁!
2.新建一個DLL1的dll工程,加入一源文件名爲dll1.cpp,加入add和subtract兩個函數,注意此時須在函數名前加_declspec(dllexport),並且編譯。用dumpbi -exports dll1.dll查看其導出的函數,發現函數名字已經被改成了 ?add@@YAHHH@Z,這種現象叫做名字粉碎,是爲了支持函數重載而做的。
3.編寫一個程序測試DLL,工程名爲DllTest,基於對話框的,放置兩個按紐add和subtract,響應按紐消息,調用這個Dll的add和subtract函數。使用這兩個函數前要先聲明函數,//extern int add(int a,int b);
//extern int subtract(int a,int b);
還需要將Dll1.lib和Dll1.dll拷貝到當前目錄下!另外還需要在Project->Setting->Link->Object/Library中加入Dll1.lib,此種方式爲隱式調用!OK!用Dumpbin -imports DllTest.exe查看它的輸入信息,可以看到它加載了dll1.dll。同時也可以用depends程序查看程序需要哪些dll文件!除了用extern外,還可以用//_declspec(dllimport) int add(int a,int b);
//_declspec(dllimport) int subtract(int a,int b);
告訴編譯器,此函數是動態鏈接庫中的函數,這樣可以提高效率。
4.通常寫Dll時在dll1.h中聲明函數,然後在DllTest.h中包含這個頭文件,另外會用一組宏來取代_declspec(dllimport)
Dll1.h
#ifdef DLL1_API
#else
#define DLL1_API extern "C" _declspec(dllimport)
#endif
DLL1_API int _stdcall add(int a,int b);
DLL1_API int _stdcall subtract(int a,int b);
Dll1.cpp
的代碼:
#define DLL1_API extern "C" _declspec(dllexport)
#i nclude "Dll1.h"
#i nclude <Windows.h>
#i nclude <stdio.h>
int _stdcall add(int a,int b)
{
return a+b;
}
int _stdcall subtract(int a,int b)
{
return a-b;
}
5.
在Dll1中加入類Point它有一個函數output(int a,intb),它的功能是在屏幕上輸出x,y值。須包含頭文件windows.h和stdio.h.然後在DllTest中加入一個按紐來測試這個函數!此時我們可以dumpbin來查看dll1.dll和dllTest.exe的導出導入情況。注意,也可以只導出類的某個函數。
6.我們希望導出的函數名不被改變,加extern "C"大寫的C!即可,#define DLL1_API extern "C" _declspec(dllexport),但它只能導出全局函數,不能導出類的成員函數,並且如果調用約定被改成了別的方式,此時函數名也被改變。所以這種方式不太好。
7.解決之道是用模塊定義文件。
  1.新建dll2.dll工程;
  2.加dll2.cpp中寫兩個函數add和subtract
  3.在目錄中新建dll2.def文件,增加到工程。
  4.在dll2.def中加入如下代碼:
LIBRARY Dll2
EXPORTS
add
subtract
   5.
編譯後用dumpbin查看函數名是否被改變?
   6.測試,我們這次用動態加載的方法來調用dll文件。以前是用隱式鏈接的方法,嘿嘿。動態加載的好處是需要時再加載,可以提高執行的效率。代碼如下:
HINSTANCE hInst;
hInst=LoadLibrary("Dll3.dll");
typedef int (/*_stdcall*/ *ADDPROC)(int a,int b);
//ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"?add@@YAHHH@Z");
ADDPROC Add=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
if(!Add)
{
  MessageBox("獲取函數地址失敗!");
  return;
}
CString str;
str.Format("5+3=%d",Add(5,3));
MessageBox(str);
FreeLibrary(hInst);
    7.此時你改變調用約定,函數名不會被改變,但如果你加上_stdcall定義函數,調用時也需要加入_stdcall,否則會出錯!
8.DllMain()是Dll的入口點,不過不是必須的。但在DllMain中不要做複雜的調用。爲什麼?因爲DllMain加載時,某些核心Dll文件不一定已經被加載。
9.創建一個基於MFC的DLL工程,簡介。
10.當不使用DLL時,調用FreeLibrary減少DLL的使用計數,釋放DLL資源,減少系統負擔。明白?
11.上面總結:1.*.def使函數名不改變;
             2.定義時爲_stdcall,調用時也必須用_stdcall.
 
 
第20課 鉤子與數據庫編程
1.Hook簡介:作用是攔截某些消息,關鍵函數是SetWindowsHookEX()
2.示例程序:
  1.新建一基於對話框工程,InnerHook,此過程的鉤子是隻攔截本進程的。
  2.在OnInitDialog()中添加代碼:
   g_hWnd=m_hWnd;
g_hMouse=SetWindowsHookEx(WH_MOUSE,MouseProc,NULL,GetCurrentThreadId());設置了鼠標鉤子
g_hKeyboard=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,NULL,GetCurrentThreadId());設置了鍵盤鉤子
  3.完成鉤子函數的編寫:
HHOOK g_hKeyboard=NULL;
HHOOK g_hMouse;
HWND g_hWnd=NULL;
LRESULT CALLBACK MouseProc(
   int nCode,      // hook code
   WPARAM wParam,  // message identifier
   LPARAM lParam   // mouse coordinates
)
{
  return 1;
}
LRESULT CALLBACK KeyboardProc(
   int code,       // hook code
   WPARAM wParam,  // virtual-key code
   LPARAM lParam   // keystroke-message information
)
{
  //if(VK_SPACE==wParam || VK_RETURN==wParam)
如果是空格鍵
  /*if(VK_F4==wParam && (1==(lParam>>29 & 1)))攔截ALT+F4按鍵!
   return 1;
  else
   return CallNextHookEx(g_hKeyboard,code,wParam,lParam);*/
  if(VK_F2==wParam)按F2時程序可以退出,這是留的後門。否則程序無法關閉,只能用任務管理器來關閉它了。
  {
   ::SendMessage(g_hWnd,WM_CLOSE,0,0);
   UnhookWindowsHookEx(g_hKeyboard);當程序退出時最好將鉤子移除。
   UnhookWindowsHookEx(g_hMouse);
  }
  return 1;
}
3.編寫一個屏屏蔽所有進程和所有線程的鉤子程序。此時這個鉤子必須安裝在DLL中,然後被某個程序調用纔行。
  1.新建一個DLL工程名爲Hook
  2.增加Hook.cpp
  3.代碼如下:
#i nclude <windows.h>包含頭文件
HHOOK g_hMouse=NULL;
HHOOK g_hKeyboard=NULL;
#pragma data_seg("MySec")新建了一個節,用於將下 面的這個變量設爲全局共享。
HWND g_hWnd=NULL;這個變量是全局共享的。
#pragma data_seg()
//#pragma comment(linker,"/section:MySec,RWS")
/*HINSTANCE g_hInst;
BOOL WINAPI DllMain(
   HINSTANCE hinstDLL,  // handle to the DLL module
   DWORD fdwReason,     // reason for calling function
   LPVOID lpvReserved   // reserved
)
{
  g_hInst=hinstDLL;
}*/
LRESULT CALLBACK MouseProc(
   int nCode,      // hook code
   WPARAM wParam,  // message identifier
   LPARAM lParam   // mouse coordinates
)
{
  return 1;
攔截了鼠標消息。
}
LRESULT CALLBACK KeyboardProc(
   int code,       // hook code
   WPARAM wParam,  // virtual-key code
   LPARAM lParam   // keystroke-message information
)
{
  if(VK_F2==wParam)
如果是F2鍵,則退出。
  {
   SendMessage(g_hWnd,WM_CLOSE,0,0);
   UnhookWindowsHookEx(g_hMouse);當退出時將鉤子卸掉。
   UnhookWindowsHookEx(g_hKeyboard);
  }
  return 1;
}
void SetHook(HWND hwnd)此函數設置了鉤子。
{
  g_hWnd=hwnd;注意這種傳遞調用它的進程的句柄的方法,比較巧妙!
  g_hMouse=SetWindowsHookEx(WH_MOUSE,MouseProc,GetModuleHandle("Hook"),0);
  g_hKeyboard=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,GetModuleHandle("Hook"),0);
}
Hook.DEF的代碼如下:
LIBRARY Hook
EXPORTS
SetHook  @2
SEGMENTS
MySec READ WRITE SHARED  也可以設置節的屬性。
    4.新建一個工程調用此鉤子函數。工程名爲HookTest,基於對話框的。在OnInitDialog()中調用SetHook(),要事先聲明_declspec(dllimport) void SetHook(HWND hwnd);
      然後在Project->Setting->Link->加入../Hook/Debug/Hook.lib,並將Hook.Dll拷貝到當前目錄。
int cxScreen,cyScreen;
cxScreen=GetSystemMetrics(SM_CXSCREEN);
cyScreen=GetSystemMetrics(SM_CYSCREEN);
SetWindowPos(&wndTopMost,0,0,cxScreen,cyScreen,SWP_SHOWWINDOW);將窗口保持在最前面。
SetHook(m_hWnd);
    5.DLL的調試方法,設置斷點,然後運行時斷點時,step into即可。
4.數據庫編程
  1.ODBC,ADO簡介:ADO可以認爲是建立在ODBC上的。
   ADO的三個核心對象
Connection對象
   Connection對象表示了到數據庫的連接,它管理應用程序和數據庫之間的通信。 Recordset和Command對象都有一個ActiveConnection屬性,該屬性用來引用Connection對象。
Command對象
   Command對象被用來處理重複執行的查詢,或處理需要檢查在存儲過程調用中的輸出或返回參數的值的查詢。
Recordset對象
   Recordset對象被用來獲取數據。 Recordset對象存放查詢的結果,這些結果由數據的行(稱爲記錄)和列(稱爲字段)組成。每一列都存放在Recordset的Fields集合中的一個Field對象中。
  2.演示在VB中使用ADO的方法,方法比較簡單,使用方便。另外在VB中演示了Connection和Command和Recordset的方法,用這三種方法都可以執行SQL語句。
  3.在VC中利用ADO訪問數據庫。
    1.新建一個基於對話框的工程,名爲ADO。
    2.在對話框中放一ListBox和一個Button控件。
    3.在使用時須導入MSADO15.dll,方法是在StdAfx.h中#import "D:/Program Files/Common Files/System/ado/msado15.dll" no_namespace rename("EOF","rsEOF")
    至少於將EOF改名爲rsEOF,是爲了避免與文件中的EOF重名。然後編譯程序,將產生的debug目錄下的兩個文件MSADO15.tlh和MSADO15.tli加到工程中,其目的只是方便我們查看而已。並不是編譯需要它。
    ADO也是COM組件,須初始化COM庫方法是CoInitialize(NULL);使用完後須CoUninitialize();
    代碼如下:
    void CAdoDlg::OnBtnQuery()
{
// TOD Add your control notification handler code here
CoInitialize(NULL);初始化
_ConnectionPtr pConn(__uuidof(Connection));產生connection智能指針
_RecordsetPtr pRst(__uuidof(Recordset));產生recordset智能指針
_CommandPtr pCmd(__uuidof(Command));產生command智能指針
pConn->ConnectionString="Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial Catalog=pubs";數據庫信息
pConn->Open("","","",adConnectUnspecified);打開數據庫
//pRst=pConn->Execute("select * from authors",NULL,adCmdText);用記錄集查詢數據
//pRst->Open("select * from authors",_variant_t((IDispatch*)pConn),
// adOpenDynamic,adLockOptimistic,adCmdText);
pCmd->put_ActiveConnection(_variant_t((IDispatch*)pConn));
pCmd->CommandText="select * from authors";用這種方法也可以查詢數據
pRst=pCmd->Execute(NULL,NULL,adCmdText);
while(!pRst->rsEOF)將查詢到的數據加到列表框咯。
{
  ((CListBox*)GetDlgItem(IDC_LIST1))->AddString(
   (_bstr_t)pRst->GetCollect("au_lname"));
  pRst->MoveNext();
}
pRst->Close();
pConn->Close();
pCmd.Release();
pRst.Release();
pConn.Release();
CoUninitialize();
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章