Can I suppress selected input before the application's main loop? - visual-studio-2010

As part of my Visual Studio utilities add-in SamTools, I have a mouse input routine that catches Ctrl+MouseWheel and sends a pageup/pagedown command to the active text window. Visual Studio 2010 added a new "feature" that uses that gesture for zoom in/out (barf). Currently, my add-in does send the scrolling command, but Visual Studio still changes the font size because I'm not eating the input.
I set my hook with a call to SetWindowsHookEx. Here's the callback code. My question is: is the best way to prevent Visual Studio from handling the Ctrl+MouseWheel input as a zoom command to simply not call CallNextHookEx when I get a mouse wheel event with the Ctrl key down?
(Please bear in mind this is some old code of mine.) :)
private IntPtr MouseCallback(int code, UIntPtr wParam, ref MOUSEHOOKSTRUCTEX lParam)
{
try
{
// the callback runs twice for each action - this is the latch
if (enterHook)
{
enterHook = false;
if (code >= 0)
{
int x = lParam.mstruct.pt.X;
int y = lParam.mstruct.pt.Y;
uint action = wParam.ToUInt32();
switch (action)
{
case WM_MOUSEWHEEL:
OnMouseWheel(new MouseEventArgs(MouseButtons.None, 0, x, y, ((short)HIWORD(lParam.mouseData)) / (int)WHEEL_DELTA));
break;
default:
// don't do anything special
break;
}
}
}
else
{
enterHook = true;
}
}
catch
{
// can't let an exception get through or VS will crash
}
return CallNextHookEx(mouseHandle, code, wParam, ref lParam);
}
And here's the code that executes in response to the MouseWheel event:
void mouse_enhancer_MouseWheel( object sender, System.Windows.Forms.MouseEventArgs e )
{
try
{
if ( Keyboard.GetKeyState( System.Windows.Forms.Keys.ControlKey ).IsDown && Connect.ApplicationObject.ActiveWindow.Type == vsWindowType.vsWindowTypeDocument )
{
int clicks = e.Delta;
if (e.Delta < 0)
{
Connect.ApplicationObject.ExecuteCommand( "Edit.ScrollPageDown", "" );
}
else
{
Connect.ApplicationObject.ExecuteCommand( "Edit.ScrollPageUp", "" );
}
}
}
catch ( System.Runtime.InteropServices.COMException )
{
// this occurs if ctrl+wheel is activated on a drop-down list. just ignore it.
}
}
PS: SamTools is open source (GPL) - you can download it from the link and the source is in the installer.
PSS: Ctrl+[+] and Ctrl+[-] are better for zooming. Let Ctrl+MouseWheel scroll (the vastly more commonly used command).

According to MSDN, it's possible to toss mouse messages that you process. Here's the recommendation:
If nCode is less than zero, the hook
procedure must return the value
returned by CallNextHookEx.
If nCode is greater than or equal to
zero, and the hook procedure did not
process the message, it is highly
recommended that you call
CallNextHookEx and return the value it
returns; otherwise, other applications
that have installed WH_MOUSE hooks
will not receive hook notifications
and may behave incorrectly as a
result. If the hook procedure
processed the message, it may return a
nonzero value to prevent the system
from passing the message to the target
window procedure.
In other words, if your mouse callback ends up using the mouse message, you don't have to call the next CallNextHookEx -- just return a nonzero value and (in theory, at least) the mouse movement should get swallowed. If that doesn't work the way you want, comment and we can iterate.
BTW, another possible alternative: it's possible that VS's mapping to the mouse wheel shows up in the Tools...Customize... UI, just like key mappings do. In that case, you could simply remap your add-in's commands instead of working at the hook level. But it's also posible (likely?) that this gesture is hard-coded.

Related

On Windows 10, the Cancel object never returns RPC_E_CALL_COMPLETE

