概括
物质 : 通过ID将代码与控件绑定,通过DoDataExchange实现数据交换
运动 : 重写消息控件实现交互,注意重写虚函数
pch.h
Pre-Compiled Header
在编译程序时,如果使用了pch.h,编译器会优先处理这个文件,并将它编译成一个中间文件。在编译其它源文件时,编译器会直接使用这个中间文件,而不再重新编译pch.h。这样可以减少编译时间,因为这些常用的头文件不需要每次都重新编译
MFC Microsoft Foundation Classes
驱动,后台用Win32,设计界面的用MFC
MVC model view controller 模型(doc,存数据),视图,控制
对于控件(图标)以ID来标识,每个页面由一个类来管理
实现机制
封装了Win32
执行阶段
构造类
create 将窗口与类绑定
vie 选择ShowWindow/DoModal 文本/对话框 (可否切换)
销毁窗口
析构类
消息和控件事件
建立键鼠消息等的映射机制,更加方便
类视图->属性->消息,就可以添加对应消息的程序了
IMPLEMENT_DYNCREATE(CPaintView, CView)
BEGIN_MESSAGE_MAP(CPaintView, CView)
//标准消息
ON_WM_MOUSEMOVE()
ON_WM_CHAR()
//命令消息,控件->响应事件
//通告消息,通知事件的发生,即按键的先后顺序,按下一个后另一个接受通知激活
ON_COMMAND(ID_LINE, &CPaintView::OnLine)
//用户自定义消息
END_MESSAGE_MAP()
类
数据序列化 : 将数据扁平化 json(JavaScript Object Notation,轻量级数据传输格式)
1️⃣线性排列的数组,2️⃣数据如{x:100,y:200},根据实际需要弄成文本3️⃣直接复制内存
CObject
-
支持序列化 CArchive
-
提供运行时的类信息
_GetBaseClass(),GetThieClass()
因为类在编译好后一般不包含类信息
- 支持动态创建以及对象诊断输出 如子窗口
AssertVaild() Dump()
关键类
流程
C是Class的意思
CWinapp main函数->thread(创建app后初始化线程)
CDocManager 用来维护一系列文档模板
CDocTemplate 拥有下面三个成员变量 类型为CRUNTIMECLASS
InitInstance->我们的代码
CDocument : 单文档/多文档/对话框 文档模板,负责储存数据
SDI SingleDocumentInterface MDI multiple Dlg dialog
CFrameWnd : 窗口框架 划分区域 菜单栏,工具栏,文件目录等
CView : 负责展示数据,把框架具体化
InitInstance 的部分功能
注 Init只是画图与绑定,运行是WinMain中最后的
nReturnCode = pThread->Run();
// 唯一的 CPaintApp 对象
CPaintApp theApp;
// CPaintApp 初始化
BOOL CPaintApp::InitInstance()
{
//初始化自己
CWinAppEx::InitInstance();
//初始化socket
if (!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
}
// 初始化 OLE 库 activeX
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
//各种manager,afx是一个工作室的名字
AfxEnableControlContainer();
//解决win7的任务栏bug
EnableTaskbarInteraction(FALSE);
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));//注册表
LoadStdProfileSettings(16); // 加载标准 INI 文件选项(包括 MRU)
InitContextMenuManager();
InitKeyboardManager();
InitTooltipManager();
CMFCToolTipInfo ttParams;
ttParams.m_bVislManagerTheme = TRUE;
theApp.GetTooltipManager()->SetTooltipParams(AFX_TOOLTIP_TYPE_ALL,
RUNTIME_CLASS(CMFCToolTipCtrl), &ttParams);
// 注册应用程序的文档模板。 文档模板
// 将用作文档、框架窗口和视图之间的连接
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CPaintDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CPaintView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
// 分析标准 shell 命令、DDE、打开文件操作的命令行
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 启用“DDE 执行”
EnableShellOpen();
RegisterShellFileTypes(TRUE);
// 调度在命令行中指定的命令。 如果
// 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// 唯一的一个窗口已初始化,因此显示它并对其进行更新
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
// 仅当具有后缀时才调用 DragAcceptFiles
// 在 SDI 应用程序中,这应在 ProcessShellCommand 之后发生
// 启用拖/放
m_pMainWnd->DragAcceptFiles();
return TRUE;
}
ID绑定
CView层
添加响应函数和变量时,也是类的操作
.h中定义, .cpp中进行绑定,对变量利用初始化列表,函数就自己双击控件定义
在xxxdlg.cpp中
与响应函数绑定
void CRunBTNDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_BTN1, BTN1);
DDX_Control(pDX, IDC_BTN2, BTN2);
}
与变量绑定
BEGIN_MESSAGE_MAP(CRunBTNDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BTN1, &CRunBTNDlg::OnBnClickedBtn1)
ON_BN_CLICKED(IDC_BTN2, &CRunBTNDlg::OnBnClickedBtn2)
END_MESSAGE_MAP()
变量的数据更新
UpdateData();//将编辑框的数据获取到变量
UpdateData(FALSE);//将变量的数据更新到编辑框
基于文档
线和画刷
利用DC device context 设备环境,定义了一系列关于绘图的东西
.h
protected:
//用于绘制线条起点,当前,终点,以及按下后的状态
CPoint m_start;
CPoint m_cur;
CPoint m_stop;
BOOL m_status;
.cpp
void CPaintView::OnDraw(CDC* pDC)//重绘时调用
{
CPaintDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
//更换后记得还原,否则在类析构后越界访问
CPen pen(PS_DOT,1,RGB(0,255,0));//只有1的时候能看出来
CPen* pOldPen = pDC->SelectObject(&pen);
CBrush brush(RGB(255, 0, 0));
CBrush* pOldBrush = pDC->SelectObject(&brush);
if (m_status) {
pDC->FillRect(CRect(m_start, m_cur), pOldBrush);
//pDC->MoveTo(m_start);
//pDC->LineTo(m_cur);
}
else {
pDC->FillRect(CRect(m_start, m_stop), &brush);
//pDC->MoveTo(m_start);
//pDC->LineTo(m_stop);
}
pDC->SelectObject(pOldPen);
pDC->SelectObject(pOldBrush);
}
//下面三个是捕获消息的
void CPaintView::OnLButtonDown(UINT nFlags, CPoint point)
{
m_start = point;
m_status = TRUE;
CView::OnLButtonDown(nFlags, point); //消息传递
}
void CPaintView::OnLButtonUp(UINT nFlags, CPoint point)
{
m_stop = point;
InvalidateRect(NULL);
m_status = FALSE;
CView::OnLButtonUp(nFlags, point);
}
void CPaintView::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_status) {
InvalidateRect(NULL);
m_cur = point;
}
CView::OnMouseMove(nFlags, point);
}
文本和光标
OnDraw部分
//解决TextOut只能显示单行文本
CString sub = _T("");
int y=0;
for (int i = 0; i < m_strText.GetLength(); i++)
{
if (m_strText.GetAt(i) == '\r'|| m_strText.GetAt(i) == '\n')//'\n'来处理复制粘贴
{
pDC->TextOut(0, y, sub);
CSize sz = pDC->GetTextExtent(sub);
sub.Empty();
y += sz.cy+3;//获取字符宽度,并加上行间距
continue;
}
sub += m_strText.GetAt(i);
}
if (sub.IsEmpty() == false)
pDC->TextOut(0, y, sub);
CSize sz = pDC->GetTextExtent(sub);//获取扩展信息
//CPoint pt;
//pt.y = y;
//pt.x = sz.cx;
SetCaretPos(CPoint(sz.cx+2,y));
//::SetCaretPos(sz.cx+2,y);//系统api
消息捕获部分
int CPaintView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
CClientDC dc(this);
TEXTMETRIC tm;//text metric度量
dc.GetTextMetrics(&tm);
CreateSolidCaret(3, tm.tmHeight);
ShowCaret();
return 0;
}
void CPaintView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
CClientDC dc(this);
m_strText += (TCHAR)nChar;
InvalidateRect(NULL);
CView::OnChar(nChar, nRepCnt, nFlags);
}
菜单栏
资源视图->Menu->IDR_MAINFRAME
描述文字+快捷键 画图(&P) Alt+P
单独 新建(&N)\tCtrl+N ctrl+N
组合
右键添加相应 选择C*View类列表
重载函数的响应优先级 按类View>Doc>Frame>App
调试技巧 输出日志 TRACE("%s(%d):%s\r\n", __FILE__, __LINE__, __FUNCTION__);
工具栏
资源视图->Toolbar->IDR_MAINFRAME(_256)两个
删除 : 按住不动,拖到外面
ID可以与菜单栏一样,就会触发同样的函数
基于对话框
显示窗口
ChtdMFCdllApp theApp;
ChtdMFCdllApp* pTheApp;
CWndMain* m_WndMain;
m_WndMain = new CWndMain();
m_WndMain->Create(IDD_MAIN);
m_WndMain->ShowWindow(true);
拖控件,写响应事件
按住ctrl进行批量操作,对齐时以最后一个为准(黑体边框)
AfxMessageBox
AfxMessageBox(L"afx", MB_OK | MB_ICONINFORMATION, 0);
MessageBox
MessageBox(_T("Ok/Cancel"), _T("标题"),MB_CANCELTRYCONTINUE);
MessageBoxA(0,"hello world","hello",0);
//MB_CANCELTRYCONTINUE
//MB_ICONERROR弹出错误
TRACE
不支持宽字节,进行转化
CString temp = m_list.GetItemText(i, j);
char text[256];
memset(text, 0, sizeof(text));
size_t total;
wcstombs_s(&total, text, sizeof(text), temp, temp.GetLength());
TRACE("%s\n", text);
新建窗口
窗口都是通过建立一个类来管理,各种函数通过重写来弄(初始化,映射)
创建子窗口 : 添加类(从Dialog继承)
资源视图->Dialog->添加资源
模态和非模态窗口的创建
动态创建的好处 : 在使用时再创建,不提前占用资源
当创建时间太长时,可以在开始的时候开一个线程默默创建
#include "CSonWnd.h"
CSonWnd dlg;
void CDlgDlg::OnBnClickedSonWnd()
{
//CSonWnd dlg;
//dlg.DoModal();
//模态对话框,子窗口关闭后才能继续操作父窗口,代码会卡在这里
if(dlg.m_hWnd==NULL)
dlg.Create(IDD_DIG_EDY, this);
dlg.ShowWindow(SW_SHOW);//SW_HIDE
//非模态,因为不阻塞,要保证创建后不消失,就需要在全局进行声明
}
窗口大小
按钮
protected:
CButton m_button;
void CSonWnd::OnBnClickedOk()
{
if (m_button.m_hWnd == NULL)
m_button.Create(_T("dynamic"), BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD, CRect(100, 100, 200, 150), this, 9999);
}
编辑框
多行显示: 行为->multiline,want return, 外观->vertical scroll 这三个设成true
格式化输入: 外观->小写/大写/数字
事件: 输入改变检查(代码基本的语法),屏蔽关键词(脏话)
- 读取
CWnd* pEdit01 = GetDlgItem(IDC_EDIT_ONE);
pEdit01->SetWindowText(_T("100"));
SetDlgItemText(IDC_EDIT_ONE, _T("200"));
CString strText;
pEdit01->GetWindowText(strText);
GetDlgItemText(IDC_EDIT_ONE, strText);
重写控件 ,逃跑按钮
Cbutton的Zorder更高
思路 mousemove和按钮位置后进行调整,
失败 因为按钮Zorder大于主窗口,会捕获mousemove,但是又不会处理,所以需要重载类来响应
CRect curRect;
GetWindowRect(curRect);
curRect.bottom; curRect.top;curRect.Width()
类向导->添加类(右上角)->MFC类->父类为CButton(用来替换原本为CButton类的)
然后添加成员函数mousemove用来响应
void BTN::OnMouseMove(UINT nFlags, CPoint point)
{
ShowWindow(SW_HIDE);
Other->ShowWindow(SW_SHOW);
CButton::OnMouseMove(nFlags, point);
}
属性页
Dlg->button->sheet->page
相关控件
- 单选框(radio)
ctrl+D查看顺序(默认是创建顺序,Tab顺序),可以通过点击来改变
单选框第一个需要设置成group属性
- 复选框(checked)
在添加变量时可以添加数组,而不是一个一个添加
memset(m_skill, 0, sizeof(m_skill));
- 列表框(list ) 类视图->重写OnInitDialog
属性中把排序可以关掉,因为对中文排序规则有点奇怪
BOOL PROP_01::OnInitDialog()
{
CPropertyPage::OnInitDialog();
// TODO: 在此添加额外的初始化
//两种添加方式
CListBox* pListBox = (CListBox*)GetDlgItem(IDC_LIST1);
if (pListBox) {
pListBox->AddString(_T("ALI"));
pListBox->AddString(_T("HUAWEI"));
pListBox->AddString(_T("JD"));
m_com.AddString(_T("APPLE"));
}
return TRUE;
}
- 下拉框 (Combo) 属性->行为->Data->以分号隔开
多页面切换
创建页面
资源视图,新建对话框
添加MFC类用来管理页面,继承自CPropertyPage
类名应该与页面名相同IDD_PAGE_01应该用PAGE_01
切换页面
通过创建CPropertySheet的继承类来实现切换
.h
#include "PROP_01.h"
#include "PROP_02.h"
public:
PROP_01 m_prop1;
PROP_02 m_prop2;
.cpp
有两个构造函数,都要添加
AddPage(&m_prop1);
AddPage(&m_prop2);
- 跳转到多页面切换
其实都可以做成数组形式,代码就更简洁
void CRunBTNDlg::OnBnClickedQueryBtn()
{
CPropSheet dlg(_T("调查"), this);
dlg.SetWizardMode();//wizard 向导 下一步上一步
if (ID_WIZFINISH == dlg.DoModal())
{
//创建成功后,将数据返回到父窗口
CString strMsg = _T("your lang: ");
//单选
switch (dlg.m_prop1.m_lang)
{
case 0:strMsg += "C++";break;
case 1:strMsg += "Java";break;
case 2:strMsg += "Python";break;
}
//多选
CString strSkill[4]={
_T("数据结构"),_T("计算机组成原理"),_T("操作系统"),_T("计算机网络")}
for(int i=0;i<4;i++)
if(dlg.m_prop1.m_skill[i])
strMsg += strSkill[i] + _T(",");
MessageBox(strMsg);
}
}
- sheet相关按钮自定义
重写每一页类的OnSetActive()
BOOL PROP_01::OnSetActive()
{
//第一页只需要next //PSWIZB_BACK | PSWIZB_FINISH 用于中间页和最后一页
((CPropertySheet*)GetParent())->SetWizardButtons(PSWIZB_NEXT);
//去掉帮助按钮
((CPropertySheet*)GetParent())->GetDlgItem(IDHELP)->ShowWindow(SW_HIDE);
return CPropertyPage::OnSetActive();
}
- 检查是否选择
LRESULT PROP_01::OnWizardNext()//wizard向导,巫师
{
UpdateData(TRUE);
//TRUE表示将控件的值传到变量 FALSE相反
//检查单选框
if (m_lang == -1){
MessageBox(_T("请选择开发语言"));
return -1;
}
//检查复选框
bool skillchoose = false;
for(auto x : skill)
skillchoose |= x;
if(!skillchoose){
MessageBox(_T("请选择技能"));
return -1;
}
//检查列表框
if (m_company == ""){
MessageBox(_T("请选择公司"));
return -1;
}
return CPropertyPage::OnWizardNext();
}
其他常用控件
列表框
当设置为多选时,添加控件和响应函数
void PROP_01::OnBnClickedOk()
{
CString strText;
int total = m_com.GetSelCount();
if (total == 0) {
MessageBox(_T("没有选择公司"));return;}
else {
int* index = new int[total];
m_com.GetSelItems(total, index);
CString strTemp;
for (int i = 0; i < total; i++)
{
m_com.GetText(index[i], strTemp);
strText += strTemp + _T(" ");
}
delete[] index;
MessageBox(strText);
}
}
下拉框
添加项 : 属性->行为->Data->以分号隔开
是编辑框和列表框的部分集合
m_simple.GetCurSel();
GetLBText();//LB ListBox
进度条
添加控件,利用定时器来定时获取后台数据,循环的i,j映射到(0,100)
int m_pro_pos;//全局的进度,用来存放i,j的映射
//初始化
{
//m_progress为控件名,设置范围和定时器
m_progress.SetRange(0, 100);//默认范围为int,可设置为SetRange32
SetTimer(99, 500, NULL);//(ID,触发间隔,NULL),可以设多个定时器,触发间隔不低于30帧,误差小
}
void PROP_01::OnTimer(UINT_PTR nIDEvent)
{
//添加消息中的timer
if (nIDEvent == 99)
m_progress.SetPos(m_pro_pos);
CPropertyPage::OnTimer(nIDEvent);
if(m_pro_pos==100)KillTimer(99);
/*if (nIDEvent == 99)//进度条自增
{
m_progress.SetPos(m_pro_pos);
if (m_pro_pos < 100)m_pro_pos++;
}*/
//int lower,upper
//m_progress.SetRange(lower, upper);
}
marquee 选取框 循环的进度,表示加载中,设为True后,初始化时填下面代码
m_progress.SetMarquee(TRUE, 50);
报表控件
对窗口大小和报表样式,都是先获得CRECT或DWORD,设置后再赋值回去
mTab.GetClientRect(&rect);
Pages[i]->MoveWindow(&rect);
DWORD extStyle = ExeList.GetExtendedStyle();
ExeList.SetExtendedStyle(extStyle);
list control 类似任务管理器那种
外观->always show selection/->true 视图->report 单选/多选
行为->排序/none
添加控件 CListCtrl m_list;
后,在初始化时
//背景色和文字背景
m_list.SetBkColor(RGB(0,0,255));
m_list.SetTextBkColor(RGB(0,0,255));
//整体风格
DWORD extStyle = m_list.GetExtendedStyle();
//LVS -- List View Style
extStyle |= LVS_EX_FULLROWSELECT;//选择一行
extStyle |= LVS_EX_GRIDLINES;//网格背景
extStyle |= LVS_EX_CHECKBOXES;//勾选
m_list.SetExtendedStyle(extStyle);
//设置列
//list view coloumn format
m_list.InsertColumn(0, _T("序号"), LVCFMT_LEFT, 50);
m_list.InsertColumn(1, _T("IP"), LVCFMT_LEFT, 150);
m_list.InsertColumn(2, _T("ID"), LVCFMT_LEFT, 100);
//添加行
m_list.InsertItem(0, _T("0"));//(新建一行
m_list.SetItemText(0, 1, _T("192.168.0.1"));//(插入某行的第几个
m_list.SetItemText(0, 2, _T("2e4a4f"));
获取数据
int LineCount = m_list.GetItemCount();
CHeaderCtrl* pHeader = m_list.GetHeaderCtrl();
int ColumnCount = pHeader->GetItemCount();
for (int i = 0; i < LineCount; i++)
{
//获取选中状态
if (m_list.GetCheck(i)) {
TRACE("选中");}
else TRACE("未选中");
for (int j = 0; j < ColumnCount; j++)
{
CString temp = m_list.GetItemText(i, j);
MessageBox(temp);
}
}
获取双击状态
void CWndINJ::OnNMDblclkList1(NMHDR* pNMHDR, LRESULT* pResult)
{
//获取选中状态 NM : notification message HDR : handler
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
*pResult = 0;
//只有下面一句,前面是自动生成的
int index = pNMItemActivate->iItem;
}
树形控件
如左边的文件树
public:
CTreeCtrl m_tree;
CImageList m_icons;
//创建图像列表来管理图像
m_icons.Create(IDB_TREE, 32, 2, 0);
m_tree.SetImageList(&m_icons, TVSIL_NORMAL);
//文件的内容,句柄用以关联子结点
HTREEITEM hRoot = m_tree.InsertItem(_T("root"),0,1);
HTREEITEM hLeaf1 = m_tree.InsertItem(_T("leaf"),1,1,hRoot);
Tab control
属性->动态布局->调整大小类型-> 两者
然后添加每个tab对话框,注意将边框设为None,外观->样式->child
创建子窗口,因为有很多子窗口,就把该过程封装成函数
bool CGameHackerDlg::InstallPage(CDialogEx* wnd, int IDD_WND, CString&& _PageName)
{
if (CurPage >= MAX_PAGE)return false;
else
{
//用数组的方式显示窗口
Pages[CurPage] = wnd;
Pages[CurPage]->Create(IDD_WND);
Pages[CurPage]->SetParent(this);
//PageINJ.Create(IDD_PAGE_1, this);//有问题
Pages[CurPage]->ShowWindow(false);
//更改窗口的位置,防止切换页面时子窗口覆盖,(⊙﹏⊙)
CRect rect;
mTab.GetClientRect(&rect);
rect.top += 48;
rect.left += 6;
Pages[CurPage]->MoveWindow(&rect);
mTab.InsertItem(CurPage, _PageName);
CurPage++;
return true;
}
}
获取消息 mTab.GetCurSel()
void CGameHackerDlg::OnTcnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)
{
*pResult = 0;
int n = mTab.GetCurSel();
for (int i = 0; i < CurPage; i++)
Pages[i]->ShowWindow(i == n);
}