0%

一、MFC概念和作用

  • MFC:微软基础类库 (Microsoft Foundation Class Library),封装了windows应用程序的各种API和相关机制的C++类库

  • 总结:

    1. MFC是一个大的类库
    2. MFC是一个应用程序框架
  • 为什么使用MFC?

    • 应用提供的框架,可以快速开发
  • MFC常用的头文件

    • afx.h-将各种MFC头文件包含在内
    • afxwin.h-包含了各种MFC窗口类。包含afx.h和windows.h
    • afxext.h-提供了扩展窗口类的支持,例如工具栏

二、空win32程序模板

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
89
90
91
92
#include <Windows.h>

// 窗口过程函数声明
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

// 应用程序入口点
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
// 定义窗口类名
static TCHAR szAppName[] = TEXT("HelloWin");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;

// 设置窗口类属性
wndclass.style = CS_HREDRAW | CS_VREDRAW; // 水平和垂直重绘
wndclass.lpfnWndProc = WndProc; // 指向窗口过程函数的指针
wndclass.cbClsExtra = 0; // 类附加内存
wndclass.cbWndExtra = 0; // 窗口附加内存
wndclass.hInstance = hInstance; // 当前实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // 加载默认应用程序图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); // 加载箭头光标
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 设置背景颜色为白色
wndclass.lpszMenuName = NULL; // 无菜单
wndclass.lpszClassName = szAppName; // 窗口类名

//--------------------- 以下为 窗口类的注册 --------------------------------------------
if (!RegisterClass(&wndclass))
{
// 注册失败,显示错误消息
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
return 0;
}

// ---------------------- 创建窗口 --------------------------
hwnd = CreateWindow(szAppName, // 窗口类名
TEXT("The Hello Program"), // 窗口标题
WS_OVERLAPPEDWINDOW, // 窗口样式
CW_USEDEFAULT, // 初始水平位置
CW_USEDEFAULT, // 初始垂直位置
CW_USEDEFAULT, // 初始宽度
CW_USEDEFAULT, // 初始高度
NULL, // 父窗口句柄
NULL, // 菜单句柄
hInstance, // 当前实例句柄
NULL); // 窗口创建数据

// --------------------- 显示窗口 ------------------------
ShowWindow(hwnd, iCmdShow);
// ---------------------- 绘制用户区 ---------------------------
UpdateWindow(hwnd);

// ---------------------- 消息循环 -----------------------------------
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); // 翻译消息
DispatchMessage(&msg); // 分发消息
}
return msg.wParam; // 返回消息的wParam值
}

//-------------------- 以下为窗口函数WndProc--------------------------------
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;

switch (message)
{
case WM_CREATE:
// 创建窗口时播放声音
PlaySound(TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC);
return 0;

case WM_PAINT:
// 绘制窗口内容
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect); // 获取客户区矩形
DrawText(hdc, TEXT("Hello, Windows 98!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); // 绘制文本
EndPaint(hwnd, &ps);
return 0;

case WM_DESTROY:
// 销毁窗口时退出消息循环
PostQuitMessage(0);
return 0;
}
// 默认窗口过程
return DefWindowProc(hwnd, message, wParam, lParam);
}

三、加载bmp文件

bmp文件是MFC中使用的位图,用来显示图片

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
// 函数:LoadBitmapFromFile
// 说明:从文件加载一个位图,并返回该位图的句柄
// 参数:
// - HINSTANCE hInstance: 当前应用程序实例的句柄
// - LPCTSTR lpszName: 包含位图文件路径的字符串
// 返回值:
// - 如果成功,返回加载的位图的句柄
// - 如果失败,返回NULL
HBITMAP LoadBitmapFromFile(HINSTANCE hInstance, LPCTSTR lpszName) {
// 定义一个变量 hBitmap 并初始化为 NULL,用于存储加载的位图句柄
HBITMAP hBitmap = NULL;

// 使用 LoadImage 函数从文件加载位图
hBitmap = (HBITMAP)LoadImage(
hInstance, // 当前应用程序实例的句柄
lpszName, // 位图文件路径
IMAGE_BITMAP, // 指明要加载的是位图
0, 0, // 位图的宽度和高度,0 表示使用文件的原始大小
LR_LOADFROMFILE | LR_CREATEDIBSECTION // 从文件加载图像并创建一个 DIB 节
);

// 检查 LoadImage 是否成功加载了位图
if (hBitmap == NULL) {
// 如果 hBitmap 仍然是 NULL,表示加载失败
// 在这里添加错误处理代码,例如:
// MessageBox(NULL, TEXT("Failed to load bitmap"), TEXT("Error"), MB_ICONERROR);
}

// 返回加载的位图句柄
return hBitmap;
}