In our application that we've had for 15+ years, we have a type of progress bar.
This progress bar is for long lasting C++ operations and there is also the case for when we make a COM call and it takes a long time for the COM call to return.
In general, we want the user to know that something is taking a long time to complete and give him a chance to cancel if he thinks it is taking too much time.
For COM operations, many years ago we implemented a custom IMessageFilter for COM calls that take too long. We would like the user to be able to cancel but also for the prompt to cancel go away on its own when the operation completes. It has been working fine for years. Most of our customers are conservative and are still running Windows 7. Recently a customer using Windows 10 has found an issue where a COM call on Windows 10 never seems to never finish.
Our progress bar comes up and the progress control cycles and recycles, but the operation never seems to finish. After investigating it, it seems that the ICancelMethodCalls::TestCancel() method always returns RPC_S_CALLPENDING, it never returns RPC_E_CALL_COMPLETE. On Windows 7, previous versions of Windows, and Windows 8.1, it works fine, but not on Windows 10.
I created a minimal test solution in Visual Studio 2013 that demonstrates the problem. One project is an ATL server, and the other project is an MFC client application. A link to a zip file of a sample solution is here: https://www.dropbox.com/s/1dkchplsi7d6tda/MyTimeOutServer.zip?dl=0
Basically the ATL server has a property that sets a delay, and a method that just waits the delay length. The purpose is to simulate a COM operation that is taking too long.
interface IMyTimoutServer : IDispatch{
[propget, id(1), helpstring("Timeout value in milliseconds")] HRESULT TimeOut([out, retval] LONG* pVal);
[propput, id(1), helpstring("Timeout value in milliseconds")] HRESULT TimeOut([in] LONG newVal);
[id(2)] HRESULT DoTimeOut([out, retval] VARIANT_BOOL* Result);
};
The next important thing is the IMessageFilter in the client application. At some point, someone decided it was good to derive from COleMessageFilter, the default MFC implementation. (Let's not argue whether that is a good idea.)
The important methods of the the class are the MessagePending() and MyNotResponding().
DWORD CMyMessageFilter::XMyMessageFilter::MessagePending(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType)
{
METHOD_PROLOGUE_EX(CMyMessageFilter, MyMessageFilter);
ASSERT_VALID(pThis);
MSG msg;
if (dwTickCount > pThis->m_nTimeout && !pThis->m_bUnblocking && pThis->IsSignificantMessage(&msg))
{
if (pThis->m_bEnableNotResponding)
{
pThis->m_bUnblocking = TRUE; // avoid reentrant calls
// eat all mouse and keyboard messages in our queue
while (PeekMessage(&msg, NULL, WM_MOUSEFIRST, AFX_WM_MOUSELAST, PM_REMOVE | PM_NOYIELD));
while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE | PM_NOYIELD));
// show not responding dialog
pThis->m_dwTickCount = dwTickCount;
bool bCancel = pThis->MyNotResponding(htaskCallee) == RPC_E_CALL_CANCELED;
pThis->m_bUnblocking = FALSE;
return bCancel ? PENDINGMSG_CANCELCALL : PENDINGMSG_WAITNOPROCESS; // if canceled, the COM call will return RPC_E_CALL_CANCELED
}
}
// don't process re-entrant messages
if (pThis->m_bUnblocking)
return PENDINGMSG_WAITDEFPROCESS;
// allow application to process pending message
if (::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE | PM_NOYIELD))
pThis->OnMessagePending(&msg); // IK: could also return a value from extended OnMessagePending() to cancel the call
// by default we return pending MSG wait
return PENDINGMSG_WAITNOPROCESS;
}
After a timeout, we display a status type window that updates in the NotMyResponding() method.
int CMyMessageFilter::MyNotResponding(HTASK hTaskBusy)
{
TRY
{
CComPtr<ICancelMethodCalls> pCancel;
HRESULT hRes = ::CoGetCancelObject(0, IID_ICancelMethodCalls, (void**)&pCancel);
ATLASSERT(SUCCEEDED(hRes)); // COM should automatically create Cancel objects for pending calls [both on client and server]
if (pCancel == NULL)
{
return COleBusyDialog::retry;
}
m_pFrame->EnableWindow(FALSE);
CMyCancelDlg dlg;
dlg.Create(CMyCancelDlg::IDD);
dlg.ShowWindow(SW_SHOWNORMAL);
HWND hWndDlg = dlg.GetSafeHwnd();
do
{
MSG msg;
for (int i = 0; i < 100 && PeekMessage(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD); ++i)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (dlg.StepAndCheckCancel())
{
dlg.DestroyWindow();
m_pFrame->EnableWindow(TRUE);
return RPC_E_CALL_CANCELED; // signals to MessagePending() that the COM call should be cancelled
}
Sleep(250); // this call has been pending for many seconds now... sleep for some time to avoid CPU utilization by this loop and yield if needed
hRes = pCancel->TestCancel();
} while (hRes == RPC_S_CALLPENDING);
ATLASSERT(hRes == RPC_E_CALL_COMPLETE);
dlg.DestroyWindow();
m_pFrame->EnableWindow(TRUE);
}
END_TRY
return RPC_E_CALL_COMPLETE;
}
Basically, in MyNotResponding(), we create a Cancel object, create a window with a cancel button, pump messsages, and look for either a press on the cancel button or if TestCancel() returns something other than RPC_S_CALLPENDING.
Well, on Windows 10, it stays in the loop and RPC_S_CALLPENDING is always returned from TestCancel().
Has anyone seen anything like this on Windows 10? Are we doing something wrong that we are really only getting lucky on Windows 7?
The default implementation of the MFC puts up an OLEUIBusy dialog which pumps messages. It just never tests the cancel object.

