delphi.gif - 582,0 K
Curso de creación de componentes en Delphi

Unidad 10. Bitmaps Off-Screen: TDigitalDisplay. 
delphi.gif - 582,0 K
 
Volver al índice Por Luis Roche emailed.gif - 15503,0 K

Si, si ya lo sé. No creais que lo he olvidado. Habíamos quedado que la unidad 10 trataría del tema de los editores de componentes. Lo que pasa es que para explicar este tema... ¡necesitamos un componente apropiado! De modo que esta unidad tratará sobre un nuevo componente sobre el cual explicaremos el tema de los editores de componentes en la unidad 11. Además, así matamos dos pájaros de un sólo tiro. Me explico: últimamente en el grupo de mensajes de Delphi han aparecido varias consultas sobre como evitar el parpadeo que se produce al dibujar imágenes que cambian frecuentemente. Este parpadeo (flicker) se produce al dibujar directamente las imganes en la pantalla. La solución es sencilla: basta con dibujar previamente la imagen en un bitmap oculto (off-screen) y cuando el dibujo este finalizado, volcarlo a la pantalla. De este modo se evita el parpadeo aunque estemos dibujando el gráfico muchas veces por segundo.

Vamos a aplicar esta técnica al componente TDigitalDisplay. Se trata de un display digital tipo calculadora.

Nuestro componente tendrá las siguientes características:

Antes de ponermos manos a la obra os adelanto que es lo más interesante de este componente y, por tanto, a qué debeis poner más atención ;): Bverde.gif - .325 KConsideraciones generales

Comenzemos a diseñar nuestro componente. Nuestro display constará de un número de dígitos configurable por el usuario, por lo que necesitamos una propiedad Digits que se encargue de este aspecto. Cada dígito estará formado por siete segmentos (volveremos sobre esto en la siguiente sección). Cada segmento puede estar encendido (propiedad DigitOnColor) o apagado (propiedad DigitOffColor). Otra propiedad será el color de fondo (propiedad BckgndColor).

Por supuesto, necesitaremos almacenar el valor a representar (propiedad Value) y, además, le dotaremos de alineación (propiedad Alignment), de la posibilidad de rellenar con ceros a la izquierda (propiedad LeadingZeros) y de estar o no visible (¿adivinais? propiedad Visible).

Por último le dotaremos de unos cuantos eventos estándar: OnClick, OnDragOver, OnDragDrop, OnMouseUp... y de un evento personalizado: OnDigitClick, que se disparará cuando se haga click sobre un dígito (si se hace click sobre parte del componente que no sea un dígito se disparará el evento OnClick).

