2013.4.12 再次修改
2013.4.11 修改
注:修改内容见文章结尾处
本次练习会学到的知识点:
1、ListCtrl控件:设置ListCtrl网格,内容项隔行变色,插入一行数据,删除数据,选中整行,双缓冲技术解决闪烁问题等。。
2、模态对话框及非模态对话框的使用
3、父子窗口间传值
如果对话框是模态的,那么弹出后该程序的其它窗口就呈不可用的状态,原来的程序暂停执行,直到这个模态窗口关闭后才回到原来程序继续。 非模态的就是直接显示出来,然后原来的程序继续执行下面的语句,而且其它窗口也呈可用状态。
要求:
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&);
关于自定义消息传递
往父窗口发送自定义消息
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:在接收方头文件中声明消息响应函数
//消息响应,添加数据 自定义消息步骤2 afx_msg LRESULT OnAddData( WPARAM wParam, LPARAM lParam ); DECLARE_MESSAGE_MAP()
4:消息映射
BEGIN_MESSAGE_MAP(CExercise3Dlg, CDialog) //}}AFX_MSG_MAP ON_MESSAGE(WM_USRMSG_ADD, OnAddData) ON_MESSAGE(WM_USRMSG_FIND,OnFindData) END_MESSAGE_MAP()
5:在接收方实现文件(CPP)中定义自定义消息响应函数
LRESULT CExercise3Dlg::OnAddData( WPARAM wParam, LPARAM lParam )//自定义消息步骤3 { ......//实现功能 }
//this->SendMessage(WM_USRMSG_ADD,0,0);//自定义消息步骤4 //向父窗口发送消息,而不是给自己,所以不应该用this GetParent()->SendMessage(WM_USRMSG_ADD,WPARAM(&m_strTitle),LPARAM(&m_strContent));
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; //改用指针方式效率和性能都有提升,同时也减少了内存开销 }
const CMapStringToString* m_pMapSTSData;//父窗口CListCtrl中的内容
源码地址:http://download.csdn.net/detail/ajioy/5241570(第一版,可下载对比修改版进行学习)
http://pan.baidu.com/s/1mglDThe(修改版,代码质量较高)
http://pan.baidu.com/s/1ntkpWZf(最终版,质量相当高呀)