1、引言。
监听模式,指事件源经过事件的封装传给监听器,当事件源触发事件后,监听器接收到事件对象可以回调事件的方法。顾名思义,它就是对某一目标行为进行监听并且做出应对反映的模式。常见的应用场景有网站在线人数统计,监听用户的行为,win32的消息机制。
2、本文实例概述。
我在这里就用VC++来举例子以MVC的模式,建立一个一对多的依赖关系。
如上图所示:通过MainDialog的添加、修改、删除按钮,来操作List表中的数据对象。实现效果:LeftChild窗口对数据管理类进行监听,当MainDialog操作改变数据对象时,就应该在LeftDialog窗口上的List表中做出相应的变化。
3、设计实现。
3.1 数据管理类(被监听类)。
在这个类里面进行数据结构的构建,数据对象的存储等。
3.1.1 DataManager.h文件。
#pragma once
#include
#include
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时,会通过点击侧边栏每一页的缩略来切图,这也是监听。
入门萌新,浅知拙见,若有斧正,不胜感激。^ - ^