2

Did quite a bit of research trying to figure out how to get this to work properly. I read that if you filter WM_NOTIFY -> NM_CLICK, it would catch the actual click event of the syslink. Trouble is that it catches the event, though it gets stuck in a endless recursion and in a few seconds you have hundreds of browser windows or w/e the link open's up as.

Steps taken:

  1. Create Syslink on dialog
  2. Add title to Syslink control link and change IDD to IDC_LINK1
  3. Filter WM_NOTIFY for NM_CLICK events

       case WM_NOTIFY:
      //case NM_CLICK:
      switch(LOWORD(wParam))
      {
          case NM_CLICK:
             switch(LOWORD(wParam))
         {
                 case IDC_LINK1:
                     // Standard ShellExecute with added check for IsLinkCtrl to make sure its the right kind of control.
                     OpenLink(hWndDlg, LOWORD(wParam));            
             break;
         }
         break;
      }
      break;
    

I guess my real question is how do I do this properly? I don't see any good examples that show how a Syslink is properly filtered to execute a link as a url.

sean e
  • 11,792
  • 3
  • 44
  • 56
Nightforce2
  • 1,426
  • 5
  • 18
  • 31
  • Try calling ReleaseCapture() first. – Hans Passant May 23 '12 at 19:02
  • Ok, I tryed using ReleaseCapture(). I placed it in WM_LBUTTONUP, NM_CLICK, and IDC_LINK1. No joy. Don't I need to SetCapture first before I can call that anyways? I traced the calls with OutputDebugStringW and only WM_NOTIFY->IDC_LINK1 is being notified as soon as the application begins it starts navigating to urls in the endless loop. – Nightforce2 May 23 '12 at 20:16

3 Answers3

3

Just to clarify, I do not think you are using managed C++, there for I am not sure why you are referring to a NM_CLICK notificaton code as an event. Besides, you do not have to catch anything, you are not handling any exceptions, you are just handling a WM_NOTIFY message.

Anyway, I am not sure why you experience this particular behavior since I do not see what you do outside of the code snippet but I know what is causing it. In our code snippet you use wParam to determine a notification code and this is incorrect. This control does not use wParam. To determine notification code you should do the following:

    NMHDR* pHeader = (NMHDR*)lParam;

    switch (pHeader->code)

pHeader->code holds the notification code value you should use and pHeader->hwndFrom is the handle of the control that was clicked.

Furthermore, you using LOWORD(wParam) again and pass it as the parameter to function call. You are not showing what you do in this function but I can conclude that the code is also not correct. The same lParam is a pointer to the NMLINK structure. First member of this structure is mentioned above NMHDR, the next member is LITEM structure that should be used to determine what the URL is.

    NMLINK* pNMLink = (NMLINK*)lParam;
    LITEM iItem = pNMLink->item;

item.szUrl is the URL you should use in call to ShellExecute call open.