四、更改文本框数据

这是一个按钮控件,通过点击按钮,更改文本数据

  1. 使用CString定义一个字符串
  2. 使用Format 设置格式和内容
  3. 使用SetDlgItemText将字符串和控件练习起来
1
2
3
4
5
6
7
8
9
void CInterfaceDlg::OnBnClickedBtnTractionMax()
{
// TODO: 在此添加控件通知处理程序代码
m_iTractionMax = m_iControlMH;

CString strTemp;
strTemp.Format(TEXT("电位值:%d"), m_iTractionMax);
SetDlgItemText(IDC_TRACTION_MAX, strTemp);
}

五、读取文件

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
// 读取司控器信息配置文件函数
void CInterfaceDlg::ReadControllerInfo()
{
// 定义用于存储文件路径的缓冲区
TCHAR strFilePath[MAX_PATH];

// 获取当前工作目录并存储到缓冲区中
GetCurrentDirectory(MAX_PATH, strFilePath);

// 将文件名 "controller.ini" 拼接到当前工作目录路径后面
wcscat_s(strFilePath, TEXT("\\controller.ini"));

// 从 "controller.ini" 文件的 "Controller" 节读取 "TractionMax" 键的值
// 如果未找到该键,则返回默认值 0
m_iTractionMax = GetPrivateProfileInt(TEXT("Controller"), TEXT("TractionMax"), 0, strFilePath);

// 定义临时字符串变量用于格式化显示值
CString strTemp;

// 将读取到的 "TractionMax" 值格式化为字符串并存储在 strTemp 中
strTemp.Format(TEXT("电位值:%d"), m_iTractionMax);

// 将格式化后的字符串显示在对话框控件上,控件的ID为 IDC_TRACTION_MAX
SetDlgItemText(IDC_TRACTION_MAX, strTemp);
}

六、写入文件

1
2
3
4
5
6
7
8
9
10
11
12
void CInterfaceDlg::OnBnClickedBtnWrite()
{
// TODO: 在此添加控件通知处理程序代码
TCHAR strFilePath[MAX_PATH];
GetCurrentDirectory(MAX_PATH, strFilePath);
wcscat_s(strFilePath, TEXT("\\controller.ini"));

CString strTemp;
strTemp.Format(TEXT("%d"), m_iTractionMax);
WritePrivateProfileString(TEXT("Controller"), TEXT("TractionMax"), strTemp, strFilePath);
// 这里创建[Controller],并在它下面创建TractionMax
}

这是为空情况下自动创建的

1
2
3
4
5
[Controller]
TractionMax=0
TractionMin=5
BreakMin=2010
BreakMax=4000

这是节点之后,更新的

其中的 # 和 ;是注释,自动跳过,没有影响

1
2
3
4
5
6
7
8
9
10
11
#司控器设置

[Controller]
TractionMax =0
;司控器最大牵引位
TractionMin =5
;司控器最小牵引位
BreakMin =2010
;司控器最小制动位
BreakMax =4000
;司控器最大制动位

七、创建子窗口

