MFC傻瓜式教程
本教程重操作,轻理论,为操作减负。需了解详细原理的朋友可以自行看各种书籍。
MFC:Microsoft Foundation Class ,微软基础类库。
对话框的创建和显示
- 新建MFC AppWizard(exe)工程,单文档类型。工程名:Mybole。编译运行。
点击帮助-关于Mybole。这是MFC自动创建的。
创建自己的对话框。点击Insert-Resource。选择Dialog,点击New。VC++自动将其标识设置为IDD_DIALOG1,并自动添加到ResourceView-Dialog项中。Dialog项下还有一个对话框资源标识:IDD_ABOUTBOX,即上一步中的“关于”对话框。
选中对话框本身,右键点击属性。将Caption设置为“测试”。
- 选择View-ClassWizard,点击create a new class,OK。出现下图,并输入下图选项。
- 在随后出现的MFC ClassWizard对话框上点击OK。
注意:看看左侧类列表中是否添加好了CTestDlg,否则会影响后续操作。
接下来,我们希望在程序中显示这个对话窗口。
点击右侧菜单Menu,选中IDR_MAINFRAME。点击帮助旁边的虚线框。
对虚线框右键属性,修改为下图。
关闭属性。点击View-ClassWizard(中文是建立类向导),选择CMyboleView,用COMMAND命令消息响应函数。如图。
模态对话框的创建
需要调用CDialog类的成员函数:DoModal,它能创建并显示一个模态对话框,其返回值将作为CDialog类的另一个成员函数:EndDialog的参数,后者功能是关闭模态对话框。
在FileView中选择MyboleView.cpp,编写程序。
记得在开头添加头文件 #include “testdlg.h” (头文件大小写问题,linux区分,windows不区分)
显示模态对话框的具体实现代码:
void CMyboleView::OnDialog()
{
// TODO: Add your command handler code here
CTestDlg dlg;
dlg.DoModal();
}
编译运行,点击对话框。会发现若不确认该窗口,将无法点击其他窗口。
非模态对话框的创建
将上面的模态对话框代码注释掉。
改为:
void CMyboleView::OnDialog()
{
// TODO: Add your command handler code here
//CTestDlg dlg;
//dlg.DoModal();
CTestDlg *pDlg = new CTestDlg;
pDlg->Create(IDD_DIALOG1,this);
pDlg->ShowWindow(SW_SHOW);
}
注意:需要把之前运行的对话框关掉才能编译成功。
然而,当它生命周期结束时,所保存的内存地址就丢失了,那么程序中也就无法再引用到它所指向的那块内存。于是,我们这样解决该问题。
注意:Message里双击添加函数或者点击add Class…
void CTestDlg::PostNcDestroy()
{
// TODO: Add your specialized code here and/or call the base class
delete this;
CDialog::PostNcDestroy();
}
区别:点击确定,对话框都会消失。但是,模态对话框窗口对象被销毁了。对非模态对话框来说,只是隐藏起来了,并未被销毁。
因此,若要销毁对话框,若有一个ID为IDOK的按钮,就必须重写基类的OnOK这个虚函数,并在重写的函数中调用DestroyWindow函数,完成销毁。并不要再调用基类的OnOK函数。
同样地,若有一个ID为IDCANCEL的按钮,也必须重写基类的OnCancel虚函数,并在重写的函数中调用DestroyWindow函数,完成销毁。并不要再调用基类的OnCancel函数。
注释掉非模态对话框代码,还原模态对话框代码。
点击ResourceView-IDD_DIALOG1,打开资源,用鼠标拖出控件面板上的Button按钮控件,对按钮右键,选择属性,设置如下。
接下来,我们实现当单击Add按钮时,在对话框中动态创建一个按钮这一功能。
为CTestDlg类添加一个私有的CButton成员变量。
点击ClassView标签页右键,如图点击。
填入信息。
添加Add按钮单击消息的响应函数。
按钮点右键,选ClassWizard(建立类向导),如图。
单击Edit Code,即可定位到该函数定义处。
添加一下代码:
void CTestDlg::OnBtnAdd()
{
// TODO: Add your control notification handler code here
m_btn.Create("New",BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD,
CRect(0,0,100,100),this,123);
}
为避免多次点击Add出现非法操作,我们需要进行如下步骤。
为CTestDlg类增加一个私有的BOOL类型成员变量。
变量类型:BOOL
变量名称:m_bIsCreated
Access: private在TestDlg.cpp中找到构造函数,将m_bIsCreated初始为FALSE。如图所示。
或者改为如下亦可。
Static BOOL bIsCreated = FALSE;回到Add,双击它,进入代码部分,改之。
void CTestDlg::OnBtnAdd()
{
// TODO: Add your control notification handler code here
if(m_bIsCreated==FALSE)
{
m_btn.Create("New",BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD,
CRect(0,0,100,100),this,123);
m_bIsCreated = TRUE;
}
else
{
m_btn.DestroyWindow();
m_bIsCreated = FALSE;
}
}
或者以下亦能实现。
void CTestDlg::OnBtnAdd()
{
// TODO: Add your control notification handler code here
if(!m_btn.m_hWnd)
{
m_btn.Create("New",BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD,
CRect(0,0,100,100),this,123);
}
else
{
m_btn.DestroyWindow();
}
}
效果:
点击Add出现New窗口,再点击就销毁。
控件的访问
控件的调整
用Layout-Align,Layout-Make Same Size,Layout-Space Evenly里的选项进行调整。
静态文本控件
查看三个静态文本框,它们ID相同。我们可以更改第一个静态文本框ID为IDC_NUMBER1,再打开ClassWizard,可以在ObjectIDs看到新ID。
对BN_CLICKED进行Add Function,并Edit Code:
此时运行程序点击第一个静态文本框并没有反应。这是因为:静态文本控件在默认状态下是不发送通告消息的。
为了该控件能向父窗口发送鼠标事件,我们对该文本框右键-属性,切换到styles选项卡,勾上Notify。
现在可以显示了:
点击就改变。
总结:为了使一个静态文本控件能够响应鼠标单击消息,那么需要进行两个特殊的步骤:第一步,改变它的ID;第二步,在它的属性对话框中选中Notify选项。
编辑框控件
利用上面的对话框实现这样的功能:在前两个编辑框中分别输入一个数字,然后单击Add按钮,对前两个编辑框中的数字求和,并将结果显示在第三个编辑框中。
第一种方式
void CTestDlg::OnBtnAdd()
{
int num1, num2, num3;
char ch1[10], ch2[10], ch3[10];
GetDlgItem(IDC_EDIT1)->GetWindowText(ch1,10);
GetDlgItem(IDC_EDIT2)->GetWindowText(ch2,10);
num1 = atoi(ch1);
num2 = atoi(ch2);
num3 = num1 + num2;
itoa(num3,ch3,10);
GetDlgItem(IDC_EDIT3)->SetWindowText(ch3);
}
C语言转换函数:atoi 将一个由数字组成的字符串转换为相应的数值
itoa 数值转换为文本
itoa函数的第三个参数表示转换的进制,数字10表示十进制。
效果:
第二种方式
代码如下:
void CTestDlg::OnBtnAdd()
{
int num1, num2, num3;
char ch1[10], ch2[10], ch3[10];
//GetDlgItem(IDC_EDIT1)->GetWindowText(ch1,10);
//GetDlgItem(IDC_EDIT2)->GetWindowText(ch2,10);
GetDlgItemText(IDC_EDIT1,ch1,10);
GetDlgItemText(IDC_EDIT2,ch2,10);
num1 = atoi(ch1);
num2 = atoi(ch2);
num3 = num1 + num2;
itoa(num3,ch3,10);
//GetDlgItem(IDC_EDIT3)->SetWindowText(ch3);
SetDlgItemText(IDC_EDIT3,ch3);
}
GetDlgItemText 将返回对话框中指定ID的控件上的文本,相当于将上面的GetDlgItem和GetWindowText这两个函数功能组合起来了。
与之对应的是SetDlgItemText,用来设置对话框中指定ID的控件上的文本。
第三种方式
void CTestDlg::OnBtnAdd()
{
int num1, num2, num3;
//char ch1[10], ch2[10], ch3[10];
//GetDlgItem(IDC_EDIT1)->GetWindowText(ch1,10);
//GetDlgItem(IDC_EDIT2)->GetWindowText(ch2,10);
//GetDlgItemText(IDC_EDIT1,ch1,10);
//GetDlgItemText(IDC_EDIT2,ch2,10);
num1 = GetDlgItemInt(IDC_EDIT1);
num2 = GetDlgItemInt(IDC_EDIT2);
//num1 = atoi(ch1);
//num2 = atoi(ch2);
num3 = num1 + num2;
//itoa(num3,ch3,10);
//GetDlgItem(IDC_EDIT3)->SetWindowText(ch3);
//SetDlgItemText(IDC_EDIT3,ch3);
SetDlgItemInt(IDC_EDIT3,num3);
}
第四种方式
将这三个编辑框分别与对话框类的三个成员变量相关联,然后通过这些成员变量来检索和设置编辑框的文本,这是最简单的访问控件的方式。
打开ClassWizard对话框,切换到Member Variables选项卡,如图。
首先为IDC_EDIT1编辑框添加一个关联的成员变量,方法是在Control IDs列表中选中IDC_EDIT1,再单击Add Variable按钮,如图。
同样地,为IDC_EDIT2和IDC_EDIT3分别添加好成员变量。
接着修改代码:
void CTestDlg::OnBtnAdd()
{
UpdateData();
m_num3 = m_num1 + m_num2;
UpdateData(FALSE);
}
对编辑框控件中输入的数值设定一个范围:
打开ClassWizard-Member Variable,选中IDC_EDIT1,下方输入0和100。同样为IDC_EDIT2也设置好。
第五种方式
将编辑框控件再与一个变量相关联,代表控件本身。为IDC_EDIT1增加一个控件类型的变量:m_edit1,类别为Control。同样地,也为IDC_EDIT2和IDC_EDIT3添加。
修改代码:
void CTestDlg::OnBtnAdd()
{
int num1, num2, num3;
char ch1[10], ch2[10], ch3[10];
m_edit1.GetWindowText(ch1,10);
m_edit2.GetWindowText(ch2,10);
//num1 = GetDlgItemInt(IDC_EDIT1);
//num2 = GetDlgItemInt(IDC_EDIT2);
num1 = atoi(ch1);
num2 = atoi(ch2);
num3 = num1 + num2;
itoa(num3,ch3,10);
m_edit3.SetWindowText(ch3);
}
第六种方式
修改代码:
void CTestDlg::OnBtnAdd()
{
int num1, num2, num3;
char ch1[10], ch2[10], ch3[10];
::SendMessage(GetDlgItem(IDC_EDIT1)->m_hWnd, WM_GETTEXT, 10, (LPARAM)ch1);
::SendMessage(m_edit2.m_hWnd, WM_GETTEXT, 10, (LPARAM)ch2);
num1 = atoi(ch1);
num2 = atoi(ch2);
num3 = num1 + num2;
itoa(num3,ch3,10);
m_edit3.SendMessage(WM_SETTEXT, 0, (LPARAM)ch3);
}
第七种方式
修改代码:
void CTestDlg::OnBtnAdd()
{
int num1, num2, num3;
char ch1[10], ch2[10], ch3[10];
SendDlgItemMessage(IDC_EDIT1, WM_GETTEXT, 10, (LPARAM)ch1);
SendDlgItemMessage(IDC_EDIT2, WM_GETTEXT, 10, (LPARAM)ch2);
num1 = atoi(ch1);
num2 = atoi(ch2);
num3 = num1 + num2;
itoa(num3,ch3,10);
SendDlgItemMessage(IDC_EDIT3, WM_SETTEXT, 0, (LPARAM)ch3);
}
获得编辑框复选的内容:
在上述代码最后添加:
SendDlgItemMessage(IDC_EDIT3, EM_SETSEL, 0, -1); //0,-1表示全选若1,3表示选中1-3位复选
m_edit3.SetFocus();
效果:
总结
1 GetDlgItem()->Get(Set)WindowTest()
2 GetDlgItemText()/SetDlgItemText()
3 GetDlgItemInt()/SetDlgItemInt()
4 将控件和整型变量相关联
5 将控件和控件变量相关联
6 SendMessage()
7 SendDlgItemMessage()
最常用是1、4、5。在利用MFC编程时,6、7用得少。
对话框伸缩功能的实现
对话框上再添加一个按钮,Caption设置为“收缩<<”点击ClassWizard,添加一个命令相应函数(BN_CLICKED)。具体实现代码为:
void CTestDlg::OnButton1()
{
CString str;
if(GetDlgItemText(IDC_BUTTON1,str), str == "收缩<<")
{
SetDlgItemText(IDC_BUTTON1, "拓展>>");
}
else
{
SetDlgItemText(IDC_BUTTON1, "收缩<<");
}
}
拖动一个图像控件来划分对话框中要动态切除的部分。
修改该控件ID为IDC_SEPATATOR,styles选项卡勾上Sunken选项。
修改代码:
void CTestDlg::OnButton1()
{
CString str;
if(GetDlgItemText(IDC_BUTTON1,str), str == "收缩<<")
{
SetDlgItemText(IDC_BUTTON1, "拓展>>");
}
else
{
SetDlgItemText(IDC_BUTTON1, "收缩<<");
}
static CRect rectLarge;
static CRect rectSmall;
CRect rect1(10,10,10,10);
CRect rect2(0,0,0,0);
if(rectLarge.IsRectNull())
{
CRect rectSeparator;
GetWindowRect(&rectLarge);
GetDlgItem(IDC_SEPARATOR)->GetWindowRect(&rectSeparator);
rectSmall.left=rectLarge.left;
rectSmall.top=rectLarge.top;
rectSmall.right=rectLarge.right;
rectSmall.bottom=rectSeparator.bottom;
}
if(str == "收缩<<")
{
SetWindowPos(NULL, 0, 0, rectSmall.Width(), rectSmall.Height(), SWP_NOMOVE | SWP_NOZORDER);
}
else
{
SetWindowPos(NULL, 0, 0, rectLarge.Width(), rectLarge.Height(), SWP_NOMOVE | SWP_NOZORDER);
}
}
效果:
点击“收缩<<”:
若希望隐藏分隔条,则设置属性去掉“Visible”前的勾。
输入焦点的传递
为了屏蔽掉默认的回车键关闭对话框这一功能,应该在对话框子类(此处是CTestDlg类)中重写OK按钮的消息响应函数。
首先点击OK按钮,添加鼠标单击消息响应函数。注释掉原有函数。
法一
在ClassView选项卡的CTestDlg类添加WM_INITDIALOG消息的响应函数。对类右键,选择Add Windows Message Handler,在弹出的框左侧选择WM_INITDIALOG,直接单击Add and Edit,跳转。
修改代码为:
void CTestDlg::OnOK()
{
// TODO: Add extra validation here
//CDialog::OnOK();
}
WNDPROC prevProc;
LRESULT CALLBACK NewEditProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
if(uMsg == WM_CHAR && wParam == 0x0d)
{
::SetFocus(GetNextWindow(hwnd,GW_HWNDNEXT));
return 1;
}
else
{
return prevProc(hwnd,uMsg,wParam,lParam);
}
}
BOOL CTestDlg::OnInitDialog()
{
CDialog::OnInitDialog();
prevProc=(WNDPROC)SetWindowLong(GetDlgItem(IDC_EDIT1)->m_hWnd,
GWL_WNDPROC, (LONG)NewEditProc);
return TRUE;
}
查看第一个编辑框的属性,打开styles选项卡,勾上MultiLine(多行)。即可实现焦点的传递。
法二
只需要改变一行代码:
WNDPROC prevProc;
LRESULT CALLBACK NewEditProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
if(uMsg == WM_CHAR && wParam == 0x0d)
{
//::SetFocus(GetNextWindow(hwnd,GW_HWNDNEXT));
SetFocus(::GetWindow(hwnd,GW_HWNDNEXT));
return 1;
}
else
{
return prevProc(hwnd,uMsg,wParam,lParam);
}
}
法三
编辑框属性有一个WS_TABSTOP,如果勾选了,则在对话框中按下Tab键后,输入焦点可以转移到此控件上。
修改一行代码:
WNDPROC prevProc;
LRESULT CALLBACK NewEditProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
if(uMsg == WM_CHAR && wParam == 0x0d)
{
SetFocus(::GetNextDlgTabItem(::GetParent(hwnd),hwnd,FALSE));
//::SetFocus(GetNextWindow(hwnd,GW_HWNDNEXT));
//SetFocus(::GetWindow(hwnd,GW_HWNDNEXT));
return 1;
}
else
{
return prevProc(hwnd,uMsg,wParam,lParam);
}
}
三种方法的缺点:只修改了第一个编辑框的窗口过程,因此从第二到第三个编辑框的焦点转移无法实现,除非继续修改第二个编辑窗口。
再介绍一种方法解决这个问题。
法四
在MFC中,默认情况下,当在对话框窗口中按下回车键时,会调用对话框的默认按钮的响应函数,我们可以在此默认按钮的响应函数中把焦点依次向下传递。
首先取消第一个编辑框的MultiLine。
接着修改OnOK函数为:
void CTestDlg::OnOK()
{
// TODO: Add extra validation here
//GetDlgItem(IDC_EDIT1)->GetNextWindow()->SetFocus();
//GetFocus()->GetNextWindow()->SetFocus();
//GetFocus()->GetWindow(GW_HWNDNEXT)->SetFocus();
GetNextDlgTabItem(GetFocus())->SetFocus();
//CDialog::OnOK();
}```
注释掉的部分是各种失败的尝试,各有各的bug。现在程序是正常的。
**注意:然而该屏蔽回车键的方法并非是常规做法,应该在PreTranslateMessage中进行拦截。(return TRUE即拦截)**
具体做法:
现在Testdlg.h中添加:
```C++
class CTestDlg : public CDialog
{
protected:
virtual BOOL PreTranslateMessage(MSG* pMsg);
public:
virtual void OnOK();
……
<div class="se-preview-section-delimiter"></div>
接着:
CTestDlg::PreTranslateMessage(MSG* pMsg)
{
//屏蔽ESC关闭窗体
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_ESCAPE)
{
return TRUE;
}
//屏蔽回车关闭窗体,但会导致回车在窗体上失效.
/*
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN && pMsg->wParam)
{
return TRUE;
}
*/
else
{
return CDialog::PreTranslateMessage(pMsg);
}
}
void CTestDlg::OnOK()
{
// TODO: Add extra validation here
//CDialog::OnOK();
}
<div class="se-preview-section-delimiter"></div>
点击Layout-Tab order,这些序号就是各控件的Tab顺序。顺序可改变,依次点击希望的顺序控件即可。
调用顺序:当用户按下回车键时,Windows将查看对话框中是否存在指定的默认按钮,如果有,就调用该默认按钮单击消息的响应函数。如果没有,就会调用虚拟的OnOK函数,即使对话框没有包含默认的OK按钮(这个默认OK按钮的ID是IDOK)。
C语言对文件操作的支持
新建单文档类型的MFC应用程序,工程名为File,并为主菜单添加一个子菜单,名称为“文件操作”,然后为其添加两个菜单项,并分别为它们添加相应的命令响应函数(通过COMMAND),让CFileView类接收这些菜单项的命令响应。
文件的打开和写入
代码:
void CFileView::OnFileWrite()
{
FILE *pFile = fopen("1.txt","w");
fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile);
}
<div class="se-preview-section-delimiter"></div>
编译后可看到文件夹中生成了1.txt,打开有一行网址。
文件的关闭
增加一行代码:
void CFileView::OnFileWrite()
{
FILE *pFile = fopen("1.txt","w");
fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile);
fclose(pFile);
}
<div class="se-preview-section-delimiter"></div>
文件指针定位
代码:
void CFileView::OnFileWrite()
{
FILE *pFile = fopen("1.txt","w");
fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile);
fwrite("欢迎访问", 1, strlen("欢迎访问"), pFile);
fclose(pFile);
}
<div class="se-preview-section-delimiter"></div>
显示:http://www.sunxin.org欢迎访问
将文件指针移动到文件的开始位置处:
代码:
void CFileView::OnFileWrite()
{
FILE *pFile = fopen("1.txt","w");
fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile);
fseek(pFile, 0, SEEK_SET);
fwrite("ftp:", 1, strlen("ftp:"),pFile);
//fwrite("欢迎访问", 1, strlen("欢迎访问"), pFile);
fclose(pFile);
}
<div class="se-preview-section-delimiter"></div>
显示:ftp:://www.sunxin.org
文件的读取
在OnFileRead函数中写入代码:
void CFileView::OnFileRead()
{
FILE *pFile = fopen("1.txt","r");
char ch[100];
fread(ch, 1, 100, pFile);
fclose(pFile);
MessageBox(ch);
}
<div class="se-preview-section-delimiter"></div>
编译运行:
原因:C语言以“ ”结束。
解决方法:
法一:
修改代码:
void CFileView::OnFileWrite()
{
FILE *pFile = fopen("1.txt","w");
char buf[22] = "http://www.sunxin.org";
buf[21] = " ";
fwrite(buf, 1, 22, pFile);
fclose(pFile);
}
<div class="se-preview-section-delimiter"></div>
先点击写入文件,再点击读取文件,就可以看到正确的内容。
缺点:增加了文件大小。
法二:
void CFileView::OnFileRead()
{
FILE *pFile = fopen("1.txt","r");
char ch[100];
memset(ch, 0, 100);
fread(ch, 1, 100, pFile);
fclose(pFile);
MessageBox(ch);
}
<div class="se-preview-section-delimiter"></div>
法三:
读取文件时,不知道文件大小时的做法。
void CFileView::OnFileRead()
{
FILE *pFile = fopen("1.txt","r");
char *pBuf;
fseek(pFile, 0, SEEK_END);
int len=ftell(pFile);
pBuf = new char[len+1];
rewind(pFile);
fread(pBuf, 1, len, pFile);
pBuf[len] = 0;
fclose(pFile);
MessageBox(pBuf);
}
<div class="se-preview-section-delimiter"></div>
二进制文件和文本文件
代码:
void CFileView::OnFileWrite()
{
FILE *pFile = fopen("2.txt", "w");
char ch[3];
ch[0] = "a";
ch[1] = 10;
ch[2] = "b";
fwrite(ch, 1, 3, pFile);
fclose(pFile);
}
void CFileView::OnFileRead()
{
FILE *pFile = fopen("2.txt","r");
char ch[100];
fread(ch, 1, 3, pFile);
ch[3] = 0;
fclose(pFile);
MessageBox(ch);
}
<div class="se-preview-section-delimiter"></div>
效果:
文本方式:10实际上是换行符的ASCII码。
以文本方式和二进制方式读取文件是有明显的区别的。
文本方式和二进制方式
二进制方式:换行是由两个字符组成的,即ASCII码10(回车符)和13(换行符)。
写入和读取文件时要保持一致。如果采用文本方式写入,应采用文本方式读取;如果采用二进制方式写入数据,在读取时也应采用二进制方式。
面试题:给你一个整数,如:98341,将这个整数保存到文件中,要求在以记事本程序打开该文件时,显示的是:98341。
法一:
void CFileView::OnFileWrite()
{
FILE *pFile = fopen("3.txt", "w");
char ch[5];
ch[0] = 9 + 48;
ch[1] = 8 + 48;
ch[2] = 3 + 48;
ch[3] = 4 + 48;
ch[4] = 1 + 48;
fwrite(ch, 1, 5, pFile);
fclose(pFile);
}
<div class="se-preview-section-delimiter"></div>
或
void CFileView::OnFileWrite()
{
FILE *pFile = fopen("3.txt", "w");
int i = 98341;
char ch[5];
itoa(i, ch, 10);
fwrite(ch, 1, 5, pFile);
fclose(pFile);
}
<div class="se-preview-section-delimiter"></div>
面试题:给定一个字符串,其中既有数字字符,又有26个英文字母中的几个字符,让你判断一下哪些是数字字符。
对这种问题,实际上就是判断各字符的ASCII码,对于数字字符来说,它们的ASCII码大于等于48,小于等于57。
C++对文件操作的支持
void CFileView::OnFileWrite()
{
ofstream ofs("4.txt");
ofs.write("http://www.sunxin.org",strlen("http://www.sunxin.org"));
ofs.close;
}
void CFileView::OnFileRead()
{
ifstream ifs("4.txt");
char ch[100];
memset(ch, 0, 100);
ifs.read(ch,100);
ifs.close();
MessageBox(ch);
}
<div class="se-preview-section-delimiter"></div>
Win32 API 对文件操作的支持
文件的创建、打开和写入
void CFileView::OnFileWrite()
{
//定义一个句柄变量
HANDLE hFile;
//创建文件
hFile = CreateFile("5.txt", GENERIC_WRITE, 0, NULL, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL, NULL);
//接收实际写入的字节数
DWORD dwWrites;
//写入数据
WriteFile(hFile,"http://www.sunxin.org",strlen("http://www.sunxin.org"),
&dwWrites, NULL);
//关闭文件句柄
CloseHandle(hFile);
}
<div class="se-preview-section-delimiter"></div>
文件的读取
void CFileView::OnFileRead()
{
HANDLE hFile;
//打开文件
hFile = CreateFile("5.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
//接收实际收到的数据
char ch[100];
//接收实际读取到的字节数
DWORD dwReads;
//读取数据
ReadFile(hFile, ch, 100, &dwReads, NULL);
//设置字符串结束字符
ch[dwReads] = 0;
//关闭打开的文件对象的句柄
CloseHandle(hFile);
//显示读取到的数据
MessageBox(ch);
}
<div class="se-preview-section-delimiter"></div>
菜单命令响应函数
新建一个单文档的MFC AppWizard(exe)工程,工程名为Menu。Build运行。
左上角点击按钮,可以让属性框始终显示,不会因为点击对话框以外的地方就消失。
去掉Pop-up弹出前的勾,将ID改为ID_TEST。给Test添加响应函数在CMainFrame中,在函数中加入 MessageBox(“MainFrame Clicked”);
效果:
菜单命令的路由
程序类对菜单命令的响应顺序
响应Test
菜单项命令的顺序依次是:视类、文档类、框架类,最后才是应用程序类。
Windows消息的分类
凡是从CWnd派生的类,它们既可以接收标准消息,也可以接收命令消息和通告消息。而对于那些从CCmdTarget派生的类,则只能接收命令消息和通告消息,不能接收标准消息。
本例中的文档类(CMenuDoc)和应用程序类(CWinApp),因为它们都派生于CCmdTarget类,所以它们可以接收菜单命令消息。但它们不是从CWnd类派生的,所以不能接收标准消息。
菜单命令的路由
菜单命令消息路由的具体过程:当点击某个菜单项时,最先接收到这个菜单命令消息的是框架类。框架类将把接收到的这个消息交给它的子窗口,即视类,由视类首先进行处理。视类首先根据命令消息映射机制查找自身是否对此消息进行了响应,如果响应了,就调用相应响应函数对这个消息进行处理,消息路由过程结束;如果视类没有对此命令消息做出响应,就交由文档类,文档类同样查找自身是否对这个菜单命令进行了响应,如果响应了,就由文档类的命令消息响应函数进行处理,路由过程结束。如果文档类也未做出响应,就把这个命令消息交还给视类,后者又把该消息交还给框架类。框架类查看自己是否对这个命令消息进行了响应,如果它也没有做出响应,就把这个菜单命令消息交给应用程序类,由后者来进行处理。
基本菜单操作
标记菜单
运行刚才创建的Menu程序,点击查看,前面都有一个对号,这种类型就是标记菜单。
在CMainFrame类的OnCreate的return语句之前添加这句代码 GetMenu()->GetSubMenu(0)->CheckMenuItem(0, MF_BYPOSITION | MF_CHECKED); 或者GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_NEW, MF_BYCOMMAND | MF_CHECKED);
Build并运行,可发现新建左边已添加一个复选标记。
默认菜单项
在刚才的代码下,添加 GetMenu()->GetSubMenu(0)->SetDefaultItem(1, TRUE); 或者GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_OPEN, FALSE); 编译运行,会发现“打开”变成了粗体。
注意:“打印”的索引是5,不是4。计算菜单项索引时,一定要把分割栏菜单项计算在内。并且,一个子菜单只能有一个默认菜单项。
图形标记菜单
Insert-Resource-Bitmap,创建一个位图资源。如图。
为CMainFrame类添加一个CBitmap类型的成员变量:m_bitmap。
接着添加代码:
CString str;
str.Format(“x=%d”,y=%d”, GetSystemMetrics(SM_CXMENUCHECK),GetSystemMetrics(SM_CYMENUCHECK));
MessageBox(str);
m_bitmap.LoadBitmap(IDB_BITMAP1);
GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0, MF_BYPOSITION, &m_bitmap, &m_bitmap);
禁用菜单项
通常把MF_GRAYED和MF_DISABLED这两个标志放在一起使用。不过这么做并不是必需的。
删除之前的代码,写入 GetMenu()->GetSubMenu(0)->EnableMenuItem(1, MF_BYPOSITION | MF_DISABLED | MF_GRAYED);
打开“文件”子菜单,发现“打开”菜单栏变灰,点击不起作用。
移除和装载菜单
再添加一行代码: SetMenu(NULL); 此时菜单栏被移除了。
再添加几行代码:
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME);
SetMenu(&menu);
menu.Detach();
此时菜单栏又装载了。
CMenu menu;
menu.CreateMenu();
GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test1");
menu.AppendMenu(MF_STRING, 111, "Hello");
menu.AppendMenu(MF_STRING, 112, "Bye");
menu.AppendMenu(MF_STRING, 113, "Mybole");
menu.Detach();
CMenu menu1;
menu1.CreateMenu();
GetMenu()->InsertMenu(2, MF_POPUP | MF_BYPOSITION, (UINT)menu1. m_hMenu,"Test");
menu1.Detach();
GetMenu()->GetSubMenu(2)->AppendMenu(MF_STRING, 118, "Welcome");
GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING, 114, "Welcome");
GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN, MF_BYCOMMAND | MF_STRING, 115, "VC编程");
<div class="se-preview-section-delimiter"></div>
MFC菜单命令更新机制
MFC命令更新机制:当要显示菜单时,操作系统发出WM_INITMENUPOPOP消息,然后由程序窗口的基类如CFrameWnd接管,它会创建一个CCmdUI对象,并与程序的第一个菜单项相关联,调用该对象的一个成员函数DoUpdate()。这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有一个指向CCmdUI对象的指针。这时,系统会判断是否存在一个ON_UPDATE_COMMAND_UI宏去捕获这个菜单项消息。如果找到这样一个宏,就调用相应的消息响应函数进行处理,在这个函数中,可以利用传递过来的CCmdUI对象去调用相应的函数,使该菜单项可以使用,或禁用该菜单项。当更新完第一个菜单项后,同一个CCmdUI对象就设置为与第二个菜单项相关联,依此顺序进行,直到完成所有菜单项的处理。
添加代码:
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->Enable();
}
<div class="se-preview-section-delimiter"></div>
编辑-剪切 可用了。
如果要把工具栏上的一个工具按钮与菜单栏中的某个菜单项相关联,只要将它们的ID设置为同一个标识就可以了。
如果希望禁用文件-新建,为ID_FILE_NEW添加UPDATE_COMMAND_UI消息响应函数。
代码如下:
void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->Enable(FALSE);
}
<div class="se-preview-section-delimiter"></div>
或者
void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI)
{
if (2 == pCmdUI->m_nIndex)
pCmdUI->Enable();
}
<div class="se-preview-section-delimiter"></div>
快捷菜单
1. 新增一个新的菜单资源。点开,顶级菜单设置任意的文本,如abc。添加两个菜单项:
显示 IDM_SHOW
退出 IDM_EXIT
2. 给CMenuView类添加WM_RBUTTONDOWN消息响应函数。
void CMenu2View::OnRButtonDown(UINT nFlags, CPoint point)
{
CMenu menu;
menu.LoadMenu(IDR_MENU1);
CMenu* pPopup = menu.GetSubMenu(0);
ClientToScreen(&point);
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
CView::OnRButtonDown(nFlags, point);
}
<div class="se-preview-section-delimiter"></div>
效果:
3.对“显示”右键ClassWizard,可以取消创建新类的询问。分别为CMainFrame类和CMenuView类添加一个响应。
代码:
void CMenu2View::OnShow()
{
MessageBox("View show");
}
<div class="se-preview-section-delimiter"></div>
void CMainFrame::OnShow()
{
MessageBox("Main show");
}
结果是显示“View show”。说明只有视类才能对快捷菜单项命令做出响应。若想让CMainView类对此快捷菜单项进行响应的话,修改代码:
void CMenu2View::OnRButtonDown(UINT nFlags, CPoint point)
{
CMenu menu;
menu.LoadMenu(IDR_MENU1);
CMenu* pPopup = menu.GetSubMenu(0);
ClientToScreen(&point);
//pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, GetParent());
CView::OnRButtonDown(nFlags, point);
}
同时删去视类的显示。
动态菜单操作
### 添加菜单项目
在CMainFrame类的OnCreate函数中添加代码:
CMenu menu;
menu.CreateMenu();
GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test");
menu.Detach();
插入菜单项目
CMenu menu;
menu.CreateMenu();
/*GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test");
menu.Detach();*/
GetMenu()->InsertMenu(2, MF_POPUP | MF_BYPOSITION, (UINT)menu. m_hMenu,"Test");
menu.Detach();
如果要在新插入的子菜单中添加菜单项的话,同样可以使用AppendMenu函数来实现。
CMenu menu;
menu.CreateMenu();
/*GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test");
menu.Detach();*/
GetMenu()->InsertMenu(2, MF_POPUP | MF_BYPOSITION, (UINT)menu. m_hMenu,"Test");
menu.AppendMenu(MF_STRING, 111, "Hello");
menu.AppendMenu(MF_STRING, 112, "Bye");
menu.AppendMenu(MF_STRING, 113, "Mybole");
menu.Detach();
111、112、113是随便赋予的ID号。
若要在“文件”子菜单下添加一个菜单项Welcome,再添加一行代码: GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING, 114, “Welcome”);
若要在“文件”中的“新建”和“打开”插入一个菜单项VC编程,再添加一行代码:
GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN, MF_BYCOMMAND | MF_STRING, 115, “VC编程”);
删除菜单
删除“编辑”:在CMainFrame类的OnCreate函数最后(return之前)添加:
GetMenu()->DeleteMenu(1, MF_BYPOSITION);
删除“文件”下的“打开”:
GetMenu()->GetSubMenu(0)->DeleteMenu(2, MF_BYPOSITION);
动态添加的菜单项的命令响应
Resource.h中添加新ID
#define IDM_HELLO 111
将menu.AppendMenu(MF_STRING, 111, “Hello”); 改为 menu.AppendMenu(MF_STRING, IDM_HELLO, “Hello”);
三部曲:
1. 点开MainFrm.h,增加为
```C++
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnShow();
//}}AFX_MSG
afx_msg void OnHello();
DECLARE_MESSAGE_MAP()
<div class="se-preview-section-delimiter"></div>
2. 点开MainFrm.cpp,增加为
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_COMMAND(IDM_SHOW, OnShow)
ON_COMMAND(IDM_HELLO, OnHello)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
<div class="se-preview-section-delimiter"></div>
3. CMainFrame类中添加
void CMainFrame::OnHello()
{
MessageBox("Hello");
}
<div class="se-preview-section-delimiter"></div>
电话本示例程序
删除之前写入CMainFrame类的OnCreate函数,留下原始函数。
动态添加子菜单的实现
利用ClassWizard添加WM_CHAR消息。在Menu2View.h中添加:
private:
int m_nIndex;
CMenu m_menu;
<div class="se-preview-section-delimiter"></div>
在Menu2View.cpp里,添加:
CMenu2View::CMenu2View()
{
// TODO: add construction code here
m_nIndex = -1;
}
<div class="se-preview-section-delimiter"></div>
显示输入的字符
添加菜单项及其命令响应函数
在资源编辑器中打开程序的菜单,在“帮助”后添加一个新菜单abc,添加4个菜单项。名称为1,ID为IDM_PHONE1,以此类推。用ClassWizard为CMenu2View类分别加上这四个菜单项的命令响应函数。
修改CMenu2View类的头文件,如下:
protected:
//{{AFX_MSG(CMenu2View)
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg void OnPhone1();
afx_msg void OnPhone2();
afx_msg void OnPhone3();
afx_msg void OnPhone4();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
<div class="se-preview-section-delimiter"></div>
CMenu2View.cpp中,
//{{AFX_MSG_MAP(CMenu2View)
ON_WM_CHAR()
//}}AFX_MSG_MAP
ON_COMMAND(IDM_PHONE1, OnPhone1)
ON_COMMAND(IDM_PHONE2, OnPhone2)
ON_COMMAND(IDM_PHONE3, OnPhone3)
ON_COMMAND(IDM_PHONE4, OnPhone4)
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
<div class="se-preview-section-delimiter"></div>
void CMenu2View::OnPhone1()
{
CClientDC dc(this);
dc.TextOut(0, 0, m_strArray.GetAt(0));
}
void CMenu2View::OnPhone2()
{
CClientDC dc(this);
dc.TextOut(0, 0, m_strArray.GetAt(1));
}
void CMenu2View::OnPhone3()
{
CClientDC dc(this);
dc.TextOut(0, 0, m_strArray.GetAt(2));
}
void CMenu2View::OnPhone4()
{
CClientDC dc(this);
dc.TextOut(0, 0, m_strArray.GetAt(3));
}
框架类窗口截获菜单命令消息
右键单击CMainFrame,选择Add Virtual Functions-OnCommand,单击Add Handler,再点击Edit Existing。
代码:
BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam)
{
int MenuCmdID = LOWORD(wParam);
CMenu2View *pView = (CMenu2View *)GetActiveView();
if (MenuCmdID >= IDM_PHONE1 && MenuCmdID < IDM_PHONE1 + pView->m_strArray.GetSize())
{
//MessageBox("Test");
CClientDC dc(pView);
dc.TextOut(0, 0, pView->m_strArray.GetAt(MenuCmdID - IDM_PHONE1));
return TRUE;
}
return CFrameWnd::OnCommand(wParam, lParam);
}
将MainFrm.cpp里添加#include “Menu2View.h” 。
将Menu2View.cpp中的#include “Menu2Doc.h”剪切到Menu2View.h文件的前部(#endif // _MSC_VER > 1000下面)。
最终代码:
void CMenu2View::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
CClientDC dc(this);
if (0x0d == nChar)
{
if (0 == ++m_nIndex)
{
m_menu.CreatePopupMenu();
GetParent()->GetMenu()->AppendMenu(MF_POPUP, (UINT)m_menu.m_hMenu, "PhoneBook");
GetParent()->DrawMenuBar();
}
m_menu.AppendMenu(MF_STRING, IDM_PHONE1 + m_nIndex, m_strLine.Left(m_strLine.Find(" ")));
m_strArray.Add(m_strLine);
m_strLine.Empty();
Invalidate();
}
else
{
m_strLine += nChar;
dc.TextOut(0, 0, m_strLine);
}
CView::OnChar(nChar, nRepCnt, nFlags);
}
效果:
MFC消息映射机制
与消息有关的三处信息:1.头文件XXXX.h中 2.源文件XXXX.cpp中 3.源文件XXXX.cpp的响应函数中
绘制线条
对CDrawView右键点击Add Member Variable,变量名称:m_ptOrigin,类型:CPoint,访问权限设置:Private。
代码:
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point)
{
m_ptOrigin = point;
CView::OnLButtonDown(nFlags, point);
}
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
//首先获得窗口的设备描述表
HDC hdc;
hdc = ::GetDC(m_hWnd);
//移动到线条的起点
MoveToEx(hdc, m_ptOrigin.x, m_ptOrigin.y, NULL);
//画线
LineTo(hdc, point.x, point.y);
//释放设备描述表
::ReleaseDC(m_hWnd, hdc);
CView::OnLButtonUp(nFlags, point);
}
利用MFC的CDC类实现画线功能
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
/*//首先获得窗口的设备描述表
HDC hdc;
hdc = ::GetDC(m_hWnd);
//移动到线条的起点
MoveToEx(hdc, m_ptOrigin.x, m_ptOrigin.y, NULL);
//画线
LineTo(hdc, point.x, point.y);
//释放设备描述表
::ReleaseDC(m_hWnd, hdc);*/
CDC* pDC = GetDC();
pDC->MoveTo(m_ptOrigin);
pDC->LineTo(point);
ReleaseDC(pDC);
CView::OnLButtonUp(nFlags, point);
}
利用MFC的CWindowDC类实现画线功能
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
CWindowDC dc(GetParent());
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
CView::OnLButtonUp(nFlags, point);
}
在桌面窗口中画线
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
CWindowDC dc(GetDesktopWindow());
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
CView::OnLButtonUp(nFlags, point);
}
注意:在桌面上画图需要权限(一般写代码时需要避免软件以外的操作)。
绘制彩色线条
在程序中,当构造一个GDI对象后,该对象并不会立即生效,必须选入设备描述表,它才会在以后的绘制操作中生效。
一般情况下,在完成绘图操作之后,都要利用SelectObject函数把之前的GDI对象选入设备描述表,以便使其恢复到先前的状态。
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
CClientDC dc(this);
CPen* pOldPen = dc.SelectObject(&pen);
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
dc.SelectObject(pOldPen);
CView::OnLButtonUp(nFlags, point);
}
运行的效果是红色线条。
改为 CPen pen(PS_DASH, 1, RGB(255, 0, 0)); 是虚线。(其中第二个参数需小于等于10)
CPen pen(PS_DOT, 1, RGB(255, 0, 0)); 是点线。
使用画刷绘图
简单画刷
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
//创建一个红色画刷
CBrush brush(RGB(255, 0, 0));
//创建并获得设备描述表
CClientDC dc(this);
//利用红色画刷填充鼠标拖拽过程中形成的矩形区域
dc.FillRect(CRect(m_ptOrigin, point),&brush);
CView::OnLButtonUp(nFlags, point);
}```

