1、引言。

监听模式,指事件源经过事件的封装传给监听器,当事件源触发事件后,监听器接收到事件对象可以回调事件的方法。顾名思义,它就是对某一目标行为进行监听并且做出应对反映的模式。常见的应用场景有网站在线人数统计,监听用户的行为,win32的消息机制。

2、本文实例概述。

我在这里就用VC++来举例子以MVC的模式,建立一个一对多的依赖关系。

sesstionstorage 监听 监听模式_监听模式


如上图所示:通过MainDialog的添加、修改、删除按钮,来操作List表中的数据对象。实现效果:LeftChild窗口对数据管理类进行监听,当MainDialog操作改变数据对象时,就应该在LeftDialog窗口上的List表中做出相应的变化。

3、设计实现。

3.1 数据管理类(被监听类)。
在这个类里面进行数据结构的构建,数据对象的存储等。

3.1.1 DataManager.h文件。

#pragma once
#include <vector>
#include <set>
using namespace std;

//构建数据结构
struct Student
{
	CString name;
	CString sex;
	int age;
	CString guid;
public:
	Student()
		:name(""),
		sex(""),
		age(0),
		guid("")
	{
	}
	void ResetData()
	{
		name = "";
		sex = "";
		age = 0;
		guid = "";
	}
	CString CreateGuid()
	{
		GUID guid;
		HRESULT hResult = CoCreateGuid(&guid);
		CString strGuid;
		if (S_OK == hResult)
		{
			CComBSTR bstrGuid(guid);
			strGuid = bstrGuid;
		}
		strGuid.TrimLeft(TEXT('{'));
		strGuid.TrimRight(TEXT('}'));
		return strGuid;
	}
};

//关键操作!!!必须在被监听类中设置监听接口,并且在监听类中继承此接口。
class IDataListener
{
public:
	virtual void OnDataAdd(Student item) = 0;
	virtual void OnDataModify(Student item) = 0;
	virtual void OnDataDelete(Student item) = 0;
};

class CDataManager
{
public:
	CDataManager();
	~CDataManager();
	
	//注册监听 把左右监听者对象指针全部存放在被监听者的类中。
	void RegistListener(IDataListener* p)
	{
		m_listeners.insert(p);
	}
	//取消注册 取消注册所有监听者的监听行为。
	void UnRegistListener(IDataListener* p)
	{
		m_listeners.erase(p);
	}
private:
	vector<Student> m_arrStudent;	//被操作的容器里的数据
	set<IDataListener*> m_listeners;//存起来的监听者
public:
	//以下方法都是类外操作数据的行为方法。
	void LoadInfo();
	void GetAllData(vector<Student>& arr);
	void AppendData(Student item);
	void ModifyData(Student item);
	void DeleteData(Student item);
};

extern CDataManager g_dataManager;

3.1.2 DataManager.cpp文件。

#include "stdafx.h"
#include "DataManager.h"

CDataManager::CDataManager()
{
}

CDataManager::~CDataManager()
{
}

void CDataManager::LoadDefaultInfo()
{
#if _DEBUG
	Student ss;
	ss.name = "zhangsan";
	ss.sex = "boy";
	ss.age = 12;
	ss.guid = ss.CreateGuid();
	m_arrStudent.push_back(ss);

	ss.name = "wangcai";
	ss.sex = "boy";
	ss.age = 18;
	ss.guid = ss.CreateGuid();
	m_arrStudent.push_back(ss);

	ss.name = "lili";
	ss.sex = "girl";
	ss.age = 15;
	ss.guid = ss.CreateGuid();
	m_arrStudent.push_back(ss);

	ss.name = "zhaobai";
	ss.sex = "girl";
	ss.age = 16;
	ss.guid = ss.CreateGuid();
	m_arrStudent.push_back(ss);
#endif
}

void CDataManager::GetAllData(vector<Student>& arr)
{
	arr = m_arrStudent;
}

void CDataManager::AppendData(Student item)
{
	m_arrStudent.push_back(item);

	for (auto it : m_listeners)
	{
		it->OnDataAdd(item);
	}
}

void CDataManager::ModifyData(Student item)
{
	for (int i = 0; i < m_arrStudent.size();i++)
	{
		if (m_arrStudent[i].guid == item.guid)
		{
			m_arrStudent[i] = item;
		}
	}

	for (auto it : m_listeners)
	{
		it->OnDataModify(item);
	}
}