Nuestro componente descenderá de TGraphicControl y la tárea de dibujado del mismo se llevará a cabo en el método Paint, el cuál habrá que redefinir (override). Por cierto ¿es "redefinir" la traducción adecuada de "override"? No estoy seguro, así que, por favor, sacarme de dudas :(

Con esto ya tenemos el marco en el que desarrollaremos el componente, asi que ¡manos a la obra!

Bverde.gif - .325 KCómo dibujar los digitos
 
Cómo hemos mencionado en el punto anterior, cada dígito está formado por siete segmentos. En el dibujo se han numerado en color azul. Además de los siete segmentos constituyentes de cada dígito, se han añadido tres segmentos más: uno para representar el punto decimal(.) y otros dos para la separación horaria (:).
El problema principal al dibujar el componente es que nuestro componente tendrá un ancho y alto definidos por el usuario, y estos valores pueden oscilar en un amplio rango de valores. Así el usuario puede querer un display de 100x50 (pequeño) o uno de 600x300 (enorme). Y el componente tiene que tener una buena "apariencia" en ambos casos. ¿Cómo lograrlo? Tenemos dos opciones:

  1. Crear un bitmap para cada uno de los dígitos (0..9, . y :) y guardarlo en un fichero de recursos. Después en el método Paint ajustar el tamaño de cada dígito al tamaño real del componente mediante el método StretchDraw. Este es el método más sencillo pero el que da peores resultados, ya que si por ejemplo dibujamos los digitos con un tamaño de 50x100, estos se verán muy mál a tamaños inferiores y a tamaños muy superiores.
  2. Dibujarlos a escala. Para cada segmento, guardamos en un array las coordenadas de sus vértices y luego, a la hora de dibujarlo multiplicamos estas coordenadas por la escala correspondiente. Así, al unir los vértices mediante líneas, la apariencia será perfecta. Este es el método más costoso, pero es el que ofrece mejores resultados y será el que adoptemos.
Si nos fijamos en los segmentos, veremos que los hay de 4,5 y 6 vértices. Por simplicidad haremos que todos tengan 6 vértices para almacenarlos en una matriz de 6 elementos. A los segmentos que les falten vértices, simplemente se los duplicaremos.

Se ha tomado como origen el punto 0,0 y el punto más lejano del origen (el vértice inferior derecho del segmento 3) tiene como coordenadas (26,32), es decir, he dibujado los segmentos sobre una plantilla de 26x34 pixels. Este tamaño es arbitrario, ya que luego cada vértice se le multiplicará por la escala correspondiente. De este modo he definido dos matrices: una para las coordenadass x y otra para las coordenadas y. Por supuesto se podría haber hecho con una sóla matriz, pero como hay que añadirle otra dimensión a la matriz (el indice del segmento) queda un poco más claro así.

Const
  {Coordenadas de los puntos}
  {Coordenadas de los 10 (1..10) segmentos posibles: 7 segmentos + 1 punto decimal (.), 2 puntos de hora (:)
   Cada segmento tiene 6 (0..5) vértices}
  {     _   }
  {  . |_|  }
  {  : |_|  }
  Px : Array[1..10,0..5] of integer = ((9,9,24,24,20,13),(26,26,26,24,22,22),(26,26,26,22,22,24),
                                      (24,24,9,9,13,20),(7,7,7,9,11,11),(7,7,7,11,11,9),
                                      (11,13,20,22,20,13),(2,2,5,5,5,2),(2,2,5,5,5,2),(2,2,5,5,5,2)) ;
  Py : Array[1..10,0..5] of integer = ((0,0,0,0,4,4),(2,2,14,16,14,6),(20,20,32,28,20,18),
                                      (34,34,34,34,30,30),(32,32,20,18,20,28),(14,14,2,6,14,16),
                                      (17,15,15,17,19,19),(32,32,32,32,34,34),(22,22,22,22,25,25),(10,10,10,10,13,13));

  {Para cada digito del 1 al 9, 1 representa segmento encendido, 0 apagado}
  DigitsArray : Array[0..9] of string = ('1111110','0110000','1101101','1111001','0110011','1011011',
    '1011111','1110000','1111111','1111011');

  Seg_dot = 8;     {Segmento correspondiente al .}
  Seg_DotDown = 9; {Segmento correspondiente al punto inferior de :}
  Seg_DotUp = 10;  {Segmento correspondiente al punto superior de :}
Hemos definido las dos matrices constantes px y py con las coordenadas de los vértices. Además, se define la matriz DigitsArray de 10 elementos (0..9) que contiene que segmentos están encendidos y apagados para cada dígito. Por ejemplo, el dígito 1 tiene encendidos los segmentos 2y 3 y apagados el resto. Aquí no se tienen en cuenta los segmentos extras (. y :).

Por último a los segmentos correspondientes a el punto decimal y a los puntos de la hora se les asigna un número y una constante para luego poder referirse a ellos con mayor facilidad.

Ya tenemos las bases necesarias para poder pintar el display en el método Paint. Eso sin olvidar que debemos eliminar el parpadeo...

Bverde.gif - .325 KDibujando sin parpadeos: el método Paint

Cómo en todo componente gráfico el método Paint se encarga de dibujar el componente en pantalla. Recordemos que este método es llamado por Delphi cada vez que se necesita un redibujado del componente, bien sea en respuesta a un mensaje de Windows o a una llamada nuestra, tal y como hacemos en los métodos Set de las diferentes propiedades, al llamar a repaint provocamos el redibujado del componente (Más adelante volveremos sobre este aspecto).

Para evitar el parpadeo no dibujaremos directamente sobre el canvas del componente (esto lo hicimos en el componente TGradiente, unidad 5), sino que lo haremos sobre otro bitmap. el proceso es el siguiente:

  1. En el método create creamos nuestro bitmap de trabajo (off-screen):

  2. FBitmap:=TBitmap.Create;
  3. En el método Paint pintamos sobre el Canvas de este bitmap.
  4. Una vez terminado el dibujado, volcamos este bitmap sobre el canvas real del componente:

  5. Canvas.Draw(0,0,FBitmap);
  6. Se vuelve al punto 2
  7. En el método Destroy liberamos el bitmap de trabajo:

  8. FBitmap.Free;
Simple ¿verdad? Pues así ¡hemos eliminado el molesto parpadeo de una vez por todas! Ahora bien, quedán un par de aspectos a considerar para que nuestro componente este libre de parpadeo al 100%: Ahora si, nuestro componente esta libre de parpadeos al 100% (bueno, quizás exagere un poco y sea realmente al 99%) :) Y si no me creeis, hacer la prueba con el programa mostrado al final de la unidad ¡Incrédulos! ;)

