views:

1031

answers:

6

I'm writing a license agreement dialog box with Win32 and I'm stumped. As usual with these things I want the "accept/don't accept" buttons to become enabled when the slider of the scroll bar of the richedit control hits bottom, but I can't find a way to get notified of that event. The earliest I've been able to learn about it is when the user releases the left mouse button.

Is there a way to do this?

Here's what I tried so far:

  • WM_VSCROLL and WM_LBUTTONUP in richedit's wndproc
  • EN_MSGFILTER notification in dlgproc (yes the filter mask is getting set)
  • WM_VSCROLL and WM_LBUTTONUP in dlgproc.
  • EN_VSCROLL notification in dlgproc

I got so desperate I tried polling but that didn't work either because apparently timer messages stop arriving while the mouse button is down on the slider. I tried both:

  • timer callback (to poll) in dlgproc
  • timer callback (to poll) in richedit's wndproc
+1  A: 

You need to sub-class the edit box and intercept the messages to the edit box itself. Here's an artical on MSDN about subclassing controls.

Skizz

EDIT: Some code to demonstrate the scroll bar enabling a button:

#include <windows.h>
#include <richedit.h>

LRESULT __stdcall RichEditSubclass
(
  HWND window,
  UINT message,
  WPARAM w_param,
  LPARAM l_param
)
{
  HWND
    parent = reinterpret_cast <HWND> (GetWindowLong (window, GWL_HWNDPARENT));

  WNDPROC
    proc = reinterpret_cast <WNDPROC> (GetWindowLong (parent, GWL_USERDATA));

  switch (message)
  {
  case WM_VSCROLL:
    {
      SCROLLINFO
        scroll_info = 
        {
          sizeof scroll_info,
          SIF_ALL
        };

      GetScrollInfo (window, SB_VERT, &scroll_info);

      if (scroll_info.nPos + static_cast <int> (scroll_info.nPage) >= scroll_info.nMax ||
          scroll_info.nTrackPos + static_cast <int> (scroll_info.nPage) >= scroll_info.nMax)
      {
        HWND
          button = reinterpret_cast <HWND> (GetWindowLong (parent, 0));

        EnableWindow (button, TRUE);
      }
    }
    break;
  }

  return CallWindowProc (proc, window, message, w_param, l_param);
}

LRESULT __stdcall ApplicationWindowProc
(
  HWND window,
  UINT message,
  WPARAM w_param,
  LPARAM l_param
)
{
  bool
    use_default_proc = false;

  LRESULT
    result = 0;

  switch (message)
  {
  case WM_CREATE:
    {
      CREATESTRUCT
        *creation_data = reinterpret_cast <CREATESTRUCT *> (l_param);

      RECT
        client;

      GetClientRect (window, &client);

      HWND
        child = CreateWindow (RICHEDIT_CLASS,
                              TEXT ("The\nQuick\nBrown\nFox\nJumped\nOver\nThe\nLazy\nDog\nThe\nQuick\nBrown\nFox\nJumped\nOver\nThe\nLazy\nDog"),
                              WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | ES_DISABLENOSCROLL,
                              0, 0, client.right, client.bottom - 30,
                              window,
                              0,
                              creation_data->hInstance,
                              0);

      SetWindowLong (window, GWL_USERDATA, GetWindowLong (child, GWL_WNDPROC));
      SetWindowLong (child, GWL_WNDPROC, reinterpret_cast <LONG> (RichEditSubclass));
      SetWindowLong (child, GWL_ID, 0);

      child = CreateWindow (TEXT ("BUTTON"), TEXT ("Go Ahead!"), WS_CHILD | WS_VISIBLE | WS_DISABLED, 0, client.bottom - 30, client.right, 30, window, 0, creation_data->hInstance, 0);

      SetWindowLong (window, 0, reinterpret_cast <LONG> (child));
      SetWindowLong (child, GWL_ID, 1);
    }
    break;

  case WM_COMMAND:
    if (HIWORD (w_param) == BN_CLICKED && LOWORD (w_param) == 1)
    {
      DestroyWindow (window);
    }
    break;

  default:
    use_default_proc = true;
    break;
  }

  return use_default_proc ? DefWindowProc (window, message, w_param, l_param) : result;
}

