How to make my TCustomControl descendant component stop flickering? - delphi

I have a graphical TCustomControl descendant component with a TScrollBar on it. The problem is that when I press the arrow key to move the cursor the whole canvas is painted in background color, including the region of the scroll bar, then the scroll bar is repainted and that makes scroll bar flicker. How can I solve this ?
Here is the code. There is no need install the component or to put something on the main form, just copy the code and assign TForm1.FormCreate event:
Unit1.pas
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, SuperList;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
List: TSuperList;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
List:=TSuperList.Create(self);
List.Top:=50; List.Left:=50;
List.Visible:=true;
List.Parent:=Form1;
end;
end.
SuperList.pas
unit SuperList;
interface
uses Windows, Controls, Graphics, Classes, Messages, SysUtils, StdCtrls, Forms;
type
TSuperList = class(TCustomControl)
public
DX,DY: integer;
ScrollBar: TScrollBar;
procedure Paint; override;
constructor Create(AOwner: TComponent); override;
procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
published
property OnMouseMove;
property OnKeyPress;
property OnKeyDown;
property Color default clWindow;
property TabStop default true;
property Align;
property DoubleBuffered default true;
property BevelEdges;
property BevelInner;
property BevelKind default bkFlat;
property BevelOuter;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Marus', [TSuperList]);
end;
procedure TSuperList.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
inherited;
Message.Result:= Message.Result or DLGC_WANTARROWS;
end;
procedure TSuperList.WMKeyDown(var Message: TWMKeyDown);
begin
if Message.CharCode=VK_LEFT then begin dec(DX,3); Invalidate; exit; end;
if Message.CharCode=VK_RIGHT then begin inc(DX,3); Invalidate; exit; end;
if Message.CharCode=VK_UP then begin dec(DY,3); Invalidate; exit; end;
if Message.CharCode=VK_DOWN then begin inc(DY,3); Invalidate; exit; end;
inherited;
end;
procedure TSuperList.WMLButtonDown(var Message: TWMLButtonDown);
begin
DX:=Message.XPos;
DY:=Message.YPos;
SetFocus;
Invalidate;
inherited;
end;
constructor TSuperList.Create(AOwner: TComponent);
begin
inherited;
DoubleBuffered:=true;
TabStop:=true;
Color:=clNone; Color:=clWindow;
BevelKind:=bkFlat;
Width:=200;
Height:=100;
DX:=5; DY:=50;
ScrollBar:=TScrollBar.Create(self);
ScrollBar.Kind:=sbVertical;
ScrollBar.TabStop:=false;
ScrollBar.Align:=alRight;
ScrollBar.Visible:=true;
ScrollBar.Parent:=self;
end;
procedure TSuperList.Paint;
begin
Canvas.Brush.Color:=Color;
Canvas.FillRect(Canvas.ClipRect);
Canvas.TextOut(10,10,'Press arrow keys !');
Canvas.Brush.Color:=clRed;
Canvas.Pen.Color:=clBlue;
Canvas.Rectangle(DX,DY,DX+30,DY+20);
end;
end.

I think the first thing that I would do is remove that scroll bar control. Windows come with ready made scroll bars. You just need to enable them.
So, start by removing ScrollBar from the component. Then add a CreateParams override:
procedure CreateParams(var Params: TCreateParams); override;
Implement it like this:
procedure TSuperList.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.Style := Params.Style or WS_VSCROLL;
end;
Yippee, your control now has a scroll bar.
Next you need to add a handler for WM_VSCROLL:
procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
And that's implemented like this:
procedure TSuperList.WMVScroll(var Message: TWMVScroll);
begin
case Message.ScrollCode of
SB_LINEUP:
begin
dec(DY, 3);
Invalidate;
end;
SB_LINEDOWN:
begin
inc(DY, 3);
Invalidate;
end;
...
end;
end;
You'll need to fill out the rest of the scroll codes.
I would also suggest that you do not set DoubleBuffered in the constructor of your component. Let the user set that if they wish. There's no reason for your control to require double buffering.

