Este
ejemplo muestra el uso de varios tipos de controles que facilitan la interacción
de los usuarios con la aplicación. También se muestra en
forma muy elemental las posibilidades de graficación usando la propiedad
de clase TCanvas.
El contenido de los archivos que confroman este ejemplo de aplicación se muestra en forma de tablas, en la columna izquierda el código en ObjectPascal y en la derecha explicaciones y comentarios. |
En
los archivos de programa (.dpr y .pas), las líneas
que son generadas automáticamente por Delphi están en color
amarillo; en blanco sólo las que el programador tuvo que agregar.
Aparecen resaltadas en ambos casos las palabras reservadas de ObjectPascal,
tal como lo hace el Editor de Delphi.
Son tres los archivos que conforman la aplicación: Dibujando.dpr: es el archivo raíz o índice del proyecto, de extensión .dpr (Delphi PRoject); DibujoF.pas: es la Unit que contiene la programación correspondiente a la forma de interfaz de usuario; DibujoF.dfm: es parte de la visualización en modo texto de la Forma diseñada por el programador como interfaz de usuario. Este archivo de extensión .dfm (Delphi ForM) contiene las declaraciones correspondientes al diseño de la Forma; es generado por Delphi transcribiendo la construcción que hace el usuario en forma visual. No hace falta que el programador vea este archivo ni debe modificar manualmente el texto, Delphi se encarga de su actualización cada vez que visualmente se modifique la apariencia o el contenido de la Forma asociada, ya sea por acciones directas sobre la Forma o modificando propiedades con el Object Inspector . |
Diseņo de la Forma de Interfaz de Usuario
program
Dibujando;
uses
{$R *.RES} begin
|
Para proyectos diseñados con formas de interfaz gráfica para usuarios, Delphi genera el archivo de extensión .dpr. En la sección uses coloca los nombres y las ubicaciones de todas las Units incorporadas al proyecto, además de la referencia a la Delphi-Unit Forms, que contiene la Clase TForm, clase primitiva de todas las formas. |
unit DibujoF;
interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type |
La palabra interface
señala
el comienzo de bloque de declaraciones, donde se colocan todas las declaraciones
o definiciones (tipos, variables, constantes, funciones y procedimientos,
labels).
En la instrucción uses
Delphi genera la lista de Units utilizadas en la aplicación, a la
cual se pueden agregar las que el usuario desee, preferiblemente usando
el botón de Agregar Unit en la barra de herramientas del menú
principal.
|
TForm1 = class(TForm) PaintBox1: TPaintBox; ColorDialog1: TColorDialog;
|
Delphi genera las declaraciones
de los controles y componentes colocados por el programador en la forma
y registra en el archivo de la forma (DibujoF.dfm
en este caso) las propiedades asignadas con el ObjectInspector.
TPaintBox es
una clase para definir áres rectangulares dentro de una forma. Posee
la propiedad Canvas,
de clase TCanvas:
área de dibujo.
|
GroupBox1: TGroupBox; //Agrupa tipos de
figuras
Shape11: TShape; // Línea Shape12: TShape; // Cuadrado Shape13: TShape; // Círculo RadioButton1: TRadioButton; // Línea RadioButton2: TRadioButton; // Cuadrado RadioButton3: TRadioButton; // Círculo |
Los controles de tipo TGroupBox
sirven para agrupar controles relacionados en la forma, con letrero descriptivo
(propiedad Caption).
En GroupBox1 se agrupan controles para selección de tipo de figura que se puede colocar en el área de dibujo: línea, cuadrado y círculo, represeantados visualmente por los controles de tipo TShape. La selección se hace con controles de tipo TRadioButton, que se comportan de manera excluyente: uno y sólo uno del grupo puede estar seleccionado. Se podría haber usado el tipo de control TRadioGroup, que es específico para este uso, en vez del control genérico TGroupBox. |
GroupBox2: TGroupBox; //Agrupa tipos de
línea
CheckBox1: TCheckBox; // Ancho 1 pixel CheckBox2: TCheckBox; // ... CheckBox3: TCheckBox; CheckBox4: TCheckBox; Shape21: TShape; Shape24: TShape; Shape23: TShape; Shape22: TShape; |
En GroupBox2
se agrupan controles para selección, según su grosor, del
tipo de línea que se puede colocar en el área de dibujo:
1, 2, 3 y 4 pixeles, represeantados visualmente por los controles de tipo
TShape.
La selección se hace con controles de tipo TCheckBox, que se comportan de manera independiente: ninguno, uno, varios o todos pueden estar seleccionados, lo cual es inapropiado en este caso. ¿Por qué ? |
GroupBox3: TGroupBox; //Agrupa mensaje
y botón de fin
Label1: TLabel; //Mensaje BTerminar: TButton; //Boton para terminar |
En GroupBox3 se agrupan controles para indicar el modo de uso de la aplicación, incluido un botón para finalizar. |
GroupBox4: TGroupBox; //Agrupa selección
de color
ShapeColor: TShape; //Cuadrito de selección de color Label2: TLabel; //Mensaje |
En GroupBox4 se agrupan controles para selección de color de dibujo para cualquiera de las selecciones de dibujo (contorno de figuras con relleno y líneas). El control de clase TShape se rellena con el color que esté seleccionado. Se lo hace funcionar como un botón, ya que el método de respuesta al evento OnMouseUp sobre él hace que se active el Diálogo para seleccionar color. |
procedure PaintBox1MouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure SelLinea(Sender: TObject); procedure SelFigura(Sender: TObject); procedure Inicializar(Sender: TObject); procedure Terminar(Sender: TObject); procedure PaintBox1Paint(Sender: TObject); procedure ShapeColorMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); private { Private declarations } public { Public declarations } end; |
Métodos de respuesta a los eventos considerados y fin de la declaración de la forma. Delphi coloca los encabezados private y public por si el usuario necesita definiciones adicionales. El evento OnMouseUp es equivalente al OnClick, pero con la diferencia de que entrega al método de respuesta las coordenadas en pixeles de la posición del puntero del ratón sobre el control. A este tipo de evento responde el PaintBox1 y el ShapeColor, de clases TPaintBox y TShape respectivamente. Los demás controles activos responden al evento OnClick. |
TFigura
= (linea, cuadrado, circulo);
var Form1: TForm1; elColor: TColor; laFigura: TFigura; laLinea: integer; |
Se define un tipo enumerado
para distinguir los tipos de figuras considerados.
Delphi coloca la declaración de la variable de referencia a la forma definida como objeto de la clase TForm1. Luego se definen las variables que registran las selecciones consideradas: elColor (referencia a la clase del color activo), laFigura (tipo de figura) y laLinea (grosor). |
implementation
{$R *.DFM} |
Comienzo de bloque de implementación de métodos de las clases definidas y de procedimientos y funciones no asociados a ninguna clase. |
procedure
TForm1.PaintBox1MouseUp(Sender:
TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin with PaintBox1.Canvas do begin Brush.Color:=elColor; Pen.Color:=elColor; Pen.Width:=laLinea; case laFigura of linea: begin MoveTo(X,Y); LineTo(X+60,+60); end; cuadrado: Rectangle(X,Y,X+60,Y+60); circulo : Ellipse(X,Y,X+60,Y+60); end; end; end; |
En respuesta
al evento OnMouseUp
sobre PaintBox1
se dibuja a partir de las coordenadas de la posición del puntero
del ratón según el tipo de figura, color y línea que
estén selccionados.
Observe el uso de la instrucción Pascalwith que permite abreviar las referencias a propiedades, atributos y métodos del objeto considerado (o los campos si se trata de un record). En este caso el objeto es la propiedad Canvas de clase TCanvas del objeto PaintBox1 de clase TPaintBox. Ver propiedades y métodos de TCanvas en el Help. La propiedad Color de la propiedad Brush del PaintBox1.Canvas es el color de relleno de figuras. La propiedad Color de la propiedad Pen del PaintBox1.Canvas es el Color de dibujo de líneas y contornos y la propiedad Width es el ancho de línea. Si la figura seleccionda es linea, se dibuja una línea partiendo del punto donde estaba el puntero del ratón (método MoveTo..) al hacer Click hasta (método LineTo..) un punto fijo en la diagonal abajo y a la derecha. (Como ejercicio se propone que la aplicación pueda dibujar línea continuas, para lo cual necesitaría guardar la posición actual del ratón y proporcionar algún mecanismo para indicar fin de línea). Si la figura seleccionda es cuadrado, se dibuja con el método Rectangle... un rectángulo de lados iguales. Si la figura seleccionda es circulo,
se dibuja con el método
|
procedure
TForm1.SelLinea(Sender:
TObject);
begin with TCheckBox(Sender) do if Checked then laLinea:=TabOrder + 1; end; |
En respuesta al evento
OnClick
en cualquiera de los Check Boxes se coloca como ancho de línea
su número de orden dentro de su TGroupBox,
ya que la propiedad TabOrder
da el orden de colocación de los controles en el grupo, empezando
desde 0. El orden de colocación se hizo en el orden del ancho de
línea, dado por la propiedad Height,
del TShape
adyacente. También se hubiera podido definir la propiedad Tag
directamente con el valor del ancho para el mismo propósito.
Observe la necesidad de usar typecasting (nombre de un tipo de clase [TCheckBox] aplicado a un objeto) ya que el parámetro que representa al objeto en el que ocurrió el evento (Sender) es del tipo más general en Delphi, TObject. Observe también la inconveniencia de
usar TCheckBox
en vez de TRadioButton.
Sin embargo, por programación se podría imitar el comportamiento
de los botones de radio, haciendo que al seleccionar uno (propiedad Checked
en true)
se coloque la propiedad en false
en todos los demás.
|
procedure
TForm1.SelFigura(Sender:
TObject);
begin with TRadioButton(Sender) do if Checked then laFigura:=TFigura(TabOrder); end; |
En respuesta al evento
OnClick
en cualquiera de los Radio Buttons se coloca como tipo de figura
el tipo correspondiente a su número de orden dentro de su TGroupBox,
ya que la propiedad TabOrder
da el orden de colocación de estos controles en el grupo, empezando
desde 0. El orden de colocación se hizo en el orden del tipo de
figura, dado por la propiedad Shape,
del TShapeadyacente.
También se hubiera podido definir la propiedad Tag
con el valor del orden del tipo de figura para el mismo propósito.
Observe la necesidad de usar typecasting
ya que la declaración del parámetro que representa al objeto
en el que ocurrió el evento (Sender)
es de la clase más general en Delphi, TObject.
También es necesario usar typecasting
para reinterpretar el número entero dado por TabOrder como
el tipo de figura definido en la posición correspondiente
a ese orden en el tipo TFigura.
|
procedure
TForm1.Inicializar(Sender:
TObject);
begin elColor:=clBlack; laLinea:=1; laFigura:=linea; end; |
En respuesta al evento OnActivate de la forma, generado por Windows, se inicializan los valores de las selecciones consideradas: color, ancho de línea y tipo de figura inciales. |
procedure
TForm1.Terminar(Sender:
TObject);
begin Close; end; |
En respuesta al evento OnClick en el Boton que indica Terminar, se cierra la forma con lo cual también termina la aplicación. |
procedure
TForm1.PaintBox1Paint(Sender:
TObject);
begin with PaintBox1 do Canvas.Rectangle(0,0,Width,Height); end; |
En respuesta al evento
OnPaint
en la forma o en un TPaintBox,
el programador puede programar la restauración parcial o total del
dibujo contenido en su Canvas,
esto es, refrescar el contenido de la propiedad Canvas
de clase TCanvas.
El evento OnPaint
es generado por Windows en la primera aparición al crear la forma
o cuando la forma se descubre después de haber estado minimizada
o cubierta por otra forma, lo cual a su vez desecandena la ocurrencia de
eventos OnPaint
en los TPaintBox
que estén colocados en la forma.
En este caso sólo se refresca el marco del área del TPaintBox, lo que haya estado dibujado se pierde. Se presenta más adelante una versión de esta Unit en la que se refresca el área de dibujo. |
procedure
TForm1.ShapeColorMouseUp(Sender:
TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin with ColorDialog1 do if Execute then begin elColor:=Color; ShapeColor.Brush.Color:=elColor; end; end; |
En respuesta al evento OnMouseUp sobre el ShapeColor de clase TShape, se despliega el diálogo standard de selección de color. Si el usuario escoge un color, se actualiza la variable global que registra el color activo y se cambia el color de relleno del control ShapeColor. |
end. | Fin de la Unit. |
Forma
DibujoF.dfm
vista
como texto
object
Form1: TForm1
Left = 192 Top = 107 BorderStyle = bsToolWindow Caption = 'Dibujando...' ClientHeight = 453 ClientWidth = 503 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] OldCreateOrder = False OnActivate = Inicializar PixelsPerInch = 96 TextHeight = 13 object PaintBox1: TPaintBox Left = 8 Top = 14 Width = 329 Height = 419 OnMouseUp = PaintBox1MouseUp OnPaint = PaintBox1Paint end object GroupBox2: TGroupBox Left = 352 Top = 144 Width = 145 Height = 129 Caption = 'Ancho línea' TabOrder = 0 object Shape21: TShape Left = 56 Top = 32 Width = 65 Height = 1 end object Shape24: TShape Left = 56 Top = 104 Width = 65 Height = 5 Brush.Color = clBlack end object Shape23: TShape Left = 56 Top = 80 Width = 65 Height = 4 Brush.Color = clBlack end object Shape22: TShape Left = 56 Top = 56 Width = 65 Height = 2 Brush.Color = clBlack end object CheckBox1: TCheckBox Left = 16 Top = 24 Width = 33 Height = 17 Caption = '1' Checked = True State = cbChecked TabOrder = 0 OnClick = SelLinea end object CheckBox2: TCheckBox Left = 16 Top = 48 Width = 33 Height = 17 Caption = '2' TabOrder = 1 OnClick = SelLinea end object CheckBox3: TCheckBox Left = 16 Top = 72 Width = 33 Height = 17 Caption = '3' TabOrder = 2 OnClick = SelLinea end object CheckBox4: TCheckBox Left = 16 Top = 96 Width = 33 Height = 17 Caption = '4' TabOrder = 3 OnClick = SelLinea end end object GroupBox1: TGroupBox Left = 352 Top = 8 Width = 145 Height = 129 Caption = 'Figura' TabOrder = 1 object Shape11: TShape Left = 96 Top = 24 Width = 33 Height = 1 Brush.Color = clBlack end object Shape12: TShape Left = 96 Top = 40 Width = 33 Height = 33 end object Shape13: TShape Left = 96 Top = 80 Width = 33 Height = 41 Shape = stCircle end object RadioButton1: TRadioButton Left = 16 Top = 16 Width = 57 Height = 17 Caption = 'Línea' Checked = True TabOrder = 0 TabStop = True OnClick = SelFigura end object RadioButton2: TRadioButton Left = 16 Top = 48 Width = 73 Height = 17 Caption = 'Cuadrado' TabOrder = 1 OnClick = SelFigura end object RadioButton3: TRadioButton Left = 16 Top = 88 Width = 73 Height = 17 Caption = 'Círculo' TabOrder = 2 OnClick = SelFigura end end object GroupBox3: TGroupBox Left = 352 Top = 336 Width = 145 Height = 97 Caption = 'Dibujo' TabOrder = 2 object Label1: TLabel Left = 8 Top = 24 Width = 134 Height = 13 Caption = 'Marque punto en el cuadro' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [fsItalic] ParentFont = False end object BTerminar: TButton Left = 8 Top = 56 Width = 129 Height = 25 Caption = 'Terminar' TabOrder = 0 OnClick = Terminar end end object GroupBox4: TGroupBox Left = 352 Top = 280 Width = 145 Height = 49 Caption = 'Color' TabOrder = 3 object ShapeColor: TShape Left = 8 Top = 16 Width = 25 Height = 25 Brush.Color = clBlack Pen.Color = clWhite Pen.Width = 2 Shape = stSquare OnMouseUp = ShapeColorMouseUp end object Label2: TLabel Left = 40 Top = 16 Width = 90 Height = 13 Caption = ' Click para cambiar' end end object ColorDialog1: TColorDialog Ctl3D = True Top = 424 end end |
Unit
DibujoF.pas (versión resfrescando el PaintBox1.Canvas, se
muestran sólo los cambios)
La manera de redibujar o refrescar el contenido del Canvas que se muestra a continuación es efectiva pero, si el área del Canvas es grande, tiene el inconveniente de que consume memoria porque hay que mantener una copia guardada de todos los puntos del área. Esto hace que el proceso de refresacado pueda hacerse lento y que toda la aplicación pueda funcionar también más lentamente si la memoria requerida tiene que ser administrada en modo virtual por no haber disponible suficiente memoria real. | Una
forma alternativa que no consuma tanta memoria sería manteniendo
una Lista o registro de todos los elementos colocados en el Canvas,
de manera que al refrescar se reproduzca el contenido reponiendo todos
los elementos.
Este tipo de solución debe ser más eficiente en uso de memoria y tiempo pero requiere más trabajo de programación, aunque facilitado por Delphi que dispone también de clases predefinidas para manejo de listas. |
var
Form1: TForm1; elColor: TColor; laFigura: TFigura; laLinea: integer; losPixeles: variant;
|
Se agrega la declaración
de losPixeles,
como variable de tipo predefinido variant,
que es un tipo de referencia a arreglos que se crean asignando dinámicamente
el espacio o tamaño de memoria requerido, esto es, en tiempo de
ejecución. (Ver Help: variant arrays)
El arreglo se usa para guardar el contenido en pixeles de la propiedad Canvas del PaintBox1: valores de color de cada uno de los puntos del área rectangular. |
procedure
TForm1.PaintBox1MouseUp(Sender:
TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var i,j: integer; begin with PaintBox1.Canvas do begin Brush.Color:=elColor; Pen.Color:=elColor; Pen.Width:=laLinea; case laFigura of linea: begin MoveTo(X,Y); LineTo(X+60,Y+60); end; cuadrado: Rectangle(X,Y,X+60,Y+60); circulo : Ellipse(X,Y,X+60,Y+60); end; end; // Guardado de todos los puntos del área del Canvas for i:=0 to PaintBox1.Width do for j:=0 to PaintBox1.Height do losPixeles[i,j]:=PaintBox1.Canvas.Pixels[i,j]; end; |
Se agrega la declaración
de variables enteras para el recorrido del área rectangular.
Se guarda el contenido en pixeles de toda el área del Canvas del PaintBox1, porque no se sabe cual pedazo pudiera quedar cubierto que necesite ser refrescado al descubrirse. Para mayor eficiencia pudiera guardarse sólo los pixeles que se hayan modificado por el dibujo de nuevas figuras. |
procedure
TForm1.Inicializar(Sender:
TObject);
var i, j: integer; begin elColor:=clBlack; laLinea:=1; laFigura:=linea; losPixeles := VarArrayCreate([0,PaintBox1.Width, 0,PaintBox1.Height],varInteger); with PaintBox1 do begin Canvas.Rectangle(0,0,Width,Height); for i:=0 to Width do for j:=0 to Height do losPixeles[i,j]:=Canvas.Pixels[i,j]; end; end; |
Se agrega la declaración
de variables enteras para el recorrido de todos los puntos del área
rectangular.
Se aprovecha también el evento OnActivate
de la forma para hacer la creación del arreglo-matriz con capacidad
para almacenar los valores de todos los pixeles del área de dibujo
Canvas
del PaintBox1
(Ver Help: variant arrays),
para
|
procedure
TForm1.PaintBox1Paint(Sender:
TObject);
var i, j: integer; begin with PaintBox1.Canvas.ClipRect do for i:=Left to Right do for j:=Top to Bottom do PaintBox1.Canvas.Pixels[i,j]:=losPixeles[i,j]; end; |
Se agrega la declaración
de variables enteras para el recorrido de todos los puntos del área
rectangular.
Cuando una forma que había quedado cubierta total o parcialmente se descubre, queda definida en la propiedad ClipRect del Canvas, de tipo TRect, la porción rectangular ahora descubierta y que necesita ser redibujada (refrescada), tal como Windows lo detecta. El área queda definida por los valores de los campos del registro (record) tipo TRect: Left, Top, Right y Bottom, en coordenadas relativas al punto esquina izquierda arriba del rectángulo del Canvas. Sólo es necesario entonces refrescar esa porción, lo cual contribuye a la eficiencia de la aplicación ya que redibujar un área grande consume cierto tiempo. El refrescado se hace restaurando en la propiedad Pixeles del Canvas los valores guardados en el arreglo dinámico losPixeles. |