问题1:比如我鼠标按下 就会触发mousedown事件
为什么会触发呢?
问题2:如何才能自己定义个事件呢?
解答:
当用户点了鼠标,鼠标会收到一个信号,信号会被编码,通过总线传递给CPU,结果是引发CPU一个硬件中断。CPU收到中断请求会转到中断处理程序。中断处理程序是操作系统定义的,它的上层是驱动程序。驱动程序会解析硬件传来的参数,并且发给图形界面子系统,变成一条Windows消息投递给相关的窗体。我们的.NET Framework程序在下层不断接受Windows传来的消息,并且封装成一个事件调用,也就是填充合适的参数,比如鼠标位置信息等,然后调用事件。应用程序对应的事件处理程序被调用,实现对应的功能.
这是一个完全用Windows API写的C程序,它说明了消息处理的本质,我加上一些关键注释给你看
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wcex; //这里定义一个窗体类,在C里面,并不像C#,有类的概念,所谓的类,就是一个结构体,包含我们要创建的窗体的风格、特征、属性参数。 wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; //这里指定了一个处理消息的函数,在C语言里面,没有委托和事件这些概念,传递的是函数的指针。它的调用,是由Windows反过来调用的,所以叫回调。 wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION)); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION)); if (!RegisterClassEx(&wcex)) { MessageBox(NULL, _T("Call to RegisterClassEx failed!"), _T("Win32 Guided Tour"), NULL); return 1; } hInst = hInstance; // Store instance handle in our global variable // The parameters to CreateWindow explained: // szWindowClass: the name of the application // szTitle: the text that appears in the title bar // WS_OVERLAPPEDWINDOW: the type of window to create // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y) // 500, 100: initial size (width, length) // NULL: the parent of this window // NULL: this application dows not have a menu bar // hInstance: the first parameter from WinMain // NULL: not used in this application HWND hWnd = CreateWindow( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 100, NULL, NULL, hInstance, NULL ); //在这里,我们创建了窗体 if (!hWnd) { MessageBox(NULL, _T("Call to CreateWindow failed!"), _T("Win32 Guided Tour"), NULL); return 1; } // The parameters to ShowWindow explained: // hWnd: the value returned from CreateWindow // nCmdShow: the fourth parameter from WinMain ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // Main message loop: MSG msg; while (GetMessage(&msg, NULL, 0, 0)) //这是重点,一个窗体运行的时候,它在不断的接受消息、处理消息,做循环,直到窗体被关闭,这个循环叫做消息循环 { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; TCHAR greeting[] = _T("Hello, World!"); switch (message) { case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Here your application is laid out. // For this introduction, we just print out "Hello, World!" // in the top left corner. TextOut(hdc, 5, 5, greeting, _tcslen(greeting)); // End application specific layout section. EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; case WM_BUTTONDOWN: //这里触发一个鼠标左键被按下的消息。 MessgeBox(hWnd, "Mouse pressed!", "title", MB_OK); break; default: return DefWindowProc(hWnd, message, wParam, lParam); break; } return 0;
完整的程序可以看这里:点击打开链接
这个程序只是让你明白,WinForms程序,只是对API的封装。消息循环和事件处理程序由.NET框架库接管。但是事件的触发,来源于下层消息的触发,消息是Windows发出的,Windows之所以能发出消息,是硬件触发CPU中断的结果
2、第二个大牛的解答:
所有的winform中的Control都继承了这个方法
[SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode), SecurityPermission(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] protected virtual void WndProc(ref Message m) { if ((this.controlStyle & ControlStyles.EnableNotifyMessage) == ControlStyles.EnableNotifyMessage) { this.OnNotifyMessage(m); } switch (m.Msg) { case 1: this.WmCreate(ref m); return; case 2: this.WmDestroy(ref m); return; case 3: this.WmMove(ref m); return; case 7: this.WmSetFocus(ref m); return; case 8: this.WmKillFocus(ref m); return; case 15: if (!this.GetStyle(ControlStyles.UserPaint)) { this.DefWndProc(ref m); return; } this.WmPaint(ref m); return; case 0x10: this.WmClose(ref m); return; case 20: this.WmEraseBkgnd(ref m); return; case 0x18: this.WmShowWindow(ref m); return; case 0x19: case 0x132: case 0x133: case 0x134: case 0x135: case 310: case 0x137: case 0x138: case 0x2132: case 0x2133: case 0x2134: case 0x2135: case 0x2136: case 0x2137: case 0x2138: case 0x2019: this.WmCtlColorControl(ref m); return; case 0x20: this.WmSetCursor(ref m); return; case 0x2b: this.WmDrawItem(ref m); return; case 0x2c: this.WmMeasureItem(ref m); return; case 0x2d: case 0x2e: case 0x2f: case 0x39: case 0x114: case 0x115: if (!ReflectMessageInternal(m.LParam, ref m)) { this.DefWndProc(ref m); } return; case 70: this.WmWindowPosChanging(ref m); return; case 0x47: this.WmWindowPosChanged(ref m); return; case 0x3d: this.WmGetObject(ref m); return; case 0x100: case 0x101: case 0x102: case 260: case 0x105: this.WmKeyChar(ref m); return; case 0x7e: this.WmDisplayChange(ref m); return; case 0x4e: this.WmNotify(ref m); return; case 80: this.WmInputLangChangeRequest(ref m); return; case 0x51: this.WmInputLangChange(ref m); return; case 0x53: this.WmHelp(ref m); return; case 0x55: this.WmNotifyFormat(ref m); return; case 0x7b: this.WmContextMenu(ref m); return; case 0x10d: this.WmImeStartComposition(ref m); return; case 270: this.WmImeEndComposition(ref m); return; case 0x111: this.WmCommand(ref m); return; case 0x112: if (((((int) ((long) m.WParam)) & 0xfff0) != 0xf100) || !ToolStripManager.ProcessMenuKey(ref m)) { this.DefWndProc(ref m); return; } m.Result = IntPtr.Zero; return; case 0x117: this.WmInitMenuPopup(ref m); return; case 0x11f: this.WmMenuSelect(ref m); return; case 0x120: this.WmMenuChar(ref m); return; case 0x128: this.WmUpdateUIState(ref m); return; case 0x200: this.WmMouseMove(ref m); return; case 0x201: this.WmMouseDown(ref m, MouseButtons.Left, 1); return; case 0x202: this.WmMouseUp(ref m, MouseButtons.Left, 1); return; case 0x203: this.WmMouseDown(ref m, MouseButtons.Left, 2); if (this.GetStyle(ControlStyles.StandardDoubleClick)) { this.SetState(0x4000000, true); } return; case 0x204: this.WmMouseDown(ref m, MouseButtons.Right, 1); return; case 0x205: this.WmMouseUp(ref m, MouseButtons.Right, 1); return; case 0x206: this.WmMouseDown(ref m, MouseButtons.Right, 2); if (this.GetStyle(ControlStyles.StandardDoubleClick)) { this.SetState(0x4000000, true); } return; case 0x207: this.WmMouseDown(ref m, MouseButtons.Middle, 1); return; case 520: this.WmMouseUp(ref m, MouseButtons.Middle, 1); return; case 0x209: this.WmMouseDown(ref m, MouseButtons.Middle, 2); if (this.GetStyle(ControlStyles.StandardDoubleClick)) { this.SetState(0x4000000, true); } return; case 0x20a: this.WmMouseWheel(ref m); return; case 0x20b: this.WmMouseDown(ref m, this.GetXButton(NativeMethods.Util.HIWORD(m.WParam)), 1); return; case 0x20c: this.WmMouseUp(ref m, this.GetXButton(NativeMethods.Util.HIWORD(m.WParam)), 1); return; case 0x20d: this.WmMouseDown(ref m, this.GetXButton(NativeMethods.Util.HIWORD(m.WParam)), 2); if (this.GetStyle(ControlStyles.StandardDoubleClick)) { this.SetState(0x4000000, true); } return; case 0x210: this.WmParentNotify(ref m); return; case 530: this.WmExitMenuLoop(ref m); return; case 0x215: this.WmCaptureChanged(ref m); return; case 0x282: this.WmImeNotify(ref m); return; case 0x2a1: this.WmMouseHover(ref m); return; case 0x2a3: this.WmMouseLeave(ref m); return; case 0x30f: this.WmQueryNewPalette(ref m); return; case 0x286: this.WmImeChar(ref m); return; case 0x2055: m.Result = (Marshal.SystemDefaultCharSize == 1) ? ((IntPtr) 1) : ((IntPtr) 2); return; case 0x318: if (this.GetStyle(ControlStyles.UserPaint)) { this.WmPrintClient(ref m); return; } this.DefWndProc(ref m); return; } if ((m.Msg == threadCallbackMessage) && (m.Msg != 0)) { this.InvokeMarshaledCallbacks(); } else if (m.Msg == WM_GETCONTROLNAME) { this.WmGetControlName(ref m); } else if (m.Msg == WM_GETCONTROLTYPE) { this.WmGetControlType(ref m); } else { if (mouseWheelRoutingNeeded && (m.Msg == mouseWheelMessage)) { Keys none = Keys.None; none |= (UnsafeNativeMethods.GetKeyState(0x11) < 0) ? 8 : 0; none |= (UnsafeNativeMethods.GetKeyState(0x10) < 0) ? 4 : 0; IntPtr focus = UnsafeNativeMethods.GetFocus(); if (focus == IntPtr.Zero) { this.SendMessage(m.Msg, (IntPtr) ((((int) ((long) m.WParam)) << 0x10) | none), m.LParam); } else { IntPtr zero = IntPtr.Zero; IntPtr desktopWindow = UnsafeNativeMethods.GetDesktopWindow(); while (((zero == IntPtr.Zero) && (focus != IntPtr.Zero)) && (focus != desktopWindow)) { zero = UnsafeNativeMethods.SendMessage(new HandleRef(null, focus), 0x20a, (int) ((((int) ((long) m.WParam)) << 0x10) | none), m.LParam); focus = UnsafeNativeMethods.GetParent(new HandleRef(null, focus)); } } } if (m.Msg == NativeMethods.WM_MOUSEENTER) { this.WmMouseEnter(ref m); } else { this.DefWndProc(ref m); } } }
注意其中处理201消息
private void WmMouseDown(ref Message m, MouseButtons button, int clicks) { MouseButtons mouseButtons = MouseButtons; this.SetState(0x8000000, true); if (!this.GetStyle(ControlStyles.UserMouse)) { this.DefWndProc(ref m); } else if ((button == MouseButtons.Left) && this.GetStyle(ControlStyles.Selectable)) { this.FocusInternal(); } if (mouseButtons == MouseButtons) { if (!this.GetState2(0x10)) { this.CaptureInternal = true; } if ((mouseButtons == MouseButtons) && this.Enabled) { this.OnMouseDown(new MouseEventArgs(button, clicks, NativeMethods.Util.SignedLOWORD(m.LParam), NativeMethods.Util.SignedHIWORD(m.LParam), 0)); } } } protected virtual void OnMouseDown(MouseEventArgs e) { MouseEventHandler handler = (MouseEventHandler) base.Events[EventMouseDown]; if (handler != null) { handler(this, e); } }
这里首先判断是否需要设置其焦点,然后就触发了(调用handler)控件的MouseDown事件。
4、一些解释:
上面基本上都是为了回答你第一个问题的,从外设、到驱动、到操作系统、到.net框架、到应用程序捕获事件。
解释事件是说什么“它是观察者模式”,这对许多没有死记硬背过设计模式的雷人的名词的人,会更加费解。
使用事件模式,通常因为要依赖倒置地通知宿主程序。比如写TextBox控件,有这样的事件定义
C# code
public event TextChangedEventHandler TextChanged; public delegate void TextChangedEventHandler(object sender, TextChangedEventArgs e);
这个控件它不知道自己的对象会放到哪一个宿主窗口中,它就是直接回调TextChanged委托从而将TextChanged事件抛出而已。
采取事件通知的关键原因就是TextBox对象不可能调用你的程序的接口功能来通知你消息,它的通知机制只能依赖于 TextChangedEventHandler 这个公共的类型定义,而你的程序只要注册到 TextChanged 事件上就会被按照这个接口类型的要求被回调。
关于写上几行代码,你可以搜索,买一本入门书,或者看msdn上的关于event的介绍文章。
这里只是介绍一点设计知识。
事件作为一种委托,完成了依赖倒置消息通知的功能,当你不能、或者不应该使用继承时,也可以用来实现功能扩展。比如说你写一个“货车”类,目的是在一个地图上模拟其运动方位、速度、油耗、磨损等等状态,以及自动为其进行寻路。假设当“寻路”时,输入目标地点,你考察的每一段路都要知道路况才能选择出来比较好的道路。而这个获取实时路况的工作不是这个货车类的职责(或者说必须假设这个功能需要独立地变化),那么有两种最基本的扩展方式,一种就是将这个“获取某条道路的实时路况”功能作为一个 virtual 的方法,让使用这个货车类的开发人员随后在子类中去实现,另外一种则是使用事件方式调用宿主程序并且从返回值中获取路况。
显然只有开发货车的子类才能应用第一种方式,而且这是假设这个职责是货车自己的职责(只不过委派给子类去实现。但是子类对象当然也仍然是货车)。而事件则纯粹是基于“客户-服务”的对象关系来建立,并不是像子类去委派这个功能,而是向管理和控制货车对象的客户程序去委派这个功能。
结合成百上千的现成的控件,你可以看到所有控件的都要使用事件通知机制将一些预想中的工作委派给客户去实现,当事件回调完成以后,自己继续(根据返回值)执行下面的工作。实际上在你设计的程序中可能更加需要大量的依赖倒置的事件委派机制。如果很少用到,那么可能是因为你现在还只能做个自娱自乐的小程序,而不能设计出协调很多第三方组件、协调很多人合作工作的产品。