Related

How to reflect the changes after I resize the non-client area?

I want to make a custom control with a selectable border size. See the code below. The border is drawn in the non-client area and his width can be 0, 1 or 2 pixels. I've successfully done the border drawings in the WM_NCPAINT. The problem is that after I change the property that control the border size I don't know how to tell the system to recalculate the new dimensions of client and non-client areas. I've noticed that when I resize the window (with the mouse) the changes are applied, but I donn't know how to do that immediately after I change the border size.
SuperList.pas
unit SuperList;
interface
uses Windows, Controls, Graphics, Classes, Messages, SysUtils, StdCtrls, UxTheme;
type
TBorderType = (btNone, btSingle, btDouble);
TSuperList = class(TCustomControl)
private
HHig,HMidH,HMidL,HLow:TColor;
BCanvas: TCanvas;
FBorderSize: TBorderType;
procedure SetBorderSize(const Value:TBorderType);
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
procedure WMSize(var Message: TWMSize); message WM_SIZE;
procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
procedure WMNCPaint(var Message: TWMNCPaint); message WM_NCPAINT;
protected
procedure Paint; override;
procedure CreateParams(var Params: TCreateParams); override;
public
constructor Create(AOwner:TComponent); override;
published
property BorderType:TBorderType read FBorderSize write SetBorderSize default btDouble;
end;
implementation
constructor TSuperList.Create(AOwner:TComponent);
begin
inherited;
BCanvas:=TCanvas.Create;
FBorderSize:=btDouble;
HHig:=clWhite; HMidH:=clBtnFace; HMidL:=clGray; HLow:=cl3DDkShadow;
end;
procedure TSuperList.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.Style := Params.Style or WS_VSCROLL or WS_HSCROLL;
end;
procedure TSuperList.SetBorderSize(const Value:TBorderType);
begin
if Value<>FBorderSize then begin
FBorderSize:=Value;
// .... ?????? I think here must be done something...
Perform(WM_NCPAINT,1,0); // repainting the non-client area (I do not know how can I invalidate the non-client area differently)
Invalidate; // repainting the client area
// I've tried even with the... RedrawWindow(Handle,nil,0,RDW_FRAME or RDW_INVALIDATE or RDW_UPDATENOW or RDW_INTERNALPAINT);
end;
end;
procedure TSuperList.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result:=1;
end;
procedure TSuperList.WMSize(var Message: TWMSize);
begin
inherited;
Perform(WM_NCPAINT,1,0);
end;
procedure TSuperList.WMNCCalcSize(var Message: TWMNCCalcSize);
begin
inherited;
if FBorderSize>btNone then
InflateRect(Message.CalcSize_Params^.rgrc0,-Integer(FBorderSize),-Integer(FBorderSize));
end;
procedure TSuperList.Paint;
begin
Canvas.Brush.Color:=clWhite;
Canvas.FillRect(ClientRect);
end;
procedure TSuperList.WMNCPaint(var Message: TWMNCPaint);
var DC: HDC;
R: TRect;
HS_Size,VS_Size:Integer;
HS_Vis,VS_Vis:Boolean;
begin
inherited;
Message.Result:=0;
if FBorderSize>btNone then
begin
DC:=GetWindowDC(Handle); if DC=0 then Exit;
BCanvas.Handle:=DC;
BCanvas.Pen.Color:=clNone;
BCanvas.Brush.Color:=clNone;
try
VS_Size:=GetSystemMetrics(SM_CXVSCROLL);
HS_Size:=GetSystemMetrics(SM_CYHSCROLL);
VS_Vis:=GetWindowLong(Handle,GWL_STYLE) and WS_VSCROLL <> 0;
HS_Vis:=GetWindowLong(Handle,GWL_STYLE) and WS_HSCROLL <> 0;
R:=ClientRect;
OffsetRect(R,Integer(FBorderSize),Integer(FBorderSize));
if VS_Vis and HS_Vis then begin
ExcludeClipRect(DC, R.Left, R.Top, R.Right, R.Bottom+HS_Size);
ExcludeClipRect(DC, R.Left, R.Top, R.Right+VS_Size, R.Bottom);
BCanvas.Brush.Color:=HMidH;
R.Right:=Width-Integer(FBorderSize); R.Left:=R.Right-VS_Size;
R.Bottom:=Height-Integer(FBorderSize); R.Top:=R.Bottom-HS_Size;
BCanvas.FillRect(R);
end else begin
if VS_Vis then Inc(R.Right,VS_Size);
if HS_Vis then Inc(R.Bottom,HS_Size);
ExcludeClipRect(DC, R.Left, R.Top, R.Right, R.Bottom);
end;
BCanvas.MoveTo(0,Height-1);
BCanvas.Pen.Color:=HMidL; BCanvas.LineTo(0,0); BCanvas.LineTo(Width-1,0);
if IsThemeActive then begin
BCanvas.Pen.Color:=HMidL;
BCanvas.LineTo(Width-1,Height-1);
BCanvas.LineTo(-1,Height-1);
end else begin
if FBorderSize=btDouble then begin
BCanvas.Pen.Color:=HHig;
BCanvas.LineTo(Width-1,Height-1);
BCanvas.LineTo(-1,Height-1);
end else begin
if VS_Vis then BCanvas.Pen.Color:=HHig else BCanvas.Pen.Color:=HMidL;
BCanvas.LineTo(Width-1,Height-1);
if HS_Vis then BCanvas.Pen.Color:=HHig else BCanvas.Pen.Color:=HMidL;
BCanvas.LineTo(-1,Height-1);
end;
end;
if FBorderSize=btDouble then begin
BCanvas.MoveTo(1,Height-2);
BCanvas.Pen.Color:=HLow; BCanvas.LineTo(1,1); BCanvas.LineTo(Width-2,1);
BCanvas.Pen.Color:=HMidH; BCanvas.LineTo(Width-2,Height-2); BCanvas.LineTo(0,Height-2);
end;
finally
ReleaseDC(Handle,DC);
end;
end;
end;
end.
Unit1.pas
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, SuperList, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
public
List: TSuperList;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
List:=TSuperList.Create(self);
List.Parent:=Form1;
List.Margins.Left:=20; List.Margins.Right:=20;
List.Margins.Top:=50; List.Margins.Bottom:=20;
List.AlignWithMargins:=true;
List.Align:=alClient;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
List.BorderType:=btNone;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
List.BorderType:=btSingle;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
List.BorderType:=btDouble;
end;
end.
Send a CM_BORDERCHANGED message:
Perform(CM_BORDERCHANGED, 0, 0);
This will fire the handler in TWinControl:
procedure TWinControl.CMBorderChanged(var Message: TMessage);
begin
inherited;
if HandleAllocated then
begin
SetWindowPos(Handle, 0, 0,0,0,0, SWP_NOACTIVATE or
SWP_NOZORDER or SWP_NOMOVE or SWP_NOSIZE or SWP_FRAMECHANGED);
if Visible then
Invalidate;
end;
end;
And from the documentation on SetWindowPos:
SWP_FRAMECHANGED: Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message to the window, even if the window's size is not being changed. If this flag is not specified, WM_NCCALCSIZE is sent only when the window's size is being changed.