1
2
3
4
5
void CInterfaceDlg::OnBnClickedBtnViewSend()
{
// TODO: 在此添加控件通知处理程序代码
m_pDlgSend->ShowWindow(SW_NORMAL);
}
  1. 创建主窗口类

    1
    2
    3
    4
    5
    6
    // CMainDlg.h
    class CMainDlg : public CDialogEx
    {
    private:
    CChildDialog* m_pChildDlg; // 指向子窗口的指针
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // CMainDlg.cpp
    void CMainDlg::OnBnClickedBtnCreateChild()
    {
    if (m_pChildDlg == nullptr)
    {
    m_pChildDlg = new CChildDialog(this); // 创建子对话框对象
    m_pChildDlg->Create(IDD_CHILD_DIALOG, this); // 创建子对话框
    m_pChildDlg->ShowWindow(SW_SHOW); // 显示子对话框
    }
    }
  2. 创建子窗口类

  3. 添加对话框资源
    确保在资源文件中添加了 IDD_MAIN_DIALOG(主对话框)和 IDD_CHILD_DIALOG(子对话框)的对话框资源,并在主对话框中添加一个按钮,其 ID 设置为 IDC_BTN_CREATE_CHILD,用来创建和显示子对话框。

八、添加列表控件

设置列表控件样式和列头

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
BOOL CDlgSend::OnInitDialog()
{
CDialog::OnInitDialog();

// 设置列表控件的扩展样式,包括全行选中、网格线、头部拖放和单行选择
DWORD dwExListStyle = m_listSend.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP | LVS_EX_SINGLEROW;
m_listSend.SetExtendedStyle(dwExListStyle);

// 定义列头的标题和宽度
CString strListHead[] = { TEXT("ID"), TEXT("Name"), TEXT("Value") };
int nListWidth[] = { 50, 580, 50 };

// 添加列头到列表控件
for (int i = 0; i < sizeof(nListWidth) / sizeof(int); i++)
{
m_listSend.InsertColumn(i, strListHead[i], LVCFMT_LEFT, nListWidth[i], -1);
}

// 填充列表控件的数据
FillListSend();

// 设置定时器,每隔100毫秒执行一次
SetTimer(IDT_TIMER_SEND, 100, NULL);

return TRUE; // return TRUE unless you set the focus to a control
// 异常: OCX 属性页应返回 FALSE
}

void CDlgSend::FillListSend()
{
m_listSend.DeleteAllItems();

for (int i = 0; i < sizeof(strSend)/sizeof(strSend[0]); i++)
{
m_listSend.InsertItem(i, TEXT(""));

CString strTemp;
strTemp.Format(TEXT("%d"), i + 1);
m_listSend.SetItemText(i, 0, strTemp);
m_listSend.SetItemText(i, 1, strSend[i]);
m_listSend.SetItemText(i, 2, TEXT("0"));

m_listSend.SetItemData(i, i + 1);
}
}

详细解释

  1. **设置列表控件样式 (LVS_EX_ 开头的扩展样式)**:

    • LVS_EX_FULLROWSELECT:允许整行选择。
    • LVS_EX_GRIDLINES:显示网格线。
    • LVS_EX_HEADERDRAGDROP:允许头部拖放。
    • LVS_EX_SINGLEROW:只允许选择单行。
    • 这些样式通过按位或运算符 | 结合到 dwExListStyle 中,然后通过 m_listSend.SetExtendedStyle(dwExListStyle); 应用到列表控件。
  2. 定义和添加列头

    • CString strListHead[] 定义了列头的标题。
    • int nListWidth[] 定义了每列的宽度。
    • 通过 m_listSend.InsertColumn(i, strListHead[i], LVCFMT_LEFT, nListWidth[i], -1); 将每个标题和对应的宽度添加到列表控件中。
  3. 填充列表控件数据

    • FillListSend(); 函数用于填充列表控件的数据。这个函数可能会从数据源获取数据,并将数据插入到列表控件的行中。
  4. 设置定时器

    • SetTimer(IDT_TIMER_SEND, 100, NULL); 设置了一个定时器,每隔100毫秒触发一次定时器消息。这通常用于执行定时任务,如周期性地更新列表数据或其他界面操作。
  5. 返回值

    • 函数最后返回 TRUE,除非将焦点设置到某个控件,否则应该返回 TRUE。在 MFC 对话框中,通常是 TRUE,除非你有特定的需求返回 FALSE

总结

CDlgSend::OnInitDialog() 函数中,你初始化了 m_listSend 列表控件的样式、列头和数据。这些操作确保了列表控件的外观和行为符合预期,并且设置了定时器以便于定时执行任务。如果需要进一步的解释或有其他问题,请随时告诉我!

九、套接字

这个套接字通讯是在你的程序,与电路板之间进行服务的,发送给电路板使用的是sendto()函数,需要注意,发送信息的DO数据,需要nMsg和nID都定义好,才可以发送给对方

你的代码展示了一个在 MFC 应用程序中初始化和关闭服务器端套接字的过程。以下是对这段代码的详细解释:

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
void CInterfaceDlg::InitSocket()
{
WORD wVersionRequested; // 存储请求的Winsock版本
WSADATA wsaData; // 接受Winsock的详细信息
int err;

//加载套接字库 1.1版本
wVersionRequested = MAKEWORD(1, 1);

// 初始化库,获取的信息保存到wsaData中
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
MessageBox(TEXT("Load winsock failed!\n"));
return;
}

if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup(); // 清理资源
return;
}

