Boletín Pascal #23 - 23-JUN-2001
INDICE
1. UNAS PALABRAS DEL EDITOR
2. HASH TABLES - Por Alirio A. Gavidia B.
- Cómo tomar una decisión
- Las alternativas – orden
- Más cerca del "case..of"
- Pros y contras
- Las listas o tablas "Hash"
- Enfrentando la manera de hallar el índice - Método de
multiplicación
- Manipulando cadenas de caracteres
- Conclusión
3. KYLIX DESKTOP DEVELOPER EDITION POR SOLO $199!
4. CARGANDO UN ARCHIVO CON UNA IMAGEN JPEG EN UN BITMAP
5. PE EXPLORER
6. PESTPATROL
7. DIVISION DE PALABRAS (GUIONADO)
8. GREATIS TFORMDESIGNER 3.0
9. EL NOVATO Y EL EXPERTO - Por Alejandro Giraldo Niño
10. OBTENIENDO EL ICONO DE UNA APLICACION O DOCUMENTO
¡AYUDENOS!
________________________________________________________________________
1. UNAS PALABRAS DEL EDITOR
Antes de comenzar, quisiera decir que lamento el retraso, pero debido a
razones laborales no he podido sacar antes esta edición del boletín.
Para aquellos que estén interesados en una comparación entre Delphi y
Visual Basic, dénle una mirada a la última edición de nuestro Boletín
para Desarrolladores:
http://www.latiumsoftware.com/es/developers/0016.php
De la mano de Alejandro Giraldo Niño vuelve la sección "El Novato y el
Profesional", dedicada a mostrar a los más principiantes en Delphi
algunas características un poco más avanzadas, en este caso mostrándonos
cómo usar la propiedad Tag para identificar un componente de un
formulario.
El Grupo Albor está en campaña solicitando a Borland un Delphi en
español. No adherimos a los términos de la campaña, pero sí a la
intención: http://www.grupoalbor.com/castellano/Castellano.htm
¡En Latium Software estamos celebrando nuestro primer aniversario! Con
sus picos y valles, y casi sin darnos cuenta, ha transcurrido ya todo un
año... Recuerdo que en su primera emisión este boletín comenzó con no
más de 100 suscriptores, y a un año del lanzamiento, ya suman más de
2.650! Me gustaría agradecerles a todos ustedes, especialmente a quienes
nos han acompañado desde el principio, a los webmasters que enlazaron a
nuestro sitio, a aquellos que colaboraron con el boletín con sus
artículos (particularmente a Alirio Gavidia), a quienes nos contactaron
para felicitarnos por el boletín y nos animaron a seguir con este
esfuerzo, a todos los que nos recomendaron a sus colegas, a todos los
que votaron por nosotros en los rankings, y finalmente no me quiero
olvidar de todas las empresas que confiaron en nosotros para presentar
sus productos.
En este nuevo año habrá muchos cambios, y esperamos poder cubrir sus
expectativas mucho mejor. La publicación "AbracaDelphi!" ya no se emite
más, pero les haremos un lugar en este boletín a los más principiantes,
comenzando en el próximo número con un ejemplo de una aplicación de
bases de datos.
También va un agradecimiento muy especial por su participación a los
miembros de nuestro Foro para Programadores en Delphi en Yahoo! Groups,
y por supuesto también para los del Foro para Desarrolladores de
Software. Es para mí un honor un placer compartir los foros con estas
personas. A propósito, aprovecho para recordarles las direcciones e
invitarlos a todos a participar:
Programadores en Delphi
http://espanol.groups.yahoo.com/group/delphi-intermedio
Suscripción: delphi-intermedio-subscribe@gruposyahoo.com
Cantidad de miembros: 140
Promedio de mensajes diarios: 3,6
Desarrolladores de Software
http://es.groups.yahoo.com/group/desarrolladores-software
Suscripción: desarrolladores-software-subscribe@yahoogroups.com
Cantidad de miembros: 51
Promedio de mensajes diarios: 1,3
Muchas gracias a todos y sigan con nosotros!
Ernesto De Spirito
eds2008 @ latiumsoftware.com
________________________________________________________________________
JfControls Lib. Multilenguaje. Multiapariencia. Skins. Privilegios. Más
de 40 componentes integrados y personalizables. Múltiples problemas de
programación resueltos. Administración centralizada de recursos. Para
Delphi 3-2006 y C++ Builder 3-6. http://www.jfactivesoft.com/spindex.htm
________________________________________________________________________
2. HASH TABLES - Por Alirio A. Gavidia B. <alirio@gavidia.org>
Cómo tomar una decisión
=======================
Un problema común en la programación está en la toma de un rumbo basado
en el valor de una secuencia literal de caracteres. Esto es simplemente
poder ejecutar una opción si por ejemplo la variable "Pago" es
'efectivo', 'cheque', 'tarjeta' o 'transferencia'.
Podríamos diseñar una secuencia de código como la siguiente:
Const
STransfer = 'transferencia';
SCheck = 'cheque';
SCard = 'tarjeta';
SCash = 'efectivo';
SPayMode = 'Forma de pago';
SUnknow = 'desconocido';
If pago=SCash then
...
If pago=SCheck then
...
If pago=SCard then
...
If pago=STransfer then
...
Esto funcionaría, y para ser sincero no me preocuparía mucho por ello.
Es práctico. Sin embargo si hablamos de más opciones (unas 32, se me
ocurre) se puede volver algo incómodo y ciertamente poco elegante.
La opción de un "case" es, en este nivel, inutilizable. Simplemente se
requiere tipos matemáticamente "contables" (enteros, letras y enume-
rados). Esto permite que el "case" sea realmente eficiente (a diferencia
del "Case" de lenguajes xBase).
Mejorando el ejemplo anterior podemos ir a:
If pago=SCash then
...
else If pago= SCheck then
...
else If pago= SCard then
...
else If pago= STransfer then
...
Esto cambio evita que se hagan "n" lecturas para igual número de
opciones, pero, para el caso de la última opción, o peor aún, el caso
en que la opción sea diferente a las esperadas, igual se realizan "n"
consultas.
Las alternativas – orden
========================
El orden parece ser la vía. Se establece una lista ordenada y se realiza
una búsqueda binaria (divide y conquista). Supongamos tenemos 32
elementos: Dividimos en dos de 16 y seleccionamos, dividimos en dos de 8
y seleccionamos, dividimos en dos de 4 y seleccionamos, dividimos en dos
de 2 y seleccionamos, y se escoge el final. Total: 5 decisiones (nótese
el hecho de que 2 elevado a la 5ta potencia es 32).
Hasta ahora la búsqueda binaria nos garantiza para 32 opciones un máximo
de 5 (log N) consultas (en realidad, 50% de las búsquedas se resolverían
en 5 consultas, piénselo). Los "if" anidados nos darían en promedio 16
consultas (n/2).
Más cerca del "case..of"
========================
Una idea que siempre se presenta, y que he visto usar, es tomar la
primera letra. Para el ejemplo inicial esto nos dejaría usar una
estructura tipo "Case", pero al llegar a la letra "t" de "tarjeta" y de
"trasferencia" (o "c" de "cash", "check"o "card" en inglés) necesita-
ríamos, al menos, colocar un "if". Otra aproximación sería crear una
función que nos dé un número por identificador. Propongo para el ejemplo
dado lo siguiente:
Function IdentIndex(AIdent: String): integer;
Begin
Result := length(AIdent) + Ord(AIdent[3])
end
sCash 109 // en español
SCheck 107 // en español
SCard 121 // en español
STransfer 110 // en español
sCash 119 // en inglés
SCheck 106 // en inglés
SCard 118 // en inglés
STransfer 105 // en inglés
Ahora es posible usar "Case":
Var
Ident : string;
begin
Ident := 'efectivo';
if InputQuery('Hash', SPayMode, Ident) then
case IdentIndex(Ident) of
109: ShowMessage(SCash);
107: ShowMessage(SCheck);
121: ShowMessage(SCard);
110: ShowMessage(STransfer);
else
ShowMessage(SUnknown);
end
end;
Pros y contras
==============
El método es interesante ante el hecho de reducir el número de consultas
necesarias a una. Sin embargo tenemos ahora la sobrecarga de determinar
la función adecuada que devuelva los índices. Una incorrecta determi-
nación de esta fórmula nos presenta los siguientes problemas:
* Repetición de índices
* Índices esparcidos
* Lentitud en evaluación de la fórmula
El último punto es muy obvio y no lo comentaré.
La repetición de índices nos obliga a pasos adicionales dentro del
"Case". Si tenemos dos identificadores generando el mismo índice hay que
hacer al menos un análisis adicional para reconocer cuál es el camino
correcto.
Los índices esparcidos son un inconveniente si pretendemos construir una
tabla. En este caso podríamos diseñar un arreglo como el que a conti-
nuación ejemplifico:
Begin
:
FillChar(JumpTable,SizeOf(JumpTable), 0);
JumpTable[IdentIndex(SCash)] := PayModeCash;
JumpTable[IdentIndex(SCheck)] := PayModeCheck;
JumpTable[IdentIndex(SCard)] := PayModeCard;
JumpTable[IdentIndex(sTransfer)] := PayModeTransfer;
:
End;
Var
JumpTable : array [100..130] of TRoutine;
Index : Integer;
Ident : string;
Begin
Ident := SCash;
InputQuery('Hash', SPayMode, Ident);
Index := IdentIndex(Ident);
if (Index in [100..130]) and (Assigned(JumpTable[Index])) then
JumpTable[Index]
end;
Tenemos JumpTable como un arreglo de rutinas que se ejecutan dado un
índice. Pero hay un desperdicio ya que utilizamos desde 30 posibles
valores sólo cuatro. Lo ideal sería que "IdentIndex" nos otorgara cuatro
valores consecutivos. A veces tenemos que vivir con eso, pero a veces
no. En el caso del analizador de un compilador normalmente es posible
dedicar esfuerzo para determinar la fórmula adecuada sin desperdicio.
Pero cuando son muchas palabras, o no se conocen todas de antemano,
tenemos que reenfocar todo este asunto.
Las listas o tablas "Hash"
==========================
La fórmula para determinar el índice debe por lo general ser limitada
dado que no solemos estar en capacidad de crear arreglos con índices de
14 dígitos. La solución más simple es dividir. Si queremos limitarnos a
sólo 8 posibilidades podríamos dividir entre 8 y obtener el resto.
Así los nuevos valores serán
sCash 5 // en español
SCheck 3 // en español
SCard 1 // en español
STransfer 6 // en español
sCash 7 // en inglés
SCheck 2 // en inglés
SCard 6 // en inglés
STransfer 1 // en inglés
Demasiado bueno, pero a veces no resulta así, por ejemplo limitándolo a
cuatro se hace presente la repetición de los índices.
sCash 1 // en español
SCheck 3 // en español
SCard 1 // en español
STransfer 2 // en español
sCash 3 // en inglés
SCheck 2 // en inglés
SCard 2 // en inglés
STransfer 1 // en inglés
El límite sugerido para una tabla suele ser un número primo que no sea
muy cercano a una potencia de 2.
Para tablas con repetición de índices normalmente se obtiene el índice,
se consulta la tabla, y si la celda esta libre se toma, sino se busca la
siguiente libre de manera linear. Cada falla en esta búsqueda reduce la
eficiencia del método.
Las listas de hashing se construyen como listas de listas. Cada nodo de
la lista principal apunta a la lista de identificadores que corresponden
al índice dado. Hay otras soluciones a este problema, pero no las
analizaré aquí.
Enfrentando la manera de hallar el índice - Método de multiplicación
====================================================================
Me gusta esta manera de resolver el problema cuando tenemos un número en
un rango amplio y queremos reducirlo en un rango menor.
Para número de posibles valores 2^N (2 elevado a la N). "Key" es un
valor generado directamente al evaluar el identificador.
La fórmula se determina de la siguiente manera:
Hash := ((K*Key) and M) mod S;
Dónde:
- K es 158 para índices de 8 bits, 40503 para índices de 16 bits y
2654435769 para índices de 32 bits.
- S es 2 elevado la diferencia del número de bits y N.
- M es el tamaño de la tabla menos 1. (2^N-1).
Para N=10 tenemos
K=40503 (16 bits)
S=2^(16-10) = 2^6 = 64
M=1023
Hash := ((40503*Key) and 1023) mod 64;
El resultado será un valor desde 0 hasta 63 (de una rango original de 0
a 1023).
Manipulando cadenas de caracteres
=================================
Usualmente se puede simplemente tomar cada carácter de una cadena,
sumarlos y tomar el modulo con 256. Sin embargo esto genera problemas
con los anagramas ('arroz' y 'zorra' por ejemplo) y palabras similares.
Una solución es utilizar xor ("o" exclusivo) en lugar de la adición y
agregar una tabla intermedia de valores pseudo-aleatorios que nos den
una mejor distribución, además considerar en el algoritmo la posición
de cada letra y no sólo su valor intrínseco.
Conclusión
==========
Utilizar "hash tables" puede dar resultados espectaculares en situa-
ciones donde se tiene conocimiento de los identificadores a buscar y/o
dónde podemos desperdiciar memoria a cambio de velocidad. En lo
particular, los compiladores e interpretadores son, a mi parecer, los
mejores casos. También es útil en sistemas de base de datos donde el
problema no sea espacio sino la necesidad de mínima cantidad de
lecturas. Sin embargo un problema a tener en cuenta es la eliminación de
registros en la tabla, algo usual en bases de datos.
-------------------
El código fuente que acompaña este artículo puede descargarse desde:
http://www.latiumsoftware.com/es/file.php?id=p23
------------------
Para aprender más:
* D. E. Knuth, 1973, The Art of Computer Programming, Vol. 3: Sorting
and Searching, Reading, MS: Addison-Wesley.
------------------------------------
Copyright © 2001 por Alirio A. Gavidia B. Todos los derechos reservados.
Se permite la publicación de este material por cualquier medio por parte
de cualquiera siempre que este no sea modificado en contenido y se cite
la fuente original.
________________________________________________________________________
3. KYLIX DESKTOP DEVELOPER EDITION POR SOLO $199!
Borland está tratando de vender tantas licencias de Kylix como sea
posible y por un tiempo limitado ha bajado significativamente el precio
de Kylix DDE de US $999 a solamente US $199. Sí, una licencia de Kylix
DDE cuesta solamente US $199! Si está intereado en el desarrollo de
aplicaciones bajo Linux, este es definitvamente el mejor momento para
comprar:
http://shop.borland.com/Product/0,1057,3-15-CQ100479,00.html
La oferta expira el 23 de Agosto del 2001. El formulario de orden de
pedido permite direcciones de facturación internacionales, pero hay que
proveer una dirección en los EE.UU. para el despacho. Si no tiene una
dirección en los EE.UU. -ya sea propia o provista por un tercero- puede
preguntar a su representante Borland local para ver a cuánto lo están
vendiendo. Generalmente cuesta un poco más, pero no tiene que preocu-
parse del traslado, aduanas, etc.
________________________________________________________________________
4. CARGANDO UN ARCHIVO CON UNA IMAGEN JPEG EN UN BITMAP
Esta es una pregunta recurrente en foros y grupos de noticias. Para
cargar una imagen JPEG podemos usar la unidad "jpeg" que viene con
Delphi (si no la tiene instalada, creo que puede encontrarla en el
directorio "Extras" en el CD ROM de Delphi). Puede encontrar alterna-
tivas en la red, pero voy a usar esta porque viene con Delphi y todos
la tienen.
Para cargar una imagen JPEG en un componente Image tiene que hacer
algo como lo siguiente:
uses jpeg;
procedure TForm1.Button1Click(Sender: TObject);
var
jpg: TJpegImage;
begin
jpg := TJpegImage.Create;
try
jpg.LoadFromFile('d:\path\file.jpg');
Image1.Picture.Assign(jpg); // Carga la imagen como JPEG
finally
jpg.Free;
end;
end;
La imagen es cargada como JPEG, lo que está bien, a menos que queramos
acceder a las propiedades Pixels y ScanLine del Bitmap. Por ejemplo, lo
siguiente no funcionará:
Image1.Canvas.Pixels[0,0] := clWhite; // Pixel sup-izq en blanco
Si pretendemos realizar algún procesamiento de imagen deberemos cargar
la imagen JPEG como un bitmap. Para hacer eso tenemos que usar el método
Assing de un Bitmap, no un Picture, así que sólo tenemos que hacer un
pequeño cambio en el procedimiento que presentáramos arriba:
Image1.Picture.Bitmap.Assign(jpg); // Carga la imagen como BMP
________________________________________________________________________
5. PE EXPLORER
PE Explorer es un analizador de código fuente, herramienta de recursos y
desensamblador. PE Explorer le permite ver, editar y reparar la
estructura interna y recursos de archivos PE (Portable Executable) tales
como EXE, DLL, DRV, BPL, DPL, SYS, CPL, OCX, SCR y otros ejecutables
Win32. Su capacidad de edición visual le permite modificar rápidamente
los recursos sin tener que escribir ningún script.
Aplicación : PE Explorer v1.20
Sitio Web : Heaven Tools http://www.heaventools.com
Licencia : Shareware (evaluación de 30 días)
Descarga : http://www.heaventools.com/download/pexsetup.zip (~1Mb)
________________________________________________________________________
6. PESTPATROL
PestPatrol detecta y remueve más de 16.000 pestes no-virus, y está
diseñado para ser usado con software anti-virus para proveer protección
completa. Corre rápido, detectando y removiendo programas y documentos
maliciosos o no deseados incluyendo Bombas ANSI, relacionados con
Anarquía, Molestias, Pestes AOL, Puertas Traseras, Tarjetería, Negación
de Servicio, Exploits, Explosivos, Java y ActiveX Hostiles, Guarda-
claves, Bombarderos de Correo, Herramientas de Craqueo, Herramientas de
Craqueo y Captura de Contraseñas, Herramientas Phreak, Escaneadores de
Puertos, Control/Administración Remota, Husmeadores, Burlones, Espías,
Troyanos y Herramientas de Creación de Troyanos, War Dialers, Gusanos,
etc.
Aplicación : PestPatrol
Sitio Web : http://www.pestpatrol.com
Licencia : Tryware (detecta pero no remueve)
Descarga : http://consumerdownloads.ca.com/consumer/apps/2008/2/
na_aspy_ca_32_en_ASPYLE_MET_trial.exe (~20.2 MB)
________________________________________________________________________
7. DIVISION DE PALABRAS (GUIONADO)
A veces necesitamos mostrar o imprimir un texto, y quisiéramos dividir
con guiones las palabras largas que no caben en al final de una línea,
evitando que "caiga" entera en la línea siguiente dejando demasiado
espacio sin usar.
El problema principal que se presenta es cómo dividir una palabra en
sílabas. Para ello he escrito la siguiente función que separa una
palabra española en sílabas. Es imperfecta, pero al menos constituye
una base:
procedure Syllabify(Silabas: TStringList; s: string);
const
Consonantes = ['b','B','c','C','d','D','f','F','g','G',
'h','H','j','J','k','K','l','L','m','M','n','N',
'ñ','Ñ','p','P','q','Q','r','R','s','S','t','T',
'v','V','w','W','x','X','y','Y','z','Z'];
VocalesFuertes = ['a','A','á','Á','e','E','é','É',
'í','Í','o','ó','O','Ó','ú','Ú'];
VocalesDebiles = ['i','I','u','U','ü','Ü'];
Vocales = VocalesFuertes + VocalesDebiles;
Letters = Vocales + Consonantes;
var
i, j, n, m, guion: integer;
begin
j := 2;
s := #0 + s + #0;
n := Length(s) - 1;
i := 2;
Silabas.Clear;
while i <= n do begin
guion := 0; // No dividir
if s[i] in Consonantes then begin
if s[i+1] in Vocales then begin
if s[i-1] in Vocales then guion := 1;
end else if (s[i] in ['s', 'S']) and (s[i-1] in ['n', 'N'])
and (s[i+1] in Consonantes) then begin
guion := 2;
end else if (s[i+1] in Consonantes) and
(s[i-1] in Vocales) then begin
if s[i+1] in ['r','R'] then begin
if s[i] in ['b','B','c','C','d','D','f','F','g',
'G','k','K','p','P','r','R','t','T','v','V']
then guion := 1 else guion := 2;
end else if s[i+1] in ['l','L'] then begin
if s[i] in ['b','B','c','C','d','D','f','F','g',
'G','k','K','l','L','p','P','t','T','v','V']
then guion := 1 else guion := 2;
end else if s[i+1] in ['h', 'H'] then begin
if s[i] in ['c', 'C', 's', 'S', 'p', 'P']
then guion := 1 else guion := 2;
end else
guion := 2;
end;
end else if s[i] in VocalesFuertes then begin
if (s[i-1] in VocalesFuertes) then guion := 1
end else if s[i] = '-' then begin
Silabas.Add(Copy(s, j, i - j));
Silabas.Add('-');
inc(i);
j := i;
end;
if guion = 1 then begin // Dividir aquí
Silabas.Add(Copy(s, j, i - j));
j := i;
end else if guion = 2 then begin // Dividir después
inc(i);
Silabas.Add(Copy(s, j, i - j));
j := i;
end;
inc(i);
end;
m := Silabas.Count - 1;
if (j = n) and (m >= 0) and (s[n] in Consonantes) then
Silabas[m] := Silabas[m] + s[n] // Ultima letra
else
Silabas.Add(Copy(s, j, n - j + 1)); // Ultima sílaba
end;
Para probar este procedimiento puede poner un Textbox y una Label en un
formulario y en el evento Change del Textbox escribir:
procedure TForm1.Edit1Change(Sender: TObject);
var
Silabas: TStringList;
begin
Silabas := TStringList.Create;
try
Syllabify(Silabas, Edit1.Text);
Label1.Caption := StringReplace(Trim(Silabas.Text),
#13#10, '-', [rfReplaceAll]);
finally
Silabas.Free;
end;
end;
Ahora que tenemos un procedimiento de silabeo, debemos notar que no
podemos guionar una palabra en cualquier corte de sílaba. Usualmente es
correcto y/o deseable unir las sílabas pequeñas de los extremos
izquierdo y/o derecho de una palabra para garantizar por ejemplo que
haya por lo menos dos sílabas de cada lado de la palabra cuando sea
dividida, o -como en el siguiente ejemplo- asegurarse que tengamos por
lo menos cuatro caracteres de cada lado:
procedure ApplyRules(Silabas: TStringList);
// Garantizar que haya al menos cuatro letras en los extremos
// izquierdo y derecho de las palabra
begin
with Silabas do begin
if Count = 1 then exit;
while Count > 1 do begin
if Length(Strings[0]) >= 4 then break;
Strings[0] := Strings[0] + Strings[1];
Delete(1);
end;
while Silabas.Count > 1 do begin
if Length(Strings[Count-1]) >= 4 then break;
Strings[Count-2] := Strings[Count-2]
+ Strings[Count-1];
Delete(Count-1);
end;
end;
end;
Finalmente, llega la hora de recorrer el texto separando las líneas de
un párrafo determinando qué palabras deberían ser guionadas. El
siguiente ejemplo hace eso con un texto a mostrar en un Memo:
procedure Hyphenate(Memo: TMemo; OriginalText: TStrings);
var
parrafo, i, j, k, m, n, MaxLineWidth: integer;
s, line: string;
Bitmap: TBitmap;
Canvas: TCanvas;
Silabas: TStringList;
begin
Silabas := TStringList.Create;
try
// Necesitamos un lienzo (canvas) para usar su método TextWidth
// para saber el ancho del texto y ver si cabe en el área
// cliente o no. La clase TMemo no tiene una propiedad Canvas
// así que por eso creamos uno nosotros.
Bitmap := TBitmap.Create;
Canvas := Bitmap.Canvas;
try
Canvas.Font := Memo.Font;
MaxLineWidth := Memo.ClientWidth - 6; // Ancho máximo
Memo.Lines.Clear;
for parrafo := 0 to OriginalText.Count - 1 do begin
// Por cada párrafo
s := OriginalText[parrafo]; // Obtener el párrafo original
// Obtener las líneas en que tenemos que cortar un párrafo
while Canvas.TextWidth(s) > MaxLineWidth do begin
// Primero encontramos (en "j") el indice del comienzo de la
// primera palabra que no cabe (la que hay que dividir)
j := 1;
n := Length(s);
i := 2;
while i <= n do begin
if (s[i-1] = ' ') and (s[i] <> ' ') then
j := i; // ultimo comienzo de una palabra
if Canvas.TextWidth(Copy(s, 1, i)) > MaxLineWidth then
break; // alcanzado un ancho que no entra
inc(i);
end;
// ¿Dónde ocurre el corte?
if s[i] = ' ' then begin
// Bien! Cortamos en un espacio
Memo.Lines.Add(Copy(s, 1, i - 1)); // Agregamos la línea
s := Copy(s, i + 1, n - i); // Removemos la línea
end else begin
// Cortamos en una palabra. Ahora encontramos (en "k") el
// primer espacio después de la palabra (k)
k := j + 1;
while (k <= n) and (s[k] <> ' ') do inc(k);
// Dividir la palabra en sílabas
Syllabify(Silabas, Copy(s, j, k - j));
ApplyRules(Silabas);
// Chequear (en "m") cuántas sílabas entran
m := 0;
Line := Copy(s, 1, j-1);
while Canvas.TextWidth(Line + Silabas[m] + '-')
<= MaxLineWidth do begin
Line := Line + Silabas[m];
inc(m);
end;
if (m <> 0) and (Silabas[m-1] <> '-') then begin
// Guionar
Line := Line + '-';
j := Length(Line);
if Silabas[m] = '-' then inc(j);
end;
Memo.Lines.Add(Line); // Agregamos la línea
s := Copy(s, j, n - j + 1); // Removemos la línea
end;
end;
Memo.Lines.Add(s); // Agregar la última línea (entra)
end;
finally
Bitmap.Free;
end;
finally
Silabas.Free;
end;
end;
Para probar el procedimiento, coloque un componente Memo en un formu-
lario, hágalo alinear por ejemplo a la parte superior del formulario
(Align = alTop) y escriba el siguiente código en el evento Resize del
formulario:
procedure TForm1.FormResize(Sender: TObject);
var
TextoOriginal: TStringList;
begin
TextoOriginal := TStringList.Create;
try
TextoOriginal.Add('Si se ha preguntado cómo hacen los '
+ 'programas procesamiento de textos para dividir palabras '
+ 'con de guiones al final de una línea, he aquí un '
+ 'ejemplo sencillo (en comparación con los que usan las '
+ 'aplicaciones de procesamiento de textos).');
TextoOriginal.Add('Este es un segundo párrafo que se provee '
+ 'con fines de ejemplo.');
Hyphenate(Memo1, TextoOriginal);
finally
TextoOriginal.Free;
end;
end;
El código fuente completo de este artículo podrá encontrarlo adjunto a
este boletín.
________________________________________________________________________
8. GREATIS TFORMDESIGNER 3.0
¿Qué es Greatis TFormDesigner?
------------------------------
TFormDesigner es un componente para Delphi 3-5 y C++ Builder 3-5 que le
permite mover y cambiar el tamaño de cualquier control de su formulario.
Sólo coloque un componente TFormDesigner en su formulario, establezca su
propiedad Active en True, y disfrute - no tiene que preparar su formu-
lario para usar TFormDesigner.
Lo lindo de TFormDesigner es que funciona en tiempo de ejecución,
mientras que el diseñador estándar funciona sólo en tiempo de diseño.
Para facilitar su uso, TFormDesigner replica la cara del diseñador de
formularios estándar de Delphi y C++ Builder.
Una vez que TFormDesigner haya sido activatado, puede seleccionar
cualquier control en su formulario con la tecla Tab o con un clic del
ratón, y luego moverlo o modificar su tamaño.
Características
---------------
- Selección múltiple
- Compatibilidad con formularios ActiveX
- Compatibilidad con formularios MDI
- Bloque y protección de controles
- Diálogo de Alineación
- Diálogo de Tamaño
- Paleta de Alineación
- Manejadores de arrastre configurables
- Rejilla de diseño configurable
- Pistas de tamaño/coordenadas
- Manual de Usuario imprimible
- Versión de evaluación para Delphi 3-5 y C++ Builder 3-5
- Editor de formularios totalmente funcional como demo gratis
Descarga
--------
Un EXE-demo compilado, documentación imprimible en formato PDF y la
versión de prueba de TFormDesigner para Delphi 3-5 y C++ Builder 3-5 se
incluyen en el kit demostrativo, que puede descargar de la siguiente
dirección: http://www.greatis.com/formdesdemo.zip (~756K)
Licencia
--------
TFormDesigner cuesta US$ 29.95 para una licencia de un solo usuario.
Más información
---------------
Vea más información en la página web de TFormDesigner:
http://www.greatis.com/formdes.htm
Para más información, contacte a Greatis Software <b-team@greatis.com>
________________________________________________________________________
9. EL NOVATO Y EL EXPERTO - Por Alejandro Giraldo Niño
Algunas veces, cuando estamos aprendiendo, buscamos ser algo iterativos
en nuestro código. La propiedad TAG que tienen todos los componentes nos
permite identificar el Componente de manera única, incluso hasta podemos
hacer grupos de Componentes utilizando el mismo valor de TAG. Este es un
buen ejemplo del uso iterativo de la propiedad TAG.
Supongamos, que necesitamos almacenar en un fichero *.ini todos los
parámetros que el usuario modificó en un formulario que contiene 30
Instancias del Componente (TCheckBox), de los cuales, por ejemplo 15
pertenecen a una sección "Activos" y los otros 15 a la sección de
"Pasivos".
NOVATO:
uses IniFiles;
procedure TForm1.GuardarParametros;
var
Ini: TIniFile;
begin
Ini := TIniFile.Create(ExtractFilePath(Application.ExeName)
+'Prueba.ini');
// Guardar Activos
Ini.WriteBool('Activos','Activo01',Checkbox1.Checked);
Ini.WriteBool('Activos','Activo02',Checkbox2.Checked);
Ini.WriteBool('Activos','Activo03',Checkbox3.Checked);
Ini.WriteBool('Activos','Activo04',Checkbox4.Checked);
Ini.WriteBool('Activos','Activo05',Checkbox5.Checked);
Ini.WriteBool('Activos','Activo06',Checkbox6.Checked);
Ini.WriteBool('Activos','Activo07',Checkbox7.Checked);
Ini.WriteBool('Activos','Activo08',Checkbox8.Checked);
Ini.WriteBool('Activos','Activo09',Checkbox9.Checked);
Ini.WriteBool('Activos','Activo10',Checkbox10.Checked);
Ini.WriteBool('Activos','Activo11',Checkbox11.Checked);
Ini.WriteBool('Activos','Activo12',Checkbox12.Checked);
Ini.WriteBool('Activos','Activo13',Checkbox13.Checked);
Ini.WriteBool('Activos','Activo14',Checkbox14.Checked);
Ini.WriteBool('Activos','Activo15',Checkbox15.Checked);
// Guardar Pasivos
Ini.WriteBool('Pasivos','Pasivo01',Checkbox16.Checked);
Ini.WriteBool('Pasivos','Pasivo02',Checkbox17.Checked);
Ini.WriteBool('Pasivos','Pasivo03',Checkbox18.Checked);
Ini.WriteBool('Pasivos','Pasivo04',Checkbox19.Checked);
Ini.WriteBool('Pasivos','Pasivo05',Checkbox20.Checked);
Ini.WriteBool('Pasivos','Pasivo06',Checkbox21.Checked);
Ini.WriteBool('Pasivos','Pasivo07',Checkbox22.Checked);
Ini.WriteBool('Pasivos','Pasivo08',Checkbox23.Checked);
Ini.WriteBool('Pasivos','Pasivo09',Checkbox24.Checked);
Ini.WriteBool('Pasivos','Pasivo10',Checkbox25.Checked);
Ini.WriteBool('Pasivos','Pasivo11',Checkbox26.Checked);
Ini.WriteBool('Pasivos','Pasivo12',Checkbox27.Checked);
Ini.WriteBool('Pasivos','Pasivo13',Checkbox28.Checked);
Ini.WriteBool('Pasivos','Pasivo14',Checkbox29.Checked);
Ini.WriteBool('Pasivos','Pasivo15',Checkbox30.Checked);
Ini.Free;
end;
EXPERTO:
uses IniFiles;
procedure TForm1.GuardarParametros;
var
Ini: TIniFile;
I: Integer;
begin
Ini := TIniFile.Create(ExtractFilePath(Application.ExeName)
+'Prueba.ini');
for I := 0 to ComponentCount -1 do
if Components[I] is TCheckbox then
if TCheckBox(Components[I]).Tag = 1 then
// Guardar Activos
Ini.WriteBool('Activos', Components[I].Name,
TCheckBox(Components[I]).Checked)
else
// Guardar Pasivos
Ini.WriteBool('Pasivos', Components[I].Name,
TCheckBox(Components[I]).Checked);
Ini.Free;
end;
EXPLICACION:
El Componente TForm hereda de la clase TComponent la propiedad
ComponentCount. Esta propiedad devuelve la cantidad de componentes que
tiene el formulario y nosotros simplemente la utilizamos para saber a
cuántos componentes le tenemos que preguntar si son o no de tipo
TCheckBox. La propiedad Components, también heredada de la clase
TComponent, es la que apunta a todos los componentes del formulario y
la utilizamos para saber cual es el TAG de cada Componente, y de esta
forma saber qué tenemos que hacer...
Okay, este es un pequeño ejemplo de cómo utilizar un mismo procedimiento
OnClick para 5 los botones del formulario. Claro esta!! utilizando la
propiedad TAG.
procedure TForm1.Button1Click(Sender: TObject);
begin
case TButton(Sender).Tag of
1: Showmessage('Soy el boton 1');
2: Showmessage('Soy el boton 2');
3: Showmessage('Soy el boton 3');
4: Showmessage('Soy el boton 4');
5: Showmessage('Soy el boton 5');
end;
end;
A cada uno de los Botones hay que especificarles, en el Inspector de
Objetos, en lo eventos que tomen el evento "Button1Click" y modificarles
el Tag.
------------------
Copyright (c) 2001 Alejandro Giraldo Niño.
alejogn@hotmail.com, alejogn@yahoo.com, UINICQ:6414205.
________________________________________________________________________
10. OBTENIENDO EL ICONO DE UNA APLICACION O DOCUMENTO
Marian Hubinsky me envió el siguiente código en respuesta a mi artículo
"Obteniendo el icono de una aplicación o documento", diciéndo me que
GetAssociatedIcon (la función que yo escribí) "no extrae correctamente
algunos iconos pequeños, esta otra es más chica y funciona un poco
mejor":
// obtener icono del Shell
uses ShellApi;
function SHSmallIcon(xpath : string; getopen : boolean) : hicon;
var
fili : TSHFileInfo;
aka : Integer;
begin
aka := SHGFI_ICON or SHGFI_SMALLICON;
if getopen then aka := aka or SHGFI_OPENICON;
SHGetFileInfo(Pchar(xpath), 0, fili, sizeof(TSHFileInfo), aka);
Result := fili.hIcon;
end;
// ejemplo de uso:
procedure TForm1.Button3Click(Sender: TObject);
var
smi : hicon;
begin
smi := SHSmallIcon('c:\windows', true);
DrawIconex(Form1.Canvas.Handle, 10, 10, smi, 0, 0, 0, 0, DI_normal);
DestroyIcon(smi);
end;
// Marian Hubinsky <treeumph@tn.sknet.sk> http://wtools32.szm.com
Agradezco a Marian por su colaboración.
________________________________________________________________________
¡TÚ PUEDES AYUDARNOS!
Necesitamos tu ayuda para que este boletín pueda continuar y crecer. Una
forma en que puedes ayudarnos es enviando este enlace a tus amigos:
http://www.latiumsoftware.com/es/pascal/index.php
Otra forma es votándonos en alguno de estos rankings para darle más
visibilidad a nuestro sitio web y aumentar así el número de suscrip-
ciones al boletín, que esperamos en el futuro se traduzca también en un
mayor número de colaboraciones de artículos, trucos, etc.:
http://www.programmingpages.com/?r=latiumsoftwarecomenpascal
http://top100borland.com/in.php?who=20
http://www.lawebdelprogramador.com/buscar/votar.php?id=615
Por favor vota. Son sólo unos segundos para ti que REALMENTE pueden
hacer la diferencia. Necesitamos tu ayuda para poder continuar.
________________________________________________________________________
Si no has recibido el archivo con el código fuente completo de los
ejemplos que se presentan en este boletín, puedes descargarlo de la
siguiente dirección: http://www.latiumsoftware.com/es/file.php?id=p23
________________________________________________________________________
Página principal: http://www.latiumsoftware.com/es/pascal/index.php
Página del grupo: http://espanol.groups.yahoo.com/group/boletin-pascal/
Para suscribirse / apuntarse: boletin-pascal-subscribe@gruposyahoo.com
Para cancelar / removerse: boletin-pascal-unsubscribe@gruposyahoo.com
Para reportar problemas con la suscripción: eds2008 @ latiumsoftware.com
________________________________________________________________________
Este boletín se provee "TAL Y COMO ESTA", sin garantía de ninguna clase.
Su uso implica la aceptación de nuestros términos de licencia y de la
ausencia de garantía que puedes leer en nuestro sitio web. Allí también
encontrarás una nota sobre marcas registradas. Te animamos a que redis-
tribuyas este boletín, siempre y cuando lo hagas en forma completa
(incluyendo la información de copyright), sin modificaciones y de manera
gratuita. Los artículos son copyright de sus respectivos autores y se
reproducen aquí con el permiso de los mismos.
________________________________________________________________________
Latium Software http://www.latiumsoftware.com/es/index.php
Copyright (c) 2001 por Ernesto De Spirito. Todos los derechos reservados
________________________________________________________________________
|