我有一个应用程序,其中双击图像视图区域会更改图像视图的布局。同样,单击时,图像上将放置一个点。我的问题是,双击时这两个功能都可以工作。
当然我知道,当双击发生时,控件首先转到 LButtonDown。当双击发生时,我不希望点功能起作用。我已经为此工作了一个多星期。请帮忙。
我有一个应用程序,其中双击图像视图区域会更改图像视图的布局。同样,单击时,图像上将放置一个点。我的问题是,双击时这两个功能都可以工作。
当然我知道,当双击发生时,控件首先转到 LButtonDown。当双击发生时,我不希望点功能起作用。我已经为此工作了一个多星期。请帮忙。
解决这个问题的最简单方法是构建一个有限状态机来处理鼠标点击。基本上,这将是一个单例对象,它从您当前使用的鼠标单击事件中获取输入。它的输出将是SingleClickDetected, DoubleClickDetected, ...
. 红色箭头表示您正在向应用程序的其余部分报告的事件。括号表示您要报告的事件。
当然,如果您必须直接处理事件而不是事件,则必须修改此MouseDown
状态MouseUp
机MouseClick
。会稍微大一点,但是思路基本一样。
编辑:从评论看来,Windows 似乎没有清楚地报告单击与双击,您需要将它们分开。此场景的状态机:
这对于您要尝试做的事情可能有点过头了,特别是因为大多数(如果不是所有)基于 GUI 的程序在所有事物的历史上都从未使用过双击拖动。它确实展示了基本思想,并展示了如何扩展状态机来处理不同类型的按钮点击。此外,如果您愿意,您可以处理双击右键、同时涉及左右按钮的拖动,或者您可以想到并合并到 UI 中的任何其他场景。
我编写了以下代码并且它有效。
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;
}
,...
}
尝试存储最后一个 LButtonDown 的时间戳;如果最后一个时间戳和当前事件产生的时间戳之间的时间差太短,你可以取消你的操作(但仍然存储新的 LButtonDown 时间戳)
您唯一能做的就是在每次收到点击事件时等待一小段时间,并在执行单击响应之前测试在此期间是否没有发生相当于双击事件的事件。这可能是新错误和无响应 UI 的来源。也许尝试更改用户交互以解决问题。
编辑:你在这个问题上工作了一个多星期的事实是糟糕的用户交互设计的症状。“双击”仍然意味着发生了两次单击,这意味着应用程序自然应该执行单击的操作。检查桌面上安装的应用程序的用户界面以验证这一点。您是否考虑过使用不同的用户媒介来触发 UI 响应?例如,您可以使用右键将点放在图像上。
@ET 的回答很到位。要实现类似的功能,您确实需要一个与消息循环一起运行的计时器。
在我的应用程序中,我希望能够将鼠标向下/向上与双击区分开来,因为我希望双击不撤消拖动操作(想象一下带有左键拖动和双击以适应的选择框)。
以下代码使用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);
}
我真的很喜欢有限状态机的答案,但它有一个缺陷。没有可以超过的“单击时间”之类的东西。
如果您仔细观察鼠标的工作,您会发现:-
单击不是一个事件,而是= WM_LBUTTONDOWN,WM_LBUTTONUP,这与两者之间的时间无关,无论如何都会发生适当的动作
双击鼠标左键实际上会生成四个消息序列:WM_LBUTTONDOWN、WM_LBUTTONUP、WM_LBUTTONDBLCLK 和 WM_LBUTTONUP,因此您应该使用第 3 个标志来发挥自己的优势
顺便说一句,我也在做这样的事情。谢谢!