JohnCz
  • 1,613
  • 1
  • 9
  • 9
  • Correct! Native Win32 as the tags suggest. Good call! I am well aware of .NET structure. Yes, I realize that the structure here for Syslink is NMLINK I have that setup for NM_RETURN just as MSDN says todo. I successfully got the links to execute in WM_NOTIFY->IDC_LINK1 without using NMLINK before all of this and only compare IID(wParam) code, then give it to OpenLink which checks if its a ctrl by its IID and if it matches it returns the correct URL from a link table for ShellExecute. Just want to know why its going into a recursion on startup. 100's of "www.msn.com" spawn instantly. – Nightforce2 May 24 '12 at 01:33
  • Ok, Changing approach. This is what I am attempting based on what your saying. case WM_NOTIFY: switch(((NMHDR *)lParam)->code) { case NM_CLICK: times++; winapi::Output("NM_CLICK: Fired %d time%s!\n", times, (times <= 1) ? L"" : L"s"); NMLINK* pNMLink = (NMLINK*)lParam; LITEM iItem = pNMLink->item; if(iItem->szID == IDC_LINK1) – Nightforce2 May 24 '12 at 17:45
2

I found a successful way to get this to work without going into recursion. The message loop seems to keep reading in a continuous loop and if you don't filter by using the correct structure it will fall into a infinite recursion. This works for me. If you have better input by all means please do addon.

The correct steps to take.

  1. Create WM_NOTIFY Message filter event.
  2. Create Switch statement using NMHDR to parse the code for NM_CLICK.
  3. Check wParam to identify correct control being clicked.
  4. Execute link with ShellExecute.

The following code was used.

       case WM_NOTIFY:
           //NMHDR* pHeader = (NMHDR*)lParam;
           //NMLINK* pNMLink = (NMLINK*)lParam;
           //LITEM iItem = pNMLink->item;
           switch(((NMHDR *)lParam)->code)
           {
               case NM_CLICK:
               { // Included to avoid "case" skip statements.
                   times++;

                   NMLINK* pNMLink = (NMLINK*)lParam;
                   LITEM iItem = pNMLink->item;
                   // Custom OutputDebugString
                   winapi::Output("NM_CLICK: Fired %d time%s!\n", times, (times <= 1) ? L"" : L"s");
#ifdef DEBUG
                   assert(iItem.szID);
                   MessageBox(NULL, (LPCWSTR)lParam, L"Assert", MB_OK|MB_ICONINFORMATION);
#endif
                   if(wParam == IDC_LINK1)
                   {
                       winapi::Output("Success!");
                       OpenLink(hWndDlg, LOWORD(wParam));
                   }

Note:Use NMLINK structure if you rely on your link containing HTML attributes (link or here) to feed Shellexecute its url path.

Nightforce2
  • 1,426
  • 5
  • 18
  • 31
1

Either

  1. your OpenLink function results in another message being sent (most likely), or

  2. This isn't the only code where OpenLink is called, or

  3. you reach this code not from WM_NOTIFY, but from fall-through of the case above, or

  4. you call DefWindowProc for the message, even though you already handled it.

Try

  break; // make sure there's no fall-through here
  case NM_CLICK:
     switch(LOWORD(wParam))
     {
         case IDC_LINK1:
             // Standard ShellExecute with added check for IsLinkCtrl to make sure its the right kind of control.
             OpenLink(hWndDlg, LOWORD(wParam));            
             return TRUE; // handled, don't pass to DefWindowProc
     }
     break;
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Good explaination! The OpenLink is just a filter for the IID and links to a Link table to fetch the correct URL for the control. How would OpenLink being sending another message? Without filtering with a switch to NMHDR structure I received 2 DebugOutput string messages. Filtering this way gives me the correct 1 execution per click. I also don't have DefWindowProc in the message loop ;\ never thought about that one, good point though! – Nightforce2 May 24 '12 at 18:54
  • @Nightforce2: Many Windows API calls actually send messages in order to do their job. Have you tried putting a breakpoint on `OpenLink` and checking the call stack? – Ben Voigt May 24 '12 at 18:58
  • @Nightforce2: Also, `DefWindowProc` shouldn't be in the message loop. Your message loop should use `DispatchMessage` which calls your window procedure (or dialog procedure). Inside the window procedure you should call `DefWindowProc`. In a dialog, [the system-provided window procedure will call `DefWindowProc` unless your dialog procedure returns TRUE.](http://msdn.microsoft.com/en-us/library/windows/desktop/ms645469.aspx) I goofed up the return code before b/c I didn't notice you were using a dialog -- now fixed. – Ben Voigt May 24 '12 at 19:02
  • No, Actually I didn't. DefWindowProc is not in the message loop either. – Nightforce2 May 24 '12 at 22:02
  • I actually used a return call previous to this and it didn't seem to have a effect. I posted my working code below. – Nightforce2 May 24 '12 at 22:08