Bien, una vez conocida la teoría, es cuestión de aplicarla. Este es el método Paint de nuestro componente:

procedure TDigitalDisplay.Paint;
Var
  ex, ey : single;   {Escala a la que dibujar el display}
  i, Digito : byte;
  Incx, x,y, offsetx, offsety :integer;
  DigitSpace : integer;
  SrcRect : TRect;
  Digitos : byte;
  Caracter : char;
  FillString : string;
begin
  SrcRect:=Rect(0,0,Width,Height);   {Obtenemos el rectángulo del componente}
  FBitmap.Width:=Width;              {Ajustamos nuestro bitmap}
  FBitmap.Height:=Height;
  {Cálculo del offset a dejar alrededor}
  offsetx:=Trunc(0.2*width);
  offsety:=trunc(0.2*height);
  {Calculamos el espacio disponible para cada dígito}
  DigitSpace:=Trunc((Width-offsetx) / FDigits);
  {Posicionamos la x e y}
  x:=offsetx div 2;
  y:=offsety div 2;
  {Cálculo de la escala}
  ex:=DigitSpace/27;
  ey:=(Height-offsety)/35;
  {Inicializamos el nº de digitos dibujados}
  Digitos:=0;
  {Asignamos colores}
  FBitmap.Canvas.Brush.Color:=FBckgndColor;
  FBitmap.Canvas.FillRect(SrcRect);
  {Asignamos a la variable text el valor a representar}
  Text:=FValue;
  {Creamos una cadena auxiliar de relleno por la izquierda}
  if FLeadingZeros then
    FillString:='000000000000000000000000000000'
  else
    FillString:='                              ';
  {Rellenar la variable text con ceros o espacios (FillString) según sea la alineación}
  case FAlignment of
    taRightJustify : Text:=Copy(FillString,1,FDigits-GetNumberOfDigits)+FValue;
    taCenter : Text:=Copy(FillString,1,(FDigits-GetNumberOfDigits) div 2)+FValue;
  end;
  {Bucle para el dibujo de cada dígito}
  for i:=1 to Length(Text) do
  begin
    Caracter:=Copy(Text,i,1)[1];   {Caracter a dibujar}
    Case caracter of
      '0'..'9': begin
                  Digito:=StrToInt(Caracter);
                  DrawDigit(Digito,x,y,ex,ey);
                  x:=x+DigitSpace;
                  Inc(Digitos);
                  if Digitos = FDigits then break;
                end;
      ':' : begin
              DrawDot(seg_DotUp,x,y,ex,ey);
              DrawDot(seg_DotDown,x,y,ex,ey);
            end;
      '.',',': DrawDot(seg_Dot,x,y,ex,ey);
      ' ':     x:=x+DigitSpace;
    end;
  end;
  {Una vez dibujado totalmente el display en el bitmap off-screen, lo volcamos
   al canvas del componente. Resultado ¡No hay parpadeo!}
  Canvas.Draw(0,0,FBitmap);
