Módulo C.P.2017 (II): Reloj Analógico.

Buen camino a todos:

Subimos otro peldaño en la serie que aborda el reloj de fichar. Hoy vamos a darle forma a nuestro componente.

Recordar antes que nada, que existe un taller creado al efecto de acompañar la serie y ampliar muchos conceptos que por extensión quedan omitidos. Su contenido, al igual que  el de esta y sucesivas entradas se dirige especialmente a los compañeros que se inician en Delphi, y no tanto a quienes lleváis tiempo en la Comunidad; En cualquier caso sois todos invitados a sumarse a él si estáis siguiendo la serie y que podamos desde allí discutir y detallarlos.

Haciendo un poco de memoria, en la última entrada, Módulo C.P.2017 (I): Reloj Analógico, habíamos dejado preparada la estructura de un grupo de proyectos, que cnuevocomponente-14ontenía el componente del reloj analógico, junto a un proyecto vacío que os podría permitir evaluar los avances que se vayan haciendo. No es que sea necesario tenerlo así, pero la experiencia nos dice que es conveniente ir evaluando progresivamente cada etapa y decidir si realmente se ajusta a lo que esperamos. Y ese proyecto vacío, es un estupenda mesa de pruebas para ir verificando que todo es correcto: simplemente arrastrando de la paleta de componentes al formulario sucesivamente, evaluando así cada funcionalidad nueva que expone.

Si observamos el código fuente del reloj, al momento actual, no contiene nada deslumbrante. Delphi ha hecho todo el trabajo «duro» por nosotros.

Veamos el código creado:

unit MiReloj;

interface

uses
  System.SysUtils, System.Classes, FMX.Types, FMX.Controls, FMX.Objects;

type
  TMiReloj = class(TCircle)
  private
    { Private declarations }
  protected
    { Protected declarations }
  public
    { Public declarations }
  published
    { Published declarations }
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TMiReloj]);
end;

end.

La unidad, MiReloj, se corresponde físicamente con un único archivo «MiReloj.pas» del que nuestro IDE conoce la ubicación a través del archivo de proyecto: Nosotros no tenemos separación de archivos de cabecera y de implementación como sí ocurre en otros lenguajes y entornos. La primera linea declarada (unit MiReloj;) debe corresponderse siempre con el nombre de ese archivo «.pas» (sin extensión), y establece la primera premisa que es la unicidad: cada unidad del proyecto debe ser única. Cualquier intento de agregar otra unidad con el mismo nombre (ubicada en otra ruta alternativa) lanzará una excepción que nos advertirá del error.iceberg

La siguiente declaración que nos encontramos es «interface«, y nos invita a reflexionar sobre la accesibilidad entre unidades. Todo lo declarado a partir de ese punto, hasta encontrar la declaración «implementación«, se corresponde con la parte visible de esa unidad y accesible por otras unidades del proyecto. El mejor símil lo podéis encontrar en la imagen de un gran iceberg. Lo que queda en el exterior del agua, representa la parte visible de la unidad, donde conocemos que tipos y clases declara, así como las unidades que enlaza, o las variables que hace publicas, etc… Imaginariamente, bajo el mar, oculto de nuestra mirada, se esconde la implementación, el cómo lo hace, representado en el código asociado a cada método que se expone desde la parte pública. Igualmente, esta sección podrá componerse de nuevas declaraciones de tipos pero estos quedarían ocultos a otras unidades externas.

En nuestro caso concreto, la unidad MiReloj, hace públicas la declaración de la clase TMiReloj, bajo la sección de tipos (types), y el método Register, responsable de que el IDE pueda gestionar su inclusión en la pestaña deseada de la paleta de Componentes.

Y la implementación, solo contiene la información concreta de ese registro: el nombre de esa pestaña y el nombre de la clase que va a registrar.

Es un ecosistema peculiar…

Nuestra unidad es un pequeño ecosistema dentro de otro ecosistema mayor que es el proyecto. Y la declaración de tipos (type), inicia una sección que puede contener un numero igualmente indeterminado de clases que conviven bajo una reglas «estrictas». La información detallada la podéis encontrar, como siempre, en la documentación oficial: Clases y Objetos (Delphi)ecosistema

