区分C++中的单击和双击

3

我有一个应用程序,双击图像视图区域会更改图像视图的布局。同时单击图像上会放置一个点。 我的问题是,当双击时这两个功能都起作用。

当然我知道,当发生双击时控件首先会进入LButtonDown状态。但我不希望在发生双击时点的功能也起作用。我已经为此调试了一周多时间,请帮忙解决。


1
查找CS_DBLCLKS。这是处理此问题的快速而简单的方法。 - AbstractDissonance
6个回答

11

解决这个问题最简单的方法是构建一个处理鼠标点击的有限状态机。 基本上,这将是一个单例对象,它接收来自当前使用的鼠标点击事件的输入。 它的输出将是SingleClickDetected, DoubleClickDetected, ...。 红色箭头表示您正在向应用程序的其余部分报告的事件。 括号表示您正在报告的事件。

FSM

当然,如果你需要直接处理MouseDown和MouseUp事件而不是MouseClick事件,那么这个状态机将需要进行修改。它会稍微变大一点,但基本思路是相同的。
编辑: 从评论中看来,Windows不能清晰地报告单击和双击,所以你需要将它们分开。 这种情况下的状态机如下图所示: FSM2 对于你想要做的事情来说,这可能有些过度设计,特别是因为几乎所有基于GUI的程序从未使用过双击拖动。它展示了基本的思路,并展示了如何扩展状态机以处理不同类型的按钮点击。此外,如果你愿意,你可以处理双右击、涉及左右两个按钮的拖动,或者任何其他你能想到并融入你的UI的场景。

