Craig
Craig

Reputation: 1946

How to correctly respond to focus messages in a custom control?

I need to create my own panel which will be derived from TCustomPanel - Here I will be doing my own custom painting on the panel such as drawing a header at the top.

One of the requirements I need is that I need to be able to draw two different colors depending on whether my panel has focus or not, I also need a way of knowing whether or not a child control inside my panel has focus too - of course though there is no knowing of what the child contents could be.

So for example, if the panel (or any child controls inside the panel) has focus, then the color could be clSkyBlue. If the panel (or none of the child controls inside the panel) does not have focus, then the color could be clWhite.

Here is a quick layout of the custom panel: (to keep it simple for the example there is no header drawing, just basic color changing)

type
  TMyPanel = class(TCustomPanel)
  private
    //
  protected
    // procedure Paint; override;

    procedure WMMouseDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;        
    procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
    procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

constructor TMyPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  Self.ParentBackground := False;
  Self.ParentColor := False;
end;

destructor TMyPanel.Destroy;
begin
  inherited Destroy;
end;

procedure TMyPanel.WMMouseDown(var Message: TWMLButtonDown);
begin
  Self.SetFocus;
end;

procedure TMyPanel.WMKillFocus(var Message: TWMKillFocus);
begin
  Self.Color := clWhite;
  Invalidate;
end;

procedure TMyPanel.WMSetFocus(var Message: TWMSetFocus);
begin
  Self.Color := clSkyBlue;
  Invalidate;
end;

The first problem I ran into is making the panel focusable, I'm sure there will be a proper solution that I have overlooked but in this case I used a dirty trick by intercepting the WM_LBUTTONDOWN message and making the panel focused. This of course also denies the other requirement I mentioned before where child controls also decide what color the panel should be depending on whether or not the panel or it's child controls are focused.

This is what happens when I click on my panel:

enter image description here

This is what happens when clicking on the child control:

enter image description here

As you can see the panel turned white, but I need it to turn to clSkyBlue to indicate that the panel or it's child contents has focus, which should look like:

enter image description here

So, what is the solution to correctly identify whether or not my custom control or it's child controls has the focus or not?

I had thought about iterating through each child control inside the panel to determine if it had focus or not but I am not sure how I would fire such an event in the first place as clicking directly on a child control would surely ignore any messages that the custom panel is waiting to intercept.

Upvotes: 6

Views: 2067

Answers (2)

Ondrej Kelle
Ondrej Kelle

Reputation: 37211

All TWinControl descendants are already focusable and so is your TCustomPanel descendant, too. There's no need to do any additional work in this regard.

What is not done automatically for you (and I think you want to do) is to show the focus state of your component visually. One possible way to do this is by handling CM_ENTER and CM_EXIT messages:

  TMyPanel = class(TCustomPanel)
  private
    procedure FocusChanged(Value: Boolean);
  protected
    procedure CMEnter(var Message: TCMEnter); message CM_ENTER;
    procedure CMExit(var Message: TCMExit); message CM_EXIT;
  end;

procedure TMyPanel.CMEnter(var Message: TCMEnter);
begin
  FocusChanged(True);
end;

procedure TMyPanel.CMExit(var Message: TCMExit);
begin
  FocusChanged(False);
end;

procedure TMyPanel.FocusChanged(Value: Boolean);
const
  Colors: array[Boolean] of TColor = (clWhite, clSkyBlue);
begin
  Color := Colors[Value];
end;

Upvotes: 8

Leo Melo
Leo Melo

Reputation: 196

An easy way is to create your own Edit instead of you TPanel.

Your derived Edit may control the OnEnter(); and OnExit() events and chance its Parent colour.

As a can see you know how to intercept events so it will be an easier approach.

Upvotes: 0

Related Questions