2

I am implementing an AutoComplete, but one that will match text anywhere in the list of enumerated strings—rather than just the beginning of a suggestion as is the behavior of the built-in SHAutoComplete control

For example:

enter image description here

The "popup" window is a TForm that must:

  • set CS_DROPSHADOW style during CreateParams (subject to checking SPI_GETDROPSHADOW of course; I'm not a heathen who doesn't pay his taxes)

    Params.WindowClass.Style := Params.WindowClass.Style or CS_DROPSHADOW;
    
  • and of course set its WndParent to the form that holds the TEdit (so that the drop-down appears on top of the form it's popping up over)

    Params.WndParent := FWndParent;
    

I have the Up, Down, Return, Escape keyboard navigation working fine. The typist can quickly select the item they want, and the list of suggestions closes.

The form contains a TListView for the actual items (much like CAutoComplete SHAutoComplete class does in Windows).

Clicking takes focus

If the user clicks an item in the drop-down, then using Inspect (the accessiblity tool in the SDK) we can see that focus was given to one of the listitems in the (now hidden) drop-down menu:

enter image description here

You can even see the title-bar indicates that the entire window on longer has focus.

How can I prevent my window from obtaining input focus?

Attempts

Return MA_NOACTIVATE

I tried responding to the WM_MOUSEACTIVATE window message and return MA_NOACTIVATE:

Return code/value Description
MA_ACTIVATE (1) Activates the window, and does not discard the mouse message.
MA_ACTIVATEANDEAT (2) Activates the window, and discards the mouse message.
MA_NOACTIVATE (3) Does not activate the window, and does not discard the mouse message.
MA_NOACTIVATEANDEAT (4) Does not activate the window, but discards the mouse message.

Such as:

procedure TfrmAutoComplete.WMMouseActivate(var Msg: TMessage);
begin
    inherited;
    Msg.Result := MA_NOACTIVATE;
end;

But it still gets focus.

SetFocus during WM_ACTIVATE

I tried messing with things that ought not be messed with, and calling SetFocus during WM_ACTIVATE:

procedure TfrmAutoComplete.WMActivate(var Msg: TMessage);
begin
    inherited;
    Msg.Result := MA_NOACTIVATE;

    Windows.SetFocus(FEdit.Handle);
end;

Raymond Chens talks about why this is a bad idea; although i must confess i don't understand:

The dangers of messing with activation when handling a WM_ACTIVATE message

Raymond Chen, August 9th, 2005

This is basically the same thing as The dangers of playing focus games when handling a WM_KILLFOCUS message, just with activation in place of focus. One developer discovered the hard way that if you mess with activation inside your WM_ACTIVATE handler, things get weird. The author noted that if he posted a message and did the work from the posted message, then everything came out okay.

A follow-up to the original message noted that passing the SWP_NOACTIVATE flag to the SetWindowPos function solves the problem. Do you understand why?

And, of course, when the TEdit loses focus, it hides the suggestion drop-down. So there's probably a focus lock-out race going on.

Short Version

Is there a better way?

Bonus Reading (aka Research Effort)

20
  • Have you checked How to simulate drop-down form in Delphi?. From what I see that question pretty much covers your goal. Commented Jul 2 at 6:05
  • In win32, just set the extended style of the window and refer to WS_EX_NOACTIVATE. SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_NOACTIVATE); Commented Jul 2 at 6:24
  • Why reinvent the wheel instead of just providing your own autocomplete source (see How to Enable Autocomplete Manually)? Commented Jul 2 at 6:36
  • @IInspectable I did provide my own autocomplete source (see the screenshot in the question). You can see in the question why that doesn't solve my problem.
    – Ian Boyd
    Commented Jul 2 at 18:37
  • @SilverWarior Yes, since it's my question, i know it well. Unfortunately it doesn't cover my goal. Getting focus to work correctly in Windows is voodoo nightmare of untold proportions. I've been dealing with one unfixable focus bug since c. 2003
    – Ian Boyd
    Commented Jul 2 at 18:38

1 Answer 1

0
procedure TfrmAutoComplete.WMActivate(var Msg: TWMActivate);
begin
  inherited;

  if Msg.Active = 0 then
  begin
    if Visible then
    begin
      Hide;
    end;
  end
  else
  begin
    SendMessage(FWndParent, WM_NCACTIVATE, 1, 0);
  end;
end;

Not the answer you're looking for? Browse other questions tagged or ask your own question.