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);
        }
    }
}