//创建套接字
for (int i = 0; i < 1; i++)
{
// 创建套接字
// AF_INET IPv4协议
// SOCK_DGRAM: 使用数据报套接字UDP
// 0:使用默认协议UDP
// 返回套接字描述符
m_sockServer[i] = socket(AF_INET, SOCK_DGRAM, 0);
if (m_sockServer[i] == INVALID_SOCKET)
{
MessageBox(TEXT("Create socket failed!\n"));
WSACleanup();
return;
}

char strPcIP[MAX_PATH];
hostent* pHost;
gethostname(strPcIP, MAX_PATH); //获得主机名
pHost = gethostbyname(strPcIP); //获得主机结构

m_addrServer[i].sin_addr.S_un.S_addr = (*(struct in_addr *)pHost->h_addr_list[0]).S_un.S_addr; //本地IP地址
m_addrServer[i].sin_family = AF_INET;
m_addrServer[i].sin_port = htons(m_myModuleInfo[i].nModulePcPort); //本地端口号

char strIP[MAX_PATH];
WideCharToMultiByte(CP_ACP, 0, m_myModuleInfo[i].strModuleIP, -1, strIP, MAX_PATH, NULL, NULL);

m_addrClient[i].sin_addr.S_un.S_addr = inet_addr(strIP); //模块IP地址
m_addrClient[i].sin_family = AF_INET;
m_addrClient[i].sin_port = htons(m_myModuleInfo[i].nModulePort); //模块端口号

//绑定套接字
if (bind(m_sockServer[i], (SOCKADDR*)&m_addrServer[i], sizeof(SOCKADDR)) == SOCKET_ERROR)
{
MessageBox(TEXT("Bind socket failed!\n"));
closesocket(m_sockServer[i]);
WSACleanup();
return;
}

//异步套接字
if (WSAAsyncSelect(m_sockServer[i], this->m_hWnd, WM_SOCKET, FD_READ | FD_WRITE) == SOCKET_ERROR)
{
MessageBox(TEXT("WSAAsyncSelect failed!\n"));
closesocket(m_sockServer[i]);
WSACleanup();
return;
}
}
}

//结束服务器端套接字函数
void CInterfaceDlg::DestroySocket()
{
//关闭套接字
for (int i = 0; i < m_iModuleNum; i++)
{
closesocket(m_sockServer[i]);
WSACleanup();
}
}

