MFC傻瓜式教程




2017.8.13已更新新章节

  
  本教程重操作,轻理论,为操作减负。需了解详细原理的朋友可以自行看各种书籍。
  直接上菜。

MFC:Microsoft Foundation Class ,微软基础类库。

对话框

对话框的创建和显示

  1. 新建MFC AppWizard(exe)工程,单文档类型。工程名:Mybole。编译运行。
    新建
  2. 点击帮助-关于Mybole。这是MFC自动创建的。
    关于

  3. 创建自己的对话框。点击Insert-Resource。选择Dialog,点击New。VC++自动将其标识设置为IDD_DIALOG1,并自动添加到ResourceView-Dialog项中。Dialog项下还有一个对话框资源标识:IDD_ABOUTBOX,即上一步中的“关于”对话框。
    Insert Resource对话框

新建的对话框资源

  1. 选中对话框本身,右键点击属性。将Caption设置为“测试”。
  2. 选择View-ClassWizard,点击create a new class,OK。出现下图,并输入下图选项。
    New Class
  3. 在随后出现的MFC ClassWizard对话框上点击OK。
    列表
    注意:看看左侧类列表中是否添加好了CTestDlg,否则会影响后续操作。

接下来,我们希望在程序中显示这个对话窗口。

  1. 点击右侧菜单Menu,选中IDR_MAINFRAME。点击帮助旁边的虚线框。
    Menu

  2. 对虚线框右键属性,修改为下图。
    属性

  3. 关闭属性。点击View-ClassWizard(中文是建立类向导),选择CMyboleView,用COMMAND命令消息响应函数。如图。
    COMMAND

模态对话框的创建

  需要调用CDialog类的成员函数:DoModal,它能创建并显示一个模态对话框,其返回值将作为CDialog类的另一个成员函数:EndDialog的参数,后者功能是关闭模态对话框。

  在FileView中选择MyboleView.cpp,编写程序。
  记得在开头添加头文件 #include “testdlg.h” (头文件大小写问题,linux区分,windows不区分)
编程
  显示模态对话框的具体实现代码:

1
2
3
4
5
6
void CMyboleView::OnDialog() 
{
// TODO: Add your command handler code here
CTestDlg dlg;
dlg.DoModal();
}

编译运行,点击对话框。会发现若不确认该窗口,将无法点击其他窗口。
模态对话框1

模态对话框2

非模态对话框的创建

将上面的模态对话框代码注释掉。

改为:

1
2
3
4
5
6
7
8
9
10
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);
}

注意:需要把之前运行的对话框关掉才能编译成功。

然而,当它生命周期结束时,所保存的内存地址就丢失了,那么程序中也就无法再引用到它所指向的那块内存。于是,我们这样解决该问题。
MFC ClassWizard

注意: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按钮时,在对话框中动态创建一个按钮这一功能。

  1. 为CTestDlg类添加一个私有的CButton成员变量。
      点击ClassView标签页右键,如图点击。
    ClassView

  填入信息。
添加成员变量

  1. 添加Add按钮单击消息的响应函数。
      按钮点右键,选ClassWizard(建立类向导),如图。
    建立类向导

  单击Edit Code,即可定位到该函数定义处。
  添加一下代码:

1
2
3
4
5
6
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出现非法操作,我们需要进行如下步骤。

  1. 为CTestDlg类增加一个私有的BOOL类型成员变量。
    变量类型:BOOL
    变量名称:m_bIsCreated
    Access: private

  2. 在TestDlg.cpp中找到构造函数,将m_bIsCreated初始为FALSE。如图所示。
    这里写图片描述

  或者改为如下亦可。
Static BOOL bIsCreated = FALSE;

  1. 回到Add,双击它,进入代码部分,改之。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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;
    }

    }

  或者以下亦能实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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按钮,对前两个编辑框中的数字求和,并将结果显示在第三个编辑框中。

第一种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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表示十进制。

效果:
这里写图片描述

第二种方式

  代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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的控件上的文本。

第三种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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分别添加好成员变量。
  接着修改代码:

1
2
3
4
5
6
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添加。
这里写图片描述

  修改代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}

第六种方式

  修改代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}

第七种方式

  修改代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)。具体实现代码为:

1
2
3
4
5
6
7
8
9
10
11
12
void CTestDlg::OnButton1() 
{
CString str;
if(GetDlgItemText(IDC_BUTTON1,str), str == "收缩<<")
{
SetDlgItemText(IDC_BUTTON1, "拓展>>");
}
else
{
SetDlgItemText(IDC_BUTTON1, "收缩<<");
}
}

  拖动一个图像控件来划分对话框中要动态切除的部分。
这里写图片描述

  修改该控件ID为IDC_SEPATATOR,styles选项卡勾上Sunken选项。
  修改代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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,跳转。
  修改代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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(多行)。即可实现焦点的传递。

法二

  只需要改变一行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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键后,输入焦点可以转移到此控件上。

  修改一行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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函数为:

1
2
3
4
5
6
7
8
9
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中添加:

1
2
3
4
5
6
7
8
9
class CTestDlg : public CDialog
{

protected:
virtual BOOL PreTranslateMessage(MSG* pMsg);

public:
virtual void OnOK();
……

  接着:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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();
}

  点击Layout-Tab order,这些序号就是各控件的Tab顺序。顺序可改变,依次点击希望的顺序控件即可。

  调用顺序:当用户按下回车键时,Windows将查看对话框中是否存在指定的默认按钮,如果有,就调用该默认按钮单击消息的响应函数。如果没有,就会调用虚拟的OnOK函数,即使对话框没有包含默认的OK按钮(这个默认OK按钮的ID是IDOK)。

文件和注册表操作

C语言对文件操作的支持

  新建单文档类型的MFC应用程序,工程名为File,并为主菜单添加一个子菜单,名称为“文件操作”,然后为其添加两个菜单项,并分别为它们添加相应的命令响应函数(通过COMMAND),让CFileView类接收这些菜单项的命令响应。
这里写图片描述
这里写图片描述

文件的打开和写入

  代码:

1
2
3
4
5
void CFileView::OnFileWrite() 
{
FILE *pFile = fopen("1.txt","w");
fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile);
}

  编译后可看到文件夹中生成了1.txt,打开有一行网址。

文件的关闭

  增加一行代码:

1
2
3
4
5
6
void CFileView::OnFileWrite() 
{
FILE *pFile = fopen("1.txt","w");
fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile);
fclose(pFile);
}

文件指针定位

  代码:

1
2
3
4
5
6
7
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);
}

  显示:http://www.sunxin.org欢迎访问

  将文件指针移动到文件的开始位置处:
  代码:

1
2
3
4
5
6
7
8
9
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);
}

  显示:ftp:://www.sunxin.org

文件的读取

  在OnFileRead函数中写入代码:

1
2
3
4
5
6
7
8
9
void CFileView::OnFileRead() 
{
FILE *pFile = fopen("1.txt","r");
char ch[100];
fread(ch, 1, 100, pFile);
fclose(pFile);
MessageBox(ch);

}

  编译运行:
  
这里写图片描述

  原因:C语言以“\0”结束。

  解决方法:
  法一:
  修改代码:

1
2
3
4
5
6
7
8
void CFileView::OnFileWrite() 
{
FILE *pFile = fopen("1.txt","w");
char buf[22] = "http://www.sunxin.org";
buf[21] = '\0';
fwrite(buf, 1, 22, pFile);
fclose(pFile);
}

  先点击写入文件,再点击读取文件,就可以看到正确的内容。
  缺点:增加了文件大小。

法二:

1
2
3
4
5
6
7
8
9
10
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);

}

法三:
  读取文件时,不知道文件大小时的做法。

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}

二进制文件和文本文件

  代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
}

  效果:
  
这里写图片描述

  文本方式:10实际上是换行符的ASCII码。

  以文本方式和二进制方式读取文件是有明显的区别的。

文本方式和二进制方式

  二进制方式:换行是由两个字符组成的,即ASCII码10(回车符)和13(换行符)。
  写入和读取文件时要保持一致。如果采用文本方式写入,应采用文本方式读取;如果采用二进制方式写入数据,在读取时也应采用二进制方式。

  面试题:给你一个整数,如:98341,将这个整数保存到文件中,要求在以记事本程序打开该文件时,显示的是:98341。
  法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);

}

  或

1
2
3
4
5
6
7
8
9
10
11
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);

}

  面试题:给定一个字符串,其中既有数字字符,又有26个英文字母中的几个字符,让你判断一下哪些是数字字符。

  对这种问题,实际上就是判断各字符的ASCII码,对于数字字符来说,它们的ASCII码大于等于48,小于等于57。

C++对文件操作的支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}

Win32 API 对文件操作的支持

文件的创建、打开和写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}

文件的读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}

菜单

菜单命令响应函数

  新建一个单文档的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();
  此时菜单栏又装载了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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编程");

MFC菜单命令更新机制

这里写图片描述

  MFC命令更新机制:当要显示菜单时,操作系统发出WM_INITMENUPOPOP消息,然后由程序窗口的基类如CFrameWnd接管,它会创建一个CCmdUI对象,并与程序的第一个菜单项相关联,调用该对象的一个成员函数DoUpdate()。这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有一个指向CCmdUI对象的指针。这时,系统会判断是否存在一个ON_UPDATE_COMMAND_UI宏去捕获这个菜单项消息。如果找到这样一个宏,就调用相应的消息响应函数进行处理,在这个函数中,可以利用传递过来的CCmdUI对象去调用相应的函数,使该菜单项可以使用,或禁用该菜单项。当更新完第一个菜单项后,同一个CCmdUI对象就设置为与第二个菜单项相关联,依此顺序进行,直到完成所有菜单项的处理。

  添加代码:

