【问题标题】:Distinguish single click from double click C++区分单击和双击 C++
【发布时间】:2017-06-22 10:20:23
【问题描述】:

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

当然我知道,当双击发生时,控件首先转到 LButtonDown。当双击发生时,我不希望点功能起作用。我已经为此工作了一个多星期。请帮忙。

【问题讨论】:

  • 查找 CS_DBLCLKS。这是处理这个问题的快速而肮脏的方法。

标签: visual-c++ mfc mouse double-click


【解决方案1】:

解决这个问题的最简单方法是构建一个finite-state machine 来处理鼠标点击。 基本上,这将是一个单例对象,它从您当前使用的鼠标单击事件中获取输入。 它的输出将是SingleClickDetected, DoubleClickDetected, ...。 红色箭头表示您正在向应用程序的其余部分报告的事件。 括号表示您要报告的事件。

当然,如果您必须直接处理MouseDownMouseUp 事件,而不是MouseClick 事件,则必须修改此状态机。 会稍微大一点,但思路基本一样。

编辑: 从 cmets 来看,Windows 似乎没有清楚地报告单击与双击,您需要将它们分开。 此场景的状态机:

这对于您正在尝试做的事情来说可能有点过头了,特别是因为大多数(如果不是所有)基于 GUI 的程序在所有事物的历史上都从未曾经使用过双击拖动。 它确实展示了基本思想,并展示了如何扩展状态机来处理不同类型的按钮点击。 此外,如果您愿意,您可以处理双击右键、同时涉及左右按钮的拖动,或者您可以想到并整合到您的 UI 中的任何其他场景。

【讨论】:

  • from microsoft 双击鼠标左键实际上会生成四个消息序列:WM_LBUTTONDOWN、WM_LBUTTONUP、WM_LBUTTONDBLCLK 和 WM_LBUTTONUP
  • 感谢您的回复。但无法弄清楚你的实际意思。因为当控件第一次进入 LbuttonDown 时,我们无法确定用户是单击还是双击。这是真正的问题。
  • @VipinPaul 这正是我的解决方案正在处理的问题。每个黑色箭头都处理将库、操作系统或 API 提供的输入转换为程序其余部分处理的输出。因此,您的程序从不 再触及 LButtonDown 本身。只有一个鼠标处理子系统会接触这些输入,然后为程序的其余部分提供明确的鼠标点击。如果您不知道如何阅读 FSM 图,您基本上只是按照箭头来回走。我还建议您完整阅读 Wikipedia 文章和一些教程。
  • @E.T 感谢您的支持。我试试看
【解决方案2】:

我写了以下代码,它可以工作。

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;
        }
    ,...
    }

【讨论】:

    【解决方案3】:

    尝试存储最后一个 LButtonDown 的时间戳;如果最后一个时间戳和当前事件产生的时间戳之间的时间差太短,你可以取消你的操作(但仍然存储新的 LButtonDown 时间戳)

    【讨论】:

    • 但是对于双击事件,控件只进入LbuttonDown一次,下次进入LBtnDoubleClick。所以我认为存储时间戳值是没有用的。
    【解决方案4】:

    您唯一能做的就是在每次收到点击事件时等待一小段时间,并在执行单击响应之前测试在此期间是否没有发生相当于双击事件的事件。这可能是新错误和无响应 UI 的来源。也许尝试改变用户交互来解决问题。

    编辑:您在这个问题上工作了一个多星期的事实是糟糕的用户交互设计的症状。 “双击”仍然意味着发生了两次单击,这意味着应用程序自然应该执行单击的操作。检查桌面上安装的应用程序的用户界面以验证这一点。您是否考虑过使用不同的用户媒介来触发 UI 响应?例如,您可以使用右键将点放在图像上。

    【讨论】:

    • 其实,双击和单击总是分开的。从早期的 Windows、Mac 等开始就是这样。是的,双击需要按两次鼠标按钮。不,不适合执行两次单击操作。唯一合适的时间是超过双击的阈值时间,并且检测到两个不同的单击事件。
    • 应用程序方面,如果平台不直接以clickdoubleclick从不两者)的形式向您提供事件,则它不是一个单独的东西。
    • 那么这是问题库的问题,需要这样处理,因为它不允许应用程序开发人员轻松区分单击和双击。使用备用库,或者在您正在使用的任何 API 中使用较低级别。或者,您可以使用报告的事件构建一个有限状态机,将报告的点击清理为实际正常工作的点击检测。
    • 我的观点:收到doubleclick 始终意味着您收到了相当于click 的信息。由于这种相关性,您不能说a double click is always a separate thing from a single click。 ps:我从来没有说过点击的动作应该执行两次
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      我真的很喜欢有限状态机的答案,但它有一个缺陷。 没有可以超过的“单击时间”。

      如果您仔细观察鼠标的工作,您会发现:-

      单击不是一个事件而是 = WM_LBUTTONDOWN, WM_LBUTTONUP 与中间的时间无关,无论如何都会发生适当的操作

      双击鼠标左键实际上会生成四个消息序列:WM_LBUTTONDOWN、WM_LBUTTONUP、WM_LBUTTONDBLCLK和WM_LBUTTONUP 所以你应该使用第三个标志来发挥你的优势

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-04-29
        • 2015-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多