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.
Related
I am looking to create an effect similar to the lightbox effect seen on many website where the background of the screen fades out and the content you want to emphasize does not. What would be the best way to go about creating such an effect in delphi ?
The content I want to emphasize in this case is a movable panel located on my form and basically all I want to do is to fade out any area of the screen that is not directly under that panel.
Thanks.
Oscar
Create a new form and add this code to the FormCreate method. You could also change the properties using the properties inspector, but I'm choosing to show you the relevant properties using code:
unit Unit1;
// This is a full screen partially transparent black form.
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure FormClick(Sender: TObject);
end;
var
Form1: TForm1;
implementation
uses Unit2;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
BorderStyle := bsNone;
Self.WindowState := wsMaximized;
AlphaBlend := true;
Alphablendvalue := 127;
Color := clBlack;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
Form2.Show;
end;
procedure TForm1.FormClick(Sender: TObject);
begin
Close;
end;
end.
Here's a second form which has no border, which I am showing over top. It does not have alpha blending turned on, and the form style should be fsStayOnTop, or else you should use the ParentWindow property (on versions of Delphi that support that).
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm2 = class(TForm)
Label1: TLabel;
procedure FormDeactivate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormActivate(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
FAutoDeactivate: Boolean;
FCounter: Integer;
procedure WMUser1(var Message:TMessage); message WM_USER+1;
public
property AutoDeactivate:Boolean read FAutoDeactivate write FAutoDeactivate;
end;
var
Form2: TForm2;
implementation
uses Unit1;
{$R *.dfm}
procedure TForm2.FormDeactivate(Sender: TObject);
begin
if Self.Visible and FAutoDeactivate then
begin
FAutoDeactivate := false;
Form1.Close;
end;
end;
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Form1.Close;
end;
procedure TForm2.FormActivate(Sender: TObject);
begin
PostMessage(Self.Handle, WM_USER+1, 0, 0);
end;
procedure TForm2.WMUser1(var Message: TMessage);
begin
FAutoDeactivate := true;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
BorderStyle := bsNone;
Color := clWhite;
FormStyle := fsStayOnTop; // or set parent
end;
end.
That addresses how to make the whole screen "go dim", and then show something on top of that "dimmed area", but what you describe as "showing a panel in your main form" would require you to move that content out of your main form, or else clip a region out of form1, or use a combination of alpha blend plus transparency, but I don't have any code for those to show you.
If I was doing it, I would just float the thing I want not to be dimmed, above the full screen borderless 50% alpha form, as shown below.
But as you see, the screen isn't dimmed (screen brightness is not reduced), it's merely that we've done a 50% transparent layer of black which has blended in and darkened the overall screen appearance.
I have the same need as Oscar. After some search on the net, I found what is shown here.
It has helped me to do this, since it works. You can move what is emphasized in a Form instead of a Panel.
I use two forms. The first is use as "fader" and the second as dialogbox.
First
unit uFormFaded;
interface
uses
...
type
TFormFaded = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
var
FormFaded: TFormFaded;
implementation
{$R *.dfm}
procedure TFormFaded.FormCreate(Sender: TObject);
begin
Align := alClient;
AlphaBlend := true;
AlphaBlendValue := 100;
BorderStyle := bsNone;
Color := clBlack;
Enabled := false;
FormStyle := fsStayOnTop;
end;
end.
Second
unit UFormDlgBox;
interface
uses
...
type
TFormDlgBox = class(TForm)
procedure FormShow(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
var
FormDlgBox: TFormDlgBox;
implementation
{$R *.dfm}
uses uFormFaded;
procedure TFormDlgBox.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FormFaded.Close;
end;
procedure TFormDlgBox.FormShow(Sender: TObject);
begin
FormFaded.Show;
end;
end.
The use
FormDlgBox.ShowModal;
I tried to reproduce this schema creating the forms in run-time an make the TFormDlgBox Owns and create the TFormFaded but it doesn't work. It seems it works only with forms created in design-time.
I want to be notified when my computer power source changes.
So first I 've created a simple Delphi application and listening for
WM_POWERBROADCAST at the main form.
WM_POWERBROADCAST
type
TForm38 = class(TForm)
public
procedure WM_POWERBROADCAST(var Msg: TMessage); message WM_POWERBROADCAST;
end;
implementation
procedure TForm38.WM_POWERBROADCAST(var Msg: TMessage);
begin
Caption := Msg.LParam.ToString;
end;
Then I got my notifications, but Msg.LParam is allways 0 (zero)
Then I've tried to call RegisterPowerSettingNotification and found an example in this old SO Question, but I still have the same problem: Msg.LParam is allways 0 (zero)
RegisterPowerSettingNotification
type
TForm38 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FHPOWERNOTIFY: HPOWERNOTIFY;
public
{ Public declarations }
procedure WM_POWERBROADCAST(var Msg: TMessage); message WM_POWERBROADCAST;
end;
implementation
const
GUID_ACDC_POWER_SOURCE: TGUID = '{5D3E9A59-E9D5-4B00-A6BD-FF34FF516548}';
procedure TForm38.FormCreate(Sender: TObject);
begin
FHPOWERNOTIFY := RegisterPowerSettingNotification(Handle, GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE);
end;
procedure TForm38.FormDestroy(Sender: TObject);
begin
UnregisterPowerSettingNotification(FHPOWERNOTIFY);
end;
procedure TForm38.WM_POWERBROADCAST(var Msg: TMessage);
begin
Caption := Msg.LParam.ToString;
end;
The application run on Windows 10.
What am I doing wrong?
THE RESULT
Using the code from the answer to this question, I've ended up writing this class:
unit PowerWatcherU;
interface
uses
Winapi.Windows, System.Classes, System.SyncObjs, Winapi.Messages;
{$M+}
type
TPowerSource = (PoAc = 0, PoDc = 1, PoHot = 2);
TPowerSourceChanged = procedure(const PowerSource: TPowerSource) of object;
TPowerWatcher = class(TComponent)
private
FMyHWND: HWND;
FHPOWERNOTIFY: HPOWERNOTIFY;
FOnPowerSourceChanged: TPowerSourceChanged;
procedure DoPowerSourceChanged(const Value: TPowerSource);
procedure WndHandler(var Msg: TMessage);
procedure SetOnPowerSourceChanged(const Value: TPowerSourceChanged);
published
property OnPowerSourceChanged: TPowerSourceChanged read FOnPowerSourceChanged write SetOnPowerSourceChanged;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
const
GUID_ACDC_POWER_SOURCE: TGUID = '{5D3E9A59-E9D5-4B00-A6BD-FF34FF516548}';
implementation
uses
System.SysUtils;
{ TPowerWatcher }
constructor TPowerWatcher.Create;
begin
inherited;
FMyHWND := AllocateHWND(WndHandler);
FHPOWERNOTIFY := RegisterPowerSettingNotification(FMyHWND, GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE);
end;
destructor TPowerWatcher.Destroy;
begin
DeallocateHWND(FMyHWND);
UnregisterPowerSettingNotification(FHPOWERNOTIFY);
inherited;
end;
procedure TPowerWatcher.DoPowerSourceChanged(const Value: TPowerSource);
begin
if Assigned(FOnPowerSourceChanged) then
FOnPowerSourceChanged(Value);
end;
procedure TPowerWatcher.SetOnPowerSourceChanged(const Value: TPowerSourceChanged);
begin
FOnPowerSourceChanged := Value;
end;
procedure TPowerWatcher.WndHandler(var Msg: TMessage);
begin
if (Msg.Msg = WM_POWERBROADCAST) and (Msg.WParam = PBT_POWERSETTINGCHANGE) then
begin
if PPowerBroadcastSetting(Msg.LParam)^.PowerSetting = GUID_ACDC_POWER_SOURCE then
DoPowerSourceChanged(TPowerSource(PPowerBroadcastSetting(Msg.LParam)^.Data[0]));
end
else
Msg.Result := DefWindowProc(FMyHWND, Msg.Msg, Msg.WParam, Msg.LParam);
end;
end.
It is possible that you are suffering from window re-creation. Your code as posted works fine for me but this may not be the case in Win10. With that aside, the only other oddity is that you are duplicating an identifier by naming a method WM_POWERBROADCAST, although this should not cause the code to break. Working example using a dedicated HWND :
unit Unit1;
interface
uses
Windows, SysUtils, Classes, Forms, StdCtrls, Vcl.Controls, Vcl.ExtCtrls,
Messages;
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FMyHWND : HWND;
FHPowerNotify: HPOWERNOTIFY;
public
procedure WndHandler(var Msg: TMessage);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
const
GUID_ACDC_POWER_SOURCE: TGUID = '{5D3E9A59-E9D5-4B00-A6BD-FF34FF516548}';
procedure TForm1.FormCreate(Sender: TObject);
begin
FMyHWND := AllocateHWND(WndHandler);
FHPowerNotify := RegisterPowerSettingNotification(FMyHWND,
GUID_ACDC_POWER_SOURCE,
DEVICE_NOTIFY_WINDOW_HANDLE);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
UnregisterPowerSettingNotification(FHPowerNotify);
DeallocateHWND(FMyHWND);
end;
procedure TForm1.WndHandler(var Msg: TMessage);
begin
if (Msg.Msg = WM_POWERBROADCAST) and
(Msg.WParam = PBT_POWERSETTINGCHANGE) then
begin
if PPowerBroadcastSetting(Msg.LParam)^.PowerSetting = GUID_ACDC_POWER_SOURCE then
case cardinal(PPowerBroadcastSetting(Msg.LParam)^.Data[0]) of
0: Caption := 'AC Power';
1: Caption := 'DC Power';
2: Caption := 'HOT - UPS, etc';
end;
end else
msg.Result := DefWindowProc(FMyHWND, Msg.Msg, Msg.WParam, Msg.LParam);
end;
end.
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.
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.
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.