end;
Como se puede ver el método Paint utiliza una serie de rutinas auxiliares para el dibujado del display. Estos métodos son: Creo que no hace falta explicar cómo se procede al dibujado de cada dígito en el canvas, ya que las funciones utilizadas para ello son de todos bien conocidas (Canvas.Pen, Canvas.Brush y Canvas.Polygon). En cualquier caso si os surgen dudas no teneis más que escribirme :)

Vamos ahora con el último aspecto interesante del componente: hacer que otros desarrolladores puedan descender sus propios componentes de nuestro Display original

Bverde.gif - .325 KPreparando la herencia: métodos dinámicos.

Cuando pretendemos que un componente pueda ser un futuro "padre" de futuros componentes debemos diseñar juiciosamente que aspectos debemos permitir que los hijos reimplementen y que aspectos deben permanecer ocultos a los mismos.

En una primera aproximación queda claro que todos los métodos Set y Get de las propiedades serán privados, ya que los hijos deben acceder a las propiedades directamente, no a través de estos métodos. Por ello declaramos estos métodos en las sección privada de la declaración del componente, lo mismo que los campos "F" asociados a estas propiedades (FBackgndColor, FDigitOnColor...).

Por otra parte, tanto el constructor como el destructor deben ser públicos así que con ellos no hay problema. Pero, ¿qué hacemos con el método Paint? Este método si que puede ser redefinido por futuros componentes hijos, por ejemplo, si un desarrollador quiere utilizar otro método para dibujar los digitos. Podríamos dejar el método Paint en la parte privada, pero entonces la única manera que tendría el desarrollador de escribir este nuevo método ¡sería en el componente original! Pero ¿y si no tiene el código fuente, sino tan sólo el fichero .dcu? Pues que no puede hacerlo :(

De modo que no seamos crueles y declaremos el procedimiento Paint como protected; así un componente hijo para implementar su propio método de dibujado del display sólo tendrá que escribir:

procedure Paint; override;

Que, dicho sea de paso, es lo mismo que hemos tenido que hacer nosotros :) Y es que no olvidemos que nuestro componente, a su vez, es hijo de TGraphicControl y ¡gracias a Dios! en dicho componente el método Paint se declaro protected y no private ;)

Nos queda por implementar un evento, OnDigitClick que se debe disparar cuando el usuario del componente haga click sobre un digito del display. Hay que distinguir entre el click sobre un dígito (que disparará el evento OnDigitClick) y el click sobre el resto del display (que disparará el evento OnClick).

Lo primero es declarar un campo FOnDigitClick para el nuevo evento. Además declararemos el tipo de procedimiento del evento:

TOnDigitClick = procedure(Sender : TObject; Digit : integer) of object;

Para disparar este evento, redefiniremos el procedimiento MouseDown. Para ello declararemos el método en la parte protected (ya sabeis, para que los hijos...) y añadiremos la palabra override. Esta es la implementación de dicho método:

procedure TDigitalDisplay.MouseDown(Button : TMouseButton; Shift : TShiftState; X,Y : Integer);
{Método que se encarga de determinar si se ha pulsado con el ratón sobre un dígito del display.
 En caso afirmativo se dispara el evento correspondiente}
var
  i, xt, xoff, yoff, DigitSpace : integer;
begin
  inherited MouseDown(Button,Shift,X,Y);
  {Cálculo del offset horizontal}
  xoff:=Trunc(0.2*width);
  xt:=Trunc(0.2*width) div 2;
  {Cálculo del espacio ocupado por un dígito}
  DigitSpace:=Trunc((Width-xoff) / FDigits);
  {Vamos comprobando si el click se ha ha hecho sobre el dígito (i)}
  for i:=1 to FDigits do
  begin
    if (x>=xt) AND (x<=xt+DigitSpace) then
      DigitClick(i);
    xt:=xt+DigitSpace;
  end;