Is there a way to forward TTN_NEEDTEXT to CFrameWnd?

I have an application that already deals with the generating of tooltips. I am modifying a CWnd derived class that has a parent frame. It doesn't implement tooltips.
From this, I can get tooltips to show up by adding the following code:
BEGIN_MESSAGE_MAP(CMyWindow, CWnd)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
END_MESSAGE_MAP()
BOOL CMyWindow::OnToolTipNotify(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
{
UNREFERENCED_PARAMETER(id);
UNREFERENCED_PARAMETER(pResult);
// need to handle both ANSI and UNICODE versions of the message
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
CStringA strTipText;
UINT_PTR nID = pNMHDR->idFrom;
if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
}
if (nID != 0) // will be zero on a separator
strTipText.Format("Control ID = %d", nID);
if (pNMHDR->code == TTN_NEEDTEXTA)
{
strncpy_s(pTTTA->szText, sizeof(pTTTA->szText), strTipText,
strTipText.GetLength() + 1);
}
else
{
::MultiByteToWideChar(CP_ACP, 0, strTipText, strTipText.GetLength() + 1,
pTTTW->szText, sizeof(pTTTW->szText) / (sizeof pTTTW->szText[0]));
}
return TRUE; // message was handled
}
I can use GetParentFrame() to get the frame window, so I'd like to leverage the tooltip code that is already in place so that I get a consistent look. Is there some way that I can forward the TTN_NEEDTEXT message so that it gets handled by the frame window?
In your message map, you use ON_NOTIFY_whatever, which should hint that the message that TTN_NEEDTEXT uses is WM_NOTIFY — and indeed this is the case. So you can just produce the WM_NOTIFY yourself and send it to the parent.
The documentation for WM_NOTIFY says wParam is the control's identifier and lParam is the NMHDR pointer. There's an example on the bottom of the page that shows wParam is just the idFrom member of the NMHDR, so you have everything you need to reconstruct the message:
LRESULT lr = this->GetParentFrame()->SendMessage(WM_NOTIFY, pNMHDR->idFrom, (LPARAM) pNMHDR);
When you should issue this call is up to what you need to do. In this case, you would probably want to make it the first call, override your string, and return TRUE. I'm not fully sure, though.
Note: if MFC provides a function to do this, I don't know it; I personally don't use MFC, but the concepts are the same.
So what about the return value from that SendMessage()? Well, you can return it via pResult (for instance, if you're just tweaking the parent's alterations slightly), or you can ignore it and return a custom value. But for TTN_NEEDTEXT, it won't matter; TTN_NEEDTEXT doesn't care what the LRESULT is.

Modal MFC dialog not shown due to idle checks in CWnd::RunModalLoop