1
2
3
4
5
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI) 
{
// TODO: Add your command update UI handler code here
pCmdUI->Enable();
}

  编辑-剪切 可用了。
  如果要把工具栏上的一个工具按钮与菜单栏中的某个菜单项相关联,只要将它们的ID设置为同一个标识就可以了。

  如果希望禁用文件-新建,为ID_FILE_NEW添加UPDATE_COMMAND_UI消息响应函数。
  代码如下:

1
2
3
4
5
6

void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->Enable(FALSE);
}

  或者

1
2
3
4
5
void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI) 
{
if (2 == pCmdUI->m_nIndex)
pCmdUI->Enable();
}

快捷菜单

  1. 新增一个新的菜单资源。点开,顶级菜单设置任意的文本,如abc。添加两个菜单项:
  显示 IDM_SHOW
  退出 IDM_EXIT
  2. 给CMenuView类添加WM_RBUTTONDOWN消息响应函数。

1
2
3
4
5
6
7
8
9
10
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);
}

  效果:
  

  3.对“显示”右键ClassWizard,可以取消创建新类的询问。分别为CMainFrame类和CMenuView类添加一个响应。
  代码:

1
2
3
4
void CMenu2View::OnShow() 
{
MessageBox("View show");
}

1
2
3
4
void CMainFrame::OnShow() 
{
MessageBox("Main show");
}

  结果是显示“View show”。说明只有视类才能对快捷菜单项命令做出响应。若想让CMainView类对此快捷菜单项进行响应的话,修改代码:

1
2
3
4
5
6
7
8
9
10
11
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函数中添加代码:

1
2
3
4
CMenu menu;
menu.CreateMenu();
GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test");
menu.Detach();

插入菜单项目

1
2
3
4
5
6
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函数来实现。

1
2
3
4
5
6
7
8
9
10
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

1
#define IDM_HELLO	111

将menu.AppendMenu(MF_STRING, 111, “Hello”); 改为 menu.AppendMenu(MF_STRING, IDM_HELLO, “Hello”);

  三部曲:
  1. 点开MainFrm.h,增加为

1
2
3
4
5
6
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnShow();
//}}AFX_MSG
afx_msg void OnHello();
DECLARE_MESSAGE_MAP()

  2. 点开MainFrm.cpp,增加为

1
2
3
4
5
6
7
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()

  3. CMainFrame类中添加

1
2
3
4
void CMainFrame::OnHello()
{
MessageBox("Hello");
}

电话本示例程序

  删除之前写入CMainFrame类的OnCreate函数,留下原始函数。

动态添加子菜单的实现

  利用ClassWizard添加WM_CHAR消息。在Menu2View.h中添加:

1
2
3
private:
int m_nIndex;
CMenu m_menu;

在Menu2View.cpp里,添加:

1
2
3
4
5
CMenu2View::CMenu2View()
{
// TODO: add construction code here
m_nIndex = -1;
}

显示输入的字符

添加菜单项及其命令响应函数

  在资源编辑器中打开程序的菜单,在“帮助”后添加一个新菜单abc,添加4个菜单项。名称为1,ID为IDM_PHONE1,以此类推。用ClassWizard为CMenu2View类分别加上这四个菜单项的命令响应函数。
  修改CMenu2View类的头文件,如下:

1
2
3
4
5
6
7
8
9
10
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()

CMenu2View.cpp中,

1
2
3
4
5
6
7
8
9
10
11
12
	//{{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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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。
这里写图片描述
  代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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下面)。

  最终代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

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。
  代码:

1
2
3
4
5
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) 
{
m_ptOrigin = point;
CView::OnLButtonDown(nFlags, point);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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类实现画线功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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类实现画线功能

1
2
3
4
5
6
7
8
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
CWindowDC dc(GetParent());
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);

CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

在桌面窗口中画线

1
2
3
4
5
6
7
8
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
CWindowDC dc(GetDesktopWindow());
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);

CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

  注意:在桌面上画图需要权限(一般写代码时需要避免软件以外的操作)。

绘制彩色线条

  在程序中,当构造一个GDI对象后,该对象并不会立即生效,必须选入设备描述表,它才会在以后的绘制操作中生效。
一般情况下,在完成绘图操作之后,都要利用SelectObject函数把之前的GDI对象选入设备描述表,以便使其恢复到先前的状态。

1
2
3
4
5
6
7
8
9
10
11
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)); 是点线。

使用画刷绘图

简单画刷

1
2
3
4
5
6
7
8
9
10
11
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,在这里发挥灵魂画手的天赋吧!
  代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}

这里写图片描述
  我画的是不是很滑稽(手动滑稽)

透明画刷

  先进行一种尝试:

1
2
3
4
5
6
7
8
9
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
//创建并获得设备描述表
CClientDC dc(this);
//绘制一个矩形
dc.Rectangle(CRect(m_ptOrigin,point));

CView::OnLButtonUp(nFlags, point);
}

这里写图片描述
  如果希望矩形内部是透明的,能够看到被遮挡的图形,就要创建一个透明画刷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}

这里写图片描述

绘制连续线条

  首先为视类增加鼠标移动消息(WM_MOUSEMOVE)的响应函数(默认OnMouseMove),并为视类添加一个BOOL型的私有成员变量m_bDraw。
在视类头文件定义:

1
2
 Private:
 BOOL m_bDraw;

在视类的构造函数中:

1
m_bDraw = FALSE;

在OnLButtonDown中:

1
m_bDraw = TRUE;

在OnLButtonUp中:

1
m_bDraw = FALSE;

1
2
3
4
5
6
7
8
9
10
11
12
13
void CDrawView::OnMouseMove(UINT nFlags, CPoint point) 
{
CClientDC dc(this);
if(m_bDraw == TRUE)
{
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
//修改线段的起点
m_ptOrigin = point;
}

CView::OnMouseMove(nFlags, point);
}

这里写图片描述

给线条换色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void CDrawView::OnMouseMove(UINT nFlags, CPoint point) 
{
CClientDC dc(this);
//创建一个红色的、宽度为1的实线画笔
CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
//把创建的画笔选入设备描述表
CPen *pOldPen = dc.SelectObject(&pen);
if (m_bDraw == TRUE)
{
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
//修改线段的起点
m_ptOrigin = point;
}
//恢复设备描述表
dc.SelectObject(pOldPen);

CView::OnMouseMove(nFlags, point);
}

绘制扇形效果的线条

  去掉上述代码中的 m_ptOrigin = point;

  效果:
  这里写图片描述

  绘制一个带边线的扇形:
  为CDrawView类增加一个CPoint类型的私有成员变量m_ptOld,用来保存鼠标上一个移动点。

  在OnLButton中:

1
m_ptOld = point;

  在OnMouseMove中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void CDrawView::OnMouseMove(UINT nFlags, CPoint point) 
{
CClientDC dc(this);
//创建一个红色的、宽度为1的实线画笔
CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
//把创建的画笔选入设备描述表
CPen *pOldPen = dc.SelectObject(&pen);
if (m_bDraw == TRUE)
{
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
dc.LineTo(m_ptOld);
//修改线段的起点
//m_ptOrigin = point;
m_ptOld = point;
}
//恢复设备描述表
dc.SelectObject(pOldPen);

CView::OnMouseMove(nFlags, point);
}

  最好将OnLButtonUp里原来写的代码删除或注释之。
  效果:
这里写图片描述

  MFC提供一个设置绘图模式的函数SetROP2,带有一个参数R2_BLACK、R2_WHITE、R2_MERGENOTPEN等。
  例如,在CClientDC dc(this); 下方添加代码: dc.SetROP2(R2_MERGENOTPEN); 编译运行后看不到绘制的线条,这就是设置了R2_MERGENOTPEN这种绘图模式。
使用R2_BLACK,将会发现绘制的线条颜色始终都是黑色的。

文本编程

插入符

创建文本插入符

  创建一个单文档类型的MFC AppWizard(exe)工程,取名为Text。
为CTextView类添加WM_CREATE消息的响应函数OnCreate,在此函数中创建一个宽度为20、高度为100的插入符。代码如下。

1
2
3
4
5
6
7
8
9
int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;

CreateSolidCaret(20,100);
ShowCaret();
return 0;
}

这里写图片描述

  让插入符适应于当前字体的大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;

//创建设备描述表
CClientDC dc(this);
//定义文本信息结构体变量
TEXTMETRIC tm;
//获得设备描述表中的文本信息
dc.GetTextMetrics(&tm);
//根据字体大小,创建何时的插入符(除以8是经验值)
CreateSolidCaret(tm.tmAveCharWidth/8, tm.tmHeight);
//显示插入符
ShowCaret();

return 0;
}

  运行结果就比较符合常规了。

创建图形插入符

  新建一个位图资源,画一个图形。
在TextView.h中添加

1
2
private:
CBitmap bitmap;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;


bitmap.LoadBitmap(IDB_BITMAP1);
CreateCaret(&bitmap);
ShowCaret();

return 0;
}

这里写图片描述

窗口重绘