How to create Delphi component inherited from few other components?

Tutorials that I found about how to create delphi components were nice, but they only used one of existing components as object to inherit actions from. Something like this
unit CountBtn;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;
type
TCountBtn = class(TButton)
private
FCount: integer;
protected
procedure Click;override;
public
procedure ShowCount;
published
property Count:integer read FCount write FCount;
constructor Create(aowner:Tcomponent); override;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Mihan Components', [TCountBtn]);
end;
constructor TCountBtn.Create(aowner:Tcomponent);
begin
inherited create(Aowner);
end;
procedure Tcountbtn.Click;
begin
inherited click;
FCount:=FCount+1;
end;
procedure TCountBtn.ShowCount;
begin
Showmessage('On button '+ caption+' you clicked: '+inttostr(FCount)+' times');
end;
end.
But what should I do if I need component which use few elements? Lets say, I got Button and Edit field. And on button click there in edit field should appers text the same as on button. I start to make it like this, but seems like it's not gonna work as I want:
unit TestComp;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TUiCompU = class(TCustomControl)
private
{ Private declarations }
FButton: TButton;
FEdit: TEdit;
protected
{ Protected declarations }
procedure Paint; override;
//wrong!
procedure FButton.Click;override
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
published
{ Published declarations }
//wrong!
property ButtonText: String read FButton.Caption write FButton.Caption;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Ui', [TUiCompU]);
end;
{ TUiCompU }
constructor TUiCompU.Create(AOwner: TComponent);
begin
inherited;
Width := 200;
Height := 50;
FButton := TButton.Create(Self);
FButton.SetSubComponent(True);
FButton.Parent := Self;
FButton.Top := 8;
FButton.Left := 50;
FButton.Width := 35;
FButton.Name := 'Button';
FEdit := TEdit.Create(Self);
FEdit.SetSubComponent(True);
FEdit.Parent := Self;
FEdit.Top := 8;
FEdit.Left := 84;
FEdit.Width := 121;
FEdit.Name := 'Edit';
end;
procedure TUiCompU.Paint;
begin
Canvas.Rectangle(ClientRect);
end;
end.
How should I add here Click procedure, which is realte to click on the button? And is there are good tutorial about how to made good components using others? (I need to create something like slideshow component btw).
Thank you, and sorry for my english.
You can write methods for the subcomponent events, but it has one big weakness; if you publish those subcomponents, there is a risk that someone will steal you this binding by writing own method:
type
TUiCompU = class(TCustomControl)
private
FEdit: TEdit;
FButton: TButton;
procedure ButtonClick(Sender: TObject);
procedure EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
public
constructor Create(AOwner: TComponent); override;
end;
implementation
constructor TUiCompU.Create(AOwner: TComponent);
begin
inherited;
FButton := TButton.Create(Self);
...
FButton.OnClick := ButtonClick;
FEdit := TEdit.Create(Self);
...
FEdit.OnKeyDown := EditKeyDown;
end;
procedure TUiCompU.ButtonClick(Sender: TObject);
begin
// do whatever you want here
end;
procedure TUiCompU.EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
// do whatever you want here
end;