Below I've put the source to CWnd::RunModal, which is the message loop run when you call CDialog::DoModal - it takes over as a nested message loop until the dialog is ended.
Note that with a couple of special case exception ShowWindow is only called when the message queue is idle.
This is causing a dialog not to appear for many seconds in some cases in our application when DoModal is called. If I debug into the code and put breakpoints, I see the phase 1 loop is not reached until this time. However if I create the same dialog modelessly (call Create then ShowWindow it appears instantly) - but this would be an awkward change to make just to fix a bug without understanding it well.
Is there a way to avoid this problem? Perhaps I can call ShowWindow explicitly at some point for instance or post a message to trigger the idle behaviour? I read "Old New Thing - Modality" which was very informative but didn't answer this question and I can only find it rarely mentioned on the web, without successful resolution.
wincore.cpp: CWnd::RunModalLoop
int CWnd::RunModalLoop(DWORD dwFlags)
{
ASSERT(::IsWindow(m_hWnd)); // window must be created
ASSERT(!(m_nFlags & WF_MODALLOOP)); // window must not already be in modal state
// for tracking the idle time state
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && !(GetStyle() & WS_VISIBLE);
HWND hWndParent = ::GetParent(m_hWnd);
m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);
MSG *pMsg = AfxGetCurrentMessage();
// acquire and dispatch messages until the modal state is done
for (;;)
{
ASSERT(ContinueModal());
// phase1: check to see if we can do idle work
while (bIdle &&
!::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))
{
ASSERT(ContinueModal());
// show the dialog when the message queue goes idle
if (bShowIdle)
{
ShowWindow(SW_SHOWNORMAL);
UpdateWindow();
bShowIdle = FALSE;
}
// call OnIdle while in bIdle state
if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0)
{
// send WM_ENTERIDLE to the parent
::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd);
}
if ((dwFlags & MLF_NOKICKIDLE) ||
!SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))
{
// stop idle processing next time
bIdle = FALSE;
}
}
// phase2: pump messages while available
do
{
ASSERT(ContinueModal());
// pump message, but quit on WM_QUIT
if (!AfxPumpMessage())
{
AfxPostQuitMessage(0);
return -1;
}
// show the window when certain special messages rec'd
if (bShowIdle &&
(pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN))
{
ShowWindow(SW_SHOWNORMAL);
UpdateWindow();
bShowIdle = FALSE;
}
if (!ContinueModal())
goto ExitModal;
// reset "no idle" state after pumping "normal" message
if (AfxIsIdleMessage(pMsg))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));
}
ExitModal:
m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL);
return m_nModalResult;
}
So to answer my own question, the solution I found was to explicitly call the following two methods:
ShowWindow(SW_SHOWNORMAL);
UpdateWindow();
CWnd::RunModalLoop is supposed to call these, but only when it detects the message queue is empty/idle. If that doesn't happen then the dialog exists and blocks input to other windows, but isn't visible.
From tracking messages I found WM_ACTIVATE was the last message being sent before things got stuck, so I added an OnActivate() handler to my Dialog class.
I found a very similar problem in my application. It only happened when the application was under a heavy load for drawing (the user can open many views, the views show real time data, so they are constantly refreshing, and each view must be drawn independently, and the process of drawing takes a lot of time). So, if under that scenario, the user tries to open any modal dialog (let's say, the "About" dialog, or if the app needs to show any modal dialog like a MessageBox), it "freezes" and the dialog only shows after pressing the ALT key.
Analyzing RunModalLoop() I got to the same conclusion as you did, that is, the first loop (titled "phase1" in the code comments), is never called, since it needs the message queue to be empty, and it never is; the app then falls in the second loop, phase2, from which it never exits, because the call to PeekMessage() at the end of phase2 never returns zero (the message queue is very busy for so many views constantly updating).
Since this code is in wincore.cpp, my solution was to find the closest function that could be overloaded, and lucky me found ContinueModal() which is virtual. Since I already had a class derived from CDialog which I always use as a replacement, I only had to define in that class a BOOL variable m_bShown, and an overload of ContinueModal():
BOOL CMyDialog::ContinueModal()
{
// Begin extra code by Me
if (m_nFlags & WF_CONTINUEMODAL)
{
if (!m_bShown && !(GetStyle() & WS_VISIBLE))
{
ShowWindow(SW_SHOWNORMAL);
UpdateWindow();
m_bShown = TRUE;
}
}
// End extra code
return m_nFlags & WF_CONTINUEMODAL;
}
This causes ContinueModal() to actually show the window if it has never been shown before. I set m_bShown to FALSE both in its declaration and in OnInitDialog(), so it arrives to RunModalLoop() in FALSE, and set it to TRUE immediately after showing the window to disable the little additional code snippet.
This solved the problem for me and supposedly should solve it for anybody. The only doubts remaining are, 1) is RunModalLoop() called from somewhere else within MFC that would conflict with this modification? and 2) Why did Microsoft program RunModalLoop() this weird way leaving this glaring hole there that can cause an app to freeze without any apparent reason?
I recently had the same problem.
I solved by sending the undocumented message 0x118 before calling DoModal(), which is handled in the phase2.
...
PostMessage(0x118, 0, 0);
return CDialog::DoModal();