OnDraw函数

  实现在程序窗口中输出一串文字的功能。

1
2
3
4
5
6
7
8
void CTextView::OnDraw(CDC* pDC)
{
CTextDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

CString str("VC++ 深入编程");
pDC->TextOut(50, 50, str);
}

这里写图片描述

添加字符串资源

  点击Resource View-String Table选项,在此字符串表最底部的空行上双击,即可弹出添加新字符串资源的对话框。ID:IDS_STRINGVC,Caption:“VC++编程 文本编程”。代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
void CTextView::OnDraw(CDC* pDC)
{
CTextDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

//CString str("VC++ 深入编程");
CString str;
str = "VC++ 深入编程";
pDC->TextOut(50, 50, str);

str.LoadString(IDS_STRINGVC);
pDC->TextOut(0, 200, str);
}

这里写图片描述

路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

void CTextView::OnDraw(CDC* pDC)
{
CTextDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

//CString str("VC++ 深入编程");
CString str;
str = "VC++ 深入编程";
pDC->TextOut(50, 50, str);

CSize sz = pDC->GetTextExtent(str);

str.LoadString(IDS_STRINGVC);
pDC->TextOut(0, 200, str);

pDC->BeginPath();
pDC->Rectangle(50, 50, 50+sz.cx, 50+sz.cy);
pDC->EndPath();

for(int i=0; i<300; i+=10)
{
pDC->MoveTo(0, i);
pDC->LineTo(300, i);
pDC->MoveTo(i,0);
pDC->LineTo(i,300);
}

这里写图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void CTextView::OnDraw(CDC* pDC)
{
CTextDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

//CString str("VC++ 深入编程");
CString str;
str = "VC++ 深入编程";
pDC->TextOut(50, 50, str);

CSize sz = pDC->GetTextExtent(str);

str.LoadString(IDS_STRINGVC);
pDC->TextOut(0, 200, str);

pDC->BeginPath();
pDC->Rectangle(50, 50, 50+sz.cx, 50+sz.cy);
pDC->EndPath();
pDC->SelectClipPath(RGN_DIFF);

for(int i=0; i<300; i+=10)
{
pDC->MoveTo(0, i);
pDC->LineTo(300, i);
pDC->MoveTo(i,0);
pDC->LineTo(i,300);
}

}

这里写图片描述
  这正是RGN_DIFF模式的效果。
  如果是RGN_AND,效果是新的裁剪区域是当前裁剪区域和当前路径层的交集。

  路径层的作用:实现特殊效果。如,希望整幅图形中某一部分与其他部分有所区别,就可以把这部分的图形设置到一个路径层中,然后利用SelectClipPath函数设置一种模式,让路径层和裁剪区域进行互操作以达到一种特殊效果。

字符输入

  当用户在键盘上按下某个字符按键后,要把该字符输出到程序窗口上。
首先让CTextView捕获WM_CHAR消息,接着为该类定义一个CString类型的成员变量:m_strLine,并在CTextView类的构造函数中将这个变量初始化:m_strLine = “”;

1
2
3
4
5
6
void CTextView::OnLButtonDown(UINT nFlags, CPoint point) 
{
SetCaretPos(point);

CView::OnLButtonDown(nFlags, point);
}

这里写图片描述
  为CTextView类再增加一个CPoint类型的成员变量,取名m_ptOrigin,权限为私有。在CTextView类的构造函数中设置其初值为0。

1
2
3
4
5
6
7
8
void CTextView::OnLButtonDown(UINT nFlags, CPoint point) 
{
SetCaretPos(point);
m_strLine.Empty();
m_ptOrigin = point;

CView::OnLButtonDown(nFlags, point);
}

  注意:回车字符的ASCII码十六进制是0x0d,退格键的ASCII码十六进制值是0x08。

  最终代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void CTextView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
CClientDC dc(this);
TEXTMETRIC tm;
dc.GetTextMetrics(&tm);
if (0x0d == nChar)
{
m_strLine.Empty();
m_ptOrigin.y += tm.tmHeight;
}
else if(0x08 == nChar)
{
COLORREF clr = dc.SetTextColor(dc.GetBkColor());
dc.TextOut(m_ptOrigin.x, m_ptOrigin.y, m_strLine);
m_strLine = m_strLine.Left(m_strLine.GetLength() - 1);
dc.SetTextColor(clr);
}
else
{
m_strLine += nChar;
}
CSize sz = dc.GetTextExtent(m_strLine);
CPoint pt;
pt.x = m_ptOrigin.x + sz.cx;
pt.y = m_ptOrigin.y;
SetCaretPos(pt);

dc.TextOut(m_ptOrigin.x, m_ptOrigin.y, m_strLine);


CView::OnChar(nChar, nRepCnt, nFlags);
}

这里写图片描述

设置字体

1
2
3
4
5
6
7
8
9
10
11
void CTextView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
CClientDC dc(this);
CFont font;
font.CreatePointFont(300, "华文行楷", NULL);
CFont *pOldFont = dc.SelectObject(&font);
……
dc.SelectObject(pOldFont);

CView::OnChar(nChar, nRepCnt, nFlags);
}

这里写图片描述

字幕变色功能的实现

  在这个Text例子中,我们在视类的OnCreate 函数中设置定时器,设置一个时间间隔为100ms,标识为1的定时器。

1
2
3
4
5
6
7
8
int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
……

SetTimer(1, 100, NULL);

return 0;
}

  给CTextView类添加WM_TIMER消息的响应函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void CTextView::OnTimer(UINT nIDEvent) 
{
m_nWidth += 5;

CClientDC dc(this);
TEXTMETRIC tm;
dc.GetTextMetrics(&tm);
CRect rect;
rect.left =0;
rect.top = 200;
rect.right = m_nWidth;
rect.bottom = rect.top + tm.tmHeight;

dc.SetTextColor(RGB(255, 0, 0));
CString str;
str.LoadString(IDS_STRINGVC);
dc.DrawText(str, rect, DT_LEFT);

rect.top = 150;
rect.bottom = rect.top + tm.tmHeight;
dc.DrawText(str, rect, DT_RIGHT);

CSize sz = dc.GetTextExtent(str);
if (m_nWidth > sz.cx)
{
m_nWidth = 0;
dc.SetTextColor(RGB(0, 255, 0));
dc.TextOut(0, 200, str);
}

CView::OnTimer(nIDEvent);
}

  红色渐变效果可看到。
这里写图片描述

绘图控制

简单绘图

  新建一个单文档类型的MFC AppWizard(exe)工程,取名:Graphic。
  添加的菜单项:
这里写图片描述
  给CGraphicView类中添加一个私有变量:

1
UINT m_nDrawType;

  在视类构造函数中将此变量初始化为0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void CGraphicView::OnDot() 
{
m_nDrawType = 1;

}

void CGraphicView::OnLine()
{
m_nDrawType = 2;
}

void CGraphicView::OnRectangle()
{
m_nDrawType = 3;
}

void CGraphicView::OnEllipse()
{
m_nDrawType = 4;
}

  CGraphicView类再增加一个CPoint类型的私有成员变量:m_ptOrigin。在CGraphicView类构造函数中,将该变量的值设置为0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void CGraphicView::OnLButtonDown(UINT nFlags, CPoint point) 
{
m_ptOrigin = point;

CView::OnLButtonDown(nFlags, point);
}

void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
CClientDC dc(this);
//为边框设定颜色
CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
dc.SelectObject(&pen);
//能看到图形内部内容(透明)
CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
dc.SelectObject(pBrush);

switch(m_nDrawType)
{
case 1:
dc.SetPixel(point,RGB(255, 0, 0));
break;
case 2:
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
break;
case 3:
dc.Rectangle(CRect(m_ptOrigin,point));
break;
case 4:
dc.Ellipse(CRect(m_ptOrigin, point));
break;
}
CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

设置对话框

  再增加一个对话框资源,ID为IDD_DLG_SETTING,Caption为Setting,Font为宋体。

设置线宽

  添加一个静态文本框,并将Caption设为“线宽”。再添加一个编辑框,ID:IDC_LINE_WIDTH。为此对话框资源创建一个响应的对话框类,类名为CSettingDlg。对编辑框右键,ClassWizard,为它添加一个成员变量:m_nLineWidth,类型为UINT。为绘图菜单下再增加一个菜单项为“设置”,ID为IDM_SETTING。为此菜单项添加一个命令响应,选择视类做出响应。为CGraphicView类添加一个私有成员变量:m_nLineWidth,类型:UINT,并在CGraphicView类的构造函数初始化为0。

1
2
3
4
5
6
7
8
9
void CGraphicView::OnSetting() 
{
CSettingDlg dlg;
dlg.m_nLineWidth = m_nLineWidth; //将保存的用户先前设置的线宽再传回给该设置对话框
if(IDOK == dlg.DoModal())//点击OK才保持线宽值
{
m_nLineWidth = dlg.m_nLineWidth;
}
}

  在源文件前部添加:

1
Include “SettingDlg.h”

  修改OnLButtonUp函数:

1
2
3
4
5
6
7
void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point) 
{
CClientDC dc(this);
//为边框设定颜色(m_nLineWidth定义线宽)
CPen pen(PS_SOLID, m_nLineWidth, RGB(255, 0, 0));
……
}