void CDataManager::DeleteData(Student item)
{
	for (auto it : m_listeners)
	{
		it->OnDataDelete(item);
	}

	for (int i = 0; i < m_arrStudent.size();i++)
	{
		if (m_arrStudent[i].guid == item.guid)
		{
			m_arrStudent.erase(m_arrStudent.begin() + i);
			break;
		}
	}
}

CDataManager g_dataManager;

3.2 主窗口类(数据操作类)。
在这个类里面对数据对象进行增加、修改、删除基本操作。

3.2.1 List列表初始化。

/*
函数功能:对主窗口List表的初始化。
*/
void CdemoDlg::Init()
{
	m_listCtrl.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
	m_listCtrl.InsertColumn(0, "姓名", LVCFMT_CENTER, 70);
	m_listCtrl.InsertColumn(1, "性别", LVCFMT_CENTER, 50);
	m_listCtrl.InsertColumn(2, "年龄", LVCFMT_CENTER, 50);

	vector<Student> arr;
	g_dataManager.LoadInfo();
	g_dataManager.GetAllData(arr);

	m_listCtrl.DeleteAllItems();
	CString str;
	for (int i = 0; i < arr.size();i++)
	{
		int nRowIdx = m_listCtrl.GetItemCount();
		m_listCtrl.InsertItem(nRowIdx, "");
		m_listCtrl.SetItemText(nRowIdx, 0, arr[i].name);
		m_listCtrl.SetItemText(nRowIdx, 1, arr[i].sex);
		str.Format("%d", arr[i].age);
		m_listCtrl.SetItemText(nRowIdx, 2, str);
		m_listCtrl.SetItemText(nRowIdx, 3, arr[i].guid);
	}
}

3.2.2 主窗口界面添加数据成员。

/*
函数功能:通过主窗口上的三控件组建新的数据对象,更新List列表,把数据对象压入数据管理类的容器,并且通过回调去改变LeftChild窗口的List表。
*/
void CdemoDlg::OnBnClickedButtonAdd()
{
	CString str;
	Student ss;

	GetDlgItem(IDC_EDIT_NAME_ADD)->GetWindowText(str);
	ss.name = str;
	GetDlgItem(IDC_EDIT_SEX_ADD)->GetWindowText(str);
	ss.sex = str;
	GetDlgItem(IDC_EDIT_AGE_ADD)->GetWindowText(str);
	ss.age = atoi(str);
	ss.guid = ss.CreateGuid();

	int nRowIdx = m_listCtrl.GetItemCount();
	m_listCtrl.InsertItem(nRowIdx, "");
	m_listCtrl.SetItemText(nRowIdx, 0, ss.name);
	m_listCtrl.SetItemText(nRowIdx, 1, ss.sex);
	str.Format("%d", ss.age);
	m_listCtrl.SetItemText(nRowIdx, 2, str);
	m_listCtrl.SetItemText(nRowIdx, 3, ss.guid);

	g_dataManager.AppendData(ss);	//关键步骤,由此进入回调,方可对LeftChild窗口的List进行变换操作。
}

3.2.3 主窗口界面修改数据成员。

/*
函数功能:通过主窗口上的三控件组建新的数据对象,把数据对象压入数据管理类的容器,并且通过回调去改变LeftChild窗口的List表,最后根据最新的数据来重写表。
m_stdSelect 是从列表中选中的数据对象。
*/
void CdemoDlg::OnBnClickedButtonModify()
{
	//先修改数据
	CString str;
	GetDlgItem(IDC_EDIT_NAME_MODIFY)->GetWindowText(str);
	m_stdSelect.name = str;
	GetDlgItem(IDC_EDIT_SEX_MODIFY)->GetWindowText(str);
	m_stdSelect.sex = str;
	GetDlgItem(IDC_EDIT_AGE_MODIFY)->GetWindowText(str);
	m_stdSelect.age = atoi(str);

	//再修改子窗口列表
	g_dataManager.ModifyData(m_stdSelect);

	//最后修改主窗口列表
	vector<Student> arr;
	g_dataManager.GetAllData(arr);
	m_listCtrl.DeleteAllItems();

	for (int i = 0; i < arr.size(); i++)
	{
		int nRowIdx = m_listCtrl.GetItemCount();
		m_listCtrl.InsertItem(nRowIdx, "");
		m_listCtrl.SetItemText(nRowIdx, 0, arr[i].name);
		m_listCtrl.SetItemText(nRowIdx, 1, arr[i].sex);
		str.Format("%d", arr[i].age);
		m_listCtrl.SetItemText(nRowIdx, 2, str);
		m_listCtrl.SetItemText(nRowIdx, 3, arr[i].guid);
	}

	m_stdSelect.ResetData();
}

3.2.4 主窗口界面删除数据成员。

