dsLed

{
A simple component that looks like 3D light effect diode (LED).
On and off  color can be set. OnChange event occurs when on/off state changes.
}


{
TdsLed1 (Light effect diode)

Let's start with a new project, save it into desired folder and declare some
vital things for our led.
}


type
  TdsLed1 = class(TGraphicControl)
  private
    FOnColor: TColor;
    FOffColor: TColor;
    FLedOn: Boolean;

    FOnChange: TNotifyEvent;

    procedure SetLedOn(Value: Boolean);
    procedure SetOnColor(Value: TColor);
    procedure SetOffColor(Value: TColor);

    procedure DrawLedEllipse;
  protected
    procedure Paint; override;
    procedure DoChange; virtual;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property OnColor: TColor read FOnColor write SetOnColor;
    property OffColor: TColor read FOffColor write SetOffColor;
    property LedOn: Boolean read FLedOn write SetLedOn;

    property Visible;

    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  end;

{
TGraphicControl will be ancestor for our TdsLed1. TGraphicControl is the
base class for all nonwindowed custom controls. You can read all about it
in Delphi help.
The reason we picked this one is because we don't need input focus for our
led (that means we don't need a Windows handle). Handles take up system
resources. Since we don't need handle we'll be low on demand to system
resources and painting will be quicker.

We want to be able to set on color and off color. FOnColor and FOffColor
are declared for this.
We want to set on/off state. FLedOn is declared for this.
OnChange event should occur when on/off state changes. FOnChange is declared
for this.
We will declare all those things as properties. Also, we will propagate Visible
property from TGraphicControl.

TGraphicControl doesn't do anything by itself. To see something happen we must
override Paint method and draw something onto the canvas.

Now let's write some code.

First of all, we want our constructor to do some initialization. We will override
constructor and write:
}


constructor TdsLed1.Create(AOwner:TComponent);
begin
  inherited Create(AOwner);
  FOnColor := clLime;
  FOffColor := clRed;
  FLedOn := false;

  Width := 24;
  Height := 24;
end;

{
As you can see, we defined DoChange as virtual. Only virtual and dynamic methods
can be overridden. We want to allow descant classes to override this method. And
what this method does:
}


procedure TdsLed1.DoChange;
begin
  if Assigned(FOnChange) then FOnChange(self);
end;

{
Nothing much. It only calls FOnChange if it is assigned. And that is all it
should do.

Next two methods are simple:
}


procedure TdsLed1.SetOnColor(Value: TColor);
begin
  if FOnColor <> Value then
  begin
    FOnColor := Value;
    Refresh;
  end;
end;

procedure TdsLed1.SetOffColor(Value: TColor);
begin
  if FOffColor <> Value then
  begin
    FOffColor := Value;
    Refresh;
  end;
end;

{
There is only one thing to discuss here. Refresh. You could call Invalidate,
but you know, that Invalidate would refresh our led, when Windows would have
time to do it. Now let's suppose, you will switch led state at the beginning
of a long process. If we would use invalidate, led would switch to a different
color when Application.ProcessMessages would be called or never, if we wouldn't
call Application.ProcessMessages. Refresh repaints control immediately, before
anything else will go on. Exactly what we need.

When we switch on/off state of a led, this should happen:
}


procedure TdsLed1.SetLedOn(Value: Boolean);
begin
  if Value <> FLedOn then
  begin
    FLedOn := Value;
    Refresh;
    DoChange;
  end;
end;

{
If state should change, switch state, refresh led and trigger OnChange event.

Now the big part. Painting.
}


procedure TdsLed1.Paint;
begin
  if FLedOn then
    Canvas.Brush.Color := FOnColor
  else
    Canvas.Brush.Color := FOffColor;

  DrawLedEllipse;
end;

procedure TdsLed1.DrawLedEllipse;
var
  R: TRect;
  off: Integer;
begin
  R := ClientRect;

  if Height > Width then
    off := Width div 5
  else
    off := Height div 5;

  with Canvas do
  begin
    Pen.Color := Canvas.Brush.Color;
    Ellipse(R.Left, R.Top, R.Right, R.Bottom);

    //3D effect
    Pen.Color := clWhite;
    Brush.Color := clWhite;
    Chord(R.Left+off, R.Top+off, R.Right-off, R.Bottom-off, R.Right div 2, R.Top+off, R.Left+off, R.Bottom div 2);
  end;
end;

{
We defined drawing an ellipse led as a separated procedure. Why? You could
write your own routines to paint rectangle, triangle or whatever. In fact,
we will do that in one of the upcoming articles.
All we do is set up the right color, draw a circle and a 3D effect. I won't
explain Chord method since you should be able to try it out for yourself.

Should we try our code now?

Write OnCreate method of a form:
}


procedure TForm1.FormCreate(Sender: TObject);
begin
  l := TdsLed1.Create(Self);
  l.Parent := Self;
end;

{
Don't forget to declare

l: TdsLed1;

in the private section of a TForm1.

Put additional button onto the form and write:
}


procedure TForm1.Button1Click(Sender: TObject);
begin
  l.LedOn := not l.LedOn;
end;