设置线型

  为对话框资源添加一个组框,Caption设为线型。ID为IDC_LINE_STYLE。在组框内放三个单选按钮,ID不变,名称分别为:实线、虚线、点线(不要改变顺序)。在第一个单选按钮上右键,属性勾上Group,使三个按钮成为一组。再为CGraphicView类添加一个Int类型的私有成员变量m_nLineStyle,在构造函数中初始化为0。
  由于WINGDI.h定义了一些符号常量,(可以在PS_SOLID右键,Go To Definition Of PS_SOLID),刚好PS_SOLID(实线)值本身就是0;PS_DASH(虚线)是1;PS_DOT(点线)是2。所以此处的排列是故意为之。
这里写图片描述
这里写图片描述

  注意:若要画出虚线和点线,线宽只能为0或1。

颜色对话框

  在绘图下增加一个子菜单,ID为IDM_COLOR,Caption为颜色。为其在视类增加一个命令响应,代码:

1
2
3
4
5
6
7
8
9
10
11
void CGraphicView::OnColor() 
{
CColorDialog dlg;
dlg.m_cc.Flags |= CC_RGBINIT;
dlg.m_cc.rgbResult = m_clr;
if (IDOK == dlg.DoModal())
{
m_clr = dlg.m_cc.rgbResult;
//dlg.m_cc.Flags |= CC_RGBINIT | CC_FULLOPEN;//让颜色对话框完全展开
}
}

  为CGraphicView类再增加一个COLORREF类型的私有成员变量:m_clr,并在构造函数中初始化为红色:

1
m_clr = RGB(255, 0, 0);

  修改该函数两处位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point) 
{
CClientDC dc(this);
//为边框设定颜色(m_nLineStyle定义线型,m_nLineWidth定义线宽,m_clr定义颜色)
CPen pen(m_nLineStyle, m_nLineWidth, m_clr);
dc.SelectObject(&pen);
//能看到图形内部内容(透明)
CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
dc.SelectObject(pBrush);

switch(m_nDrawType)
{
case 1:
dc.SetPixel(point,m_clr);
break;
case 2:
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
break;
case 3:
dc.Rectangle(CRect(m_ptOrigin,point));
break;
case 4:
dc.Ellipse(CRect(m_ptOrigin, point));
break;
}
CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

  注意://dlg.m_cc.Flags |= CC_RGBINIT | CC_FULLOPEN;//让颜色对话框完全展开
这句我没能实现展开效果。

字体对话框

  增加一个菜单,ID为IDM_FONT,Caption为字体。在视类增加命令响应,代码:

1
2
3
4
5
6
7
8
9
10
11
12
void CGraphicView::OnFont() 
{
CFontDialog dlg;
if (IDOK == dlg.DoModal())
{
if (m_font.m_hObject) //m_font对象是否已经与某字体资源相关联
m_font.DeleteObject();
m_font.CreateFontIndirect(dlg.m_cf.lpLogFont);
m_strFontName = dlg.m_cf.lpLogFont->lfFaceName;
}

}

1
2
3
4
5
6
7
8
9
10

void CGraphicView::OnDraw(CDC* pDC)
{
CGraphicDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CFont *pOldFont = pDC->SelectObject(&m_font);
pDC->TextOut(0, 0, m_strFontName);
pDC->SelectObject(pOldFont);
}

示例对话框

  在对话框中增加一个组框,Caption:示例,ID:IDC_SAMPLE。为CSettingDlg类添加编辑框控件的EN_CHANCE响应函数,对三个单选按钮都选择BN_CLICKED消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void CSettingDlg::OnRadio1() 
{
// TODO: Add your control notification handler code here
Invalidate();
}

void CSettingDlg::OnRadio2()
{
// TODO: Add your control notification handler code here
Invalidate();
}

void CSettingDlg::OnRadio3()
{
// TODO: Add your control notification handler code here
Invalidate();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void CSettingDlg::OnPaint() 
{
CPaintDC dc(this); // device context for painting

// TODO: Add your message handler code here
UpdateData();
CPen pen(m_nLineStyle, m_nLineWidth, m_clr);
dc.SelectObject(&pen);

CRect rect;
GetDlgItem(IDC_SAMPLE)->GetWindowRect(&rect);
ScreenToClient(&rect);

dc.MoveTo(rect.left+20, rect.top+rect.Height()/2);
dc.LineTo(rect.right-20, rect.top+rect.Height()/2);
// Do not call CDialog::OnPaint() for painting messages
}

这里写图片描述

  现在可以实时修改了。

10.6 改变对话框和控件的背景及文本颜色

改变整个对话框及其子控件的背景色

  为CSettingDlg类添加WM_CTLCOLOR消息,并定义一个CBrush类型的私有成员变量:m_brush,并在构造函数中初始化一个蓝色画刷:

1
m_brush.CreateSolidBrush (RGB(0, 0, 255));

1
2
3
4
5
6
7
8
9
10
HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

// TODO: Change any attributes of the DC here

// TODO: Return a different brush if the default is not desired
//return hbr;
return m_brush;
}

这里写图片描述

仅改变某个子控件的背景及文本颜色

图形的保存和重绘

坐标空间和转换

坐标空间

  Win32应用程序编程接口(API)使用四种坐标空间:世界坐标系空间、页面空间、设备空间和物理设备空间。Win32 API把世界坐标系空间和页面空间称为逻辑空间。

转换

  转换是把对象从一个坐标空间复制到另一个坐标空间时改变(或转变)这一对象的大小、方位和形态。

图形的保存和重绘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

// TODO: Change any attributes of the DC here

// TODO: Return a different brush if the default is not desired
//return hbr;
if (pWnd -> GetDlgCtrlID() == IDC_LINE_STYLE)
{
pDC->SetTextColor(RGB(255, 0, 0));
return m_brush;
}
return hbr;
}

这里写图片描述

  上述程序再加一行:

1
pDC->SetBkMode(TRANSPARENT);

这里写图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

// TODO: Change any attributes of the DC here

// TODO: Return a different brush if the default is not desired
//return hbr;
if (pWnd -> GetDlgCtrlID() == IDC_LINE_STYLE)
{
pDC->SetTextColor(RGB(255, 0, 0));
pDC->SetBkMode(TRANSPARENT);
return m_brush;
}
if (pWnd->GetDlgCtrlID() == IDC_LINE_WIDTH)
{
pDC->SetTextColor(RGB(255, 0, 0));
//pDC->SetBkMode(TRANSPARENT);
pDC->SetBkColor(RGB(0, 0, 255));
return m_brush;
}
return hbr;
}

这里写图片描述

改变控件上的文本字体

  为对话框增加一个静态文本控件,ID:IDC_TEXT,Caption:程序员,为CSettingDlg类增加一个CFont类型的私有成员变量:m_font,在构造函数中添加

1
m_font.CreatePointFont(200, "华文行楷");

  在OnCtlColor函数中添加:

1
2
3
4
if (pWnd->GetDlgCtrlID() == IDC_TEXT)
{
pDC->SelectObject(&m_font);
}

这里写图片描述

改变按钮控件的背景色及文本颜色

  在CSettingDlg类OnCtlColor函数中添加:

1
2
3
4
5
6
7
	if (pWnd->GetDlgCtrlID() == IDOK)
{
pDC->SetTextColor(RGB(255, 0, 0));
return m_brush;
}
return hbr;
}

  点Insert-New Class,选择MFC Class,新增类名:CTestBtn,基类CButton。
  为此类添加DrawItem虚函数重写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void CTestBtn::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
// TODO: Add your code to draw the specified item
UINT uStyle = DFCS_BUTTONPUSH;

ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);

if (lpDrawItemStruct->itemState & ODS_SELECTED)
uStyle |= DFCS_PUSHED;

::DrawFrameControl(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);

CString strText;
GetWindowText(strText);

COLORREF crOldColor = ::SetTextColor(lpDrawItemStruct->hDC, RGB(255, 0, 0));
::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(),
&lpDrawItemStruct->rcItem, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
::SetTextColor(lpDrawItemStruct->hDC, crOldColor);

}

  然而,此时我返回双击OK键显示“Cannot add new member”……

  按理,接下来应该是:
  利用ClassWizard打开Add Member Variable对话框,为OK按钮关联一个成员变量,名称为m_btnTest,类型CTestBtn。在SettingDlg.h文件前部添加#include “TestBtn.h”。对OK右键属性,打开Styles,选中Owner draw选项。此时OK文字变红色。

位图的显示

定制应用程序外观

修改应用程序窗口的外观

在窗口创建之前修改

  创建前,打开CMainFrame类的PreCreateWindow成员函数,修改CREATETRUCT结构体中的cx和cy成员。

1
2
3
4
5
6
7
8
9
10
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
cs.cx = 300;
cs.cy = 200;
return TRUE;
}

  创建运行,可看到初始大小为300x200的应用程序窗口。

  修改窗口标题:在上述 return TRUE; 前添加:

1
2
cs.style &= ~FWS_ADDTOTITLE;
cs.lpszName = "http://www.sunxin.org";

在窗口创建之后修改

  注释掉之前添加的代码。在OnCreate函数中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{


// TODO: Delete these three lines if you don't want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);

SetWindowLong(m_hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);


return 0;
}

  创建运行后可看到文档标题去掉了。

  去掉窗口最大化框类型:
  将上述SetWindowLong函数替换为

1
SetWindowLong(m_hWnd, GWL_STYLE, GetWindowLong(m_hWnd, GWL_STYLE) & ~WS_MAXIMIZEBOX);

  创建运行发现最大化框变灰,不能放大窗口了。