从Microsoft(http://msdn.microsoft.com/en-us/library/windows/desktop/ms645606(v=vs.85).aspx):双击左鼠标按钮实际上会生成四个消息序列:WM_LBUTTONDOWN,WM_LBUTTONUP,WM_LBUTTONDBLCLK和WM_LBUTTONUP。 - UmNyobe
谢谢您的回复。但是我无法理解您实际的意思。因为当控件第一次进入LbuttonDown时,我们无法确定用户是要单击还是双击。这才是真正的问题。 - CodeRider
@VipinPaul 这正是我的解决方案所要处理的确切问题。每个黑色箭头都涉及将库、操作系统或API提供的输入转换为程序的其余部分可以处理的输出。因此,您的程序再也不需要自己触发LButtonDown事件了。只有一个鼠标处理子系统会处理这些输入,并为程序的其余部分提供明确的鼠标点击。如果您不知道如何阅读FSM图表,您基本上只需沿着箭头一遍又一遍地走即可。我还建议您完全阅读维基百科文章和一些教程。 - E.T.

1

我编写了以下代码,它可以正常运行。

UINT TimerId;
int clicks;

    VOID CALLBACK TimerProc(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime)
    {
        KillTimer(NULL, TimerId);
        if (clicks < 2 && !double_click){
            MessageBox(hWnd, L"Show Widget", L"Widget", MB_OK);
        }

        clicks = 0;
    }



  LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {

        int wmId, wmEvent;
        PAINTSTRUCT ps;
        HDC hdc;
        TCHAR szHello[MAX_LOADSTRING];
        LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
        UINT uID;
        UINT uMouseMsg;

        uID = (UINT)wParam;
        uMouseMsg = (UINT)lParam;

        if (uMouseMsg == WM_LBUTTONDBLCLK){
            double_click = true;
            MessageBox(hWnd, L"Double click", L"CAPT", MB_OK);
            return 0;
        }
        if (uMouseMsg == WM_LBUTTONDOWN){
            double_click = false;
            clicks++;

            //single click opens context menu
            if (clicks == 1){
                TimerId = SetTimer(NULL, 0, 500, &TimerProc);
            }
            return 0;
        }
    ,...
    }

0

@E.T的回答非常准确。要实现这样的功能,您确实需要在消息循环中运行一个计时器。

在我的应用程序中,我希望能够区分鼠标按下/松开和双击,因为我不希望双击取消拖动操作(想象一下使用左键拖动选择框并双击缩放以适应屏幕)。

以下代码使用PreTranslateMessage实现了此功能。由于懒惰,我没有添加计时器。因此,如果您在按下左键后不立即移动鼠标,则UI的行为会有点“奇怪”。在我的情况下,这是一个小问题。

BOOL MyWindow::PreTranslateMessage(MSG *pMsg)
{
//From https://msdn.microsoft.com/en-us/library/windows/desktop/ms645606(v=vs.85).aspx
//Double-clicking the left mouse button actually generates a sequence of four messages: 
//WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, and WM_LBUTTONUP

//So here's the problem. If an button up message arrives, we can't just
//take it, because it may be followed by a double click message. So we check
//for this.

//Ideally you need a timer - what happens if you don't get any messages
//after the button up or down? But here we get lazy and assume a message
//will come "soon enough".

static bool upMessageStored = false;
static bool dnMessageStored = false;
static MSG upMessage, dnMessage;
static bool processDnNow = false, processUpNow = false;

//This logic sequence absorbs all button up and down messages, storing them to be sent
//if something other than a double click message immediately follows.

if (!(pMsg->message == WM_LBUTTONDBLCLK ||
      pMsg->message == WM_LBUTTONDOWN ||
      pMsg->message == WM_LBUTTONUP) &&
    (upMessageStored || dnMessageStored))
{
    //If we receive any message other than double click and we've stored the messages,
    //then post them.
    Output::Message(L"Stored messages posted.\n");
    if (upMessageStored)
    {
        processUpNow = true;
        upMessageStored = false;
        this->PostMessage(upMessage.message, upMessage.wParam, upMessage.lParam);
    }
    if (dnMessageStored)
    {
        processDnNow = true;
        dnMessageStored = false;
        this->PostMessage(dnMessage.message, dnMessage.wParam, dnMessage.lParam);
    }
    return TitlelessWindow::PreTranslateMessage(pMsg);
}

if (pMsg->message == WM_LBUTTONDOWN && !processDnNow)
{
    Output::Message(L"WM_LBUTTONDOWN absorbed; message stored\n");
    dnMessage = *pMsg;
    dnMessageStored = true;
    return TRUE; //swallow this message.
}
else if (pMsg->message == WM_LBUTTONUP && !processUpNow)
{
    Output::Message(L"WM_LBUTTONUP absorbed; message stored\n");
    upMessage = *pMsg;
    upMessageStored = true;
    return TRUE; //swallow this message.
}
else if (pMsg->message == WM_LBUTTONDBLCLK)
{
    Output::Message(L"WM_LBUTTONDBLCLK; stored message discarded\n");
    upMessageStored = false;
    dnMessageStored = false;
    processUpNow = false;
    processDnNow = false;
}

//If we get here, we are processing messages normally. Be sure we clear the flags
//for up and down.
processDnNow = false;
processUpNow = false;
return ParentClass::PreTranslateMessage(pMsg);
}

0
尝试存储上一个 LButtonDown 的时间戳;如果当前事件产生的时间戳与上一个时间戳之间的时间差太短,您可以取消操作(但仍需存储新的 LButtonDown 时间戳)。

但是对于双击事件,控件只会进入LbuttonDown一次。下一次它会进入LBtnDoubleClick。因此,我认为存储时间戳值没有用处。 - CodeRider

0
唯一能够做的就是每次接收到点击事件后等待短暂时间,测试是否在此期间会发生相当于双击事件的情况,然后再执行单击响应。这可能会导致新的错误和无响应的用户界面。也许可以尝试更改用户交互方式来解决这个问题。
编辑:你已经绕过这个问题一个多星期了,这说明用户交互设计存在问题。 "双击"仍然意味着发生了两次单击操作,这意味着应用程序自然应该执行单击操作。检查桌面上安装的应用程序的用户界面以验证此点。您考虑使用不同的用户媒介触发UI响应吗?例如,您可以使用右键将点放在图像上。

实际上,双击始终与单击是两个不同的操作。自从 Windows、Mac 等操作系统早期就是这样了。是的,双击需要按两次鼠标按钮。但不,重复执行单击操作并不合适。唯一合适的情况是当双击的时间阈值已经超过,并且检测到两个不同的单击事件。 - E.T.
如果平台不能直接将事件传递给您,作为单独的 clickdoubleclick从不同时),则就应用而言,它并不是一个独立的事物。 - UmNyobe
那么,这就是涉及到的库的问题,需要像处理其他类似问题一样来解决,因为它不允许应用程序开发人员轻松区分单击和双击。可以使用替代库或在你正在使用的任何API中降低级别。或者,您可以使用报告的事件来构建一个有限状态机,将报告的点击清理成实际正常工作的点击检测。 - E.T.
我的观点是:接收到“doubleclick”始终意味着您接收到了相当于“click”的内容。由于这种相关性,您不能说“双击始终是与单击不同的事物”。附注:我从未说过单击操作应该执行两次。 - UmNyobe

0

我非常喜欢有限状态机的答案,但其中存在一个缺陷。

实际上不存在“单击时间”这样的东西,你无法超过它。

如果你仔细观察鼠标的工作原理,你会发现:

单击并不是一个事件,而是 WM_LBUTTONDOWN、WM_LBUTTONUP 两个独立的事件,它们之间的时间不影响适当的操作。

双击左键实际上生成了四个消息:WM_LBUTTONDOWN、WM_LBUTTONUP、WM_LBUTTONDBLCLK 和 WM_LBUTTONUP,所以你应该利用第三个标志位。

顺便说一下,我也在做类似的事情。谢谢!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接