通过小练习掌握MFC知识点之起步篇-父子窗口间传值、ListCtrl隔行变色、双缓冲技术解决控件闪烁、自定义消息等

2013.4.12 再次修改  

2013.4.11 修改  

注:修改内容见文章结尾处


本次练习会学到的知识点:

1、ListCtrl控件:设置ListCtrl网格,内容项隔行变色,插入一行数据,删除数据,选中整行,双缓冲技术解决闪烁问题等。。

2、模态对话框及非模态对话框的使用

3、父子窗口间传值

如果对话框是模态的,那么弹出后该程序的其它窗口就呈不可用的状态,原来的程序暂停执行,直到这个模态窗口关闭后才回到原来程序继续。 非模态的就是直接显示出来,然后原来的程序继续执行下面的语句,而且其它窗口也呈可用状态。

题目:MFC数据结构的运用

通过小练习掌握MFC知识点之起步篇-父子窗口间传值、ListCtrl隔行变色、双缓冲技术解决控件闪烁、自定义消息等_第1张图片

要求:

1、  对话框中的表格,使用CListCtrl,采用REPORT风格、单选模式。表格由2列组成,第一列名称“标题”,第二列名称“内容”。

2、  点击“添加”按钮,弹出如下图所示的新对话框。输入完毕后,点击“确定”,添加到列表中。

通过小练习掌握MFC知识点之起步篇-父子窗口间传值、ListCtrl隔行变色、双缓冲技术解决控件闪烁、自定义消息等_第2张图片

注意:列表中的“标题”一列,要求不能重复。

3、  点击“删除”按钮,可删除列表中,当前选择行。

注意:删除后,当前行,位置下移一行。但如果删除的是末尾行,保持当前行仍然是末尾行。

4、  点击“查询”按钮。弹出如下图所示的非模式对话框,输入“标题”后,点击“查找”,系统自动显示CListCtrl列表中,与此“标题”对应的“内容”。

通过小练习掌握MFC知识点之起步篇-父子窗口间传值、ListCtrl隔行变色、双缓冲技术解决控件闪烁、自定义消息等_第3张图片

注意:上图所示对话框,要求采用非模式对话框,多次点击查询按钮,只能弹出1个查找对话框。

采用(映射)数据结构CMapStringToSting。

通过小练习掌握MFC知识点之起步篇-父子窗口间传值、ListCtrl隔行变色、双缓冲技术解决控件闪烁、自定义消息等_第4张图片

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:在接收方头文件中声明消息响应函数

//消息响应,添加数据 自定义消息步骤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
{
......//实现功能
}

6:在发送方实现文件(CPP)中需要发送消息的代码段中发送消息

	//this->SendMessage(WM_USRMSG_ADD,0,0);//自定义消息步骤4 
	//向父窗口发送消息,而不是给自己,所以不应该用this
	GetParent()->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/s/1mglDThe(修改版,代码质量较高)

     http://pan.baidu.com/s/1ntkpWZf(最终版,质量相当高呀)

你可能感兴趣的:(通过小练习掌握MFC知识点之起步篇-父子窗口间传值、ListCtrl隔行变色、双缓冲技术解决控件闪烁、自定义消息等)