修改窗口的光标、图标和背景

在窗口创建之前修改

网络编程

计算机网络基本知识

ISO/OSI七层参考模型
应用层——处理网络应用
Telnet、FTP、HTTP、DNS、SMTP、POP3

表示层——数据表示
TCP、UDP

会话层——主机间通信
传输层——端到端的连接
网络层——寻址和最短路径
IP、ICMP、IGMP

数据链路层——介质访问(接入)
物理层——二进制传输

基于TCP的网络应用程序的编写

服务器端程序

  关闭先前的工作区,新建一个工程,选择Win32 Console Application类型,名为TCPSrv。选择An empty project选项,创建一个空工程。再新建一个C++源文件:TcpSrv.cpp。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <Winsock2.h>
#include <stdio.h>

void main()
{
//加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD(1, 1);

err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0){
return;
}

if (LOBYTE(wsaData.wVersion) != 1 ||
HIBYTE(wsaData.wVersion) != 1){
WSACleanup();
return;
}
//创建用于监听的套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(7000);

//绑定套接字
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
//将套接字设为监听模式,准备接收客户请求
listen(sockSrv, 5);

SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);

while (1)
{
//等待客户请求到来
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
char sendBuf[100];
sprintf(sendBuf, "Welcome %s to http://www.sunxin.org", inet_ntoa(addrClient.sin_addr));
//发送数据
send(sockConn, sendBuf, strlen(sendBuf)+1, 0);
char recvBuf[100];
//接收数据
recv(sockConn, recvBuf, 100, 0);
//打印接收的数据
printf("%s\n", recvBuf);
//关闭套接字
closesocket(sockConn);
}
}

  Project-Setting-Link,在Object/library modules编辑框中添加ws2_32.lib文件,注意输入的库文件与前面的库文件之间一定 要有一个空格。
这里写图片描述

客户端程序

  在工作区名称上单击鼠标右键,选择Add New Project to Workspace,再创建一个Win32 Console Application类型的应用程序,创建一个空工程。为此增加一个C++源文件:TcpClient.cpp。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <Winsock2.h>
#include <stdio.h>

void main()
{
//加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD(1, 1);

err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0){
return;
}

if (LOBYTE(wsaData.wVersion) != 1 ||
HIBYTE(wsaData.wVersion) != 1){
WSACleanup();
return;
}
//创建套接字
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);

SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(7000);

//向服务器发出连接请求
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

//接收数据
char recvBuf[100];
recv(sockClient, recvBuf, 100, 0);
printf("%s\n",recvBuf);
//发送数据
send(sockClient, "This is lisi", strlen("This is lisi")+1, 0);
//关闭套接字
closesocket(sockClient);
WSACleanup();
}

  链接库文件:ws2_32.lib。
  创建运行,首先运行服务器程序,然后再运行客户端程序。

这里写图片描述

  注意:当没有报错,服务器端运行结果为“烫烫……烫”(N个烫)时,尝试换一个端口号,有可能你设置的端口号被其它的应用程序占用了。

基于UDP的网络应用程序的编写

服务器端程序

  关闭先前的工作区,新建一个工程,选择Win32 Console Application类型,名为UdpSrv。选择An empty project选项,创建一个空工程。再新建一个C++源文件:UdpSrv.cpp。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <Winsock2.h>
#include <stdio.h>

void main()
{
//加载套接字库
WORD wVersionRequired;
WSADATA wsaData;
int err;

wVersionRequired = MAKEWORD(1, 1);

err = WSAStartup(wVersionRequired, &wsaData);
if (err != 0)
{
return;
}

if (LOBYTE(wsaData.wVersion) != 1 ||
HIBYTE(wsaData.wVersion) !=1)
{
WSACleanup();
return;
}
//创建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(7000);
//绑定套接字
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

//等待并接收数据
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
char recvBuf[100];
recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len);
printf("%s\n",recvBuf);
//关闭套接字
closesocket(sockSrv);
WSACleanup();

}

  在工程设置对话框的链接选项卡下添加库文件:Ws2_32.lib的链接。

客户端程序

  在同一个UdpSrv工作区中创建客户端应用程序。创建一个空的Win32 Console Application类型的工程,名为:UdpClient。为该工程添加一个C++源文件:UdpClient.cpp。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <Winsock2.h>
#include <stdio.h>

void main()
{
//加载套接字库
WORD wVersionRequired;
WSADATA wsaData;
int err;

wVersionRequired = MAKEWORD(1, 1);

err = WSAStartup(wVersionRequired, &wsaData);
if (err != 0)
{
return;
}

if (LOBYTE(wsaData.wVersion) != 1 ||
HIBYTE(wsaData.wVersion) !=1)
{
WSACleanup();
return;
}

//创建套接字
SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(7000);
//发送数据
sendto(sockClient, "Hello", strlen("Hello")+1, 0,
(SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
//关闭套接字
closesocket(sockClient);
WSACleanup();
}

  链接库文件:ws2_32.lib。

  创建运行。服务器端程序应先启动,然后启动客户端程序。

这里写图片描述

  基于TCP和基于UDP的网络应用程序在发送和接收数据时使用的函数是不一样的:前者使用send和recv,后者使用sendto和recvfrom。

基于UDP的简单聊天程序

  在新工作区新建一个空的Win32 Console Application类型的应用程序,名为NetSrv。为该工程添加一个C++源文件:NetSrv.cpp。接着为该工程添加对WinSock库的链接,即在工程设置对话框的Link选项卡上添加ws2_32.lib文件的链接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <Winsock2.h>
#include <stdio.h>
void main()
{
//加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD(1, 1);

err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return;
}

if (LOBYTE(wsaData.wVersion) != 1 ||
HIBYTE(wsaData.wVersion) !=1)
{
WSACleanup();
return;
}

//创建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);

SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(7000);

//绑定套接字
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

char recvBuf[100];
char sendBuf[100];
char tempBuf[200];

SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while(1)
{
//等待并接收数据
recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len);
if ('q' == recvBuf[0])
{
sendto(sockSrv, "q", strlen("q")+1, 0, (SOCKADDR*)&addrClient, len);
printf("Chat end!\n");
break;
}
sprintf(tempBuf, "%s say : %s", inet_ntoa(addrClient.sin_addr), recvBuf);
printf("%s\n", tempBuf);
//发送数据
printf("Please input data:\n");
gets(sendBuf);
sendto(sockSrv, sendBuf, strlen(sendBuf)+1, 0, (SOCKADDR*)&addrClient, len);
}
//关闭套接字
closesocket(sockSrv);
WSACleanup();
}

客户端程序

  向已有工作区增加一个空的Win32 Console Application类型的工程:NetClient。为此添加一个C++源文件:NetClient.cpp。为该工程添加ws2_32.lib文件的链接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <Winsock2.h>
#include <stdio.h>
void main()
{
//加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD(1, 1);

err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return;
}

if (LOBYTE(wsaData.wVersion) != 1 ||
HIBYTE(wsaData.wVersion) !=1)
{
WSACleanup();
return;
}

//创建套接字
SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);

SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(7000);

char recvBuf[100];
char sendBuf[100];
char tempBuf[200];

int len = sizeof(SOCKADDR);

while(1)
{
//发送数据
printf("Please input data:\n");
gets(sendBuf);
sendto(sockClient, sendBuf, strlen(sendBuf)+1, 0, (SOCKADDR*)&addrSrv, len);
//等待并接收数据

recvfrom(sockClient, recvBuf, 100, 0, (SOCKADDR*)&addrSrv, &len);
if('q' == recvBuf[0])
{
sendto(sockClient, "q", strlen("q")+1, 0, (SOCKADDR*)&addrSrv, len);
printf("Chat end!\n");
break;
}
sprintf(tempBuf, "%s say : %s", inet_ntoa(addrSrv.sin_addr), recvBuf);
printf("%s\n", tempBuf);
}
//关闭套接字
closesocket(sockClient);
WSACleanup();
}

这里写图片描述

多线程

进程

程序和进程

简单多线程示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <windows.h>
#include <iostream.h>

DWORD WINAPI Fun1Proc(
LPVOID lpParameter //thread data
);
void main()
{
HANDLE hThread1;
hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
CloseHandle(hThread1);
cout<<"main thread is running"<<endl;
Sleep(10);//让主线程暂停运行,进入分线程
}

//线程1的入口函数
DWORD WINAPI Fun1Proc(
LPVOID lpParameter //thread data
)
{
cout<<"thread1 is running"<<endl;
return 0;
}

这里写图片描述

  交替运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <windows.h>
#include <iostream.h>

DWORD WINAPI Fun1Proc(
LPVOID lpParameter //thread data
);

int index = 0;

void main()
{
HANDLE hThread1;
hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
CloseHandle(hThread1);

while (index ++< 100)
{
cout<<"main thread is running"<<endl;
}
//Sleep(10);//让主线程暂停运行,进入分线程
}

//线程1的入口函数

DWORD WINAPI Fun1Proc(

LPVOID lpParameter //thread data
)

{
while (index++< 100)
cout<<"thread1 is running"<<endl;
return 0;
}

这里写图片描述

线程同步

火车站售票系统模拟程序

  由主线程创建的两个线程(1和2)负责销售火车票。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <windows.h>
#include <iostream.h>