Echando mano de mi escaso talento gráfico, he perdido unos minutos para dibujar algo que visualmente lo represente. 🙂  Cualquier parecido con unas galletas es mera coincidencia.

Las declaraciones published o publicadas y public o públicas, representadas en la galleta por el área anaranjada y rosa respectivamente, son la parte visible de cada clase. Se diferencian ambas en cuanto al destino de esa información puesto que la parte publicada o published es la que Delphi gestiona desde el tiempo de diseño y que será aprovechada por nosotros desde el inspector de objetos. El área dibujada con el color rosa representaría la parte publica, disponible en tiempo de ejecución. Simbolizan lo que vemos de esa clase y proporciona su acceso (hablando siempre entre clases no relacionadas por herencia).

Por el contrario, en color verde y rojo he pintado las áreas que no son «visibles», (notar que en el caso de la declaración protected dicha información sí es accesible para descendientes). El área roja identifica el área privada de una clase y solo es accesible por ella misma, mientras que el área verde dibuja lo accesible por herencia, en sus descendientes.

En ese ecosistema coexisten las clases y se relacionan entre si observando esa reglas de accesibilidad conocidas… al menos en teoría. Es por eso, introducir el ecosistema como «peculiar», porque suele ser frecuente que la persona que da sus primeros obvie que estas reglas se relajan entre las clases que coexisten en una unidad y por ello, he querido perder unos momentos en ello, porque me parecía necesario comentarlo. De hecho, recuerdo haberlo comentado en algunas entradas del blog antiguas. El párrafo concreto que refiero de la documentación exponía: «By placing related class declarations in the same module, you can give each class access to the private members of another class without making those members more widely accessible. For a member to be visible only inside its class, it needs to be declared strict private«. Lo he recalcado porque considero que es algo, que debéis evitar porque puede inducir a errores en un momento posterior.

Os pongo un ejemplo para que se entienda mejor. En el proyecto vacío que ya tenemos, añadid la siguiente declaración por encima de TForm1 y añadir un botón en el que implementaremos un evento al pulsarlo:

  type

  TMiClase = class
  private
    FNombre: String;
  protected
    function GetNombre: String;
  public
  published
    property Nombre: String read FNombre write FNombre;
  end;

  TForm1 = class ...

...

implementation

...

{  TMiClase }
function TMiClase.GetNombre: String;
begin
   Result:= FNombre;
end;

La regla general es que una instancia de clase solo fuera accedida desde la parte pública de ese interfaz que declara, sin embargo, el entorno nos permitiría escribir un código como este que ahora os incluyo (en respuesta del evento onClick() del botón), donde se produce la asignación sobre un campo privado FNombre, que en teoría no seria accesible desde el formulario. Igualmente, invocamos el método GetNombre, que en este caso es declarado como protected, sin que el compilador nos alerte de esta situación que rompe las reglas que conocemos.

procedure TForm1.Button1Click(Sender: TObject);
var
  s: String;
begin
  MiClase:= TMiClase.Create;
  MiClase.FNombre:= 'delphi';
  s:= MiClase.GetNombre;
  ShowMessage(s);
  MiClase.Free;
end;

Para evitar esta relación de amistad entre clases que conviven en la misma unidad o módulo, podéis utilizar la declaración strict private en lugar de private.  Probadlo… Igualmente podemos referir respecto a strict protected y protected. No obstante, aunque no los utilicéis expresamente, bastaría que lo tuvierais en cuenta porque os podrá evitar futuros errores.

Bueno, eso está muy bien… ¿pero no teníamos que hablar del reloj?

Upssss…. ¡Lo olvidaba!.

Vamos a jugar con las manecillas del reloj.

Una primera idea puede ser la de dibujarlas sobre esa circunferencia que representa el círculo. Sería una opción factible…

NO. ¡Mucho trabajo!   😀