end;
Lo primero que hace el método es llamar al antecesor del mismo mediente la llamada inherited. A continuación se calcula si se ha hecho click sobre un dígito. En caso afirmativo se ejecuta el procedimiento DigitClick pasándole como parámetro el número de dígito sobre el que se ha hecho click. El método DigitClick, por su parte, se encarga de disparar el evento anteriormente definido:
procedure TDigitalDisplay.DigitClick(Digit : integer);
{Método que dispara el evento OnDigitClick}
begin
  if Assigned(FOnDigitClick) then
    FOnDigitClick(Self,Digit);
end;
Y aquí esta el meollo de la cuestión. ¿Por qué hemos definido el método DigitClick y no hemos escrito las sentencias correspondientes dentro del método MouseDown? La respuesta es reusabilidad. Si un usuario del componente quiere que en respuesta a un click sobre un dígito dicho dígito cambie, codificara las sentencias necesarias en respuesta al evento OnDigitClick. Pero si es un nuevo programador el que esta desarrollando un hijo de TDigitalDisplay no puede hacer esto. A este programador el procedimiento MouseDown le sirve perfectamente y tan sólo tiene que reimplementar el procedimiento DigitClick y poner allí lo que quiera. Si lo hubieramos codificado todo en un único procedimiento esto no sería posible. De ahí que mantengamos separados el procedimiento que disparará el evento y el disparo del evento en sí. De este modo aseguramos una gran flexibilidad a futuros desarrolladores: por ejemplo, un hijo reimplementara DigitClick para incrementar el valor del dígito (por ejemplo, para ajustar la hora), mientras que otro lo reimplementara para poner el valor a cero (en un cronómetro). En ambos casos el método MouseDown será el mismo y bastará con trabajar sobre DigitClick directamente.

El último detalle importante es que el método DigitClick se ha declarado dynamic, ya que si no se declara así, no se puede reimplementar, ya que los métodos estáticos no se pueden redefinir, sólo los dinámicos (ya sean dynamic o virtual). En otras unidades profundizaremos más sobre este tema. Mientras os refiero a la ayuda en línea de Delphi para más información sobre este tema.

Con esto queda terminado el componente. Quizás haya sido una explicación un poco breve pero considero que con el nivel que ya tenemos no debeis teneis problema para entender el funcionamiento del mismo. Os recomiendo que estudies la forma de trabajar con off-screen bitmaps, ya que es un tema que se utiliza frecuentemente en la programación gráfica bajo Windows. Y Recordar que este componente nos servira en la próxima unidad para desarrollar el tema de los editores de propiedades.

Bverde.gif - .325 KCódigo fuente del componente.

unit DigitalDisplay;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
  {Tipo para el evento OnDigitClick}
  TOnDigitClick = procedure(Sender : TObject; Digit : integer) of object;

  TDigitalDisplay = class(TGraphicControl)
  private
    FBitmap : TBitmap;             {Bitmap oculto (off-screen) para el dibujo del display}
    FDigits : byte;                {Número de digitos a representar}
    FValue : string;               {Valor a arepresentar}
    FBckgndColor, FDigitOnColor, FDigitOffColor : TColor;   {Colores}
    FAlignment : TAlignment;       {Alineación horizontal}
    FLeadingZeros : boolean;       {Rellenar con ceros}
    FOnDigitClick : TOnDigitClick; {Campo para el evento OnDigitClick}

    procedure DrawDot(index : byte;x,y : integer;ex,ey : single);
    procedure DrawDigit(Digit : byte; x,y : integer;ex,ey : single);
    procedure SetDigits(Value:byte);
    procedure SetValue(Value:String);
    procedure SetBckgndColor(Value : TColor);
    procedure SetDigitOnColor(Value : TColor);
    procedure SetDigitOffColor(Value : TColor);
    procedure SetAlignment(Value : TAlignment);
    procedure SetLeadingZeros(Value : boolean);
    function GetNumberOfDigits : byte;
  protected
    procedure Paint; override;
    procedure MouseDown(Button : TMouseButton; Shift : TShiftState; X,Y : Integer); override;
    procedure DigitClick(Digit : integer); dynamic;
  public
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
  published
    property LeadingZeros : boolean read FLeadingZeros write SetLeadingZeros default False;
    property Alignment : TAlignment read FAlignment write SetAlignment default taRightjustify;
    property Digits : byte read FDigits write SetDigits default 4;
    property Value : string read FValue write SetValue;
    property BckgndColor : TColor read FBckgndColor write SetBckgndColor;
    property DigitOnColor : TColor read FDigitOnColor write SetDigitOnColor;
    property DigitOffColor : TColor read FDigitOffColor write SetDigitOffColor;
    property OnDigitClick : TOnDigitClick read FOnDigitClick write FOnDigitClick;
    property Visible;
    property OnClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
  end;