DWORD WINAPI Fun1Proc(
LPVOID lpParameter //thread data
);

DWORD WINAPI Fun2Proc(
LPVOID lpParameter //thread data
);

int index = 0;
int tickets = 100;
HANDLE hMutex;

void main()
{
HANDLE hThread1;
HANDLE hThread2;

//创建互斥对象
hMutex = CreateMutex(NULL, FALSE, NULL);

//创建线程
hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
Sleep(4000);
}





//线程1的入口函数
DWORD WINAPI Fun1Proc(

LPVOID lpParameter //thread data
)

{
while (TRUE)
{
WaitForSingleObject(hMutex, INFINITE);//实现线程同步
if (tickets > 0)
{
Sleep(1);
cout<<"thread1 sell ticket:"<<tickets--<<endl;
}
else
break;
ReleaseMutex(hMutex);//释放当前线程对互斥对象的所有权
}
return 0;

}


//线程2的入口函数
DWORD WINAPI Fun2Proc(

LPVOID lpParameter //thread data
)


{
while (TRUE)
{
WaitForSingleObject(hMutex,INFINITE);
if (tickets > 0)
{
Sleep(1);
cout<<"thread2 sell ticket:"<<tickets--<<endl;
}
else
break;
ReleaseMutex(hMutex);
}
return 0;
}

这里写图片描述
  这时所销售的票号正常,没有看到销售了号码为0的票。

  对互斥对象来说,谁拥有谁释放。

保证应用程序只有一个实例运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <windows.h>
#include <iostream.h>

DWORD WINAPI Fun1Proc(
LPVOID lpParameter //thread data
);

DWORD WINAPI Fun2Proc(
LPVOID lpParameter //thread data
);

int index = 0;
int tickets = 100;
HANDLE hMutex;

void main()
{
HANDLE hThread1;
HANDLE hThread2;

//创建互斥对象(注意命名)
hMutex = CreateMutex(NULL, FALSE, "1");
if (hMutex)
{
if (ERROR_ALREADY_EXISTS == GetLastError())
{
cout<<"only one instance can run!"<<endl;
return;
}
}

//创建线程
hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
WaitForSingleObject(hMutex, INFINITE);
ReleaseMutex(hMutex);
ReleaseMutex(hMutex);
Sleep(4000);
}

//线程1的入口函数
DWORD WINAPI Fun1Proc(

LPVOID lpParameter //thread data
)

{
while (TRUE)
{
WaitForSingleObject(hMutex, INFINITE);//实现线程同步
if (tickets > 0)
{
Sleep(1);
cout<<"thread1 sell ticket:"<<tickets--<<endl;
}
else
break;
ReleaseMutex(hMutex);//释放当前线程对互斥对象的所有权
}
return 0;

}


//线程2的入口函数
DWORD WINAPI Fun2Proc(

LPVOID lpParameter //thread data
)


{
while (TRUE)
{
WaitForSingleObject(hMutex,INFINITE);
if (tickets > 0)
{
Sleep(1);
cout<<"thread2 sell ticket:"<<tickets--<<endl;
}
else
break;
ReleaseMutex(hMutex);
}
return 0;
}

网络聊天室程序的实现

  新建一个基于对话框的工程,名为:Chat。
这里写图片描述
这里写图片描述

加载套接字库

  在CChatApp类的InitInstance函数开始位置

1
2
3
4
5
6
7
8
9
BOOL CChatApp::InitInstance()
{
if (!AfxSocketInit())
{
AfxMessageBox("加载套接字库失败!");
return FALSE;
}
……
}

  在stdafx.h中,添加头文件#include <Afxsock.h>

创建并初始化套接字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
BOOL CChatDlg::InitSocket()
{
//创建套接字
m_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == m_socket)
{
MessageBox("套接字创建失败!");
return FALSE;
}
SOCKADDR_IN addrSock;
addrSock.sin_family = AF_INET;
addrSock.sin_port = htons(7000);
addrSock.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

int retval;
//绑定套接字
retval = bind(m_socket, (SOCKADDR*)&addrSock, sizeof(SOCKADDR));
if (SOCKET_ERROR == retval)
{
closesocket(m_socket);
MessageBox("绑定失败!");
return TRUE;
}
return TRUE;

}
1
2
3
4
5
6
7
8
9
BOOL CChatDlg::OnInitDialog()
{
……

// TODO: Add extra initialization here
InitSocket();

return TRUE; // return TRUE unless you set the focus to a control
}

实现接收端功能

  在CChatDlg类中定义:

1
2
3
4
5
6
7
8
/////////////////////////////////////////////////////////////////////////////
// CChatDlg dialog

struct RECVPARAM
{
SOCKET sock; //已创建的套接字
HWND hwnd; //对话框句柄
};

  在Chatdlg.h中添加:static DWORD WINAPI RecvProc(LPVOID lpParameter);
在OnInitDialog()中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
BOOL CChatDlg::OnInitDialog()
{

……

// TODO: Add extra initialization here
InitSocket();
RECVPARAM *pRecvParam = new RECVPARAM;
pRecvParam->sock = m_socket;
pRecvParam->hwnd = m_hWnd;
//创建接收线程
HANDLE hThread = CreateThread(NULL, 0, RecvProc, (LPVOID)pRecvParam, 0, NULL);
//关闭该接收程句柄,释放其引用计数
CloseHandle(hThread);


return TRUE; // return TRUE unless you set the focus to a control
}

  在CChatDlg类中添加:

1
2
3
4
DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
{
return 0;
}

  若要求采用完全面向对象的思想来编程,不能使用全局函数和全局变量了,可以采用静态成员函数和静态成员变量的方法来解决。
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
{
//获取主线程传递的套接字和窗口句柄
SOCKET sock = ((RECVPARAM*)lpParameter)->sock;
HWND hwnd = ((RECVPARAM*)lpParameter)->hwnd;
delete lpParameter;

SOCKADDR_IN addrFrom;
int len = sizeof(SOCKADDR);

char recvBuf[200];
char tempBuf[300];
int retval;
while(TRUE)
{
//接收数据
retval = recvfrom(sock, recvBuf, 200, 0, (SOCKADDR*)&addrFrom, &len);
if (SOCKET_ERROR == retval)
break;
sprintf(tempBuf, "%s 说: %s", inet_ntoa(addrFrom.sin_addr), recvBuf);
::PostMessage(hwnd, WM_RECVDATA, 0, (LPARAM)tempBuf);
}
return 0;
}

  在该类添加头文件 #define WM_RECVDATA WM_USER+1

在CChatDlg类头文件中编写该消息响应函数原型的声明:

1
2
3
4
5
6
7
8
9
// Generated message map functions
//{{AFX_MSG(CChatDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
//}}AFX_MSG
afx_msg void OnRecvData(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()

在CChatDlg类的源文件中添加WM_RECVDATA消息映射。

1
2
3
4
5
6
7
8
BEGIN_MESSAGE_MAP(CChatDlg, CDialog)
//{{AFX_MSG_MAP(CChatDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_RECVDATA, OnRecvData)
END_MESSAGE_MAP()

在构造函数中

1
2
3
4
5
6
7
8
9
10
11
12
void CChatDlg::OnRecvData(WPARAM wParam, LPARAM lParam)
{
//取出接收到的数据
CString str = (char*)lParam;
CString strTemp;
//获得已有数据
GetDlgItemText(IDC_EDIT_RECV, strTemp);
str += "\r\n";
str += strTemp;
//显示所有接收到的数据
SetDlgItemText(IDC_EDIT_RECV, str);
}

实现发送端功能

双击发送,添加响应函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void CChatDlg::OnBtnSend() 
{
//获取对方IP
// TODO: Add your control notification handler code here
DWORD dwIP;
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);

SOCKADDR_IN addrTo;
addrTo.sin_family = AF_INET;
addrTo.sin_port = htons(7000);
addrTo.sin_addr.S_un.S_addr = htonl(dwIP);

CString strSend;
//获得待发送数据
GetDlgItemText(IDC_EDIT_SEND, strSend);
//发送数据
sendto(m_socket, strSend, strSend.GetLength()+1, 0,
(SOCKADDR*)&addrTo, sizeof(SOCKADDR));
//清空发送编辑框中的内容
SetDlgItemText(IDC_EDIT_SEND, "");
}

  为了让编辑框控件接受换行符,必须设置该控件支持多行数据这一属性。
这里写图片描述

  将“发送”设置为Default button,还可以选择取消Visible选项。
  本例在一个程序中同时实现了接收端和发送端的功能,所以只需在聊天双方各自的机器上安装本程序,在聊天时,通过输入对方主机的IP地址,就可以与对方进行通信了。

ActiveX控件

ActiveX控件

OCX是ActiveX控件的一种后缀名。但ActiveX控件对应的文件也可以是其他后缀名,例如DLL。作为一个典型的ActiveX控件,它具有方法、属性、事件这三种特性。在一个文件中可以包含多个ActiveX控件。

MFC ActiveX ControlWizard

动态链接库

  动态链接库有两种加载方式:隐式链接和显式加载

Win32 DLL的创建和使用

  新建一个Win32 Dynamic-Link Library类型的工程,取名为Dll1。并在AppWizard的第一步选择“An empty Dll project”,即创建一个空的动态链接库工程。然后添加一个C++源文件:Dll1.cpp。添加代码:

1
2
3
4
5
6
7
8
9
int add(int a, int b)
{
return a + b;
}