En este caso ya existe un componente que nos puede facilitar esa tarea. Me refiero a otro descendiente TShape: TLine, que como el nombre indica, dibujará una linea sobre su contenedor padre, -imagina que habitualmente sería un Formulario-. Podemos crear 3 instancias de TLine y manipular adecuadamente sus características para que hagan lo que necesitamos. Esto nos permitiría representar los segundos, minutos y horas.

Antes que nada, os propongo -nuevamente- servirnos del proyecto vacío para comprobar que la idea es acertada. Sobre nuestro formulario, arrastramos el componente TLine de la paleta de componentes, dibujando la linea comentada como os muestra la imagen siguiente (en la parte derecha os muestro los valores por defecto que guardará al ser depositado sobre el form, que podéis acceder con el botón de contexto sobre el formulario, visualizando la ficha como texto):

line1_default
  object Line1: TLine
    LineType = Diagonal
    Position.X = 152.000000000000000000
    Position.Y = 64.000000000000000000
    Size.Width = 50.000000000000000000
    Size.Height = 50.000000000000000000
    Size.PlatformDefault = False
  end

La instancia Line1, al ser depositado sobre el formulario, toma los valores decimales por defecto para el Width y el Height de 50.0., su anchura y altura. Su ángulo de rotación, la propiedad RotationAngle, representa la cantidad de grados sobre el eje X y aceptará valores positivos y negativos para  girar en un sentido u otro (en el sentido de las agujas del reloj o en contra). Incidir sobre ese valor, nos va a proporcionar la posición adecuada en función de la hora actual. Pero… ahora mismo, gira sobre si y mas que un reloj parecerá mas la hélice de un aeroplano.

Para que gire en función del punto central de nuestra circunferencia, actuaremos sobre la propiedad RotaciónCenter para fijar el punto de giro en el extremo; su nuevo valor fijará el punto de coordenadas (0,0), tras lo cual, nos basta determinar el punto de posición, Position, sobre el contenedor padre, nuestro reloj, el centro de la circunferencia, el punto central.

Por otro lado, cada manecilla tendrá una longitud distinta, en función de un porcentaje del radio de la circunferencia. No puede tener un tamaño fijo por que cualquier cambio en la dimensión del reloj, debe mantener esa proporción.

Con esas ideas, lo mejor es jugar en el formulario del proyecto vacío e ir cambiando las propiedades comentadas en el inspector de Objetos, y observar que sucede al modificarlas.

Hecho esto estamos en condiciones de enfrentar la construcción de una nueva clase. Por supuesto, podríais manipular directamente cada instancia de TLine desde el mismo reloj, pero en su lugar, vamos a crear una clase descendiente que encapsule este comportamiento que queremos. Ganaremos en claridad y legibilidad del código. Y será por supuesto mas fácil de depurar. Había una lectura muy buena que os recomiendo de nuestro amigo Juan Antonio Castillo, en la que hablaba de los conceptos de abstraer, factorizar y dividir: El programador «Copiar y Pegar»

Dadme unos minutos.

Como tener tres manecillas en una sola…

Añadimos la declaración del tipo enumerado, TTipo, con valores maSegundos, maMinutos y maHoras, que nos permitirá identificar cada instancia creada y personalizar su comportamiento en función de que represente al segundero, al minutero o a la aguja de las horas. Para ello, añadimos a su constructor un parámetro de este tipo.

Esta es la especificación que vamos a escribir y la ubicaremos antes de la declaración de la clase TMiReloj:

type
  TTipo = (maSegundos, maMinutos, maHoras);

  TManecilla = class(TLine)
  private
    FTipoManecilla: TTipo;
  protected
   procedure ParentChanged; override;
  public
   constructor CreateManecilla(AOwner: TComponent; ATipoManecilla: TTipo); virtual;
   procedure SetAnguloRotacion(const AHoraActual: TDateTime);
   procedure AjustarPosicion(ANewWidth, ANewHeight: Single); 
  published
   property Tipo: TTipo read FTipoManecilla;
  end;

