VC ++視頻教程(孫鑫)部分筆記
VC菜單學習:
子菜單、菜單項等的概念與菜單項的定位
菜單項的圖標、復選、系統的屬性獲取
MFC命令更新機制 CN_UPDATE_CCmdUI消息,捕獲是用ON_UPDATE_CCmdUI()函數,可以用ClassWizard來;
去除菜單及局部變量的非法性
通過ID來與圖標一一對應
右鍵彈出菜單 Project/Add to project/Components and Controls/Visual C++,
當然,也可以手動增加.這里就要將客戶區坐標換成屏幕坐標,因為我們給出的是我們以為的客戶區坐標,
但系統卻把它當作屏幕坐標
**********以上是關于靜態菜單 的*******************
***************以下是動態菜單(即用代碼添加的)***************
在MainFrme的OnCreate()中創建
建立對象,創建某類型的菜單,再獲取主框架的菜單,然后Append上面去或Insert上面去,注意參數的依賴
關系;
刪除菜單用DeleteMenu()
Resource.h定義了資源的ID
手動完成消息響應:在對應頭文件中寫消息處理函數原型,在其實現文件中消息對應機制
接觸集合類和在視圖中輸出、窗口重繪
VC ++ 開發過程中常見錯誤提示:在某個星號之前應增加分號,而其實是說星號前面的這個動西它不認識
只有源文件是參與編譯的,頭文件是不參與編譯的
如何在框架類中去截獲本由View類響應的消息。
對話框:
對話框:模態對話框和非模態
資源如何與一個類聯?難道僅僅是通過Load?
當用Create創建非模態對話框時,還要調用一個showwindow將其顯示出來,當然局部變量不行,但
為什么模態對話框行,因為其暫停在那,其生命周期未變;那么,要使非模態對話框能顯示,解決的
辦法有:變為成員;二在堆上分配,因為在堆上分配的變量,其生命周期與應用程序一樣;
按鈕的消息屬于通告消息
在對話框按件上放控件:
“釘子圖標”使保持顯示和不保持
三種訪問控件的方式:
第一種:
表態文本框:要獲取其文本,也即獲取窗口文本
獲取對話框上控件的指針,GetDlgItem(),這感覺就有點像在主框架窗口當中,當時要得到菜單欄一樣;
對于于靜態文本框,它是不能接受消息并響應的,為了使它能接受,必須改變其屬性,使其能夠Notify
文本的形式轉換成數值形式
第二種:
另外一個函數,GetDlgItemText()
第三種訪問控件的方式:GetDlgItemInt()和SetDlgItemInt()
第四種:將控件與成員變量關聯 -Member Varibles,并可以看看DDX_Text函數 Dialog Data Exchange
這里更涉及到DoDataExchange()和UpdateData()
還可以對控件變量關聯控件變量本身,其實也即控件對象。
第六、從Windows消息的傳遞機制,我們可以考慮:只要我們知道獲取文本的消息,它是WM_TEXT
我們可以通過發送這個SendMessage()這個Win32函數,那么在VC ++中,即通過加雙冒號表示是調用Win 32
平臺函數
SendMessage的多種實現 ,同時,注意怎樣去獲得句柄
第七種:直接給對話框子控件發送消息
獲取文本框選的一個消息,當然,這里面還要設置其為焦點
完成對話框的收縮功能,這個功能與Windows畫圖時的擴展與收縮類似
它用Picture來畫成一條線,并將其ID改為IDC_SEPARATOR,并設置屬性為Sunken
既然擴展時要還原成原來大小,那么其初始狀態要保存才好;而收縮時,要從圖像控件下面部分切除掉,那么,就
要獲取切除那一點的縱坐標。獲取窗口大小,可以用GetWindowRect()來
完成窗口伸縮與擴展功能,用SetWindowPos()
那么,剩下的問題詞是如何改變焦點方式及缺省按鈕的設置:
而這不再是簡單地按一個按鈕,就響應什么了,那么,這里,其實是通過InitDialog()函數中來改變……
用自寫的窗口過程來代替MFC寫的窗口過程 SetWindowLong是用來改變窗口的屬性,使其控件中使其下一個焦點是其下面的控件
這里又引起我的思考;強制類型轉換底層是怎樣的?
怎樣去寫窗口函數,可以用先要知道其類wndclass
那么更簡單的,實現焦點依次往下傳遞是怎樣的呢?
獲取窗口句柄:GetWindow(),GetDlgNextItem()
焦點依次往下傳遞:GetFocus()->GetNextWindow()->SetFocus()
更安全的代碼是GetNextDlgTabItem(GetFocus())->SetFocus()
基于對話框的應用程序:
逃跑按 鈕的巧妙實現——
將兩個按鈕都關聯到同一個新建的類,然后,類中可以建一個指針,來保存雙方的地址。
********************————————********************************
屬性表單和菜單項的創建
要想使視類具有修改編輯功能,我們可以選擇CEditView或CRichEditView類作為其基類
一、屬性表單是由一頁頁屬性頁組成的,那么我們需一頁頁的創建屬性頁,而每個屬性頁就
是一個窗口,那么,我們可以通過Insert/Dialog/IDD_……PropertyPage來創建一頁頁的屬性頁;
這里面本身有VC 6.0 的bug,我們要學會如何解決:使ClassWizard找到我們新增的類
我們先找到記錄工程類的信息的文件,我們進入工程文件夾里,找到工程名.clw;我們可以將其刪除,
然后重新打開工程,選擇重新建立工程的clw文件即可;
通過菜單響應,在其中增加屬性表單頁表,來調用DoModal() 并顯示
那么屬性表單實際上是屬性頁的父窗口
根據MSDN來看,如果我們要得到向導視圖一步步顯示的效果,我們可以在CPropertySheet類調用DoModal()
之前,加上調用SetWizardMode()來實現。那么中間又有一些BUG,我們需要改變第一頁,使其只有下一頁按
鈕,面最后來一頁只有上一步按鈕,那么,我們根據MSDN的提示,我們知道,解決的辦法是:在CPropertyPage類
對象中,增加OnSetActive()函數,在這人函數中調用CPropertySheet類的SetWizardButtons()函數。
那么我們再完善,要求用戶在進行了某一項選擇之后再進行下一步:
這里面有一個小的注意,我們要能在ClassWizard中的屬性頁對應的類中看到控件的ID,我們需要將控件的
Group屬性選中,那么表示與這個控件同一類的,緊跟在其后的控件值依次增1,直到遇到下一個具有Group屬性的另
一個,那么這里我們還有一個問題,對于屬性表單“下一步”的響應,我們是在哪里面響應呢,我們增加虛函數
OnWizardNext()
☆☆需要強調一下,我們是通過DoDataExchange()來與一個控件交換數據的,注意,是所有控件,只要我們想與之發生交換數據!☆☆
那么也即調用UpdateData();
☆☆而我們要使下拉列表框中有初始值,我們可以增加WM_INIT()消息響應處理函數☆☆
增加列表框里面的字符串,用到了AddString()函數
同樣,對于第三個屬性頁,我們也可以增加OnWizardNext()虛函數,并在初始化其類之后,也對其增加AddString(),注意,
我們的ComboBox(組合框)有sort()屬性,它能自排序,同時,我們可以設置讓其初始顯示一個默認值,調用SetCurSel()函數
為了使視類中輸出用戶的選擇,我們要在視類中定義相應變量來接收用戶的選擇。
☆☆在這里有一個技巧,將BOOL數組變量賦初值為FALSE,用到C語言中memset()函數☆☆
用Invalidate()來使窗口無效,從而引起完成窗口的重繪
創建字體,然后將其選擇進行設備環境,它會返回先前的字體,我們將其保存起來
那么我們輸出時,針對具體我們的選擇,我們應該利用我們在視圖類定義的與屬性頁對應控件變量的變量來進行相應改變和輸出,
當然,我們要知道它輸出的位置,我們要調用GetTextMetrics()函數來
修改程序框架及其外觀:
要想改變MFC應用程序的外觀,應在其窗口創建之前。
同樣,要想改變MFC應用程序的主框架外觀,應在主框架之前,在CMainFrame類的PreCreateWindow()函數中改變。
那么我們在開始學習VC ++編程時,就已經知道,PreCreateWindow()函數是一個虛函數,按照VC ++機制,
如果我們傳遞了子類的指針,,那么它先調用子類的相應函數。而這個函數它有一個CREATESTRUCT類型的變量,而且是
引用的,那么對其的修改,將是全局性的。
***********************************************************************************************************
如果我們要在窗口創建之后改變窗口外觀,行不行?其實,這個函數我們也已經學了,即SetWindowLong(),同樣也別忘了猜
測一下有沒有GetWindowLong()這個函數即其相應功能。
#############################################################################################################
修改窗口的圖標、光標和背景,我們應如何去修改呢?
窗口的類型和大小,是在創建和顯示窗口時設定的,而要改變窗口的圖標和光標、背景,則是在設計窗口類時改變的,那是MFC的
底層代碼,但是我們有其它辦法,可以對其修改:我們可以自己去設計窗口類,注冊它、顯示它
★★在直接用VC ++寫的應用程序,我們獲得句柄可以由其WinMain()參數來,而在MFC中,由于應用程序完全是由AppWizard生成
的,那么,我們要獲得其句柄,需要用到一個全局函數,可以用來獲取當前一個應用程序的句柄。叫做AfxGetInstanceHandle()
★★
★在LoadCursor()函數中,如果我們是要標準光標,我們需要將第一個參數設為NULL
★因為菜單的創建其實并不是在設計窗口類時完成,而是在底層完成,我們知道:在MFC中,
★在InitInstance()函數中,其在單文檔模板中將主菜單的資源標識號傳給了我們的模板,
★那么在底層代碼下,其完成資源標識與ID的轉換
#############################################################################################################
接下來我們要完成讓主框架窗口的顯示按我們設計的類來,那么,我們要做的是用cs參數來
但是,我們要注意,對于單文檔應用程序來說,我們看到的是View視圖類的窗口覆蓋在主框架窗口之上的,所以,如果我們要看到改變的
效果,我們應是在視圖類的PreCreateWindow()函數中來
我們可見,為了修改MFC應用程序圖標,我們幾乎重新設計了一個類,這顯然是不劃算的,那么MFC又提供了一個全局函數,它是
AfxRegisterWndClass()類名
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
那么如果我們是在窗口創建之后,可不可以修改窗口的圖標、光標等呢?可以,我們可以利用一個全局的API函數,SetClassLong()
完成不斷變換的圖標:
利用定時器和SetClassLong()即可完成:
先準備三幅圖標,放到res文件夾下
然后定義存儲三幅圖標的句柄數組,并在CMainFrame類中將數組元素初始化,用到了當前應用程序的實例句柄
☆獲得應用程序當前的實例句柄,AfxGetInstanceHandle()這個非常有用,別忘了!當然,我們也可以
通過CWinApp這個類本身的成員來實現,當然我們在源文件中要
用到另一個源文件中定義的全局變量,我們要用extern 聲明一下;還可以利用GetApp()函數輾轉來得到當前應用程序的實例句柄☆+
★怎樣將資源的ID號轉換為資源的字符串呢,這里面就有一個宏,MAKEINTRESOURCE()★
接下來,我們設置一個定時器,在OnCreate()函數中調用SetTimer(),然后在另外增加WM_TIMER消息,在OnTimer()函數中使用SetClassLong();
那么,當然,我們需要在響應定時器的函數中,圖標的index是在合理的變化范圍內
●這里有一個技巧:要使一個值始終在一個范圍內,最好的辦法就是取模●
###################################################################################################################
工具欄的編程:
在工具欄上增加相應圖標——>具欄上的相應圖標與菜單欄上的某菜單項同樣的ID號
在工具欄上要想增加分割符的話,我們可以左點擊控鈕,拖動一段距離;要刪除按鈕的話,拖動它到任意位置
自創建工具欄,我們先看一下MFC的WizardApp它的是怎樣創建的,我們應好好模仿
在我們MFC中,允許一人ID號對應多種資源
EnableDocking()函數,對于CControlBar類的,是設置其可以停靠,而對于主框架類的,則設置其為可以被停靠!
下面實現工具欄由復選工具箱控制顯示與隱藏:—>IsWindowVisible()來判斷一個窗口是否是可視的,然后在里面調用ShowWindow()來控制顯示或
隱藏,但是,還需使用RecalcLayout()來調整窗口顯示。還有一個問題,當我們拖動新工具欄,使其成為浮動窗口時,我們的點擊毫無意義,為此,
還要使其停靠
另一種顯示工具欄的方法是利用CFrameWnd的方法來,即ShowControlBar()
那么為了更符合我們的使用習慣,我們可以將菜單上打對勾,在對應菜單上面增加UPDATE_UI_COMMAND的消息處理,在里面可以通過調用pCmdUI->SetCheck(),
并在SetCheck()中調用IsWindowVisible()來同步我們的顯示
#######################################################################################################################
狀態欄的編程:
狀態欄分為提示欄和狀態指示器:而對于MFC已經建立的狀態欄,我們可以其全局變量數組中增加一些內容——>
首先,我們建立一個在字符串資源,在其中增加字符串及其ID,然后,我們可以通過函數來獲取相應內容,比如說,CTime來獲得當前時間,再次,我們定義一
中間臨時變量來存放我們想獲得的內容,從而并顯示;利用StatusBar的函數SetPaneText()來顯示我們想要顯示的內容,對于SetPaneText()中要獲得其索引,我們
可以通過命令標題來獲得其索引,即CommandToIndex()
有時我們要改變窗格的寬度,我們可以通過CStatusBar中的方法來,這個方法是SetPaneInfo()函數,而我們要獲得適當字符串的寬度,我們要得適當字符串的寬度,
我們在前面已經講過,可以用GetTextExtent(),它會返回CSize的對象,注意:CSize里面的成員變量cx才是其寬度;
但是,為了顯示時時的最新時間,我們可以通過定時器里來做
#########################################################################################################################
進度欄的編程實現:
類是CProgressCtrl,根據MFC的MSDN,步驟先是構造一個CProgressCtrl對象,然后通過Create()來創建具體的進度條對象;
要想將這個進度欄放到狀態欄里面,我們要獲取窗格的大小,我們可以用GetItemRect()來獲取,然后只是要在Create()函數中將其父窗口設置為狀態欄
★★★★這里面,調試時Insert BreakPoints后,再觀察,發現rect沒獲得想獲得的值班。那么,實際上,這與消息響應的先后順序有關。★★★★
★★★★★★★★我們應自定義消息,這樣一來,因為消息是放在消息隊列當中的,WM_CREATE消息先響應,這之后,才是我們自定義的消息,那么在★★★★★★★★
★★★★★★★★OnCreate()執行之后,這樣一來,我們才可能達到我預期的目的,由于系統中消息都是用一個整數表示感謝的★★★★★★★★
★★★★★★★★是#define UM_Message WM_USER(WM_USER宏為以上允許用戶自定義的消息范圍)★★★★★★★★
★★★★★★★★然后作消息映射,對于消息來說,我們是用ON_MESSAGE這個宏★★★★★★★★
★★★★★★★★然后聲明消息處理函數afx_msg …,如果我們發送消息要順帶有參數,那么參數在消息處理函數中聲明★★★★★★★★
★★★★★★★★然后在要發送消息的函數中調用SendMessage()函數★★★★★★★★
在以上操作,插入斷點,調試,仍是有問題;事實上,因為用SendMessage()函數,那么消息馬上進入到消息處理之后,然后再返回
因此我們要換另一個函數:PostMessage(),它是將消息放到消息隊列中,然后通過GetMessage()一條一個條取出運行
當然,我們需要解決當拖動窗口時,進度條在狀態欄里又出現顯示不當的情況;這里,其實它在OnPaint()函數中,那么還要判斷是否這個狀態欄是第一次創建,若是,則
創建;否則,就要移動窗口。移動窗口,有個函數,叫MoveWindow();而要移動進度條,可以用StepIt()函數,放在OnTimer()函數中
▲▲在狀態欄中放置文本,可以調用SetWindowText()函數或直接用CFrameWnd類的SetMessageText()函數直接放在狀態欄里;也可以用CFrameWnd類的GetMessageBar()▲▲
▲▲函數來獲得狀態欄的指針;第四種方式,可以用CWnd類的GetDescendantWindow()函數來獲取子孫窗口,直到找到指定ID的窗口▲▲
臨時窗口和永久窗口的概念:臨時對象是任意通過FromHandle()函數創建的沒有封裝過的C++對象
######################################################################################################################
啟動畫面的創建:利用VC給我們提供的組件庫Project->Add to Project->Components and Controls…Visual C++ Components,這里面都是C++里面有用的類
用類名::函數名,那么這個函數就是類的靜態函數
MFC文件操作:
在C語言中,指向文件的指針很多都是指向常量的指針和其它
那么,先預習前提知識:指向常量的指針和指向指針的常量——>
指向常量的指針:表示其指向的內容是常量,不允許通過指針來修改指向的內容.const char *pStr="iosi";或者寫為:char const * pStr="iosi";
那么此時若做這個操作——*pStr='W';//error 但是做pStr="wang";//OK
當然,我們仍可通過它本身這個字符數組來修改其里面的內容
指針常量——>指針的值不能改變,但是指針所指的內容可以改變.char str[5]="isao"; char * const pStr=str;//也即形式是const放在*號的后面
那么,我們此時若做這個操作——*pStr='W';//OK 但是做pStr="wnag";//error
那么指針常量必須在定義的時候即初始化
*****文件的操作*****
要獲取指向文件的指針,我們通過查MSDN,可知有函數fopen()來。C語言對文件的操作采用緩沖區來進行,所以我們如果沒有用fclose(pFile)來在操作完
之后關閉文件,那么,我們只有在結束運行之后,才能看到我們寫成功的文件
★★如果我們要對文件頻繁操作,那么,像上述那樣,每次打開、關閉較麻煩,我們有函數fflush()來刷新緩沖區,讓緩沖區的內容寫入到磁盤★★
☆☆但是,有時我們有一些特殊的應用,比如說我們有服務器端軟件,它監聽了很多設備,它們不斷把日志信息發送給服務器端軟件,并寫入服務器端磁盤,☆☆
☆☆如果我們每次去打開、關閉,那么很可能會丟失許多有用信息,那么我們此時可以使用fflush()函數來不斷刷新緩沖區進行寫入☆☆
※※那么,對于我們C語言文件來說,它其實有一個文件指針,每次自動移動,指向我們要寫入的地址※※
如果我們想移動文件指針,有許多函數可用fseek()、rewind()函數等,對文件的讀入,有時會因為找不到"\0"結束符而出現亂碼,對此,我們有多種方法解決
第一種:是在sizeof()后加1;第二種:使用memset()函數
☆★有時,我們需要獲取文件的長度,用ftell()可以得到文件當前指針的位置,那么,如果我們先用fseek()函數來使文件指針指向文件結尾處,然后我們再用★☆
☆★在對文件進行操作時,我們經常容易犯的錯誤是移動了指針,等到輸出時卻忘了要將文件指針移到我們想要開始讀出的位置★☆
★☆文件操作常犯的失誤:當以文本方式去讀與以二進制去讀時注意回車與換行的不同★☆
比如說,如果有一個面試題,讓你將整數值,例如98341存儲到文件中,打開時,還要是98341,那么,我們要明白這是考察你對文本文件與二進制文件的理解。
文件的存儲底層是以二進制的ASCII碼方式存儲,如果我們直接輸出98341,因為它占四個字節,且編碼是另外一種形式,等我們輸出,它就不是98341,而是按照
ASCII碼的形式輸出為其它字符,那么,具體地解決辦法是:一、定義一個char[5],然后用char[0]=9+48;//48為0的ASCII編碼
二、采用itoa(i,ch,10);
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
接下來,我們來看C++中對文件的操作,它是幾個類,比如說ofstream,ifstream或者iostream類
如果我們要向文件當中寫入數據,我們要用ofstream這個類,并用其方法write()來寫入數據,并用其close()方法來關閉文件
我們在讀取的時候,用ifstream這個類,當然,使用它們我們都要包含庫頭文件#include<fstream.h>
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
那么,下面我們來看一下,Win32中對文件的操作,我們要注意幾個函數
CreateFile()用來創建或打開讀取文件等諸多對象,WriteFile()用于向這些對象寫入內容,ReadFile()用于讀取數據,CloseHandle()去關閉這個對象句柄
同步IO:即當我們寫入或讀取文件的時候,如果我們沒有寫入完,程序就會掛起
異步IO:重疊IO,
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
接下來,我們來看一下MFC中提供的對文件的操作的類CFile。其打開文件的方法為根據其構造函數來選擇CFile::modeCreate或其它屬性
寫出有方法:write(),讀入有方法:read(),關閉有方法:Close()
要實現“打開”、“保存”對話框有CFileDialog這個類,同時,要過濾掉某些文件,需要用到其數據成員m_ofn的相應屬性,同時,在過濾字符串后面加上\0*.txt等,且最后一個
字符串以兩個空字符結尾,對于文件過濾器lpstrfilter的設置,選兩種格式中間的分割僅用'\0'分開
\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//
配置文件的讀寫
一般文件都寫到注冊表中,如果我們要寫到W.ini文件中,要用到一個函數WriteProfileString(),然后我們要得到其寫入的配置文件內容,我們用函數GetProfileString()函數
//那么如果我們寫入到Win.ini文件中,我們要使用Win 32 API函數::WriteProfileString() 和相應的::GetProfileString()函數,放在APP源文件的InitInstance()函數中
//當然,我們也可以用CWinApp的成員函數WriteProfileString()和GetProfileString()函數來
/\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\
下面我們來看一下注冊表的編程:
對注冊表的操作都是通過幾個函數來完成的,RegCreateKey()和RegSetValue()和最后關閉RegCloseKey()函數,如果我們要讀取數據,我們有一個函數RegQueryValue(),
當然,其中RegSetValue()函數只能設置字符串類型的值。如果我們要設置整型的值,則要用其擴展函數叫RegSetValueEx()函數,對應打工用的函數RegOpenKey()函數,
然后再用RegQueryValueEx()函數來取出
/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\
使用CArchive類對文件進行操作:
MFC文件框架內部實現機制和串行化操作: 文檔與串行化
另一種文件操作方式——>用CArchive這個類來,其"<<"和">>"這個符號用于寫出和讀入,注意,寫出的順序是怎樣的,那么讀入的順序也是一樣,其使用,具體參照MSDN來
注意,對于我們的文件保存和文件打開選項,它對應的CWinApp類的OnFileNew()和OnFileOpen()函數,而且對于單文檔用戶程序來說,它的pDocument對象只與一個文件相關聯。
下面我們要用CArchive類和Serialize()函數來實現文檔的保存和其它操作:
要按照MSDN提示,可見CArchive類的MSDN說明文檔的下部分鏈接文檔。我們可以拷貝資源
這里完成一個繪圖的程序:
它增加了一個CGraph類,這個類由CObject派生出來,目的是為了串行化。那么,同時在菜單中增加了繪制幾個圖形的命令,在…View類中實現,而在CGraph中寫了一個方法
OnDraw(CDC *pDC),它對應…View類中幾乎同樣的功能,而在…View類的OnLButtonUP()函數中通過CObArray類型的成員方法Add增加了一個CObject的指針。
現在,我們要做的是:在…Doc類中將我們保存在CObArray集合類中的繪圖信息保存到文件中;那么,首先,我們要在…Doc類中獲得視類的指針,我們有一個方法
GetFirstViewPosition()先獲得其位置,然后利用GetNextView()來獲得其視類指針
MFC文件框架內部實現機制和串行化操作: 文檔與串行化
另一種文件操作方式——>用CArchive這個類來,其"<<"和">>"這個符號用于寫出和讀入,注意,寫出的順序是怎樣的,那么讀入的順序也是一樣,其使用,具體參照MSDN來
注意,對于我們的文件保存和文件打開選項,它對應的CWinApp類的OnFileNew()和OnFileOpen()函數,而且對于單文檔用戶程序來說,它的pDocument對象只與一個文件相關聯。
下面我們要用CArchive類和Serialize()函數來實現文檔的保存和其它操作:
要按照MSDN提示,可見CArchive類的MSDN說明文檔的下部分鏈接文檔。我們可以拷貝資源
這里完成一個繪圖的程序:
它增加了一個CGraph類,這個類由CObject派生出來,目的是為了串行化。那么,同時在菜單中增加了繪制幾個圖形的命令,在…View類中實現,而在CGraph中寫了一個方法
OnDraw(CDC *pDC),它對應…View類中幾乎同樣的功能,而在…View類的OnLButtonUP()函數中通過CObArray類型的成員方法Add增加了一個CObject的指針。
現在,我們要做的是:在…Doc類中將我們保存在CObArray集合類中的繪圖信息保存到文件中;那么,首先,我們要在…Doc類中獲得視類的指針,我們有一個方法
GetFirstViewPosition()先獲得其位置,然后利用GetNextView()來獲得其視類指針;而要在視類中獲得文檔類的指針,我們可以利用其本身有的成員函數GetDocument()方法
★★在我們保存可串行化的數據對象時,實際上是利用我們對象本身的Serialize()函數本身去實現★★
線程同異步及套接字:
事件內核對象、關鍵代碼段(臨界區)的講解,以及在多線程同步中的應用。在Windows下編寫基于消息的網絡應用程序,掌握阻塞與非阻塞網絡程序的編寫,
理解在Windows平臺下,采用★★異步選擇機制可以提高網絡應用程序的性能。★★
事件對象:
創建一個事件對象,我們要用到一個函數CreateEvent()函數
下面我們用事件對象來創建線程同步應用程序。
創建事件對象,然后在內部循環中,調用事件對象。
注意:人工重置的事件對象,它一旦有信號后,所有等待該事件對象的線程都可運行,而自動重置的事件對象,它只能供一個線程使用,且一旦一個線程
等待到它時,操作系統內部自動將其設為非信號狀態。人工重置的事件對象需要顯示調用Reset()函數,才將事件對象設為非信號狀態。
通過創建一個命名的事件對象,我們可以完成讓一個應用程序一次只能有一個實例運行這樣的功能。
接下來,我們來看另一種同步方式:————》使用關鍵代碼段
關鍵代碼段工作在用戶方式下,它是可運行的一個小段代碼,它必須一次性獨立完整地執行完。
我們要想進入關鍵代碼段,我們需要調用一個函數EnterCriticalSection()。我們的臨界區對象它是由系統內部自動回復的。當我們訪問完系統資源以后,它需要
離開臨界區對象,我們又用到一個函數LeaveCriticalSection()。當我們這個公用電話亭(臨界區對象)不需要再存在時,我們就刪除它,用到函數DeleteCriticalSection()
函數。★★注意:刪除臨界區對象是在所有程序主線程最后退出之前,否則,在多線程程序中會出現內存不能為written的BUG★★
所以,我們在做多線程程序時,一定要避免線程間死鎖的發生。
互斥對象和事件對象屬于內核對象,利用內核對象進行線程同步,速度較慢,但利用互斥對象和事件對象這樣的內核對象,可以在多個進程中的各個線程間進行同步。
關鍵代碼段是工作在用戶方式下,同步速度較快,但在使用關鍵代碼段時,很容易進入死鎖狀態,因為在等待進入關鍵代碼段時無法設定超時值。
我們在MFC編程時,可以在類的構造函數中調用InitializeCriticalSection()函數,在其析構函數中調用DeleteCriticalSection()函數。但是,當有多個共有資源時,我們
要防止發生死鎖。
Windows套接字在兩種模式下執行I/O操作,阻塞和非阻塞。在阻塞模式下,在I/O操作完成前,執行操作的Winsock函數會一直等待下去,不會立即返回程序(將控制權交還給程序)。而在非阻塞模式下,Winsock函數無論如何都會立即返回。
Windows Sockets為了支持Windows消息驅動機制,使應用程序開發者能夠方便地處理網絡通信,它對網絡事件采用了基于消息的異步存取策略。
Windows Sockets的異步選擇函數WSAAsyncSelect()提供了消息機制的網絡事件選擇,當使用它登記的網絡事件發生時,Windows應用程序相應的窗口函數
將收到一個消息,消息中指示了發生的網絡事件,以及與事件相關的一些信息。也就是說,我們可以登記多種事件,當登記的事件發生時,我們可以發送消息,然后系統進行處理。
int WSAEnumProtocols( LPINT lpiProtocols, LPWSAPROTOCOL_INFO lpProtocolBuffer, ILPDWORD lpdwBufferLength );
Win32平臺支持多種不同的網絡協議,采用Winsock2,就可以編寫可直接使用任何一種協議的網絡應用程序了。通過WSAEnumProtocols函數可以獲得系統中安裝的網絡
協議的相關信息。lpiProtocols,一個以NULL結尾的協議標識號數組。這個參數是可選的,如果lpiProtocols為NULL,則返回所有可用協議的信息,否則,只返回數組
中列出的協議信息。lpProtocolBuffer,[out],一個用WSAPROTOCOL_INFO結構體填充的緩沖區。 WSAPROTOCOL_INFO結構體用來存放或得到一個指定協議的完整信息。
lpdwBufferLength,[in,out],在輸入時,指定傳遞給WSAEnumProtocols()函數的lpProtocolBuffer緩沖區的長度;在輸出時,存有獲取所有請求信息需
傳遞給WSAEnumProtocols()函數的最小緩沖區長度。這個函數不能重復調用,傳入的緩沖區必須足夠大以便能存放所有的元素。這個規定降低了該函數的
復雜度,并且由于一個 機器上裝載的協議數目往往是很少的,所以并不會產生問題。
接下來,我們采用異步套接字編寫一個網絡聊天室程序:
首先,我們要加載套接字庫,但是,采用AfxSocketInit()函數,我們加載的1.1版本的的套接字庫,而在我們這里,要用到WinSock2版本的的套接字,因此,我們還是調用
WSAStartup()這個函數。我們將其放到CWinApp的Initinstance()這個函數中。同時,我們要加載庫文件#include<Winsock2.h>,和庫Ws2_32.lib這個庫。
然后我們給應用程序類加一個析構函數,用來調用WSACleanup()函數。然后對對話框類加一個套接字成員變量。接下來,按部就班,綁定套接字,綁定之后,就可以
調用WSAAsyncSelect()函數了,接收數據,發送數據。
★★在套接字庫中,對增加的函數,函數名前面都是以WSA開頭★★
什么叫重疊套接字??異步選擇機制??
將主機名轉換成IP地址,我們可以用一個函數gethostbyname()函數。反之,則有另一個函數,叫gethostbyaddress()函數。
多線程與聊天程序:
多線程的編寫與對象的互斥:
一、互斥命名對象的概念與創建
多線程運行在程序進程的空間中,每個線程占用CPU時間片,OS以某種方式能安排時間片給不同的線程使用。
采用多線程的好處是:線程占用的資源較少,同時,進程與進程之間切換花費的時間長,代價大。
創建一個線程,首先,我們要用Windows操作系統提供的API函數,所以,我們要包含頭文件#include<windows.h>
其次,按順序,我們先要用到函數CreateThread()這個函數,完成后,我們要調用CloseHandle()來完成。注意,我們
調用CloseHandle()并沒有終止線程,只是關閉了句柄,同時,讓這個線程的內核引用計數遞減。顯而易見,當我們的主線程
退出時,從線程也就不可能再運行了。當然,這里肯定有BUG,為此,要使線程同步,也即要使一個線程在操作共有資源時,必須其全部過程完成之后,另一個線程
才能操作共有資源 ★為了調試多線程的程序,我們可以在對共有資源操作時,可以調用Sleep()函數,進行調試來發現錯誤★
我們要做線程的同步,要用到一個函數,CreateMutex()函數。
★★★互斥對象(mutext)屬于內核對象,它能確保線程擁有對單個資源的互斥訪問。
互斥對象包含一個使用數量、一個線程ID和一個計數器。
那么,在線程中,我們就要去請求這個互斥對象,要用到WaitForSingleObject()函數,而釋放互斥對象,我們又要用到一個函數。
這個互斥對象是誰擁有,誰去釋放。★★★
正因為互斥對象有線程相關的這個特性,我們才能用它來作為“鑰匙”,來作線程同步用。
★★所有與窗口有關的類,都有一個成員m_hWnd,這是一個與窗口相關的句柄★★
下面,我們用多線程來創建一個網絡聊天室程序:
當然,我們肯定是基于套接字的,為此,要加載并協商套接字,我們在MFC中用到的函數是AfxSocketInit()函數,而用到個函數,我們要加載
<AfxSock.h>頭文件,AfxSocketInit()函數是要放在CWinApp的InitInstance()函數中。
當然,注意,在調用CreateThred()函數時,線程運行時,第三個參數需要知道已經聲明的函數和其實現細節,但是,在VC++編程中,要知道其實現細節,則需要已經創建了對象。解決的話,可以將作為其成員函數時,將其作為靜態成員函數。
為什么采用多線程? 一般的,我們的接收窗口只有在有數據時才會有所響應,否則,將一直暫停,顯然,這在聊天室程序中不行,因此必須采用多線程。
怎樣進入新線程然后又發回到主線程?
網絡編程:
網絡的相關知識,網絡程序的編寫,Socket連接應用程序并bind()網絡驅動程序:
網絡及通信——>通信雙方與規則(協議),還要用端口號標識是哪個命令終端(也即網絡應用程序);
封裝就是在數據的前面加上特定的協議頭部。
運輸層提供使運行中的進程進行通信的能力。為了標識通信的進程TCP/IP提出了協議端口的概念。端口是一種抽象的軟件結構。
socket(套接字)是網絡應用程序訪問通信協議的操作系統調用。存在于通信域中。在網絡協議中需要確定網絡字節的順序。
套接字的類型:
1、流式套接字(SOCK_STREAM),提供面向連接的、可靠的、無重復的、按順序接收的通信
2、數據報式套接字(SOCK_DGRAM),提供無連接式服務。
3、原始套接字(SOCK_RAW)。
△▲客戶端/服務器模式▲△
在TCP/IP網絡應用中,通信的兩個進程間相互作用的主要模式是客戶機/服務器模式(client/server),即客戶向服務器提出請求,服務器接收到請求后,提供相應的服務。
客戶機/服務器模式的建立基于以下兩點:首先,建立網絡的起因是網絡中軟硬件資源、運算能力和信息不均等,需要共享,從而造就擁有眾多資源的主機提供服務,資源
較少的客戶請求服務這一非對等作用。其次,網間進程通信完全是異步的,相互通信的進程間既不存在父子關系,又不共享內存緩沖區,因此需要一種機制為希望通信的
進程間建立聯系,為二者的數據交換提供同步,這就是基于客戶機/服務器模式的TCP/IP。
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
★★基于TCP的網絡應用程序socket編程(面向連接的)★★
★服務器端程序:
1、創建套接字(socket)。
2、將套接字綁定到一個本地地址和端口上(bind)。
3、將套接字設為監聽模式,準備接收客戶請求(listen)。
4、等待客戶請求到來;當請求到來后,接受連接請求,返回一個新的對應于此次連接的套接字(accept)。
5、用返回的套接字和客戶端進行通信(send/recv)。
6、返回,等待另一客戶請求。
7、關閉套接字。
★客戶端程序:
1、創建套接字(socket)。
2、向服務器發出連接請求(connect)。
3、和服務器端進行通信(send/recv)。
4、關閉套接字。
★★基于UDP的網絡應用程序socket編程(面向無連接的)★★
★服務器端(接收端)程序:
1、創建套接字(socket)。
2、將套接字綁定到一個本地地址和端口上(bind)。
3、等待接收數據(recvfrom)。
4、關閉套接字。
★客戶端(發送端)程序:
1、創建套接字(socket)。
2、向服務器發送數據(sendto)。
3、關閉套接字。
下面我們來編寫命令終端式的面向連接的應用程序
要建立這類應用程序,首先是我們要加載Socket庫,這里竟不是用預處理命令,而是用到一個平臺函數WSAStartup()函數;
然后根據上面函數的MSDN參考資料,我們完成之后,接下來要創建套接字,我們用到函數socket();
創建套接字成功后,我們需要將其綁定到本地的端口上,要用到bind()函數。注意除了safamily這個參數以外,其它的都要轉化為網絡字節序,為此,我們要用到htol()函數和
htons()函數
接下來我們應該調用一個listen()函數來將我們的套接字設置為監聽模式,然后做一個死循環,不斷等待連接的到來,這即要調用一個函數,等待客戶的連接到來,此函數即為
accept()函數。接下來,在建立連接之后,我們可以進行通信了,我們可以用send()函數來發送數據。與此同時,我們也可以接收一個數據,用到方法recv()。接收完數據后,
我們需要closesocket()方法去關閉socket套接字。釋放為套接字所分配的資源。
當然,在這個程序當中,因為我們使用了套接字的函數,我們需要包含Winsock.h頭文件,同時還要鏈接一個庫文件Ws2__32.lib
接下來我們去編寫客戶端程序:
當然前半部分我們與服務器端程序差不多,不過,我們不用bind()SOCKET套接字,而是直接用connect()函數去連接。
當我們連接之后,就可接收服務器端發送過來的數據recv(),當然,我們也可以向服務器端發送數據send(),同樣,當我們通信完成之后,我們也要釋放套接字closesocket(),
最后,我們也需要終止對套接字庫的使用,我們用WSACleanup();
下面我們來編寫命令終端式的面向無連接的應用程序
對于服務器端:
同樣,順序是先加載套接字庫,然后,建立套接字socket(),并與本地地址綁定bind(),此后,這里與面向連接的不同,不再需要設置為什么監聽模式,而是直接用recvfrom()
函數來開始獲取信息。
對于客戶端:
它加載了套接字后,建立了套接字socket()后,同樣不需要綁定,而是直接可以發送各接受消息。發送消息要調用的是sendto()函數。
這個客戶端設置的端口為服務器端設置的接收消息的端口。
下面我們完成一個基于字符界面的聊天程序:
對于聊天程序,我們常用基于面向無連接的的程序,采用UDP,實時性好。注意,要從鍵盤獲取輸入信息,我們有一個函數gets(),它可以從標準I/O獲得輸入
鉤子程序與數據庫編程:
Hook編程。如何安裝鉤子過程,如何編寫全局鉤子,動態連接庫里的全局變量數據共享問題分析。ADO數據庫編程。在VB中利用ADO控件和ADO對象訪問數據庫,在VC中利用ADO技術訪問數據庫。
Hook編程:兩種鉤子——》一種是進程內鉤子,一種是全局鉤子。
什么叫Hook程序,即鉤子程序?由我們的Windows消息驅動機制,我們的應用程序由于用戶的操作,產生信號,OS將對應的消息放到消息隊列,并在一定時間,將消息取出,交由我們的應用程序按照消息對應的過程進行處理。那么,如果我們想在于這個過程中截取消息并作我們特殊的處理,那么,實現我們這個目的過程即是鉤子程序。
安裝鉤子,我們有對應的函數,叫SetWindowsHookEx(),其具體要求做哪些準備,我們可以從MSDN其函數本身說明中可知。
那么,有可能我們涉及到要建庫,我們有一個建立動態鏈接庫的需要,而在這個庫中,我們要得到DLL庫的實例句柄的話,我們可以可以通過DLLMain()函數的參數得到,另外,我們也可用方法GetModuleHandle()。
★★原來,動態鏈接庫它也有一個入口,即DLLMain()函數。★★
我們可以自建一個動態鏈接庫,然后,在其同樣的文件夾下,建一個模塊定義文件.def.注意,我們導出的文件有一個序號。
動態鏈接庫不能獨立運行,我們必須寫一個程序,然后加載這個庫。
★★動態鏈接庫的調試:★★
比如說,我們要查看SetHook()(該函數是放在動態鏈接庫中),我們可以在該函數處設置斷點。然后我們在調試時可以選擇Debug\Step Into(F11鍵)
★★我們如果想在一個函數中獲得主窗口的句柄的話,我們除了可以在函數中想到用什么函數外,我們也可以將主窗口的句柄通過函數參數傳過來★★
★★動態鏈接庫中,對于我們的數據,Windows 2000采用的是寫入式拷貝,因此,如果我們想使所有進程都共享數據,我們須在動態庫自己建一個結,將數據寫在結中,用到一個#pragma data_seg("輸入名字"),并再用一個指令#pragma data_seg()作為結尾,將我們想共享的數據放在這兩個代碼的中間,注意,這個數據必須賦初值;然后將結設置為共享。我們又用到另一個#pragma comment(linker,"/section:上面輸入的名字,RWS")★★
★★除了用以上方式呢,我們也可以在模塊定義文件中寫上SEGMENTS ┓結名 其權限,如READ WRITE SHARED★★
★★我們可以用鉤子來獲取密碼等重要信息,這是它本身經常的角色。★★
★★★★★★★★★★★★★★★★★★★★★★★數據庫的編程★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
不同的數據庫廠商,會在其提供數據產品時,也要給程序員們提供訪問其數據庫的接口,即API,那么,當我們要編寫一個數據庫時,我們需要學習這些新的API
微軟數據訪問技術有:
ODBC(開放數據互連),可以訪問各種支持ODBC的關系型數據庫
DAO(數據訪問對象)——基于Microsoft Access/Jet引擎,主要是用于VB開發面向Access數據庫的
RDO(遠程數據訪問對象)其實也是基于ODBC
OLE DB(對象鏈接與嵌入數據庫),對ODBC進行了擴展,可以訪問關系型數據庫及非關系型。OLE DB程序的基本結構是:OLE DB提供程序和用戶程序
ADO(Activex Data Object),它建立在OLE DB之上,是OLE DB用戶程序。提供了對自動化的支持,連一些腳本語言也可以用其來訪問數據庫
下面,我們來編寫ADO數據庫應用程序
ADO有三個核心對象:
一、Connection對象:用于管理用戶程序與數據庫通信,表示了到數據庫的鏈接。
二、Command對象:用來處理重復執行的查詢
三、Recordset對象:用來獲取數據庫中的數據
注意,在編寫數據庫應用程序時,在顯示時,一定不能忘了移動游標,否則,是一個死循環了。★★我們要記住,對數據庫的訪問,總是按行進行的。★★
★VB中,若是在組件旁右擊,想添加組件,則我們可以右擊選擇組件;當然,若是直接想在編碼中用數據庫相應對象,則我們在Projects/下選擇的是Reference★
★★在VC中,我們要用到ADO去訪問數據庫,我們首先要導入一個庫msado15.dll,我們可以在StdAfx.h這個預編譯頭文件中導入,用到#import 完整路徑名 no_namespace★★
★★為避免命名誤會,我們可以將用rename將一個名字改為另一個名字。因為我們說過,OLE DB是基于COM組件的,因此,如果我們要用ADO訪問控件時,我們要加載COM★★