int subtract(int a, int b)
{
return a - b;
}

  Build生成Dll1程序。在该工程的Debug目录下,可看到一个Dll1.dll文件,这就是生成的动态链接库文件。

Dumpbin命令

  应用程序如果想要访问某个DLL中的函数,那么该函数必须是已经被导出的函数。为了查看有哪些导出函数,可以用VS提供的命令行工具:Dumpbin实现。

从DLL中导出函数

  为导出函数,需在每一个将被导出的函数前添加标识符:_declspec(dllexport)。修改上述代码:

1
2
3
4
5
6
7
8
9
_declspec(dllexport) int add(int a, int b)
{
return a + b;
}

_declspec(dllexport) int subtract(int a, int b)
{
return a - b;
}

  编译后可看到又生成了两个新文件,Dll1.lib,它保存Dll1.dll中导出的函数和变量的符号名。以及DALL1.EXP文件。

隐式链接方式加载DLL

  编写一个测试程序测试这个动态链接库。新建一个基于对话框的MFC应用程序,取名DllTest,放置两个按钮,ID和Caption分别为:IDC_BTN_ADD,Add,IDC_BTN_SUBTRACT,Subtract。

利用extern声明外部函数

  为让编译器知道这两个函数,需作出声明,注意放在OnBtnAdd函数和OnBtnSubtract函数前面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extern int add(int a, int b);
extern int subtract(int a, int b);

void CDllTestDlg::OnBtnAdd()
{
// TODO: Add your control notification handler code here
CString str;
str.Format("5 + 3 = %d", add(5, 3));
MessageBox(str);
}

void CDllTestDlg::OnBtnSubtract()
{
// TODO: Add your control notification handler code here
CString str;
str.Format("5 - 3 = %d", subtract(5, 3));
MessageBox(str);
}

  Build后报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--------------------Configuration: DllTest - Win32 Debug--------------------
Compiling resources...
Compiling...
StdAfx.cpp
Compiling...
DllTest.cpp
DllTestDlg.cpp
Generating Code...
Linking...
DllTestDlg.obj : error LNK2001: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z)
DllTestDlg.obj : error LNK2001: unresolved external symbol "int __cdecl subtract(int,int)" (?subtract@@YAHHH@Z)
Debug/DllTest.exe : fatal error LNK1120: 2 unresolved externals
执行 link.exe 时出错.

DllTest.exe - 1 error(s), 0 warning(s)

  可看到编译成功,错误发生在链接时。为解决该问题,需利用动态链接库的引入库文件。
  在Dll1.dll文件所在目录下,复制Dll1.lib文件,并将其复制到DllTest程序所在目录下,这个文件中就包含了Dll1.dll中导出函数的符号名。
  然后在DllTest中,选择Porject\Settings\link,在Object/library modules中输入dll1.lib。
  再次编译,成功生成DllTest.exe文件。
  (可利用dumpbin -imports dlltest.exe查看输入信息)

  运行程序,弹出报错对话框:
报错

  将Dll1.dll放置在DllTest工程所在目录下,就好了。

  效果如图。
效果

Depends工具

  在Microsoft Visual Studio\Common\Tools中有一个DEPENDS.EXE,该工具可以查看可执行程序,还可以查看动态链接库,主要是看它们依赖于哪些动态链接库。
  打开该工具,单击File\Open,选择DllText.exe,将会看到:
DEPENDS

  DllTest程序需访问Dll1.dll这一动态链接库,但该文件名前有一个问号,说明没有找到Dll1.dll这个动态链接库。这是因为前面将动态链接库文件放在了\DllTest\Debug目录的上一级目录下了。这里,可将Dll1.dll文件再复制到\DllTest\Debug目录下,然后重启Depends工具。这时问号就没有了。(因为Dll1.dll与DllTest.exe位于同一目录,在打开DllTest.exe时,就可找到该动态链接库。)

成功

利用_declspec(dllimport)声明外部函数

  除了使用extern关键字表明函数是外部定义的之外,还可以使用标识符:_declspec(dllimport)来表明函数是从动态链接库中引入的。将之前的extern声明注释起来。添加:

1
2
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int a, int b);

  若调用的函数来自于动态链接库,应采用这种方式声明外部函数,编译器可以生成运行效率更高的代码。

完善Win32 DLL例子

  为知道DLL有哪些导出函数,通常在编写动态链接库时,会提供一个头文件,在此提供DLL导出函数原型的声明,以及函数有关注释文档。

  为DLL1工程添加一个头文件:Dll1.h,并添加代码

1
2
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int a, int b);

  然后将DllTestDlg.cpp先前添加的声明语句注释起来,并在前部添加下面的语句:

1
#include "dll1.h"

  Build并运行,结果和之前一样。

  所以在发布Dll1.dll动态链接库时,可将Dll1.h头文件一起提供给使用者。

  下面对Dll.h进行改造,使其不仅能为调用动态链接库的客户端程序服务,也能由动态链接库程序自身来使用。修改头文件:

1
2
3
4
5
6
7
#ifdef DLL1_API
#else
#define DLL1_API _declspec(dllimport)
#endif

_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int a, int b);

  修改Dll1.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#define DLL1_API _declspec(dllexport)
#include "Dll1.h"

int add(int a, int b)
{
return a + b;
}

int subtract(int a, int b)
{
return a - b;
}

  将重新生成的文件复制,运行,结果也是正确的。

从DLL中导出C++类

  在一个动态链接库中还可以导出一个C++类。
  在Dll1.h中添加如下代码:

1
2
3
4
5
class DLL1_API Point
{
public:
void output(int x, int y);
};

  在Dll1.cpp中改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define DLL1_API _declspec(dllexport)
#include "Dll1.h"
#include <Windows.h>
#include <stdio.h>

int add(int a, int b)
{
return a + b;
}

int subtract(int a, int b)
{
return a - b;
}

void Point::output(int x, int y)
{
//返回调用者进程当前正在使用的那个窗口的句柄
HWND hwnd = GetForegroundWindow();
//获取DC
HDC hdc = GetDC(hwnd);
char buf[20];
memset(buf, 0, 20);
sprintf(buf, "x = %d, y = %d", x, y);
//输出坐标
TextOut(hdc, 0, 0, buf, strlen(buf));
//释放DC
ReleaseDC(hwnd, hdc);
}

  将Dll1.dll和Dll1.lib复制到测试工程DllTest所在目录下(本例将对应Dll1.h也放在了DllTest项目工程下,所以Dll1.h也应相应复制过来)。为避免麻烦,也可以把动态链接库文件所在目录添加到系统的环境变量Path中。这样就无需复制。
  为测试这个新生成的DLL,打开DllTest工程,在对话框中增加一个按钮,属性为IDC_BTN_OUTPUT,Capition为Output。双击按钮添加响应函数OnBtnOutput。

1
2
3
4
5
6
void CDllTestDlg::OnBtnOutput() 
{
// TODO: Add your control notification handler code here
Point pt;
pt.output(5, 3);
}

  记得删除Debug下的旧Dll1.dll,放入新的,否则会报错。
Output
  可利用Dumpbin命令的exports选项查看Dll1.dll这一动态链接库的导出情况,利用imports选项查看测试程序的导入情况。

另外,在实现动态链接库时,可以不导出整个类,而只导出该类中的某些函数。
打开Dll1工程,在Dll1.h中将声明Point类时使用的DLL1_API宏注释起来,然后在output函数的声明前放置DLL1_API宏。这样就表示只导出Point类中的成员函数output。为证实这一点,为Point类再添加一个成员函数test,

1
2
3
4
5
6
class /*DLL1_API*/ Point
{
public:
void DLL1_API output(int x, int y);
void test();
};

接着在Dll1.cpp中添加test函数的实现:

1
2
3
void Point::test()
{
}

Build后,利用dumpbin命令的exports可查看Dll1.dll的导出信息。
可将所需文件再次复制到DllTest工程中,运行结果和之前相同。
在导出类的成员函数时,该函数必须具有public类型的访问权限,否则即使能被导出也不能被其他程序访问。

解决名字改编问题

C++编译器在生产厂DLL时,会对导出的函数进行名字改编,由于不同编译器改编规则不同,所以改编后名字不同。若利用不同编译器分别生成DLL和访问该DLL的客户端程序时,后者在访问该DLL的导出函数时就会出现问题。因此希望动态链接库在编译时,导出函数的名称不要发生改变。为此,在定义导出函数时,需加上限定符:extern “C”。C一定要大写。
打开Dll1工程,找到Dll1.cpp和Dll1.h中定义DLL1_API宏的代码,添加限定符。
此时,Dll1.h为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef DLL1_API
#else
#define DLL1_API extern "C" _declspec(dllimport)
#endif

DLL1_API int add(int a, int b);
DLL1_API int subtract(int a, int b);

/*class Point
{
public:
void DLL1_API output(int x, int y);
void test();
};*/

Dll1.cpp为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#define DLL1_API extern "C" _declspec(dllexport)
#include "Dll1.h"
#include <Windows.h>
#include <stdio.h>

int add(int a, int b)
{
return a + b;
}

int subtract(int a, int b)
{
return a - b;
}