Y aquí podéis observar la implementación que da respuesta a los 4 métodos declarados:

{ TManecilla }

constructor TManecilla.CreateManecilla(AOwner: TComponent; ATipoManecilla: TTipo);
begin
  inherited Create(AOwner);
  FTipoManecilla:= ATipoManecilla;
  with RotationCenter do
  begin
    X:= 0;
    Y:= 0;
  end;
  Stroke.Kind:= TBrushKind.Gradient;
    case FTipoManecilla of
      maSegundos: begin
                    Stroke.Color:= claGreen;
                    Stroke.Thickness:= 2;
                  end;
      maMinutos : begin
                    Stroke.Color:= claBlue;
                    Stroke.Thickness:= 5;
                  end;
      maHoras   : begin
                    Stroke.Color:= claBlue;
                    Stroke.Thickness:= 5;
                  end;
    end;
end;

procedure TManecilla.ParentChanged;
begin
  inherited;
  if (Parent <> nil) and (Parent is TMiReloj) then
  begin
    Position.x := (Parent as TMiReloj).ShapeRect.Width/2;
    Position.y:= (Parent as TMiReloj).ShapeRect.Height/2;
  end;
end;

procedure TManecilla.AjustarPosicion(ANewWidth, ANewHeight: Single); 
begin 
  Position.x := ANewWidth/2; 
  Position.y:= ANewHeight/2; 
end;
procedure TManecilla.SetAnguloRotacion(const AHoraActual: TDateTime);
var
  FRotacionSegundos: Real;
  FRotacionMinutos: Real;
  FRotacionHoras: Real;
  FTemp: Word;
begin
  case FTipoManecilla of
    maSegundos: begin
                  FTemp:= SecondOfTheMinute(AHoraActual);
                  FRotacionSegundos:= 225 + (FTemp) * 6;
                  RotationAngle:= FRotacionSegundos;
                end;
    maMinutos : begin
                  FTemp:= MinuteOfTheHour(AHoraActual);
                  FRotacionMinutos:= 225 +  (FTemp) * 6;
                  RotationAngle:= FRotacionMinutos;
                end;
    maHoras   : begin
                  FTemp:= HourOfTheDay(AHoraActual);
                  FRotacionHoras:= 225 + ((FTemp) * 30);
                  FTemp:= MinuteOfTheHour(AHoraActual);
                  FRotacionHoras:= FRotacionHoras + (FTemp*0.5);
                  RotationAngle:= FRotacionHoras;
                end;
  end;
end;

Cada método creado, intenta responder a una situación distinta que nos plantea el contexto sobre el que se desenvuelve el componente. Afortunadamente por sus requerimientos es ínfimo, en comparación con cualquiera de los existentes en nuestro entorno de desarrollo y el esfuerzo que hemos dedicado es moderado.

Hemos escrito un nuevo constructor que parametriza el tipo de manecilla y realiza pequeños ajustes sobre su diseño, asignando el centro de rotación en el punto (0,0) y cambiando detalles que afectan a la linea, como el color o el grosor.

También sobrescribimos el metodo ParentChanged, dando respuesta a la asignación de un nuevo Parent que tendrá lugar tras haberse creado. De no ser así, se produciría un efecto visual extraño, al tomar los valores por defecto de su constructor y no considerar los de su contenedor que van a condicionar la posición y su tamaño. Es algo sencillo de ver que podéis comprobar vosotros mismos.

El siguiente paso, es considerar que nuestro reloj va a cambiar de tamaño en tiempo de diseño, ajustándose tanto a los valores de Align como a un tamaño especifico. Por lo tanto, hemos escrito un nuevo método público que he llamado AjustarPosicion, que tomará como parámetros los nuevos valores en el Width o en el Height del componente padre. Para que todo funcione, he optado por sobrescribir en el reloj el método virtual público SetBounds, que ahora además calcula el radio de la esfera previo a su invocación, de forma que podamos corregir correctamente el tamaño y la posición de la manecilla.