### 位图画刷
Insert-Resource-Bitmap-New,在这里发挥灵魂画手的天赋吧!
代码:
```C++
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
//创建位图对象
CBitmap bitmap;
//加载位图资源
bitmap.LoadBitmap(IDB_BITMAP1);
//创建位图画刷
CBrush brush(&bitmap);
//创建并获得设备描述表
CClientDC dc(this);
//利用位图画刷填充鼠标拖拽过程中形成的矩形区域
dc.FillRect(CRect(m_ptOrigin, point),&brush);
CView::OnLButtonUp(nFlags, point);
}
<div class="se-preview-section-delimiter"></div>
我画的是不是很滑稽(手动滑稽)
透明画刷
先进行一种尝试:
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
//创建并获得设备描述表
CClientDC dc(this);
//绘制一个矩形
dc.Rectangle(CRect(m_ptOrigin,point));
CView::OnLButtonUp(nFlags, point);
}
<div class="se-preview-section-delimiter"></div>
如果希望矩形内部是透明的,能够看到被遮挡的图形,就要创建一个透明画刷。
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
//创建并获得设备描述表
CClientDC dc(this);
//创建一个空画刷
CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
//将空画刷选入设备描述表
CBrush *pOldBrush = dc.SelectObject(pBrush);
//绘制一个矩形
dc.Rectangle(CRect(m_ptOrigin, point));
//恢复先前的画刷
dc.SelectObject(pOldBrush);
CView::OnLButtonUp(nFlags, point);
}
<div class="se-preview-section-delimiter"></div>
- 上一篇: mysql查找字段空、不为空的方法总结
- 下一篇: mysql中查询字段为null或者不为null