I have a third party tree package (ElXTree by LMD Innovative) that I am using as a grid in my program. Whenever I select a cell, that row gains focus and becomes highlighted, just as I want it.
When I invoke the supplied Inplace editor by clicking on a cell in the grid, that row gains focus. Because the cell is selected in edit mode, only the cell gets highlighted (not the whole row), also just as I want it.
What I don't want is this: When I'm inplace editing one cell, and I invoke the inplace editor for another cell by clicking on it, first the row with the old cell is given focus and is highlighted. It then immediately has its focus taken away and is unhighlighted and the row with the new cell is given focus and highlighted. Then that new row immediately becomes unhighlighted except for the cell being inplace edited. This causes an annoying double flashing and I want to get rid of it.
I have the source code of the package, and I've been debugging through it. I'm sure if I can just find what is invoking the double focusing, I'll be able to figure out how to make simple modifications to prevent it.
When I place breakpoints I find that I am in the message handling loop of TApplication.Run in the Forms unit. Two of the many messages this loop is handling are the ones to set the Focus. I can trace the program line by line right through to StdWndProc in the Classes unit, where the message is Dispatched. I have all the information about the message (the Handle, Parameters, etc).
What I don't have and don't know is where the message is initiated from. There are no ElXTree units in the call stack to clue me in. One of those routines must have sent the message independent of the current call stack.
If I could just find out where that message was sent from (i.e. what routine sent it), then I'll be off and running.
Is there any way to find where the message was sent from? Or alternatively, is there any other way I might be able to get around this double focusing problem I'm having?
For reference, I'm using Delphi 2009.
Further information:
ElXTree has several dozen of its own Windows messages that it works with. In my case, the two relevant ones are:
procedure WMSetFocus(var Msg: TWMSetFocus); message WM_SETFOCUS;
procedure WMKillFocus(var Msg: TWMKillFocus); message WM_KILLFOCUS;
procedure TElXTreeView.WMSetFocus(var Msg: TWMSetFocus); { private }
begin
inherited;
FHasFocus := True;
if (FOwner.HideSelection or (FOwner.HideSelectColor <> FOwner.FocusedSelectColor) or (FOwner.HideSelectTextColor <> FOwner.FocusedSelectTextColor)) and
(FOwner.Items.Count > 0) then
Invalidate;
with FOwner do
if Flat or FUseCustomScrollBars or IsThemed then
UpdateFrame;
end; { WMSetFocus }
procedure TElXTreeView.WMKillFocus(var Msg: TWMKillFocus); { private }
begin
FMouseSel := False;
FPressed := False;
FHasFocus := False;
inherited;
FHintItemEx := nil;
DoHideLineHint;
if HandleAllocated then
begin
with FOwner do
if Flat or FUseCustomScrollBars or IsThemed then
begin
UpdateFrame;
DrawFlatBorder(False, False);
if FUseCustomScrollBars then
begin
HScrollBar.HideHint;
VScrollBar.HideHint;
end;
end;
if (FOwner.HideSelection or (FOwner.HideSelectColor <> FOwner.FocusedSelectColor) or (FOwner.HideSelectTextColor <> FOwner.FocusedSelectTextColor)) and
(FOwner.Items.Count > 0) then
Invalidate;
end;
end; { WMKillFocus }
When I put a breakpoint in, say, the WMSetFocus routine, I get the following call stack:
The only other ElXTree routine in the call stack is one on the 4th line:
procedure TElXTreeView.WndProc(var Message: TMessage);
var P1: TPoint;
Item: TElXTreeItem;
HCol: Integer;
IP: TSTXItemPart;
begin
if (FHintItem <> nil) and (FOwner.FHideHintOnMove) then
begin
if ((Message.Msg >= WM_MOUSEMOVE) and (Message.Msg <= WM_MOUSELAST)) or (Message.Msg = WM_NCMOUSEMOVE) then
begin
GetCursorPos(P1);
P1 := ScreenToClient(P1);
Item := GetItemAt(P1.X, P1.Y, IP, HCol);
if Item <> FHintItem then
DoHideLineHint;
inherited;
Exit;
end
else
if
((Message.Msg >= WM_KEYFIRST) and (Message.Msg <= WM_KEYLAST)) or
((Message.Msg = CM_ACTIVATE) or (Message.Msg = CM_DEACTIVATE)) or
(Message.Msg = CM_APPKEYDOWN) or (Message.Msg = CM_APPSYSCOMMAND) or
(Message.Msg = WM_COMMAND) or
((Message.Msg > WM_MOUSEMOVE) and (Message.Msg <= WM_MOUSELAST))
or (Message.Msg = WM_NCMOUSEMOVE) then
DoHideLineHint;
end;
if (FHintItem <> nil) and ((Message.Msg = CM_ACTIVATE) or (Message.Msg = CM_DEACTIVATE))
or (Message.Msg = WM_NCMOUSEMOVE) then
DoHideLineHint;
inherited;
end;
When I put a breakpoint in this routine, it only seems to pass through to the "inherited" line and then call system functions, ultimately getting to the StdWndProc where the messages are handled (as I described in my original question).
The problem involved in tracing this accurately is that I must make mouse clicks and keep the mouse pointer over the visual control in the program while also debugging through the code. Any mistake in moving or using my mouse while debugging can cause additional mouse events that affect the processing This makes it a real bugger to debug.
But I can carefully trace into StdWndProc and see the event that gets dispatched that focuses the line. What I can't seem to do is find out what issues the message.
Now, why don't I know what issues the message? Well, I assume it is from a PostMessage or SendMessage command as David says. When I look for where all these calls are made in ElXTree, I only find these 10:
Result := SendMessage(hWnd, SBM_SetScrollInfo, Integer(Redraw), Integer(@ScrollInfo));
SendMessage(hWnd, SBM_GetScrollInfo, 0, Integer(@ScrollInfo));
SendMessage(FHScrollBar.Handle, Message.Msg, Message.wParam, Message.lParam);
SendMessage(FVScrollBar.Handle, Message.Msg, Message.wParam, Message.lParam);
case Key of
VK_LEFT: begin
PostMessage(FOwner.Handle, WM_HSCROLL, SB_LINELEFT, 0);
Exit;
end;
VK_RIGHT: begin
PostMessage(FOwner.Handle, WM_HSCROLL, SB_LINERIGHT, 0);
Exit;
end;
end;
FScrollbarsInitialized := True;
if UseCustomScrollbars then
PostMessage(Handle, WM_UPDATESBFRAME, 0, 0);
end;
procedure TCustomElXTree.WMSysColorChange(var Msg: TWMSysColorChange);
begin
inherited;
PostMessage(FVScrollBar.Handle, Msg.Msg, TMessage(Msg).WParam, TMessage(Msg).LParam);
PostMessage(FHScrollBar.Handle, Msg.Msg, TMessage(Msg).WParam, TMessage(Msg).LParam);
PostMessage(FHeader.Handle, Msg.Msg, TMessage(Msg).WParam, TMessage(Msg).LParam);
end; { WMSysColorChange }
The first 7 deal with scrollbars. The next 3 are with ColorChange.
I've looked through all the other LMD component routines as well for the issuing of messages, and nothing there looks promising.
So I'm still stuck and need a hint or a clue as to how to find the sender of that message that is asking for the line to be focused.
Workaround:
Well, once I realized that Windows was initiating the Mouse Events, I was able to do something that stops most of the flashing. It's a real hack though. If someone knows of something better, I'd love to hear about it.
In the TElXTreeView.WndProc, I've replaced the inherited statement with the following:
if (Message.Msg = WM_SETFOCUS) or (Message.Msg = WM_KILLFOCUS) then begin
FOwner.Items.BeginUpdate;
inherited;
FOwner.Items.EndUpdate;
end
else
inherited;
What this does is stop the multiple focusing from happening within the called routines.
It does the job except in one case: Where I'm clicking on an editable entry, it still does highlight the entry first before going into edit mode. That's because the highlight occurs on the MouseDown but the going into edit mode occurs on the MouseUp. I might be able to find a way around this, but initial attempts were unsuccessful. But it's not as bad as the double flashing, and I could live with it if I have to.
Thanks to those of you who helped give my brain a push. The accepted answer goes to David who gave me the key clue.
... Maybe I spoke too soon. I found some other controls, e.g. the pages with the grid on it, would not update when paging between the controls. I tried adding a Refresh command after the EndUpdate. Once I did that, I got the double flashing again. This is a real messy problem.
I may be able to get a workaround for the paging, but I hope the developer of that control responds to me with a better fix.
Things like this are NOT one of the joys of programming. :-(
These messages are posted to the message queue rather than sent synchronously. This is clear because you are tracing them back to TApplication.Run
which is the routine that pumps your main thread's message queue. That's why you don't see the call site on the stack. They are generated by calls to PostMessage, either in the 3rd party component or possibly more likely by Windows.
I don't know these components so I doubt I can help solve your problem. I think you should contact the component vendor who should know what to do.