procedure TMiReloj.SetBounds(X,Y, AWidth, AHeight: Single);
begin
  inherited SetBounds(X,Y,AWidth,AHeight);
  FRadioEsfera:= Min(ShapeRect.Width / 2, ShapeRect.Height / 2)- KEspREsfera;
  FMSegundos.AjustarPosicion(AWidth, AHeight);
  FMMinutos.AjustarPosicion(AWidth, AHeight);
  FMHoras.AjustarPosicion(AWidth, AHeight);
end;

Y nos falta unicamente, convertir en parámetro un valor que podría ser la hora actual y ajustar el valor de rotación en función de ello. Hay que tener en cuenta que al asignar la propiedad RotationAngle, estamos asignando grados por lo que tenemos que hacer la conversión de la unidad de tiempo a sus grados adecuados. Por cierto, notad que el valor 225 corrige el ángulo inicial que por defecto toma el componente, para que los cálculos sean ajustados. Si todavía os queda alguna duda, no hay problema. El taller existe para eso y no sea excesivamente denso el contenido de estas entradas. Lo verdaderamente importante es que este rato que estamos compartiendo sea didáctico y entretenido (y útil para todos).

Con todo ello, nuestra unidad hasta el momento quedaría como podéis ver:

unit MiReloj;

interface

uses
  System.SysUtils, System.Classes, FMX.Types, FMX.Controls, FMX.Objects;

type
  TTipo = (maSegundos, maMinutos, maHoras);

  TManecilla = class(TLine)
  private
    FTipoManecilla: TTipo;
  protected
   procedure ParentChanged; override;
  public
   constructor CreateManecilla(AOwner: TComponent; ATipoManecilla: TTipo); virtual;
   procedure SetAnguloRotacion(const AHoraActual: TDateTime);
   procedure AjustarPosicion(ANewWidth, ANewHeight: Single);
  published
   property Tipo: TTipo read FTipoManecilla;
  end;

  TMiReloj = class(TCircle)
  private
    { Private declarations }
    FDateTime: TDateTime;
    FMHoras: TManecilla;
    FMMinutos: TManecilla;
    FMSegundos: TManecilla;
    FRadioEsfera: Single;
  protected
    { Protected declarations }
    procedure AjustarManecilla(ATipoManecilla: TTipo); virtual;
   procedure Paint; override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    procedure SetBounds(X,Y, AWidth, AHeight: Single); override;
    procedure SetNewTime(const ANow: TDateTime);
  published
    { Published declarations }
  end;

procedure Register;

implementation

uses FMX.Graphics, System.UITypes, System.UIConsts, System.Types,
System.Math, DateUtils;

const
  KEspREsfera = 20;

procedure Register;
begin
  RegisterComponents('Samples', [TMiReloj]);
end;

constructor TMiReloj.Create(AOwner: TComponent);
begin
  inherited;
  Width:= 400;
  Height:= 400;

  FMHoras:= TManecilla.CreateManecilla(self, maHoras);
  with FMHoras do
  begin
    Parent:= self;
    Name:= 'manecilla_horas';
    Stored:= False;
  end;

  FMMinutos:= TManecilla.CreateManecilla(Self, maMinutos);
  with FMMinutos do
  begin
    Parent:= self;
    Name:= 'manecilla_minutos';
    Stored:= False;
  end;

  FMSegundos:= TManecilla.CreateManecilla(self, maSegundos);
  with FMSegundos do
  begin
    Parent:= self;
    Name:= 'manecilla_segundos';
    Stored:= False;
  end;
end;

procedure TMiReloj.SetNewTime(const ANow: TDateTime);
begin
  //actualizamos la hora
  FDateTime:= ANow;
  //comunicamos a cada manecilla para que corrijan al angulo adecuado
  FMSegundos.SetAnguloRotacion(FDateTime);
  FMMinutos.SetAnguloRotacion(FDateTime);
  FMHoras.SetAnguloRotacion(FDateTime);
end;