How to set required Edit fields with a red border?

I want to require a little fields. And when they will not be filled in that they will be red and needed to fill in.
before the post can be done
Here is a screenshot of what do I want to achieve:
I would add a TShape, which can draw a red line around your edit box. If you want the red border to replace the normal TEdit border you can modify the properties of your Edit control so it has no Border.
If you want the shape to be unfilled, change brush style to bsClear
You might hook the WM_Paint message and draw a rectangle on the ControlCanvas if required. One way to do this could look like this:
unit Edit_WithFrame_If_Needed_But_Empty;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TEdit = Class(StdCtrls.TEdit)
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED;
procedure WMKEYUP(var Message: TWMPaint); message WM_KEYUP;
private
FPaintedRed: Boolean;
FRequired: Boolean;
procedure CheckForInvalidate;
published
public
Property Required: Boolean read FRequired Write FRequired;
End;
TForm2 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
procedure FormCreate(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
{ TEdit }
procedure TEdit.CheckForInvalidate;
begin
if Required and (Length(Trim(Text)) = 0) then
begin
if not FPaintedRed then
Invalidate;
end
else if FPaintedRed then
Invalidate;
end;
procedure TEdit.CMTextChanged(var Message: TMessage);
begin
inherited;
CheckForInvalidate;
end;
procedure TEdit.WMKEYUP(var Message: TWMPaint);
begin
CheckForInvalidate;
end;
procedure TEdit.WMPaint(var Message: TWMPaint);
var
CC: TControlCanvas;
begin
inherited;
if Required and (Length(Trim(Text)) = 0) then
begin
FPaintedRed := true;
CC := TControlCanvas.Create;
try
CC.Control := Self;
CC.Pen.Color := clRed;
CC.Pen.Width := 3;
CC.Rectangle(ClientRect);
finally
CC.Free;
end;
end
else
FPaintedRed := false;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
Edit1.Required := true;
Edit3.Required := true;
end;
end.

Transparent TMemo - text appears to remain selected when it isn't

I was hoping for some help with regards to a transparent TMemo control in Delphi 7. I found some code online that works well, to an extent, the refresh rate is a bit rubbish but I can live with that. The main problem is that unselected text can look as if it's actually selected.
Here's where all the text is selected using SelectAll();
Here's where no text is actually selected, but has been previously, note the floating line suggesting typing will happen after the 'p' in Improvement.
And finally an image just showing the difference.
What I find quite odd is that if I hit an arrow key for example, the false highlighting disappears, but when using the mouse it does not.
The code for this custom TMemo is as follows:
unit TrMemo;
interface
uses
Messages, Controls, StdCtrls, classes;
const TMWM__SpecialInvalidate=WM_USER+1111;
type
TTransparentMemo = class(TMemo)
private
procedure SpecialInvalidate(var Message:TMessage); message TMWM__SpecialInvalidate;
procedure WMHScroll(var Message: TWMHScroll); message WM_HSCROLL;
procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
procedure WMSetText(var Message:TWMSetText); message WM_SETTEXT;
procedure CNCTLCOLOREDIT(var Message:TWMCTLCOLOREDIT); message CN_CTLCOLOREDIT;
procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
protected
procedure CreateParams(var Params: TCreateParams); override;
public
constructor Create(AOwner: TComponent); override;
end;
procedure Register;
implementation
uses Windows;
{ TTransparentMemo }
procedure TTransparentMemo.WMHScroll(var Message: TWMHScroll);
begin
inherited;
PostMessage(Handle,TMWM__SpecialInvalidate,0,0);
end;
procedure TTransparentMemo.WMVScroll(var Message: TWMVScroll);
begin
SendMessage(Handle,TMWM__SpecialInvalidate,0,0);
inherited;
PostMessage(Handle,TMWM__SpecialInvalidate,0,0);
end;
procedure TTransparentMemo.CNCTLCOLOREDIT(var Message:TWMCTLCOLOREDIT);
begin
with Message do
begin
SetBkMode(ChildDC,TRANSPARENT);
Result:=GetStockObject(HOLLOW_BRUSH)
end
end;
procedure TTransparentMemo.WMSetText(var Message:TWMSetText);
begin
inherited;
if not (csDesigning in ComponentState) then PostMessage(Handle,TMWM__SpecialInvalidate,0,0)
end;
procedure TTransparentMemo.SpecialInvalidate(var Message:TMessage);
var
r:TRect;
begin
if (Parent <> nil) then
begin
r:=ClientRect;
r.TopLeft:=Parent.ScreenToClient(ClientToScreen(r.TopLeft));
r.BottomRight:=Parent.ScreenToClient(ClientToScreen(r.BottomRight));
InvalidateRect(Parent.Handle,#r,true);
RedrawWindow(Handle,nil,0,RDW_FRAME+RDW_INVALIDATE);
end;
end;
procedure TTransparentMemo.WMKeyDown(var Message: TWMKeyDown);
begin
SendMessage(Handle,TMWM__SpecialInvalidate,0,0);
inherited;
PostMessage(Handle,TMWM__SpecialInvalidate,0,0);
end;
procedure TTransparentMemo.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result:=1
end;
constructor TTransparentMemo.Create(AOwner: TComponent);
begin
inherited;
ControlStyle:=[csCaptureMouse, csDesignInteractive, csClickEvents, csSetCaption, csOpaque, csDoubleClicks, csReplicatable, csNoStdEvents];
end;
procedure TTransparentMemo.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
with Params do
begin
ExStyle:=ExStyle or WS_EX_TRANSPARENT and not WS_EX_WINDOWEDGE
and not WS_EX_STATICEDGE and not WS_EX_DLGMODALFRAME and not
WS_EX_CLIENTEDGE;
end;
end;
procedure Register;
begin
RegisterComponents('Samples', [tTransparentMemo]);
end;
end.
Any tips/hints/answers would be greatly appreciated! Cheers in advance!
This isn't a complete fix, but you could, for example, do something like
protected
procedure Click; override;
procedure TTransparentMemo.Click;
begin
PostMessage(Handle,TMWM__SpecialInvalidate,0,0);
inherited;
end;
And so on. Perhaps there's a better place to do this. Have a look in your VCL source (StdCtrls.pas) and you might find something in TCustomEdit or TCustomMemo which would be better options to override.

In my custom component, how can I augment the mouse-enter and -leave events?

I am making a custom Panel component which derives TPanel.
I want for my new component to have some code executed on the OnMouseEnter and OnMouseLeave events, however, i do not know how to implement it.
I see that TPanel has published properties OnMouseEnter, OnMouseLeave.
How do i override those and add some of my own code?
The example of my idea:
Default behaviour of TMyPanel which should be in component itself.
on event OnMouseEnter do: Color := NewColor;
on event OnMouseLeave do: Color := OldColor;
And then, i want to be able to assign some function to these events at run time.
This assignment is done in the application.
.. TButton1.Click ..
begin
MyPanel1.OnMouseEnter := DoSomethingMore;
MyPanel1.OnMouseLeave := DoSomethingElse;
end;
so in the end, when mouse is over new panel, it should change color AND do some other actions written in DoSomethingMore procedure.
Thanks
Anoher approach is to handle the windows messages yourself:
type
TMyPanel = class(TPanel)
private
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
published
end;
implementation
{ TMyPanel }
procedure TMyPanel.CMMouseEnter(var Message: TMessage);
begin
// Do whatever your want before the event
if Assigned(OnMouseEnter) then OnMouseEnter(Self);
end;
procedure TMyPanel.CMMouseLeave(var Message: TMessage);
begin
// Do whatever your want before the event
if Assigned(OnMouseLeave) then OnMouseLeave(Self);
end;
EDIT: See below for better VCL compliant version.
If they are available, you should override DoMouseEnter and DoMouseLeave. Otherwise, catch the corresponding messages, like the other answer demonstrates. Don't forget to call inherited, as this will call the events.
Here's a VCL compliant version (tested D2010)
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
TMyPanel = class(TPanel)
private
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
published
end;
TForm1 = class(TForm)
Panel1: TPanel;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
Procedure OnMEnter(Sender: TObject);
Procedure OnMLeave(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
With TMyPanel.Create(Form1) do
Begin
Parent := Form1;
Caption := 'Test';
OnMouseEnter := OnMEnter;
OnMouseLeave := OnMLeave;
End;
end;
procedure TForm1.OnMEnter(Sender: TObject);
begin
Form1.Caption := 'Entered';
end;
procedure TForm1.OnMLeave(Sender: TObject);
begin
Form1.Caption := 'Left';
end;
{ TMyPanel }
procedure TMyPanel.CMMouseEnter(var Message: TMessage);
begin
// Do whatever your want before the event
Self.Caption := 'Custom Enter';
// Call inhertied method handler
Inherited;
end;
procedure TMyPanel.CMMouseLeave(var Message: TMessage);
begin
// Do whatever your want before the event
Self.Caption := 'Custom Left';
// Call inhertied method handler
Inherited;
end;
end.

Resources