/*
函数功能:通过List列表条目上面点击,获取到当前 `Student` 对象,再删除主窗口上的条目,最后处理容器里面的数据并且通过回调处理LeftChild窗口的列表。
*/
void CdemoDlg::OnBnClickedButtonDelete()
{
	//先删除主窗口的列表
	vector<Student> arr;
	g_dataManager.GetAllData(arr);
	for (int i = 0; i < arr.size();i++)
	{
		if (arr[i].guid == m_stdSelect.guid)
		{
			m_listCtrl.DeleteItem(i);
		}
	}

	//再去处理子窗口列表
	g_dataManager.DeleteData(m_stdSelect);
	m_stdSelect.ResetData();
}

3.2.5 主窗口创建子窗口。

/*
函数功能:窗口测试使用的子窗口。
*/
void CdemoDlg::OnBnClickedButtonOpenChild()
{
	m_leftChild.DestroyWindow();
	m_leftChild.Create(CDlgLeftChild::IDD, this);
	m_leftChild.ShowWindow(SW_SHOW);
}

3.2.6 主窗口点击获取当前数据对象。

/*
函数功能:通过点击主窗口上的List,获取条目对象。
*/
void CdemoDlg::OnNMClickList1(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
	// TODO:  在此添加控件通知处理程序代码
	
	int nCuRow = m_listCtrl.GetSelectionMark();
	
	if (nCuRow >= 0)
	{
		vector<Student> arr;
		g_dataManager.GetAllData(arr);
		m_stdSelect = arr[nCuRow];
	}
	
	*pResult = 0;
}

3.3 子窗口类(监听类)。

3.3.1 子窗口类继承监听接口。
关键的步骤

class CDlgLeftChild : public CDialogEx, public IDataListener
{
	DECLARE_DYNAMIC(CDlgLeftChild)
	//...
}

3.3.2 子窗口类声明这些功能接口。

public:
	virtual void OnDataAdd(Student item);
	virtual void OnDataModify(Student item);
	virtual void OnDataDelete(Student item);

3.3.3 源文件中实现这些功能接口。

/*
函数功能:回调添加List控件的条目。
*/
void CDlgLeftChild::OnDataAdd(Student item)
{
	CString str;
	int nRowIdx = m_listCtrl.GetItemCount();
	m_listCtrl.InsertItem(nRowIdx, "");
	m_listCtrl.SetItemText(nRowIdx, 0, item.name);
	m_listCtrl.SetItemText(nRowIdx, 1, item.sex);
	str.Format("%d", item.age);
	m_listCtrl.SetItemText(nRowIdx, 2, str);
	m_listCtrl.SetItemText(nRowIdx, 3, item.guid);
}

/*
函数功能:回调修改List控件的条目。
*/
void CDlgLeftChild::OnDataModify(Student item)
{
	m_listCtrl.DeleteAllItems();

	vector<Student> arr;
	g_dataManager.GetAllData(arr);

	CString str;
	for (int i = 0; i < arr.size(); i++)
	{
		int nRowIdx = m_listCtrl.GetItemCount();
		m_listCtrl.InsertItem(nRowIdx, "");
		m_listCtrl.SetItemText(nRowIdx, 0, arr[i].name);
		m_listCtrl.SetItemText(nRowIdx, 1, arr[i].sex);
		str.Format("%d", arr[i].age);
		m_listCtrl.SetItemText(nRowIdx, 2, str);
	}
}
/*
函数功能:回调删除List控件的条目。
*/
void CDlgLeftChild::OnDataDelete(Student item)
{
	vector<Student> arr;
	g_dataManager.GetAllData(arr);

	for (int i = 0; i < arr.size();i++)
	{
		if (arr[i].guid == item.guid)
		{
			m_listCtrl.DeleteItem(i);
		}
	}
}

3.3.3 源文件中注册监听。

int CDlgLeftChild::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (__super::OnCreate(lpCreateStruct) == -1)
		return -1;

	// TODO:  在此添加您专用的创建代码
	g_dataManager.RegistListener(this);
	return 0;
}

3.3.4 最后,不可以忘记取消注册监听。

void CDlgLeftChild::OnDestroy()
{
	__super::OnDestroy();

	// TODO:  在此处添加消息处理程序代码
	g_dataManager.UnRegistListener(this);
}

4、总结。
其实,我们思考,时其实生活中监听模式用的很多啊。比如说我们点击酷狗的播放按钮,那程序如何知道要播放音乐了呢?监听。再比如说,我们查阅PPT时,会通过点击侧边栏每一页的缩略来切图,这也是监听。