2013.4.12 再次修改
2013.4.11 修改
註:修改內容見文章結尾處
本次練習會學到的知識點:
1、ListCtrl控制項:設定ListCtrl網格,內容項隔行變色,插入一行資料,刪除資料,選中整行,雙緩衝技術解決閃爍問題等。。
2、模態對話方塊及非模態對話方塊的使用
3、父子視窗間傳值
如果對話方塊是模態的,那麼彈出後該程式的其它視窗就呈停用狀態,原來的程式暫停執行,直到這個模態視窗關閉後才回到原來程式繼續。非模態的就是直接顯示出來,然後原來的程式繼續執行下面的語句,而且其它視窗也呈可用狀態。
題目:MFC資料結構的運用
要求:
1、 對話方塊中的表格,使用CListCtrl,採用REPORT風格、單選模式。表格由2列組成,第一列名稱“標題”,第二列名稱“內容”。
2、 點擊“添加”按鈕,彈出如所示的新對話方塊。輸入完畢後,點擊“確定”,添加到列表中。
注意:列表中的“標題”一列,要求不能重複。
3、 點擊“刪除”按鈕,可刪除列表中,當前選擇行。
注意:刪除後,當前行,位置下移一行。但如果刪除的是末尾行,保持當前行仍然是末尾行。
4、 點擊“查詢”按鈕。彈出如所示的非強制回應對話方塊,輸入“標題”後,點擊“尋找”,系統自動顯示CListCtrl列表中,與此“標題”對應的“內容”。
注意:所示對話方塊,要求採用非強制回應對話方塊,多次點擊查詢按鈕,只能彈出1個尋找對話方塊。
採用(映射)資料結構CMapStringToSting。
5、 點擊“發送”按鈕,彈出如所示的新對話方塊。將“習題2”對話方塊中的CListCtrl列表裡的內容,匯入新對話方塊列表中。並要求,新對話方塊風格和“習題2”對話方塊保持一致
第一版經檢查紕漏百出,各種問題迎面而來。如添加時,每次都new了一塊空間出來,而程式只在最後delete了一次,記憶體流失的情況可想而知。另外,第一版的單例模式設計得相當不得體,有分配但是沒有釋放。
//單例模式 static CFindDataDlg* GetInstance(){//問題:沒有在相應位置釋放,造成記憶體流失//if (NULL == m_pInstance)//{//m_pInstance = new CFindDataDlg;//}//return m_pInstance;static CFindDataDlg dlg; //如此一番更改後不用管理記憶體return &dlg;}
另外還要將類的建構函式,拷貝建構函式,解構函式以及賦值運算子多載都私為私人,避免被類外面的代碼調用到。
private: CFindDataDlg(CWnd* pParent = NULL);CFindDataDlg(CFindDataDlg&);virtual ~CFindDataDlg();CFindDataDlg operator= (const CFindDataDlg&);
關於單例模式的設計請參考http://blog.csdn.net/boyhailong/article/details/6645681
關於自訂訊息傳遞
往父視窗發送自訂訊息
GetParent()->SendMessage(WM_USRMSG_ADD,WPARAM(&m_strTitle),LPARAM(&m_strContent));//整型的長度與指標的長度一致,傳遞指標沒問題
LRESULT CExercise3Dlg::OnAddData( WPARAM wParam, LPARAM lParam )//自訂訊息步驟3{//CString strSrc = m_pDlgAddData->GetTitle();//以前這種方式較為死板CString strTitle = *(CString*)wParam;//此種方式較為靈活//相當於CString pStrTitle = (CString*)wParam;CString strContent = *(CString*)lParam;}
自訂訊息處理的步驟:
1:明確哪個是發送方,哪個是接收方。本例中主視窗是接收方,而添加視窗則是發送方。
2:在發送方標頭檔中添加
#define WM_USRMSG_ADD (WM_USER + 100)//自訂訊息步驟1
WM_USRMSG_ADD由使用者自己定義
3:在接收方標頭檔中聲明訊息響應函數
//訊息響應,添加資料 自訂訊息步驟2afx_msg LRESULT OnAddData( WPARAM wParam, LPARAM lParam ); DECLARE_MESSAGE_MAP()
4:訊息映射
BEGIN_MESSAGE_MAP(CExercise3Dlg, CDialog)//}}AFX_MSG_MAPON_MESSAGE(WM_USRMSG_ADD, OnAddData)ON_MESSAGE(WM_USRMSG_FIND,OnFindData)END_MESSAGE_MAP()
5:在接收方實現檔案(CPP)中定義自訂訊息響應函數
LRESULT CExercise3Dlg::OnAddData( WPARAM wParam, LPARAM lParam )//自訂訊息步驟3{......//實現功能}
6:在發送方實現檔案(CPP)中需要發送訊息的程式碼片段中發送訊息
//this->SendMessage(WM_USRMSG_ADD,0,0);//自訂訊息步驟4 //向父視窗發送訊息,而不是給自己,所以不應該用thisGetParent()->SendMessage(WM_USRMSG_ADD,WPARAM(&m_strTitle),LPARAM(&m_strContent));
怎麼樣?自訂訊息還是很簡單的吧。在這裡需要提醒一下SendMessage與PostMessage的區別:
PostMessage只負責將訊息放到訊息佇列中,不確定何時及是否處理SendMessage要等收到訊息處理的返回碼(DWord類型)後才繼續PostMessage執行後馬上返回SendMessage必須等到訊息被處理後才會返回
注意,不要通過PostMessage傳遞臨時變數指標,應該很可能訊息被處理時該變數已經銷毀,這時訪問就會出錯
使用者當前沒有選中ListCtrl控制項時,刪除按鈕應該是不可用狀態,當選中時呈可用狀態,並詢問是否刪除。如果沒有選中其中一行,刪除按鈕再次變為不可用。
添加訊息處理NM_CLICK
void CExercise3Dlg::OnNMClickList(NMHDR *pNMHDR, LRESULT *pResult){// TODO: 在此添加控制項通知處理常式代碼*pResult = 0;LPNMITEMACTIVATE pNMItem = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);if (-1 == pNMItem->iItem)//當前沒有選中行 iTem即當前選中行的行號{GetDlgItem(IDC_BTN_DEL)->EnableWindow(FALSE);}else{GetDlgItem(IDC_BTN_DEL)->EnableWindow(TRUE);}}
在修改版中存在兩個問題:
一是當先開啟"尋找"對話方塊時,主視窗ListCtrl的內容都已被複製一份到尋找類的成員變數中,此時ListCtrl傳過來的值已經確定,再開啟“添加”時,添加得到的新內容是無法傳入“尋找”類中的,所以也就無法正確尋找新添加的值。刪除操作也是這樣的的問題。
二是將可以用更簡單更容易理解的方式開啟尋找視窗,即將尋找類定義為主視窗的資料成員,結合Create()、IsWindowVisible()、DoModal()、SetFocus()這幾個函數可以保證每次只彈出一個執行個體。
在主視窗的建構函式中Create();
m_DlgFindData.Create(IDD_DIALOG_FIND, this);
接下來是在相應的函數中添加以下代碼
if (!m_DlgFindData.IsWindowVisible()) //如果視窗目前不是顯示狀態{int nCount = m_lstCtrl.GetItemCount();//CMapStringToString mapSTSData;//for (int i = 0; i < nCount; ++i)//mapSTSData[m_lstCtrl.GetItemText(i,0)] = m_lstCtrl.GetItemText(i, 1);//m_DlgFindData.SetMapData(mapSTSData);m_DlgFindData.SetMapData(&m_mapLstData); m_DlgFindData.ShowWindow(TRUE);m_DlgFindData.SetFocus();}else{m_DlgFindData.SetFocus();//設定當前焦點}
問題一的解決辦法:
方法1、可以將添加視窗改成模態的方式(當前是非模態方式,即new->Create->ShowWindow),用DoModal方法保證運行添加操作時不可執行尋找操作。當新添加一條資料後,資料會在ListCtrl控制項中更新,再執行尋找操作時會先更新尋找類的成員變數,以更新資料再進行尋找。這樣可以保證程式無差錯。此種方法最容易想,同時也最方便。讀者可以自行修改代碼,這裡不作贅述。
方法2、還是以非模態方式調用,大致思路是:在主視窗(父視窗)類中定義一個資料成員,用於即時更新ListCtrl的內容,當執行添加或者刪除操作時,對應著更新這個資料成員。執行尋找操作時,將主視窗的這個資料成員的指標傳遞給尋找視窗(子視窗)對應的類中(在尋找類中定義一個對應類型的資料成員指標),那麼取資料時直接用的主視窗的即時資料。此種方法相對於以前那種尋找視窗複製一份主視窗ListCtrl內容的方式而言非常高效,而且簡單、安全、易理解,還節約了大量記憶體空間。在大量資料傳遞時表現得尤其明顯。所以這裡Ajioy大力推薦此種方法。
//查詢void CExercise3Dlg::OnBnClickedBtnQuery(){//需要用到單例模式?if(NULL == m_pDlgFindData){m_pDlgFindData = CFindDataDlg::GetInstance();CMapStringToString mapSTSData;int nCount = m_lstCtrl.GetItemCount();for (int i = 0; i < nCount; ++i){mapSTSData[m_lstCtrl.GetItemText(i,0)] = m_lstCtrl.GetItemText(i,1);}//m_pDlgFindData->SetMapData(mapSTSData);//低效,不推薦 m_pDlgFindData->SetMapData(&m_mapLstData);//十分高效,簡單安全易理解m_pDlgFindData->Create(IDD_DIALOG_FIND,this);}else{m_pDlgFindData->SetFocus();}m_pDlgFindData->ShowWindow(TRUE);}
void CFindDataDlg::SetMapData(const CMapStringToString* mapData){//INT_PTR nCount = mapData.GetSize();//const CMapStringToString::CPair* pCurVal;//pCurVal = mapData.PGetFirstAssoc();//while (NULL != pCurVal)//{//m_mapSTSData[pCurVal->key] = pCurVal->value; //pCurVal = mapData.PGetNextAssoc(pCurVal);//}m_pMapSTSData = mapData; //改用指標方式效率和效能都有提升,同時也減少了記憶體開銷}
記得在CFindDataDlg.h標頭檔中定義資料成員變數
const CMapStringToString* m_pMapSTSData;//父視窗CListCtrl中的內容
全方完
源碼地址:http://download.csdn.net/detail/ajioy/5241570(第一版,可下載對比修改版進行學習)
http://pan.baidu.com/share/link?shareid=471517&uk=805795666(修改版,代碼品質較高)
http://pan.baidu.com/share/link?shareid=472397&uk=805795666(最終版,品質相當高呀)