How to redirect mouse wheel message to other windows?

I'm developing a Word addin for MS Word on Windows, and this addin has as 'advanced task pane' showing and docking on the left side of the Word document window (it's treeview(outline) showing a list of Word documents for fast editing multiple documents in a project).
My question is, the Word document window responds to mouse wheel message only when it's focused, but I want to be able to scroll the document with mouse wheel whenever the mouse cursor is hovering on it even the Word document window doesn't have a input focus.
Any hints in this particular case? Thank you!
Not quite sure it will work, but I'd try the following:
Implement a global low-level mouse hook using the SetWindowsHookEx function.
In the hook procedure, which should be called on mouse wheel scroll events, check if the window under mouse cursor is the Word document window. If so, set a shared flag indicating the needed scroll action.
Don't send WM_VSCROLL directly from the hook procedure! This procedure has to be really fast and simple.
Now, in your add-in's message loop check the flag and if it is set, send WM_VSCROLL to the Word document window.
Perhaps you could make use of the SetCapture(hWnd) Windows API function. This will cause all mouse events to go to your hWnd instead of whatever hWnd might normally expect to receive them. If you capture when the mouse enters the Word document window, and ReleaseCapture() when the mouse leaves or Word gains focus, it should work alright.
Disclaimer: I've used mouse capturing in C# before, but I've never done it in C++. I don't know if it behaves exactly the same way.
Try the following , this might help you.
1) Handle WM_MOUSEHOVER message.
2) In the handler , use SendMessage, with WM_VSCROLL as the message parameter .
Using Spy++ I saw that the window that gets the messages is of the class _Wwg (At least 2003 is) and it is responding to the WM_MOUSEWHEEL message. So you would send that window a WM_MOUSEWHELL message when you want it to scroll.
I've got the C++ code snipped below from a comment in https://msdn.microsoft.com/en-us/library/windows/desktop/ms645617(v=vs.85).aspx
And I used it (and variations on it) successfully.
The user who wrote it claims it was inspired by a ms recommendation on a Windows Vista best practices guide, to forward the mouse wheel event to whatever window is hovered by the mouse cursor. The virtue of his/her implementation is that it's completely unintrusive, you just drop it and it set the hooks, referencing your main thread. It avoids forwarding the event to windows belonging to other processes, but perhaps that could actually be a good thing.
namespace {
LRESULT CALLBACK mouseInputHook(int nCode, WPARAM wParam, LPARAM lParam) {
//"if nCode is less than zero, the hook procedure must pass the message to the CallNextHookEx function
//without further processing and should return the value returned by CallNextHookEx"
if (nCode >= 0) {
MSG& msgInfo = *reinterpret_cast<MSG*>(lParam);
if (msgInfo.message == WM_MOUSEWHEEL ||
msgInfo.message == WM_MOUSEHWHEEL) {
POINT pt = {};
pt.x = ((int)(short)LOWORD(msgInfo.lParam)); //yes, there's also msgInfo.pt, but let's not take chances
pt.y = ((int)(short)HIWORD(msgInfo.lParam)); //
//visible child window directly under cursor; attention: not necessarily from our process!
//http://blogs.msdn.com/b/oldnewthing/archive/2010/12/30/10110077.aspx
if (HWND hWin = ::WindowFromPoint(pt))
if (msgInfo.hwnd != hWin && ::GetCapture() == nullptr) {
DWORD winProcessId = 0;
::GetWindowThreadProcessId(//no-fail!
hWin, //_In_ HWND hWnd,
&winProcessId); //_Out_opt_ LPDWORD lpdwProcessId
if (winProcessId == ::GetCurrentProcessId()) //no-fail!
msgInfo.hwnd = hWin; //it would be a bug to set handle from another process here
}
}
}
return ::CallNextHookEx(nullptr, nCode, wParam, lParam);
}
struct Dummy {
Dummy() {
hHook = ::SetWindowsHookEx(WH_GETMESSAGE, //__in int idHook,
mouseInputHook, //__in HOOKPROC lpfn,
nullptr, //__in HINSTANCE hMod,
::GetCurrentThreadId()); //__in DWORD dwThreadId
assert(hHook);
}
~Dummy() {
if (hHook)
::UnhookWindowsHookEx(hHook);
}
private:
HHOOK hHook;
} dummy;
}

