# 6.1 鼠标输入
总的说来,有两类的鼠标事件。基本的鼠标事件使用wxMouseEvent作为参数,不加任何翻译的发送给响应的窗口事件处理函数,而窗口事件处理函数则通常把它们翻译成对应的命令事件(wxCommandEvent)。
举例来说,当你在你的事件表中增加EVT_BUTTON事件映射宏的时候,它的处理函数的参数是一个由按钮产生的wxCommandEvent类型。而在控件内部,这个wxCommandEvent类型是按钮控件对EVT_LEFT_DOWN事件宏进行处理并将对应的鼠标事件翻译成 wxCommandEvent事件的结果。(当然,在大多数平台上,按钮都是使用本地控件实现的,不需要自己处理底层的鼠标事件,但是对于别的定制的类来说,确是如此)
因为我们在前面已经介绍过处理命令事件的方法,我们将主要介绍怎样处理基本的鼠标事件。
你可以分别拦截鼠标左键,中键或者右键的鼠标按下,鼠标释放或者鼠标双击事件。你还可以拦截鼠标的移动事件(无论有没有鼠标按下)。你还可以拦截那些用来告诉你鼠标正在移入或者移出某个窗口的事件,最后,如果这个鼠标有滚轮,你还可以拦截鼠标的滚轮事件。
当你收到一个鼠标事件时,你可以获得鼠标按钮的状态信息,以及象Shift,Alt等等这些状态键的信息,你还可以获得鼠标指针相对于当前窗口客户区域左上角的座标值。
下表列出了所有对应的鼠标事件映射宏。需要注意的是,wxMouseEvent事件是不会传递给父窗口处理的,所以,为了处理这个事件,你必须重载一个新的窗口类,或者重载一个新的wxEvtHandler,然后将其挂载在某个窗口上,当然你还可以使用动态事件处理函数Connect,相关内容参见第三章。
| EVT_LEFT_DOWN(func | 用来处理wxEVT_LEFT_DOWN事件, 在鼠标左键按下的时候产生. |
|:--- |:--- |
| EVT_LEFT_UP(func) | 用来处理wxEVT_LEFT_UP事件, 在鼠标左键被释放的时候产生. |
| EVT_LEFT_DCLICK(func) | 用来处理wxEVT_LEFT_DCLICK事件,在鼠标左键被双击的时候产生. |
| EVT_MIDDLE_DOWN(func) | 用来处理wxEVT_MIDDLE_DOWN事件, 在鼠标中键被按下的时候产生. |
| EVT_MIDDLE_UP(func) | 用来处理wxEVT_MIDDLE_UP事件,当鼠标中键被释放的时候产生. |
| EVT_MIDDLE_DCLICK(func) | 用来处理wxEVT_MIDDLE_DCLICK事件,在鼠标中键被双击的时候产生. |
| EVT_RIGHT_DOWN(func) | 用来处理wxEVT_RIGHT_DOWN事件,鼠标右键被按下的时候产生. |
| EVT_RIGHT_UP(func) | 用来处理wxEVT_RIGHT_UP事件,鼠标右键被释放的时候产生. |
| EVT_RIGHT_DCLICK(func) | 用来处理wxEVT_RIGHT_DCLICK事件,鼠标右键被双击的时候产生. |
| EVT_MOTION(func) | 用来处理wxEVT_MOTION事件,鼠标指针移动的时候产生. |
| EVT_ENTER_WINDOW(func) | 用来处理wxEVT_ENTER_WINDOW事件,鼠标指针移入某个窗口的时候产生. |
| EVT_LEAVE_WINDOW(func) | 用来处理wxEVT_LEAVE_WINDOW事件,鼠标移出某个窗口的时候产生. |
| EVT_MOUSEWHEEL(func) | 用来处理wxEVT_MOUSEWHEEL事件,鼠标滚轮滚动的时候产生. |
| EVT_MOUSE_EVENTS(func) | 用来处理所有的鼠标事件. |
处理按钮和鼠标指针移动事件
按钮和指针移动事件是你想要处理的最主要的鼠标事件。
要检测当产生某个事件时状态键的状态,可以使用AltDown,MetaDown,ControlDown或者ShiftDown等函数.使用CmdDown函数来检测Mac OS X平台上的Meta键或者别的平台上的Control键的状态.本章稍后的"状态键变量"小节会对这些函数进行更详细的介绍.
要检测那个鼠标按钮正被按下,你可以使用LeftIsDown, MiddleIsDown和RightIsDown函数,或者你可以使用wxMOUSE_BTN_LEFT, wxMOUSE_BTN_MIDDLE, wxMOUSE_BTN_RIGHT或wxMOUSE_BTN_ANY参数来调用Button函数.要注意这些函数通常只是反应在事件产生那个时刻鼠标的状态,而不是反应鼠标的状态改变.(译者注:换句话说,两续同样按钮的两个事件中的按钮状态可能是一样的).
在Mac OS X上,Command键被翻译成Meta,Option键是Alt.因为在Mac系统上通常使用的是一键鼠标,当用户按下Control键点击鼠标的时候将产生右键单击事件.因此在MacOS上没有按下Control键时进行右键单击这样的事件,除非你正在使用的是一个两键或者三键的鼠标.
你还可以用下面的函数来或者鼠标事件的类型:Dragging (某个键正按下时鼠标移动), Moving (鼠标正在移动而没有鼠标键被按下), Entering, Leaving, ButtonDown, ButtonUp, ButtonDClick, LeftClick, LeftDClick, LeftUp, RightClick, RightDClick, RightUp, ButtonUp和IsButton等.
你可以使用GetPosition函数或者GetX和GetY函数获取鼠标指针当前的设备单位位置,也可以给GetLogicalPosition函数传递某个设备上下文参数以便得到对应的逻辑位置.
下面的例子演示了一个涂鸦程序中的鼠标处理过程:
```
BEGIN_EVENT_TABLE(DoodleCanvas, wxWindow)
EVT_MOUSE_EVENTS(DoodleCanvas::OnMouseEvent)
END_EVENT_TABLE()
void DoodleCanvas::OnMouseEvent(wxMouseEvent& event)
{
static DoodleSegment *s_currentSegment = NULL;
wxPoint pt(event.GetPosition());
if (s_currentSegment && event.LeftUp())
{
// 鼠标按钮释放的时候停止当前线段
if (s_currentSegment->GetLines().GetCount() == 0)
{
// 释放线段记录并且释放指针
delete s_currentSegment;
s_currentSegment = (DoodleSegment *) NULL;
}
else
{
// 已经得到一个有效的线段,把它存下来
DrawingDocument *doc = GetDocument();
doc->GetCommandProcessor()->Submit(
new DrawingCommand(wxT("Add Segment"), DOODLE_ADD,
doc, s_currentSegment));
doc->Modify(true);
s_currentSegment = NULL;
}
}
else if (m_lastX > -1 && m_lastY > -1 && event.Dragging())
{
//正在拖动鼠标,增加一行到当前的线段中
if (!s_currentSegment)
s_currentSegment = new DoodleSegment;
DoodleLine *newLine = new DoodleLine(m_lastX, m_lastY, pt.x, pt.y);
s_currentSegment->GetLines().Append(newLine);
wxClientDC dc(this);
DoPrepareDC(dc);
dc.SetPen(*wxBLACK_PEN);
dc.DrawLine( m_lastX, m_lastY, pt.x, pt.y);
}
m_lastX = pt.x;
m_lastY = pt.y;
}
```
在上面的应用程序中,线段被存在文档类型.当用户使用鼠标左键在窗口上拖拽时,上面的函数增加一个线条到当前的线段中,并且把它画出来, 当用户释放左键的时候,当前的线段被提交到文档类进行处理(文档类是wxWidgets的文档视图框架的一部分) ,以便进一步实现文档的重做或者撤消动作,而在窗口的OnPaint函数(代码没有被展示) 中,整个文档被重绘.在第19章"使用文档和视图"中,我们会完整的介绍这个例子.
如果想让这个程序更专业一点,可以在鼠标按下的时候捕获鼠标并且在鼠标释放的时候释放捕获,以便当鼠标左键按下并且移出窗口的时候仍然可以收到鼠标事件.
处理鼠标滚轮事件
当处理鼠标滚轮事件的时候,你可以使用GetWheelRotation函数获得滚轮滚过的位置的大小(可能为负数).用这个数除以 GetWheelDelta以便得到实际滚动行数的值.多数的设备每个GetWheelDelta发送一个滚轮事件,但是将来的设备也许会以更快的频率发送事件,因此你需要进行这种计算以便只有在滚轮滚过一整行的时候才滚动窗口,或者如果你可以滚动半行也可以.你还要把用户在控制面板中设置的滚轮每次滚动数量计算进去,这个数目可以通过GetLinesPerAction函数获得,要乘以这个值来得到实际用户希望滚动的数量.
另外,鼠标滚轮还可以被设置为每次滚动一页,你需要调用IsPageScroll函数来判断是否属于这种情况.
我们来举个例子,下面的代码是wxScrolledWindow的默认滚轮处理事件处理函数,其中的变量m_wheelRotation对已经滚动的位置进行累加,只有在滚动超过一行的时候才进行滚动.
```
void wxScrollHelper::HandleOnMouseWheel(wxMouseEvent& event)
{
m_wheelRotation += event.GetWheelRotation();
int lines = m_wheelRotation / event.GetWheelDelta();
m_wheelRotation -= lines * event.GetWheelDelta();
if (lines != 0)
{
wxScrollWinEvent newEvent;
newEvent.SetPosition(0);
newEvent.SetOrientation(wxVERTICAL);
newEvent.m_eventObject = m_win;
if (event.IsPageScroll())
{
if (lines > 0)
newEvent.m_eventType = wxEVT_SCROLLWIN_PAGEUP;
else
newEvent.m_eventType = wxEVT_SCROLLWIN_PAGEDOWN;
m_win->GetEventHandler()->ProcessEvent(newEvent);
}
else
{
lines *= event.GetLinesPerAction();
if (lines > 0)
newEvent.m_eventType = wxEVT_SCROLLWIN_LINEUP;
else
newEvent.m_eventType = wxEVT_SCROLLWIN_LINEDOWN;
int times = abs(lines);
for (; times > 0; times)
m_win->GetEventHandler()->ProcessEvent(newEvent);
}
}
}
```
- 第一章 介绍
- 1.1 为什么要使用wxWidgets?
- 1.2 wxWidgets的历史
- 1.3 wxWidgets社区
- 1.4 wxWidgets和面向对象编程
- 1.5 wxWidgets的体系结构
- 1.6 许可协议
- 第一章小结
- 第二章 开始使用
- 2.1 一个小例子
- 2.2 应用程序类
- 2.3 Frame窗口类
- 2.4 事件处理函数
- 2.5 Frame窗口的构造函数
- 2.6 完整的例子
- 2.7 wxWidgets程序一般执行过程
- 2.8 编译和运行程序
- 第二章小结
- 第三章 事件处理
- 3.1 事件驱动编程
- 3.2 事件表和事件处理过程
- 3.3 过滤某个事件
- 3.4 挂载事件表
- 3.5 动态事件处理方法
- 3.6 窗口标识符
- 3.7 自定义事件
- 第三章小结
- 第四章 窗口的基础知识
- 4.1 窗口解析
- 4.2 窗口类概览
- 4.3 基础窗口类
- 4.4 顶层窗口
- 4.5 容器窗口
- 4.6 非静态控件
- 4.7 静态控件
- 4.8 菜单
- 4.9 控制条
- 第四章小结
- 第五章绘画和打印
- 5.1 理解设备上下文
- 5.2 绘画工具
- 5.3 设备上下文中的绘画函数
- 5.4 使用打印框架
- 5.5 使用wxGLCanvas绘制三维图形
- 第五章小节
- 第六章处理用户输入
- 6.1 鼠标输入
- 6.2 处理键盘事件
- 6.3 处理游戏手柄事件
- 第六章小结
- 第七章使用布局控件进行窗口布局
- 7.1 窗口布局基础
- 7.2 窗口布局控件
- 7.3 使用布局控件进行编程
- 7.4 更多关于布局的话题
- 第七章小结
- 第八章使用标准对话框
- 8.1信息对话框
- 8.2 文件和目录对话框
- 8.3 选择和选项对话框
- 8.4 输入对话框
- 8.5 打印对话框
- 第八章小结
- 第九章创建定制的对话框
- 9.1 创建定制对话框的步骤
- 9.2 一个例子:PersonalRecordDialog
- 9.3 在小型设备上调整你的对话框
- 9.4 一些更深入的话题
- 9.5 使用wxWidgets资源文件
- 第九章小结
- 第十章使用图像编程
- 10.1 wxWidgets中图片相关的类
- 10.2 使用wxBitmap编程
- 10.3 使用wxIcon编程
- 10.4 使用wxCursor编程
- 10.5 使用wxImage编程
- 10.6 图片列表和图标集
- 10.7 自定义wxWidgets提供的小图片
- 第十章小结
- 第十一章剪贴板和拖放操作
- 11.1 数据对象
- 11.2 使用剪贴板
- 11.3 实现拖放操作
- 第十一章小结
- 第十二章高级窗口控件
- 12.1 wxTreeCtrl
- 12.2 wxListCtrl
- 12.3 wxWizard
- 12.4 wxHtmlWindow
- 12.5 wxGrid
- 12.6 wxTaskBarIcon
- 12.7 编写自定义的控件
- 第十二章小结
- 第十三章数据结构类
- 13.1 为什么没有使用STL?
- 13.2 字符串类型
- 13.3 wxArray
- 13.4 wxList和wxNode
- 13.5 wxHashMap
- 13.6 存储和使用日期和时间
- 13.7 其它常用的数据类型
- 第十三章小结
- 第十四章文件和流操作
- 14.1 文件类和函数
- 14.2 流操作相关类
- 第十四章小结
- 第十五章内存管理,调试和错误处理
- 15.1 内存管理基础
- 15.2 检测内存泄漏和其它错误
- 15.3 构建自防御的程序
- 15.4 错误报告
- 15.5 提供运行期类型信息
- 15.6 使用wxModule
- 15.7 加载动态链接库
- 15.8 异常处理
- 15.9 调试提示
- 第十五章小结
- 第十六章编写国际化程序
- 16.1 国际化介绍
- 16.2 从翻译说起
- 16.3 字符编码和Unicode
- 16.4 数字和日期
- 16.5 其它媒介
- 16.6 一个小例子
- 第十六章小结
- 第十七章编写多线程程序
- 17.1 什么时候使用多线程,什么时候不要使用
- 17.2 使用wxThread
- 17.3 用于线程同步的对象
- 17.4 多线程的替代方案
- 第十七章小结
- 第十八章使用wxSocket编程
- 18.1 Socket类和功能概览
- 18.2 Socket及其基本处理介绍
- 18.3 Socket标记
- 18.4 使用Socket流
- 18.5 替代wxSocket
- 第十八章小结
- 第十九章使用文档/视图框架
- 19.1 文档/视图基础
- 19.2 文档/视图框架的其它能力
- 19.3 实现Undo/Redo的策略
- 第十九章小结
- 第二十章完善你的应用程序
- 20.1 单个实例和多个实例
- 20.2 更改事件处理机制
- 20.3 降低闪烁
- 20.4 实现联机帮助
- 20.5 解析命令行参数
- 20.6 存储应用程序资源
- 20.7 调用别的应用程序
- 20.8 管理应用程序设置
- 20.9 应用程序安装
- 20.10 遵循用户界面设计规范
- 20.11 全书小结