/*
void Point::output(int x, int y)
{
//返回调用者进程当前正在使用的那个窗口的句柄
HWND hwnd = GetForegroundWindow();
//获取DC
HDC hdc = GetDC(hwnd);
char buf[20];
memset(buf, 0, 20);
sprintf(buf, "x = %d, y = %d", x, y);
//输出坐标
TextOut(hdc, 0, 0, buf, strlen(buf));
//释放DC
ReleaseDC(hwnd, hdc);
}

void Point::test()
{
}
*/

Build后生成Dll1.dll,用dumpbin命令的exports选项查看该动态链接库的导出信息,发现add和subtract函数名没有被改编。然后利用DllTest工程测试,将Point类的代码注释起来,将发现客户端可访问Dll1中的导出函数。

缺陷:extern “C”只能导出全局函数,不能导出一个类的成员函数。另外,如果导出函数的调用约定发生了改变,即使使用了extern “C”,函数名仍会发生改编。
例如,在Dll1.h中add和subtract函数添加_stdcall关键字标准调用约定。

1
2
3
4
5
6
7
#ifdef DLL1_API
#else
#define DLL1_API extern "C" _declspec(dllimport)
#endif

DLL1_API _stdcall int add(int a, int b);
DLL1_API _stdcall int subtract(int a, int b);

在Dll1.cpp中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define DLL1_API extern "C" _declspec(dllexport)
#include "Dll1.h"
#include <Windows.h>
#include <stdio.h>

int _stdcall add(int a, int b)
{
return a + b;
}

int _stdcall subtract(int a, int b)
{
return a - b;
}

没有_stdcall关键字,函数的调用约定就是C调用约定,标准调用约定是WINAPI调用约定,与C调用约定不同。
Build后生成最新Dll1.dll,利用Dumpbin的exports选项查看该动态链接库的导出情况,可看到名字变为_add@8。

这种情况下,可通过模型定义文件(DEF)的方式来解决名字改编问题。
新建一个Win32 Dynamic-Link Library类型的工程,取名为Dll2,在AppWizard第一步选择“An empty Dll project”选项。添加Dll2.cpp,

1
2
3
4
5
6
7
8
int add(int a, int b)
{
return a + b;
}
int subtract(int a, int b)
{
return a - b;
}

在Dll2工程目录下新建一个空文本文件,改后缀为.def,添加到工程Source文件并打开:

1
2
3
4
5
LIBRARY Dll2

EXPORTS
add
subtract

Bulid后利用Dumpbin的exports查看证明没有发生名字改编。

显示加载方式加载DLL

将最新的Dll2.dll复制到DllTest工程目录下。将DllTestDlg.cpp包含Dll1.h的那行代码注释起来,在link选项卡上删除对Dll1.lib的链接。
需用到LoadLibrary函数。

  To be continued…
  听听那冷雨

文章目录
  1. 1. 对话框
    1. 1.1. 对话框的创建和显示
      1. 1.1.1. 模态对话框的创建
      2. 1.1.2. 非模态对话框的创建
  2. 2. 动态创建按钮
    1. 2.1. 控件的访问
      1. 2.1.1. 控件的调整
      2. 2.1.2. 静态文本控件
      3. 2.1.3. 编辑框控件
        1. 2.1.3.1. 第一种方式
        2. 2.1.3.2. 第二种方式
        3. 2.1.3.3. 第三种方式
        4. 2.1.3.4. 第四种方式
        5. 2.1.3.5. 第五种方式
        6. 2.1.3.6. 第六种方式
        7. 2.1.3.7. 第七种方式
        8. 2.1.3.8. 总结
    2. 2.2. 对话框伸缩功能的实现
    3. 2.3. 输入焦点的传递
      1. 2.3.1. 法一
      2. 2.3.2. 法二
      3. 2.3.3. 法三
      4. 2.3.4. 法四
  3. 3. 文件和注册表操作
    1. 3.1. C语言对文件操作的支持
      1. 3.1.1. 文件的打开和写入
      2. 3.1.2. 文件的关闭
      3. 3.1.3. 文件指针定位
      4. 3.1.4. 文件的读取
      5. 3.1.5. 二进制文件和文本文件
      6. 3.1.6. 文本方式和二进制方式
    2. 3.2. C++对文件操作的支持
    3. 3.3. Win32 API 对文件操作的支持
      1. 3.3.1. 文件的创建、打开和写入
      2. 3.3.2. 文件的读取
  4. 4. 菜单
    1. 4.1. 菜单命令响应函数
    2. 4.2. 菜单命令的路由
      1. 4.2.1. 程序类对菜单命令的响应顺序
      2. 4.2.2. Windows消息的分类
      3. 4.2.3. 菜单命令的路由
    3. 4.3. 基本菜单操作
      1. 4.3.1. 标记菜单
      2. 4.3.2. 默认菜单项
      3. 4.3.3. 图形标记菜单
      4. 4.3.4. 禁用菜单项
      5. 4.3.5. 移除和装载菜单
      6. 4.3.6. MFC菜单命令更新机制
      7. 4.3.7. 快捷菜单
    4. 4.4. 动态菜单操作
      1. 4.4.1. 添加菜单项目
      2. 4.4.2. 插入菜单项目
      3. 4.4.3. 删除菜单
      4. 4.4.4. 动态添加的菜单项的命令响应
    5. 4.5. 电话本示例程序
      1. 4.5.1. 动态添加子菜单的实现
      2. 4.5.2. 显示输入的字符
      3. 4.5.3. 添加菜单项及其命令响应函数
      4. 4.5.4. 框架类窗口截获菜单命令消息
  5. 5. 简单绘图
    1. 5.1. MFC消息映射机制
    2. 5.2. 绘制线条
      1. 5.2.1. 利用MFC的CDC类实现画线功能
      2. 5.2.2. 利用MFC的CWindowDC类实现画线功能
      3. 5.2.3. 在桌面窗口中画线
      4. 5.2.4. 绘制彩色线条
    3. 5.3. 使用画刷绘图
      1. 5.3.1. 简单画刷
      2. 5.3.2. 位图画刷
      3. 5.3.3. 透明画刷
    4. 5.4. 绘制连续线条
    5. 5.5. 绘制扇形效果的线条
  6. 6. 文本编程
    1. 6.1. 插入符
      1. 6.1.1. 创建文本插入符
      2. 6.1.2. 创建图形插入符
    2. 6.2. 窗口重绘
      1. 6.2.1. OnDraw函数
      2. 6.2.2. 添加字符串资源
      3. 6.2.3. 路径
    3. 6.3. 字符输入
      1. 6.3.1. 设置字体
      2. 6.3.2. 字幕变色功能的实现
  7. 7. 绘图控制
    1. 7.1. 简单绘图
    2. 7.2. 设置对话框
      1. 7.2.1. 设置线宽
      2. 7.2.2. 设置线型
    3. 7.3. 颜色对话框
    4. 7.4. 字体对话框
    5. 7.5. 示例对话框
    6. 7.6. 10.6 改变对话框和控件的背景及文本颜色
      1. 7.6.1. 改变整个对话框及其子控件的背景色
      2. 7.6.2. 仅改变某个子控件的背景及文本颜色
  8. 8. 图形的保存和重绘
    1. 8.1. 坐标空间和转换
      1. 8.1.1. 坐标空间
      2. 8.1.2. 转换
    2. 8.2. 图形的保存和重绘
      1. 8.2.1. 改变控件上的文本字体
      2. 8.2.2. 改变按钮控件的背景色及文本颜色
    3. 8.3. 位图的显示
  9. 9. 定制应用程序外观
    1. 9.1. 修改应用程序窗口的外观
      1. 9.1.1. 在窗口创建之前修改
    2. 9.2. 在窗口创建之后修改
    3. 9.3. 修改窗口的光标、图标和背景
      1. 9.3.1. 在窗口创建之前修改
  10. 10. 网络编程
    1. 10.1. 计算机网络基本知识
    2. 10.2. 基于TCP的网络应用程序的编写
      1. 10.2.1. 服务器端程序
      2. 10.2.2. 客户端程序
    3. 10.3. 基于UDP的网络应用程序的编写
      1. 10.3.1. 服务器端程序
      2. 10.3.2. 客户端程序
    4. 10.4. 基于UDP的简单聊天程序
      1. 10.4.1. 客户端程序
  11. 11. 多线程
    1. 11.1. 进程
      1. 11.1.1. 程序和进程
    2. 11.2. 简单多线程示例
    3. 11.3. 线程同步
      1. 11.3.1. 火车站售票系统模拟程序
    4. 11.4. 保证应用程序只有一个实例运行
    5. 11.5. 网络聊天室程序的实现
      1. 11.5.1. 加载套接字库
      2. 11.5.2. 创建并初始化套接字
      3. 11.5.3. 实现接收端功能
      4. 11.5.4. 实现发送端功能
  12. 12. ActiveX控件
    1. 12.1. ActiveX控件
      1. 12.1.1. MFC ActiveX ControlWizard
  13. 13. 动态链接库
    1. 13.1. Win32 DLL的创建和使用
      1. 13.1.1. Dumpbin命令
      2. 13.1.2. 从DLL中导出函数
    2. 13.2. 隐式链接方式加载DLL
      1. 13.2.1. 利用extern声明外部函数
      2. 13.2.2. Depends工具
      3. 13.2.3. 利用_declspec(dllimport)声明外部函数
    3. 13.3. 完善Win32 DLL例子
    4. 13.4. 从DLL中导出C++类
    5. 13.5. 解决名字改编问题
    6. 13.6. 显示加载方式加载DLL
|