MouseDown and then MouseUp doesn't work

I am trying to redirect mouse inputs on my Windows 7 application to some other window.
If I do this when I get WM_LBUTTONUP, it works (where MouseDown and MouseUp are SendInput functions in Win api):
SetForegroundWindow( other window );
SetCursorPos( somewhere on the window );
MouseDown();
MouseUp();
SetCursorPos( back );
SetForegroundWindow( main window );
But I don't want to only do mouse releases, I want to be able to capture all mouse stuff, including movements and dragging.
So this is next logical thing to do but it doesn't work:
WM_LBUTTONDOWN:
Do everything like before without MouseUp()
WM_LBUTTONUP:
Do everything like before without MouseDown()
This doesn't even work for regular clicks. I can't figure out why.
Can anybody help?
Mouse buttons are funky. When it gets an down then up, at some level those are converted to a click (and I think at some point the mouse up gets eaten, but I may not be recalling that correctly).
It could be any number of things, but if the other windows is (or is not) converting the buttondown/up to mouse clicks, it could be confusing your code.
I suggest you print a lot of debugging info and try to figure out exactly what the system is doing.
It might be worth looking at the SendMessage/PostMessage P/Invoke calls, and sending the messages directly to the window of the other application. You need to do some translation on the parameters so that the co-ordinates of mouse events tie up with what you want them to in the other application, but it's not a big deal to do that...
Edit -> I dug out some code where I have done this before... This is from a window which appears over the top of a tree view and replaces the default windows tooltip for that tree view.
private IntPtr _translate(IntPtr LParam)
{
// lparam is currently in client co-ordinates, and we need to translate those into client co-ordinates of
// the tree view we're attached to
int x = (int)LParam & 0xffff;
int y = (int)LParam >> 16;
Point screenPoint = this.PointToScreen(new Point(x, y));
Point treeViewClientPoint = _tv.PointToClient(screenPoint);
return (IntPtr)((treeViewClientPoint.Y << 16) | (treeViewClientPoint.X & 0xffff));
}
const int MA_NOACTIVATE = 3;
protected override void WndProc(ref Message m)
{
switch ((WM)m.Msg)
{
case WM.LBUTTONDBLCLK:
case WM.RBUTTONDBLCLK:
case WM.MBUTTONDBLCLK:
case WM.XBUTTONDBLCLK:
{
IntPtr i = _translate(m.LParam);
_hide();
InteropHelper.PostMessage(_tv.Handle, m.Msg, m.WParam, i);
return;
}
case WM.MOUSEACTIVATE:
{
m.Result = new IntPtr(MA_NOACTIVATE);
return;
}
case WM.MOUSEMOVE:
case WM.MOUSEHWHEEL:
case WM.LBUTTONUP:
case WM.RBUTTONUP:
case WM.MBUTTONUP:
case WM.XBUTTONUP:
case WM.LBUTTONDOWN:
case WM.RBUTTONDOWN:
case WM.MBUTTONDOWN:
case WM.XBUTTONDOWN:
{
IntPtr i = _translate(m.LParam);
InteropHelper.PostMessage(_tv.Handle, m.Msg, m.WParam, i);
return;
}
}
base.WndProc(ref m);
}

Resources