procedure Register;

implementation

Const
  {Coordenadas de los puntos}
  {Coordenadas de los 10 (1..10) segmentos posibles: 7 segmentos + 1 punto decimal (.), 2 puntos de hora (:)
   Cada segmento tiene 6 (0..5) vértices}
  {     _   }
  {  . |_|  }
  {  : |_|  }
  Px : Array[1..10,0..5] of integer = ((9,9,24,24,20,13),(26,26,26,24,22,22),(26,26,26,22,22,24),
                                      (24,24,9,9,13,20),(7,7,7,9,11,11),(7,7,7,11,11,9),
                                      (11,13,20,22,20,13),(2,2,5,5,5,2),(2,2,5,5,5,2),(2,2,5,5,5,2)) ;
  Py : Array[1..10,0..5] of integer = ((0,0,0,0,4,4),(2,2,14,16,14,6),(20,20,32,28,20,18),
                                      (34,34,34,34,30,30),(32,32,20,18,20,28),(14,14,2,6,14,16),
                                      (17,15,15,17,19,19),(32,32,32,32,34,34),(22,22,22,22,25,25),(10,10,10,10,13,13));

  {Para cada digito del 1 al 9, 1 representa segmento encendido, 0 apagado}
  DigitsArray : Array[0..9] of string = ('1111110','0110000','1101101','1111001','0110011','1011011',
    '1011111','1110000','1111111','1111011');

  Seg_dot = 8;     {Segmento correspondiente al .}
  Seg_DotDown = 9; {Segmento correspondiente al punto inferior de :}
  Seg_DotUp = 10;  {Segmento correspondiente al punto superior de :}

constructor TDigitalDisplay.Create(AOwner : TComponent);
begin
  inherited Create(AOwner);
  ControlStyle:=ControlStyle+[csOpaque];  {Necesario para evitar el parpadeo}
  FBitmap:=TBitmap.Create;                {Creamos el bitmap que no servirá para dibujar el display off-screen}
  {Valores por defecto}
  FDigits:=4;
  FValue:='1997';
  FBckgndColor:=clBlack;
  FDigitOnColor:=clLime;
  FDigitOffColor:=$4000;
  FAlignment:=taRightJustify;
  Width:=108;
  Height:=35;
end;

destructor TDigitalDisplay.Destroy;
begin
  inherited Destroy;
  FBitmap.Free;        {Liberamos el bitmap off-screen}
end;

procedure TDigitalDisplay.SetAlignment(Value : TAlignment);
begin
  if Value<>FAlignment then
  begin
    FAlignment:=Value;
    repaint;
  end;
end;

procedure TDigitalDisplay.SetDigits(Value : byte);
begin
  if Value<>FDigits then
  begin
    FDigits:=Value;
    repaint;
  end;
end;

procedure TDigitalDisplay.SetLeadingZeros(Value : boolean);
begin
  if Value<>FLeadingZeros then
  begin
    FLeadingZeros:=Value;
    repaint;
  end;
end;

procedure TDigitalDisplay.SetValue(Value : string);
begin
  if Value<>FValue then
  begin
    FValue:=Value;
    repaint;
  end;
end;

procedure TDigitalDisplay.SetBckgndColor(Value : TColor);
begin
  if FBckgndColor<>Value then
  begin
    FBckgndColor:=Value;
    repaint;
  end;