procedure TMiReloj.SetBounds(X,Y, AWidth, AHeight: Single);
begin
  inherited SetBounds(X,Y,AWidth,AHeight);
  FRadioEsfera:= Min(ShapeRect.Width / 2, ShapeRect.Height / 2)- KEspREsfera;
  FMSegundos.AjustarPosicion(AWidth, AHeight);
  FMMinutos.AjustarPosicion(AWidth, AHeight);
  FMHoras.AjustarPosicion(AWidth, AHeight);
end;

procedure TMiReloj.AjustarManecilla(ATipoManecilla: TTipo);
begin
  FRadioEsfera:= Min(ShapeRect.Width / 2, ShapeRect.Height / 2)- KEspREsfera;
  case ATipoManecilla of
    maSegundos: begin
                  if Assigned(FMSegundos) then
                  begin
                    FMSegundos.Position.x := ShapeRect.Width/2;
                    FMSegundos.Position.y:= ShapeRect.Height/2;
                    FMSegundos.Width:= FRadioEsfera * 70 / 100;   //70%
                    FMSegundos.Height:= FRadioEsfera * 70 / 100;  //
                   end;
                end;
    maMinutos:  begin
                  if Assigned(FMMinutos) then
                  begin
                    FMMinutos.Position.x := ShapeRect.Width/2;
                    FMMinutos.Position.y:= ShapeRect.Height/2;
                    FMMinutos.Width:= FRadioEsfera * 55 / 100;   //55%
                    FMMinutos.Height:= FRadioEsfera * 55 / 100;  //
                  end;
                end;
    maHoras:    begin
                  if Assigned(FMHoras) then
                  begin
                    FMHoras.Position.x := ShapeRect.Width/2;
                    FMHoras.Position.y:= ShapeRect.Height/2;
                    FMHoras.Width:= FRadioEsfera * 40 / 100;     //40%
                    FMHoras.Height:= FRadioEsfera * 40 / 100;    //
                  end;
                end;
  end;
end;

procedure TMiReloj.Paint;
begin
  inherited;
  //ajustamos las manecillas
  AjustarManecilla(maSegundos);
  AjustarManecilla(maMinutos);
  AjustarManecilla(maHoras);
end;

{ TManecilla }

constructor TManecilla.CreateManecilla(AOwner: TComponent; ATipoManecilla: TTipo);
begin
  inherited Create(AOwner);
  FTipoManecilla:= ATipoManecilla;
  with RotationCenter do
  begin
    X:= 0;
    Y:= 0;
  end;
  Stroke.Kind:= TBrushKind.Gradient;
    case FTipoManecilla of
      maSegundos: begin
                    Stroke.Color:= claGreen;
                    Stroke.Thickness:= 2;
                  end;
      maMinutos : begin
                    Stroke.Color:= claBlue;
                    Stroke.Thickness:= 5;
                  end;
      maHoras   : begin
                    Stroke.Color:= claBlue;
                    Stroke.Thickness:= 5;
                  end;
    end;
end;

procedure TManecilla.ParentChanged;
begin
  inherited;
  if (Parent <> nil) and (Parent is TMiReloj) then
  begin
    Position.x := (Parent as TMiReloj).ShapeRect.Width/2;
    Position.y:= (Parent as TMiReloj).ShapeRect.Height/2;
  end;
end;

procedure TManecilla.AjustarPosicion(ANewWidth, ANewHeight: Single);
begin
  Position.x := ANewWidth/2;
  Position.y:= ANewHeight/2;
end;

procedure TManecilla.SetAnguloRotacion(const AHoraActual: TDateTime);
var
  FRotacionSegundos: Real;
  FRotacionMinutos: Real;
  FRotacionHoras: Real;
  FTemp: Word;
