Java.awt & MFC消息处理机制
一,MFC消息处理机制
WINDOWS 消息的种类:
1、标准WINDOWS消息:这类消息是以WM_为前缀,不过WM_COMMAND例外。 例如: WM_MOVE、WM_QUIT等。
2、命令消息:命令消息以WM_COMMAND为消息名。在消息中含有命令的标志符ID,以区分具体的命令。由菜单,工具栏等命令接口对象产生。
3、控件通知消息:控件通知消息也是以WM_COMMAND为消息名。由编辑框、列表框和子窗口发送给父窗口的通知消息。在消息中包含控件通知码,以区分具体控件的通知消息。
MFC中处理消息的顺序
1. AfxWndProc()接收消息,寻找消息所属的CWnd对象,然后调用AfxCallWndProc( )。
2. AfxCallWndProc()存储消息(消息标识符和消息参数)供未来参考,然后调用WindowProc( )。
3. WindowProc()发送消息给OnWndMsg( ),如果消息未被处理,则发送给DefWindowproc( )。
4. OnWndMsg()首先按字节对消息进行排序,对于WM_COMMAND消息,调用OnCommand()消息响应函数;对于WM_NOTIFY消息调用OnNotify()消息响应函数。任何被遗漏的消息将是标准消息。OnWndMsg()函数搜索类的消息映像,以找到一个能处理任何窗口消息的处理函数。如果OnWndMsg()函数不能找到这样的处理函数的话,则把消息返回到WindowProc()函数,由它将消息发送给DefWindowProc()函数。
5. OnCommand()查看这是不是一个控件通知(lParam参数不为NULL),如果它是,OnCommand()函数会试图将消息映射到制造通知的控件;如果它不是一个控件通知,或者控件拒绝映射的消息,OnCommand()就会调用OnCmdMsg()函数。
6. OnNotify( )也试图将消息映射到制造通知的控件;如果映射不成功,OnNotify( )就调用相同的OnCmdMsg( )函数。
7. 根据接收消息的类,OnCmdMsg()函数将在一个称为命令传递(Command Routing)的过程中潜在的传递命令消息和控件通知。例如:如果拥有该窗口的类是一个框架类,则命令和控件通知消息也被传递到视图和文档类,并为该类寻找一个消息处理函数。
MFC中创建窗口的顺序
1. PreCreateWindow()是一个重载函数,在窗口被创建前,可以在该重载函数中改变创建参数(可以设置窗口风格等等)。
2. PreSubclassWindow()也是一个重载函数,允许首先子分类一个窗口OnGetMinMaxInfo()为消息响应函数,响应的是WM_GETMINMAXINFO消息,允许设置窗口的最大或者最小尺寸。
3. OnNcCreate()也是一个消息响应函数,响应WM_NCCREATE消息,发送消息以告诉窗口的客户区即将被创建。
4. OnNcCalcSize()也是消息响应函数,响应WM_NCCALCSIZE消息,作用是允许改变窗口客户区大小。
5. OnCreate()也是消息响应函数,响应WM_CREATE消息,发送消息告诉一个窗口已经被创建。
6. OnSize()也是消息响应函数,响应WM_SIZE消息,发送该消息以告诉该窗口大小已经发生变化。
7. OnMove()也是消息响应函数,响应WM_MOVE消息,发送此消息说明窗口在移动。
8. OnChildNotify()为重载函数,作为部分消息映射被调用,告诉父窗口即将被告知一个窗口刚刚被创建。
SetTimer与WM_TIMER消息
Timer被设置后会一直存在,直到用KillTimer(hwnd, ID)删除。
Timer被创建后,每隔一定时间会发送 WM_TIMER 消息,当收到WM_TIMER 消息后,程序就会调用函数。也可以在创建Timer时指定回调函数。
SetTimer 原形如下,注意 CWnd 类的 SetTimer 方法没有第一个参数:
UINT_PTR SetTimer( HWND hWnd, UINT_PTR nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc);
四个参数分别是: hWnd 所属的窗口句柄,如果 lpTimerFunc 为空,这个窗口将接收 WM_TIMER 消息。
nIDEvent 计时器ID值,发送 WM_TIMER消息时会附带发送这个值。
uElapse 超时时间。每uElapse毫秒发送WM_TIMER
lpTimerFunc 回调函数,如果为空,将发送 WM_TIMER 消息给 hWnd 指定的窗口的消息队列。
lpTimerFunc 的函数原型如下:
VOID CALLBACK TimerProc( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
用一个Windows窗口实例来解释消息处理
#include <windows.h> #include <cstdio> //回调函数 LRESULT CALLBACK WinSunProc( HWND hwnd, // 窗口句柄 UINT message, // 消息代码 WPARAM wParam, // 附加参数 LPARAM lParam ) { switch(message) { case WM_CREATE: { HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hwnd, &ps); char str[20]; wsprintf(str, "窗口创建消息"); MessageBox(hwnd, str, "提示", MB_OK); break; } case WM_PAINT: { SetTimer(hwnd, 10, 100, NULL); HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hwnd, &ps); char str[20]; wsprintf(str, "Hello World"); TextOut(hdc, rand()%600, rand()%300, str, strlen(str)); break; } case WM_CHAR: char szChar[20]; sprintf(szChar, "char code is %d", wParam); MessageBox(hwnd, szChar, "您的按键", 0); break; case WM_TIMER: InvalidateRect(hwnd, NULL, true); break; case WM_DESTROY: //销毁窗口 PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow // show state ) { // 窗口类 WNDCLASS wndcls; wndcls.cbClsExtra = 0; // 类附加内存 wndcls.cbWndExtra = 0; // 窗口附加内存 wndcls.hbrBackground = (HBRUSH) GetStockObject(COLOR_WINDOW); // 背景画刷句柄 wndcls.hCursor = LoadCursor(NULL, IDC_ARROW); // 窗口光标句柄 wndcls.hIcon = LoadIcon(NULL, IDI_APPLICATION); // 窗口图标句柄 wndcls.hInstance = hInstance; // 包含窗口过程函数的程序实例 wndcls.lpfnWndProc = WinSunProc; // 只想窗口过程函数的指针 wndcls.lpszClassName = "CRoot"; // 窗口类名称 wndcls.lpszMenuName = NULL; // 菜单资源 wndcls.style = CS_HREDRAW | CS_VREDRAW; // 窗口样式 RegisterClass(&wndcls); // 创建窗口, 定义一个变量用来保存成功创建窗口后返回的句柄 HWND hwnd = CreateWindow( // 窗口创建成功时返回为窗口分配的句柄 失败时返回NULL "CRoot", // 窗口类名 "Hello World", // 窗口名字 WS_CAPTION|WS_SYSMENU, // 窗口样式 300, 300, // 窗口左上角坐标 700, 400, // 窗口宽高 NULL, // 父窗口句柄 NULL, // 窗口菜单句柄 hInstance, // 窗口所属应用程序实例 NULL // WM_CREATE消息附加参数lParam传入的数据指针 ); // 显示及刷新窗口 ShowWindow(hwnd, SW_SHOWNORMAL); UpdateWindow(hwnd); // 定义消息结构体 MSG msg; while (GetMessage( // WM_QUIT消息返回0 错误返回-1 &msg, // 指向消息的结构体 NULL, // 指定接收属于哪一窗口的消息 通常设为NULL,用来接收属于调用线程的所有窗口的窗口消息 0, // 获取消息的最小值 通常为0 0)) // 获取消息的最大值 都设为0表示接收所有消息 { TranslateMessage(&msg); // 将虚拟消息转换为字符消息 投递到调用线程的消息队列中 下次调用GetMessage时被取出 DispatchMessage(&msg); // 将消息传递给操作系统 由操作系统调用窗口过程函数对消息进行处理 } return msg.wParam; }
附加参数WPARAM与WM_COMMAND
创建一个按钮
CreateWindowEx(0,"Button","我是按钮",WS_VISIBLE|WS_CHILD,100,100,100,50,hwnd,(HMENU)10,0,0);
HMENU为按钮的ID号,监听器识别ID执行。
消息的附加信息在wParam中。
switch(message) { case WM_CREATE: { CreateWindowEx(0,"Button","我是按钮",WS_VISIBLE|WS_CHILD,100,100,100,50,hwnd,(HMENU)10,0,0); break; } case WM_COMMAND: { WORD wmId = LOWORD(wParam); switch (wmId) { case (10): MessageBoxA(NULL,"Hello Button!","My TestProject",MB_OK); break; } break; }
实例(数据结构课程设计)
MFC界面的贪吃蛇游戏
#include <iostream> #include <windows.h> #include <ctime> #include <fstream> #include <cstring> #include <string> #include <algorithm> #include "resource.h" using namespace std; #define IDT_TIMER 100 #define HEIGHT 300 #define WIDTH 500 int POINT_X[2], POINT_Y[2]; int score = 0; int rate = 100; int change = 0; struct People { char name[20]; int num; bool operator < (const People &tmp) const { return num > tmp.num; } } people[10]; int tot; char user[20]; struct point { int x, y; }; struct Node { point data; Node *next; } top; struct Link { Node *head, *last, *pre_last; Link() { last = head = ⊤ last->next = NULL; last->data.x = 5; last->data.y = 5; } } snake; char direction = 'r'; void AddList() { Node *tmp = new Node; tmp->data.x = 1000; tmp->data.y = 1000; tmp->next = NULL; snake.pre_last = snake.last; snake.pre_last->next = tmp; snake.last = tmp; } void GoAhead(int dx, int dy) { snake.pre_last->next = NULL; snake.last->data.x = snake.head->data.x + dx; snake.last->data.y = snake.head->data.y + dy; snake.last->next = snake.head; snake.head = snake.last; snake.last = snake.pre_last; snake.pre_last = snake.head; while (snake.pre_last->next != snake.last) snake.pre_last = snake.pre_last->next; } void Drow(HWND hwnd) { HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hwnd, &ps); RECT rc; //计分和静态文档 char sz[20]; wsprintf(sz, "分数:"); TextOut(hdc, WIDTH + 20, 40, sz, strlen(sz)); wsprintf(sz, "%d", score * 100); TextOut(hdc, WIDTH + 20, 70, sz, strlen(sz)); wsprintf(sz, "排行榜:"); TextOut(hdc, WIDTH + 20, 90, sz, strlen(sz)); for(int i = 0; i<tot; i++) { wsprintf(sz, "%s %d", people[i].name, people[i].num); TextOut(hdc, WIDTH + 20, 110 + i * 20, sz, strlen(sz)); } //画蛇 HBRUSH hBrush = CreateSolidBrush(RGB(0,0,0)); Node *tp = snake.head; while (tp) { rc.left = (tp->data.x) * 11; rc.right = rc.left + 10; rc.top = (tp->data.y) * 11; rc.bottom = rc.top + 10; FillRect(hdc, &rc, hBrush); tp = tp->next; } //画点1 hBrush = CreateSolidBrush(RGB(255,0,0)); rc.left = POINT_X[0] * 11; rc.right = rc.left + 10; rc.top = POINT_Y[0] * 11; rc.bottom = rc.top + 10; FillRect(hdc, &rc, hBrush); //画点2 rc.left = POINT_X[1] * 11; rc.right = rc.left + 10; rc.top = POINT_Y[1] * 11; rc.bottom = rc.top + 10; FillRect(hdc, &rc, hBrush); //画边界 MoveToEx(hdc, WIDTH + 15, HEIGHT + 15, NULL); LineTo(hdc, WIDTH + 15, 0); DeleteObject(hBrush); EndPaint(hwnd, &ps); } int getPoint() { if (POINT_X[0] == (snake.head->data.x) && POINT_Y[0] == (snake.head->data.y)) return 0; if (POINT_X[1] == (snake.head->data.x) && POINT_Y[1] == (snake.head->data.y)) return 1; return -1; } int check() { if (snake.head->data.x < 0||(snake.head->data.x)*11>WIDTH||snake.head->data.y < 0||(snake.head->data.y)*11>HEIGHT) return 1; for (Node *p = snake.head->next; p->next; p = p->next) if (p->data.x == snake.head->data.x && p->data.y == snake.head->data.y) return 2; return 0; } bool isOK(int x, int y) { Node *tp = snake.head; while (tp) { if(tp->data.x == x && tp->data.y == y) return 1; tp = tp->next; } return 0; } //回调函数 LRESULT CALLBACK FUNC(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: { tot = 0; ifstream inFile("score_list.txt"); while (!inFile.eof()) { inFile >> people[tot].name >> people[tot].num; tot++; } SetTimer(hwnd, IDT_TIMER, rate, NULL); ifstream in("user.txt"); in >> user; for(int i = 0; i<5; i++) AddList(); break; } case WM_PAINT: { //窗口客户区需要重画 srand((int)time(0)); Drow(hwnd); change = getPoint(); if (change >= 0) { AddList(); score++; srand((int)time(0)); do { POINT_X[change] = (rand() % WIDTH) / 11, POINT_Y[change] = (rand() % HEIGHT) / 11; } while(isOK(POINT_X[change], POINT_Y[change])); } int k = check(); if (k) { if(k == 2) { KillTimer(hwnd, IDT_TIMER); MessageBox(NULL, "YOU LOSE !", "message", MB_OK); HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hwnd, &ps); char str[20]; wsprintf(str, "YOU LOSE !"); TextOut(hdc, 400, 100, str, strlen(str)); strcpy(people[tot].name, user), people[tot].num = score * 100; sort(people, people + tot + 1); if(tot > 5) tot = 5; ofstream inFile("score_list.txt"); for(int i = 0; i < tot; i++) { inFile << people[i].name << " " << people[i].num; } return 0; } int dx = -1, dy = -1; if(snake.head->data.x * 11 < WIDTH / 2) dx = 1; if(snake.head->data.y * 11 < HEIGHT / 2) dy = 1; if(direction == 'r') GoAhead(0, dy), GoAhead(-1, 0), direction = 'l'; else if(direction == 'u') GoAhead(dx, 0), GoAhead(0, 1), direction = 'd'; else if(direction == 'l') GoAhead(0, dy), GoAhead(1, 0), direction = 'r'; else if(direction == 'd') GoAhead(dx, 0), GoAhead(0, -1), direction = 'u'; //PostQuitMessage(0); } return 0; } case WM_TIMER: if (direction == 'l') GoAhead(-1, 0); else if (direction == 'r') GoAhead(1, 0); else if (direction == 'u') GoAhead(0, -1); else if (direction == 'd') GoAhead(0, 1); InvalidateRect(hwnd, NULL, true); break; case WM_KEYDOWN: { Node *tp = snake.head; { switch (LOWORD(wParam)) { case VK_UP: if (direction != 'd') direction = 'u'; break; case VK_DOWN: if (direction != 'u') direction = 'd'; break; case VK_RIGHT: if (direction != 'l') direction = 'r'; break; case VK_LEFT: if (direction != 'r') direction = 'l'; break; } InvalidateRect(hwnd, NULL, TRUE); } return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wndclass; // 用描述主窗口的参数填充WNDCLASSEX结构 wndclass.cbSize = sizeof(wndclass); // 结构的大小 wndclass.style = CS_HREDRAW | CS_VREDRAW; // 指定如果大小改变就重画 wndclass.lpfnWndProc = FUNC; // 窗口函数指针 wndclass.cbClsExtra = 0; // 没有额外的类内存 wndclass.cbWndExtra = 0; // 没有额外的窗口内存 wndclass.hInstance = hInstance; // 实例句柄 wndclass.hIcon = LoadIcon(hInstance, (LPCTSTR) IDI_ICON1); // 使用预定义图标 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); // 使用预定义的光标 wndclass.hbrBackground = (HBRUSH) (COLOR_3DFACE + 1); // 使用灰色背景画刷 wndclass.lpszMenuName = NULL; // 不指定菜单 wndclass.lpszClassName = "MainWClass"; // 窗口类的名称 wndclass.hIconSm = LoadIcon(hInstance, (LPCTSTR) IDI_ICON1); // 没有类的小图标 // 注册这个窗口类 RegisterClassEx(&wndclass); // 创建主窗口 HWND hwnd = CreateWindowEx(0, // dwExStyle,扩展样式 "MainWClass", // lpClassName,类名 "贪吃蛇", // lpWindowName,标题 WS_OVERLAPPEDWINDOW & (~WS_SIZEBOX) & (~WS_MAXIMIZEBOX),// dwStyle,窗口风格 300, //CW_USEDEFAULT, // X,初始 X 坐标 300, //CW_USEDEFAULT, // Y,初始 Y 坐标 WIDTH + 150, //CW_USEDEFAULT, // nWidth,宽度 HEIGHT + 50, //CW_USEDEFAULT, // nHeight,高度 NULL, // hWndParent,父窗口句柄 NULL, // hMenu,菜单句柄 hInstance, // hlnstance,程序实例句柄 NULL); // lpParam,用户数据 if (!hwnd) { MessageBox(NULL, "无法创建游戏窗口", "error", MB_OK); return -1; } srand((int)time(0)); do { POINT_X[0] = (rand() % WIDTH) / 11, POINT_Y[0] = (rand() % HEIGHT) / 11; } while(isOK(POINT_X[0], POINT_Y[0])); do { POINT_X[1] = (rand() % WIDTH) / 11, POINT_Y[1] = (rand() % HEIGHT) / 11; } while(isOK(POINT_X[1], POINT_Y[1])); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); MSG msg; while ( GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); //将消息发送到回调函数 DispatchMessage(&msg); } //当GetMessage返回0时程序结束 return msg.wParam; }
二,JAVA GUI消息
toggle.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {}
}
对每一个控件设置事件。
或,implements ActionListener 实现ActionListener接口
public void actionPerformed(ActionEvent e) {
if (e.getSource() == btnCalc) {}
if (e.getSource() == btnCalc2) {}
}
import java.awt.BorderLayout; import java.awt.Container; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import javax.swing.*; class NumberFrame extends JFrame implements ActionListener { JTextField txtStart; JTextField txtEnd; JTextField txtFactor; JTextArea txtRes; JButton btnCalc, btnCalc2; JPanel inputPanel; JScrollPane resultPane; JPanel panel; NumberFrame(String title) { super(title); this.setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(400, 150); setLocation(300, 200); setVisible(true); Container cp = getContentPane(); panel = new JPanel(); panel.setLayout(new BorderLayout()); JPanel lblPane = new JPanel(); lblPane.setLayout(new GridLayout(4, 1)); lblPane.add(new JLabel("From")); lblPane.add(new JLabel("To")); lblPane.add(new JLabel("Factor")); lblPane.add(new JLabel()); JPanel txtPane = new JPanel(); txtPane.setLayout(new GridLayout(4, 1)); // 划分成4行1列 txtStart = new JTextField(); txtEnd = new JTextField(); txtFactor = new JTextField(); txtPane.add(txtStart); txtPane.add(txtEnd); txtPane.add(txtFactor); btnCalc = new JButton("Calculate"); btnCalc.addActionListener(this); txtPane.add(btnCalc); inputPanel = new JPanel(); inputPanel.setLayout(new BorderLayout()); inputPanel.add(lblPane, BorderLayout.WEST); inputPanel.add(txtPane, BorderLayout.CENTER); panel.add(inputPanel, BorderLayout.WEST); // result panel txtRes = new JTextArea(10, 10); txtRes.setLineWrap(true); resultPane = new JScrollPane(txtRes); panel.add(resultPane, BorderLayout.CENTER); btnCalc2 = new JButton("clean"); btnCalc2.addActionListener(this); panel.add(btnCalc2, BorderLayout.SOUTH); cp.add(panel); } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == btnCalc) { txtRes.setText(null); try { int m = Integer.parseInt(txtStart.getText().trim()); int n = Integer.parseInt(txtEnd.getText().trim()); int factor = Integer.parseInt(txtFactor.getText().trim()); for (int i = m; i <= n; ++i) { if (i % factor == 0) txtRes.append(String.valueOf(i) + " "); } } catch (NumberFormatException e1) { e1.printStackTrace(); } } if (e.getSource() == btnCalc2) { txtRes.setText(null); } } } class TestButtons extends JFrame { JButton jButton = new JButton("JButton"); // 按钮 JToggleButton toggle = new JToggleButton("Toggle Button"); // 切换按钮 JCheckBox checkBox = new JCheckBox("Check Box"); // 复选按钮 JRadioButton radio1 = new JRadioButton("Radio Button 1"); // 单选按钮 JRadioButton radio2 = new JRadioButton("Radio Button 2"); JRadioButton radio3 = new JRadioButton("Radio Button 3"); JLabel label = new JLabel("Here is Status, look here."); // 不是按钮,是静态文本 public TestButtons() { super("Test"); setVisible(true); setLocation(750, 200); setSize(200, 250); setDefaultCloseOperation(EXIT_ON_CLOSE); getContentPane().setLayout(new java.awt.FlowLayout()); // 为一般按钮添加动作监听器 jButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { label.setText("You clicked jButton"); } }); // 为切换按钮添加动作监听器 toggle.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JToggleButton toggle = (JToggleButton) e.getSource(); if (toggle.isSelected()) { label.setText("You selected Toggle Button"); } else { label.setText("You deselected Toggle Button"); } } }); // 为复选按钮添加条目监听器 checkBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { JCheckBox cb = (JCheckBox) e.getSource(); label.setText("Selected Check Box is " + cb.isSelected()); } }); // 将单选按钮添加到按钮组中,按钮组中只能选一个 ButtonGroup group = new ButtonGroup(); group.add(radio1); group.add(radio2); group.add(radio3); // 生成一个新的动作监听器对象,备用 ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { JRadioButton radio = (JRadioButton) e.getSource(); if (radio == radio1) { label.setText("You selected Radio Button 1"); } else if (radio == radio2) { label.setText("You selected Radio Button 2"); } else { label.setText("You selected Radio Button 3"); } } }; // 为各单选按钮添加动作监听器 radio1.addActionListener(al); radio2.addActionListener(al); radio3.addActionListener(al); getContentPane().add(jButton); getContentPane().add(toggle); getContentPane().add(checkBox); getContentPane().add(radio1); getContentPane().add(radio2); getContentPane().add(radio3); getContentPane().add(label); } } public class UI { public static void main(String[] args) { new NumberFrame("Number Game"); new TestButtons(); } }