end;

procedure TDigitalDisplay.SetDigitOnColor(Value : TColor);
begin
  if FDigitOnColor<>Value then
  begin
    FDigitOnColor:=Value;
    repaint;
  end;
end;

procedure TDigitalDisplay.SetDigitOffColor(Value : TColor);
begin
  if FDigitOffColor<>Value then
  begin
    FDigitOffColor:=Value;
    repaint;
  end;
end;

procedure TDigitalDisplay.Paint;
Var
  ex, ey : single;   {Escala a la que dibujar el display}
  i, Digito : byte;
  Incx, x,y, offsetx, offsety :integer;
  DigitSpace : integer;
  SrcRect : TRect;
  Digitos : byte;
  Caracter : char;
  FillString : string;
begin
  SrcRect:=Rect(0,0,Width,Height);   {Obtenemos el rectángulo del componente}
  FBitmap.Width:=Width;              {Ajustamos nuestro bitmap}
  FBitmap.Height:=Height;
  {Cálculo del offset a dejar alrededor}
  offsetx:=Trunc(0.2*width);
  offsety:=trunc(0.2*height);
  {Calculamos el espacio disponible para cada dígito}
  DigitSpace:=Trunc((Width-offsetx) / FDigits);
  {Posicionamos la x e y}
  x:=offsetx div 2;
  y:=offsety div 2;
  {Cálculo de la escala}
  ex:=DigitSpace/27;
  ey:=(Height-offsety)/35;
  {Inicializamos el nº de digitos dibujados}
  Digitos:=0;
  {Asignamos colores}
  FBitmap.Canvas.Brush.Color:=FBckgndColor;
  FBitmap.Canvas.FillRect(SrcRect);
  {Asignamos a la variable text el valor a representar}
  Text:=FValue;
  {Creamos una cadena auxiliar de relleno por la izquierda}
  if FLeadingZeros then
    FillString:='000000000000000000000000000000'
  else
    FillString:='                              ';
  {Rellenar la variable text con ceros o espacios (FillString) según sea la alineación}
  case FAlignment of
    taRightJustify : Text:=Copy(FillString,1,FDigits-GetNumberOfDigits)+FValue;
    taCenter : Text:=Copy(FillString,1,(FDigits-GetNumberOfDigits) div 2)+FValue;
  end;
  {Bucle para el dibujo de cada dígito}
  for i:=1 to Length(Text) do
  begin
    Caracter:=Copy(Text,i,1)[1];   {Caracter a dibujar}
    Case caracter of
      '0'..'9': begin
                  Digito:=StrToInt(Caracter);
                  DrawDigit(Digito,x,y,ex,ey);
                  x:=x+DigitSpace;
                  Inc(Digitos);
                  if Digitos = FDigits then break;
                end;
      ':' : begin
              DrawDot(seg_DotUp,x,y,ex,ey);
              DrawDot(seg_DotDown,x,y,ex,ey);
            end;
      '.',',': DrawDot(seg_Dot,x,y,ex,ey);
      ' ':     x:=x+DigitSpace;
    end;
  end;
  {Una vez dibujado totalmente el display en el bitmap off-screen, lo volcamos
   al canvas del componente. Resultado ¡No hay parpadeo!}
  Canvas.Draw(0,0,FBitmap);
end;

procedure TDigitalDisplay.DrawDot(index : byte;x,y : integer;ex,ey : single);
{Dibuja un punto en la posición x,y con la escala determinada por ex y ey}
Var
  j : byte;
  Puntos : array[0..5] of TPoint;
begin
  FBitmap.Canvas.Pen.Color:=DigitOnColor;
  FBitmap.Canvas.Brush.Color:=DigitOnColor;
  for j:=0 to 5 do
    Puntos[j]:=Point(x+Trunc(Px[index,j]*ex),y+Trunc(Py[index,j]*ey));
  FBitmap.Canvas.Polygon([Puntos[0],Puntos[1],Puntos[2],
                  Puntos[3],Puntos[4],Puntos[5]]);