为了更清晰地理解这个过程,我们可以看看UDP套接字通信的基本步骤:

  1. 创建套接字

    1
    m_sockSrv[0] = socket(AF_INET, SOCK_DGRAM, 0);
  2. 设置客户端地址

    1
    2
    3
    m_addrClient[0].sin_family = AF_INET;
    m_addrClient[0].sin_port = htons(port);
    m_addrClient[0].sin_addr.s_addr = inet_addr("client_ip_address");
  3. 发送数据

    1
    sendto(m_sockSrv[0], (char*)&data, sizeof(data), 0, (SOCKADDR*)&m_addrClient[0], sizeof(SOCK

在这个套接字消息响应函数中,处理了来自套接字的不同消息类型。让我们逐步分析这个函数的具体作用和逻辑:

套接字接收代码详细解释:

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
LRESULT CInterfaceDlg::OnSocket(WPARAM wParam, LPARAM lParam) // 套接字响应,获取DI信息
{
MYDATA mdRecv[2]; //每次接收两次数据 长度为0x0D*2
SOCKADDR_IN addrTemp;
int nLen = sizeof(SOCKADDR);

switch (WSAGETSELECTEVENT(lParam))
{
case FD_READ:
{
memset(mdRecv, 0, sizeof(mdRecv));
recvfrom((SOCKET)wParam, (char*)mdRecv, sizeof(mdRecv), 0, (SOCKADDR*)&addrTemp, &nLen);
// 获取远程IP地址和端口号

//// 获取远程IP地址和端口号
//CString strIP;
//strIP.Format(_T("%d.%d.%d.%d"),
// addrTemp.sin_addr.S_un.S_un_b.s_b1,
// addrTemp.sin_addr.S_un.S_un_b.s_b2,
// addrTemp.sin_addr.S_un.S_un_b.s_b3,
// addrTemp.sin_addr.S_un.S_un_b.s_b4);
//UINT nPort = ntohs(addrTemp.sin_port);

//// 显示远程IP地址和端口号
//CString strMsg;
//strMsg.Format(_T("远程IP: %s\n远程端口: %d"), strIP, nPort);
//AfxMessageBox(strMsg);

for (int i = 0; i < m_iModuleNum; i++)
{
if (m_sockServer[i] == (SOCKET)wParam)
{
for (int j = 0; j < 2; j++)
{
int nMsg = mdRecv[j].myByteData.nMsg & 0xFF;

int nID = 0x00;
for (int k = 0; k < COM_ID_NUM; k++)
{
nID = nID << 8;
nID = nID + (mdRecv[j].myByteData.nID[k] & 0xFF);
}

if (nMsg == COM_MSG && nID == COM_DI)
{

memcpy(&m_mdModuleDI[i], &mdRecv[j], sizeof(MYDATA));
// 检查和调试信息
//CString strDebug;
//strDebug.Format(_T("匹配的DI数据: nMsg = %d, nID = %d, bt_00 = %d, bt_01 = %d"),
// nMsg, nID,
// m_mdModuleDI[i].myBitData.bt_00,
// m_mdModuleDI[i].myBitData.bt_01);
//AfxMessageBox(strDebug);
}
}

break;
}
}
break;
}

case FD_WRITE:
break;

default:
break;
}

return 0;
}

代码逻辑:

  1. FD_READ: 当接收到FD_READ事件时,表示有数据可以读取。

    • 使用recvfrom从套接字读取数据到chRecv数组中。
    • 检查数据前缀是否为0x88,如果是则解析ID,并根据ID检查是否为0x00000060
    • 如果ID匹配,从数据中提取按钮状态到nBtn数组中。
    • 再次检查数据中的另一段(从索引13开始),重复上述步骤。
  2. 数据处理:

    • 遍历所有模块的套接字,找到与当前套接字匹配的模块。
    • 对接收到的两段数据(mdRecv数组中的数据)进行处理:
      • 提取消息和ID。
      • 根据消息和ID,决定将数据复制到m_mdModuleDIm_mdModuleAI中,或处理其他类型的数据。
  3. FD_WRITE: 当接收到FD_WRITE事件时,表示套接字可以写入数据。此处未进行任何操作。

  4. 默认处理: 对于其他未处理的事件类型,未进行任何操作。

注意事项:

  1. ID的计算

    1
    int nID = (chRecv[1] << 24) + (chRecv[2] << 16) + (chRecv[3] << 8) + chRecv[4];

    ID的计算方式需要使用括号确保操作顺序正确,否则可能导致计算结果不正确。

  2. 消息类型的处理

    • 确保对每种消息类型和ID的处理逻辑正确。
    • 对于未知消息类型或ID,可以考虑增加日志记录或错误处理。
  3. 内存操作

    • 使用memcpy时,确保源和目标内存区域大小匹配,防止缓冲区溢出或内存损坏。

这个函数主要用于处理从套接字接收到的数据,并将解析后的数据存储到相应的模块结构中。

程序发送数据到电路板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void CInterfaceDlg::UpdateDataRecv()
{

//m_mdModuleDO[0].myBitData.bt_00 = m_dataRecv.bRisepanto; //升弓
m_mdModuleDO[0].myBitData.bt_00 = 0; //升弓
m_mdModuleDO[0].myBitData.bt_01 = m_dataRecv.bDNPanto; //降弓
m_mdModuleDO[0].myBitData.bt_02 = m_dataRecv.bRiseMode; //升级模式


// 打印目标 IP 和端口号
//CString ipAddr(inet_ntoa(m_addrClient[0].sin_addr));
//CString port;
//port.Format(TEXT("%d"), ntohs(m_addrClient[0].sin_port));
//CString msg;
//msg.Format(TEXT("Sending to IP: %s, Port: %s"), ipAddr, port);
//AfxMessageBox(msg);

// 发送数据
sendto(m_sockServer[0], (char*)&m_mdModuleDO[0], sizeof(MYDATA), 0, (SOCKADDR*)&m_addrClient[0], sizeof(SOCKADDR));

}

注意,DO数据需要设置标志字段,直接发送是没办法收到的

十、编辑框和按钮

1、添加编辑框控件和按钮,然后更改ID和Caption

image-20240716135319855

2、编辑框添加成员变量,用来保存输入的编辑框信息
image-20240716135443062

添加之后,Dlg.h会多出变量定义,Dlg.cpp会将变量和控件绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
// Dlg.h
class CDlg
{
public:
CEdit m_edtText;
}

// Dlg.cpp
void CTextToSpeechDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDIT_TEXT, m_edtText);
}

3、设置编辑框默认文本,在OnInitDialog()中设置初始文本或水印文字

1
2
3
4
5
6
7
BOOL CTextToSpeechDlg::OnInitDialog()
{
//m_edtText.SetWindowTextW(_T("请输入"));
m_edtText.SetCueBanner(_T("请输入")); // 设置水印文字,点击时自动消失

return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}

4、双击按钮,系统会自动初始化按钮触发的函数

1
2
3
4
5
6
7
8
9
10
11
12
void CTextToSpeechDlg::OnBnClickedTranfer()
{
CString strText;
m_edtText.GetWindowText(strText); // 获取编辑框的输入
m_edtText.SetCueBanner(_T("请再次输入"));

// 使用 CString 格式化字符串,检测是否得到编辑框信息
CString message;
message.Format(_T("%s"), strText);
MessageBox(message);
}

-------------本文结束感谢您的阅读-------------