int __stdcall WinMain
(
  HINSTANCE instance,
  HINSTANCE unused,
  LPSTR command_line,
  int show
)
{
  LoadLibrary (TEXT ("riched20.dll"));

  WNDCLASS
    window_class = 
    {
      0,
      ApplicationWindowProc,
      0,
      4,
      instance,
      0,
      LoadCursor (0, IDC_ARROW),
      reinterpret_cast <HBRUSH> (COLOR_BACKGROUND + 1),
      0,
      TEXT ("ApplicationWindowClass")
    };

  RegisterClass (&window_class);

  HWND
    window = CreateWindow (TEXT ("ApplicationWindowClass"),
                           TEXT ("Application"),
                           WS_VISIBLE | WS_OVERLAPPED | WS_SYSMENU,
                           CW_USEDEFAULT,
                           CW_USEDEFAULT,
                           400, 300, 0, 0,
                           instance,
                           0);

  MSG
    message;

  int
    success;

  while (success = GetMessage (&message, window, 0, 0))
  { 
    if (success == -1)
    {
      break;
    }
    else
    {
      TranslateMessage (&message);
      DispatchMessage (&message);
    }
  }

  return 0;
}

The above doesn't handle the user moving the cursor in the edit box.

Skizz
A: 

Even though it is possible, I don't think you should do it that way - the user will have no clue why the buttons are disabled. This can be very confusing, and confusing the user should be avoided at all costs ;-)

That's why most license dialogs have radio buttons for accept/decline with decline enabled by default, so you actively have to enable accept.

Treb
Every license agreement i seen up to know have this feature.
Ilya
I doubt that. Anyway, my answer stays: Don't do it.
Treb
I wouldn't say every license agreement has it, in fact most I've seen don't. But I have seen some that do, so it certainly is possible.
Graeme Perrow
Yup, I've seen the answer above. It can be done, will edit my answer accordingly.
Treb
A: 

Skizz, I did subclass the richedit control and I did intercept messages that Windows sends to that control. I gave examples of that in my original post. For example, when I said that I had looked for WM_VSCROLL in the richedit's wndproc, it meant that I had substituted my own wndproc for the richedit's default wndproc and was intercepting its WM_VSCROLL message.

Treb, I am trying to do this the conventional way. I'll l look at some more license agreement screens but I think most of them only enable "Accept" when you scroll to the bottom. (I may have been wrong about both buttons starting off disabled, but even if it's only the "accept" button that needs to become enabled, the technical issue is the same.)

The code I posted above works as required using VS2k5. The only real gotcha was that scroll bar position != scroll bar max when the thumb is at the bottom of the scroll bar. The correct test is scroll bar position + page size >= scroll bar max.
Skizz
I don't think you understand the problem.
The problem is that as long as the user's finger is holding down the mouse button and the mouse cursor is on the slider, Windows doesn't send WM_VSCROLL to the controls's wndproc.
A: 

Skizz, in reply to your code example, my original post said that I had already tried that. The first example I gave of things I had tried was this:

"WM_VSCROLL and WM_LBUTTONUP in richedit's wndproc"

That's what you're doing in your example -- responding to WM_VSCROLL in the control's wndproc. Once again -- I'm repeating myself -- I tried that already and it doesn't work for this purpose, because the operating system does not send WM_VSCROLL to the control's wndproc while the user is holding the mouse button down with the mouse cursor on the thumb. That's the problem.

What's the point of telling me to try things that I already said I tried and found not to work?

It really does work! The code also tracks the ScrollInfo.nTrackPos which is updated even if the user has not let go of the mouse button!
Skizz
A: 

I would recommend starting up Spy++ and seeing which windows messages are getting sent to where.

http://msdn.microsoft.com/en-us/library/aa264396(VS.60).aspx

jussij
A: 

Why not use the EM_GETTHUMB message. (Assuming Rich Edit 2.0 or later).

If you are lucky this bottom position will match EM_GETLINECOUNT.

David L Morris