end;

procedure TDigitalDisplay.DrawDigit(Digit : byte; x,y : integer;ex,ey : single);
{Dibuja el dígito (0..9) pasado en la posición x,y con la escala determinada por ex y ey}
Var
  i, j : byte;
  Puntos : array[0..5] of TPoint;
begin
  for i:=1 to 7 do
  begin
    if Copy(DigitsArray[Digit],i,1) = '0' then
    begin
      FBitmap.Canvas.Pen.Color:=FDigitOffColor;
      FBitmap.Canvas.Brush.Color:=FDigitOffColor;
    end
    else
    begin
      FBitmap.Canvas.Pen.Color:=FDigitOnColor;
      FBitmap.Canvas.Brush.Color:=FDigitOnColor;
    end;
    for j:=0 to 5 do
        Puntos[j]:=Point(x+Trunc(Px[i,j]*ex),y+Trunc(Py[i,j]*ey));
    FBitmap.Canvas.Polygon([Puntos[0],Puntos[1],Puntos[2],
                      Puntos[3],Puntos[4],Puntos[5]]);
  end;
end;

function TDigitalDisplay.GetNumberOfDigits : byte;
{Devuelve el número de digitos numéricos de la propiedad Value}
Var
  i : byte;
begin
  Result:=0;
  for i:=1 to Length(FValue) do
    if Copy(FValue,i,1)[1] in ['0'..'9'] then
      Inc(Result);
end;

procedure TDigitalDisplay.MouseDown(Button : TMouseButton; Shift : TShiftState; X,Y : Integer);
{Método que se encarga de determinar si se ha pulsado con el ratón sobre un dígito del display.
 En caso afirmativo se dispara el evento correspondiente}
var
  i, xt, xoff, yoff, DigitSpace : integer;
begin
  inherited MouseDown(Button,Shift,X,Y);
  {Cálculo del offset horizontal}
  xoff:=Trunc(0.2*width);
  xt:=Trunc(0.2*width) div 2;
  {Cálculo del espacio ocupado por un dígito}
  DigitSpace:=Trunc((Width-xoff) / FDigits);
  {Vamos comprobando si el click se ha ha hecho sobre el dígito (i)}
  for i:=1 to FDigits do
  begin
    if (x>=xt) AND (x<=xt+DigitSpace) then
      DigitClick(i);
    xt:=xt+DigitSpace;
  end;
end;

procedure TDigitalDisplay.DigitClick(Digit : integer);
{Método que dispara el evento OnDigitClick}
begin
  if Assigned(FOnDigitClick) then
    FOnDigitClick(Self,Digit);
end;

procedure Register;
begin
  RegisterComponents('Curso', [TDigitalDisplay]);
end;


end.
Bverde.gif - .325 KEjemplo de uso del componente.

Como ejemplo de uso del componente y para demostraros que está realmente libre de flickering, en un form en blanco coloca un componente TDigitalDisplay y un botón. En respuesta al evento OnClick del botón codifica lo siguiente:

procedure TForm1.Button1Click(Sender: TObject);
var
  contador : integer;
begin
  for contador:=0 to 1000 do
  begin
    DigitalDisplay1.Value:=IntToStr(Contador);
    Application.ProcessMessages;
  end;
end;
¡Ejecutar el programa y disfrutar! ;)



Luis Roche revueltaroche@redestb.es

Ultima modificación 25.01.1998

Otros Links de Interés:

Cocina  -  Videos  -  Juegos Gratis  - Postales cachondas  - Cine  - Programas Gratis  -  Letras de Canciones

Listas de todos los Tutoriales Gratis. 1998- 2007 - -

Los tutoriales y cursos aquí reunidos son una recopilación de los mejores encontrados en Internet.
El crédito y copyright de los mismos si lo hubiere corresponde al autor de cada uno de ellos.
Si tu tutorial o curso está aquí, y deseas darlo de baja de esta recopilación o quieres añadir el tuyo, envíanos un mensaje desde
aquí

Publispain - Fun