begin
  case FTipoManecilla of
    maSegundos: begin
                  FTemp:= SecondOfTheMinute(AHoraActual);
                  FRotacionSegundos:= 225 + (FTemp) * 6;
                  RotationAngle:= FRotacionSegundos;
                end;
    maMinutos : begin
                  FTemp:= MinuteOfTheHour(AHoraActual);
                  FRotacionMinutos:= 225 +  (FTemp) * 6;
                  RotationAngle:= FRotacionMinutos;
                end;
    maHoras   : begin
                  FTemp:= HourOfTheDay(AHoraActual);
                  FRotacionHoras:= 225 + ((FTemp) * 30);
                  FTemp:= MinuteOfTheHour(AHoraActual);
                  FRotacionHoras:= FRotacionHoras + (FTemp*0.5);
                  RotationAngle:= FRotacionHoras;
                end;
  end;
end;

end.

Despidiendo la entrada…

Nos resta probar el estado actual del componente, compilando nuestro proyecto. Podemos desinstalarlo y volver a ejecutar su instalación, con el menú contextual de forma que se pueda evaluar que funciona correctamente sobre el proyecto vació. Y repetimos los pasos que hemos hecho anteriormente sobre el formulario vacío. Paleta de componentes. Pestaña Samples. Arrastramos MiReloj sobre el Form. Y evaluamos que las tres manecillas se sitúan correctamente sobre el punto central del circulo pintado. Luego, respondemos al evento OnCreate del form añadiendo una linea de código tal como sigue:

procedure TForm1.FormCreate(Sender: TObject);
begin
  MiReloj1.SetNewTime(Now);
end;

Si todo ha salido bien, al ejecutar el proyecto, nuestro reloj actualizará la hora en tiempo de diseño.

Para aquellos que siguen la serie, propondría una pequeñas tarea a la vista del código final. Si habéis leído la entrada de Juan Antonio Castillo,  que citaba más arriba, podríamos hacer un cambio adicional en el constructor para unificar en un solo método las rutinas que crean cada manecilla, que siguen el mismo patrón. Como es algo sencillo os lo dejo para que hagáis el cambio vosotros mismos y ya me comentáis en el taller.

Nos tenemos que despedir por esta semana. En la siguiente, añadiremos un componente TTimer, que de vida al reloj, y crearemos los diales del minutero y de las horas, para que sea mas legible. Hay muchas consideraciones y apuntes que podemos hacer sobre algunos fragmentos del código que han quedado en el tintero y que iremos introduciendo. Os pongo un ejemplo, solo por citar una que me viene a la cabeza: optimizar nuestro código con la introducción de una interfaz que representa una abstracción de funcionalidades. Nos podría permitir desacoplar una clase de otra, de forma que favorece nuestra habilidad de introducir cambios sin tener esa dependencia. Pero esto es algo que veremos mas adelante.

Y os recuerdo que podéis contactar en el correo: reloj[arroba]delphibasico[punto]com, si por alguna razón no podéis acceder o no encontráis el grupo del taller.

Nada mas por hoy. Espero que hayáis disfrutado de esta entrada.

4 comentarios sobre “Módulo C.P.2017 (II): Reloj Analógico.

Agrega el tuyo

  1. Fantástica entrada Salvador, como siempre. Por las explicaciones y la claridad.
    Un comentario, más que nada casi una cuestión de gustos. Yo hubiera ubicado el procedimiento AjustarManecilla dentro de la clase TManecilla, al igual que has hecho, por ejemplo, con el SetAnguloRotacion. Como dispone de la referencia a TReloj (ya que pertenecen a él) puedes acceder a los datos que necesitas sin problemas.

    Un saludo.

    Me gusta

  2. Apuntado queda, Germán. Si es cierto. Es una buena idea.
    Gracias por el comentario. +1

    Aprovecho para comentar que esta serie, está viva, en el sentido que se nutre de vuestras ideas y propuestas y discusiones. El formato de taller recalca este punto, y de hecho, yo voy cambiando cosas sobre la marcha al tiempo que preparo la entrada. Quizás por ello, en ocasiones, los retrasos, porque a medida que escribo, voy detectando puntos que me parecen interesantes recalcar de cara a que sea mas didáctica.

    El punto que comenta German, nos invita a evaluar los datos que necesitamos para mover el método de una clase a otra. Y ya de paso, y al hilo de pensar en ello, quizás seria necesario cambiarle el nombre al método, ya que se ejecuta en el contexto del Paint del Componente. A lo mejor es mas claro llamarlo PintarManecilla( ). Ni idea… sobre la marcha lo vemos.

    Me gusta

  3. Hola!
    En primer lugar gracias por la serie de artículos, las estoy siguiendo ahora y están fenomenal.
    A fin de tratar de colaborar, y habiendo leído en el mismo artículo acerca de buenas prácticas, dividir, no repetir código, etc etc…me pregunto si no sería más adecuado dejar así el método:
    procedure TManecilla.ParentChanged;
    begin
    inherited;
    if (Parent nil) and (Parent is TMiReloj) then
    begin
    AjustarPosicion((Parent as TMiReloj).ShapeRect.Width, (Parent as TMiReloj).ShapeRect.Height);
    end;
    end;

    De esta manera no estaríamos poniendo la misma lógica en 2 sitios del código.
    Saludos

    Me gusta

    1. Hola David: Primero y antes que nada, gracias por el comentario.
      Me ha tocado hacer memoria, porque ya habían pasado algunas semanas y al llegar tu comentario sobre el capitulo 2 de la serie había perdido el hilo y ya no sabia se era algo que se había modificado posteriormente.

      Claro que puedes hacerlo. Si te fijas, a lo largo de los capítulos de la serie vamos corrigiendo aspectos memorables sobre la marcha, optimizando y haciendo mas legible el código. Ese es el espíritu que hay que intentar mantener.
      En la vida real pasa un poco lo mismo. Nuestros proyectos van siendo revisados continuamente. A veces a partir de nuevos aspectos que pasaron inadvertidos y otras, porque versiones nuevas de nuestra herramienta nos dan capacidades que antes no teníamos.

      En la parte B de la serie, que todavia está por escribir, viviremos esas circunstancias.

      Por otro lado, mi idea tampoco era buscar que de las entradas el código fuera 100% perfecto, en el sentido de que existe abierto un grupo en la comunidad de Embarcadero que nació con el espíritu de la discusión sana y de que se fueran añadiendo las aportaciones de todos. Yo lo llamo taller de trabajo.
      https://community.embarcadero.com/my-groups/viewgroup/580-taller-de-delphi-basico-2017

      A mi me gustaría que poco a poco os fuerais uniendo al taller, quienes estáis siguiendo la serie, y desde allí podemos seguir discutiendo. Ahora estamos en un impasse de descanso de la serie. Estos días estoy centrado en la salida de la nueva versión Delphi Tokyo. Pero al tiempo, volveremos con la segunda parte.

      Un saludo,
      Salvador

      Me gusta

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Blog de WordPress.com.

Subir ↑

Marina Casado

Escritora y Doctora en Literatura Española. Periodista cultural. Madrid, España

Sigo aqui

Mi rincon del cuadrilatero

Recetas y consejos nutricionales

Indicadas para personas con diabetes, recomendadas para todos.

¡Buen camino!

ANÉCDOTAS Y REFLEXIONES SOBRE UN VIAJE A SANTIAGO…

https://lfgonzalez.visiblogs.com/

Algunas reflexiones y comentarios sobre Delphi

It's All About Code!

A blog about Delphi, C++ Builder and related technologies...

The Podcast at Delphi.org

The Podcast about the Delphi programming language, tools, news and community.

Blog de Carlos G

Algunas reflexiones y comentarios sobre Delphi

The Road to Delphi

Delphi - Free Pascal - Oxygene

La web de Seoane

Algunas reflexiones y comentarios sobre Delphi

El blog de cadetill

Cosas de programación....... y de la vida

Delphi-losophy

A Lover of Delphic Wisdom

Delphi en Movimiento

Algunas reflexiones y comentarios sobre Delphi

marcocantu.blog

Algunas reflexiones y comentarios sobre Delphi

Press F9

Algunas reflexiones y comentarios sobre Delphi

El blog de jachguate

Un blog sobre tecnología y la vida en general

A %d blogueros les gusta esto: