Boletín Pascal #3
Los ejemplos completos de código fuente de este número están disponibles para descargar.
![]() |
![]() |
Boletín Pascal #3 INDICE 1. UNAS PALABRAS DEL EDITOR 2. BUSCADOR DE ARCHIVOS: AGREGANDO UN MENU CONTEXTUAL 3. CUESTIONES DE PORTABILIDAD: CADENAS UTF-8 Tipos de cadenas en Delphi Cadenas de Caracteres MultiByte (MBCS) en Windows Longitud de una cadena Ansi Introducción a UTF-8 (UCS Transformation Format) Codificación UTF-8 Longitud de una cadena UTF-8 4. ENLACES ________________________________________________________________________ 1. UNAS PALABRAS DEL EDITOR Si han visitado nuestro sitio web la última semana o dos, deben haber visto el nuevo "look". Si no, entonces héchenle una mirada: http://www.latiumsoftware.com/es/index.php Quisiera agradecer a un amigo mío por este buen trabajo. Por favor informen si tienen algún problema para visualizar correctamente las páginas con su navegador. Hemos agregado algunos artículos sobre Delphi. La mayoría de ellos han sido publicados en ediciones pasadas de este boletín (y sus predecesores), pero también hay cosas nuevas: Determinando el nombre corto (nombre DOS) de un archivo http://www.latiumsoftware.com/es/delphi/00007.php Accediendo a propiedades ocultas http://www.latiumsoftware.com/es/delphi/00008.php Agregando nuevos métodos y propiedades sin registrar nuevos componentes http://www.latiumsoftware.com/es/delphi/00009.php Los mantendremos informados sobre nuevas adiciones al sitio. Por favor recuerden que si tienen dudas o preguntas con respecto a los artículos de este boletín, o cualquier otra pregunta sobre programación en Delphi, las pueden enviar a nuestra lista de correo. Les reiteramos que nos gustaría escuchar sobre sus necesidades de programación para ver si podemos cubrirlas en este boletín. Saludos, Ernesto De Spirito eds2004 @ 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-7 y C++ Builder 3-6. http://www.jfactivesoft.com/spindex.htm ________________________________________________________________________ 2. BUSCADOR DE ARCHIVOS: AGREGANDO UN MENU CONTEXTUAL En este artículo continuamos construyendo nuestra aplicación de búsqueda de archivos que comenzáramos en el antiguo Boletín Delphi. En esta oportunidad vamos a agregarle un menú contextual a la lista de archivos, así el usuario puede escoger abrir el archivo o la carpeta donde éste se encuentra. Agregar un menú contextual a un control es fácil: 1) Colocar un componente TPopupMenu en el formulario; 2) Editar su propiedad Items agregando los elementos de menú con sus correspondiente manejadores para el evento OnClick; y 3) Asignar el menú al control estableciendo la propiedad PopupMenu del control. De esta forma, el menú contextual aparecerá cuando el el usuario haga clic con el botón derecho del ratón sobre el control (o presione la tecla Apps en teclados para Windows 95). Para nuestro propósito vamos a hacerlo un poco más difícil omitiendo el paso 3) y llamando al menú emergente usando el método Popup cuando lo necesitemos. Tenemos que hacerlo "a mano" porque necesitamos diferenciar entre la invocación por ratón o por teclado básicamente para determinar qué archivo o carpeta es el que deberíamos abrir. También quisiéramos hacer que la opción del menú predeterminada sea "Abrir" si el usuario hace clic con el botón derecho sobre un nombre de archivo, o que sea "Abrir carpeta" lo hace sobre una carpeta. Muy bien, suficiente introducción. ¡Manos a la obra! Coloque un compo- nente TPopupMenu en el formulario y edite su propiedad Items agregando dos elementos de menú con las siguientes propiedades: 1) Name = 'Open1' 2) Name = 'OpenFolder1' Caption = 'Abrir' Caption = 'Abrir carpeta' OnClick = 'Open1Click' OnClick = 'OpenFolder1Click' Agregue el siguiente código a los manejadores de evento: procedure TForm1.Open1Click(Sender: TObject); begin if ShellExecute(Self.Handle, nil, PChar(SelectedItem.SubItems.Strings[0] + SelectedItem.Caption), nil, nil, SW_SHOWMAXIMIZED) <= 32 then begin Application.MessageBox(cstrCouldNotExecApp, 'Error', MB_ICONEXCLAMATION); end; // if end; procedure TForm1.OpenFolder1Click(Sender: TObject); begin if ShellExecute(Self.Handle, 'explore', PChar(SelectedItem.SubItems.Strings[0]), nil, nil, SW_SHOWMAXIMIZED) <= 32 then begin Application.MessageBox(cstrCouldNotExecApp, 'Error', MB_ICONEXCLAMATION); end; // if end; Estos métodos abren un archivo y un directorio respectivamente, exactamente como hemos visto en números anteriores. La única diferencia es que aquí asumimos que SelectedItem es una variable o propiedad de tipo TListItem que referencia el elemento del objeto TListView (ListView1) que se seleccionó antes de llamar al menú, así que antes de proseguir declararemos SelectedItem. Nosotros la hemos declarado como una propiedad privada del formulario: type TForm1 = class(TForm) ... private { Declaraciones Privadas } SelectedItem: TListItem; ... Ahora deberíamos capturar los eventos OnMouseDown y OnKeyDown de ListView1 para establecer el valor de SelectedItem e invocar el menú emergente: procedure TForm1.ListView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var Col: Integer; begin Last.X := X; Last.Y := Y; if Shift = [ssRight] then begin SelectedItem := TListViewX(ListView1).GetItemAtX(X, Y, Col); if (SelectedItem <> nil) and (Col <= 1) then PopupMenu1.Items[Col].Default := True; PopupMenu1.Popup( Left + ListView1.Left + X + 10, Top + ListView1.Top + Y + 20); end; end; procedure TForm1.ListView1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if (Key = VK_APPS) or (Shift = [ssShift]) and (Key = VK_F10) then begin SelectedItem := ListView1.ItemFocused; if SelectedItem <> nil then begin PopupMenu1.Items[0].Default := True; PopupMenu1.Popup( Left + ListView1.Left + SelectedItem.Position.X + 20, Top + ListView1.Top + SelectedItem.Position.Y + 35); end; end; end; El método Popup espera las coordenadas del menú. Estas coordenadas son relativas a la pantalla así que a las coordenadas del elemento que tiene el foco o de la posición del ratón (relativas al control) le sumamos las coordenadas del formulario y del control (para hacerlas relativas a la pantalla), más un pequeño desplazamiento. ¡Y eso es todo! Ya pueden probarlo... Como siempre, el código fuente completo está disponible en nuestro sitio web: http://www.latiumsoftware.com/descarga/p0003.zip ________________________________________________________________________ 3. CUESTIONES DE PORTABILIDAD: CADENAS UTF-8 Este artículo está destinado principalmente a futuros programadores en el entorno Linux e intenta presentar algunas de las diferencias que existirán con respecto al procesamiento de cadenas entre Windows y Linux. Tipos de cadenas en Delphi ========================== Una cadema (como probablemente ya lo sepa a esta altura :-) es una secuencia de caracteres. Delphi tiene tres de tipos de cadenas: * Cadenas cortas Las cadenas cortas se declaran usando la palabra ShortString. Este tipo de cadena viene desde las viejas épocas del Turbo Pascal y se soportan para compatibilidad hacia atrás. Un variable de cadena corta normalmente usa 256 bytes (octetos) en total, aunque su longitud (almacenada en el primer byte) puede variar desde 0 a 255. Por ejemplo: var s: shortstring; begin s := 'Hola!'; La cadena s ocupa 256 bytes. s[0] es la longitud de la cadena, así que en el ejemplo su valor sería #5. No puede acceder s[0] directa- mente, sino que usted debería usar Length y SetLength. s[1] es el primer caracter ('H'), s[2] es el segundo ('o'), y así sucesivamente. Desde s[6] a s[255] los valores serían indefinidos. * Cadenas ANSI Comúnmente llamadas "cadenas largas", las cadenas ANSI se declaran usando el identificador AnsiString. Las cadenas ANSI son realmente punteros a una estructura de datos que consisten de dos de enteros (que almacenan la longitud de la cadena y el conteo de referencias) y la secuencia de bytes destinada para la cadena, que puede oscilar desde 1 byte a casi 2 GB (provisto que haya suficiente memoria). Por ejemplo: var s: ansistring; begin s := 'Hola!'; La variable s en sí misma ocupa 4 bytes (un puntero de 32 bits). La estructura de datos a la que apunta ocupa 8 bytes para los dos enteros y en este caso 5 bytes para los 5 caracteres, dando 13 bytes en total. Como con las cadenas cortas, s[1] es el primer caracter ('H'), y así sucesivamente. * Cadenas anchas Las cadenas anchas, generalmente llamadas cadenas UNICODE, son cadenas especiales donde cada caracter (de tipo WideChar) ocupa dos bytes (un Word). En el conjunto de caracteres UNICODE, los primeros 256 valores corresponden al conjunto de caracteres ANSI. Las cadenas anchas son punteros, como las cadenas ANSI, pero no cuentan las referencias, así que cuando realiza una copia de una cadena anchas la cadena realmente se copia (en el caso de las cadenas ANSI sólo se incrementa el conteo de referencias), de modo que son ineficientes en comparación, pero las API COM y OLE usan este tipo de cadenas, como así también los objetos ActiveX. Por ejemplo: var s: widestring; begin s := 'Hola!'; Aquí, la variable s toma 4 bytes para el puntero y la estructura de datos ocupa 4 bytes para almacenar la longitud y 10 bytes para los 5 caracteres (2 bytes cada uno), dando 14 bytes en el total. s[1] es el primer caracter ('H'), excepto es de tipo WideChar en vez de AnsiChar y ocupa dos bytes en vez de uno. s[2] es el segundo caracter ('o') y comienza en el tercer byte (los primeros dos bytes son para s[1]). El tipo String se mapea de manera predeterminada a AnsiString. Char se mapea a AnsiChar, y PChar se mapea a PAnsiChar. Cadenas de Caracteres MultiByte (MBCS) en Windows ================================================= Cuando trabajamos con cadenas Ansi, normalmente consideramos que cada caracter ocupa un byte, lo cual que es cierto para idiomas de la Europa Occidental, pero para la mayoría de los idiomas asiáticos, 256 caracteres simplemente no son suficientes. Una solución posible usa cadenas anchas, y otra solución es codificar algunos caracteres en un byte y otros en dos (DBCS: Double-Byte Character Strings - Cadenas de caracteres de dos bytes). Para que esto funcione, debe haber una manera de saber si un byte en una cadena es un caracter o el byte líder ("lead byte") de un caracter de dos bytes. Delphi define un conjunto de caracteres llamado LeadBytes que contiene los caracteres que son los bytes líderes en la configuración regional de Windows. Para configuraciones occidentales, este conjunto está vacío (no hay bytes líderes ya que hay una equivalencia entre bytes y caracteres), y en general para otras configuraciones, si el valor del byte oscila entre 0 y 127 es un caracter ASCII, y si es mayor que 127, entonces es un byte líder y el siguiente byte se llama "byte cola" ("trail byte") y su valor puede ir de 0 a 255. Para razones de compatibilidad hacia atrás y eficiencia, Delphi viene con versiones diferentes de funciones de cadena para SBCS (Single-byte character strings - Cadenas de caracteres de un solo byte) y DBCS. Para SBCS (un byte = un caracter) no hay motivo para ver si cada byte es un caracter byte es un caracter o un byte líder (ya que no hay bytes líderes), así que para SBCS usted puede usar las funciones estándar como Pos, LowerCase, etc., mientras para DBCS debería usar funciones como AnsiPos, AnsiLowerCase, etc. que toman en cuenta que algunos caracteres pueden estar representados por más de un de byte (y consiguientemente estas funciones son más lentas). Longitud de una cadena Ansi =========================== Indexar un DBCS puede ser engañoso dado que s[i] representa el i-ésimo byte, y no necesariamente el i-ésimo caracter porque los caracteres previos podrían haber tenido dos bytes. El número de bytes de una cadena devuelto por la función Length puede o no puede representar el número real de caracteres contenido en una DBCS. Para determinar este número puede usar una función como la siguiente: function AnsiLength(const s: string): integer; var i, n: integer; begin Result := 0; n := Length(s); i := 1; while i <= n do begin inc(Result); if s[i] in LeadBytes then inc(i); inc(i); end; end; Introducción a UTF-8 (UCS Transformation Format) ================================================ Windows puede trabajar con cadenas Unicode, así también como SBCS y DBCS, pero el kernel de Linux trabaja con cadenas UTF-8, donde un caracter puede ocupar hasta seis bytes! Normalmente uno o dos en idiomas occidentales y de uno a tres en idiomas asiáticos. UTF-8 es un esquema de codificación multibyte que puede acomodar todos los caracteres del UCS (Universal Character Set - Juego de Caracteres Universal), que contiene caracteres de 31 bits capaces de representar prácticamente todos los caracteres de idiomas vivos y lenguas muertas, así como también "scripts" como Hiragana, Kiragana, etc. También deja espacio para más lenguajes, scripts y jeroglíficos, así que podemos esperar en el futuro podamos leer poesía Klingon, las Reglas de Adquisición Ferengi y profecías Bajorianas en sus versiones originales... :-) UTF-8 presenta las siguientes características importantes: * Codificación de longitud variable para caracteres UCS UTF-8 puede codificar caracteres UCS (ISO 10646) en hasta 6 bytes. * Transparencia y univoquidad para caracteres ASCII Los caracteres ASCII de 7 bits (#0..#127) son codificados directamente como caracteres ASCII de 7 bits (1 byte por caracter). Todos los caracteres no ASCII (#128..#255) se representan como valores de 8 bits no ASCII (#128..#255) para que los caracteres no ASCII no puedan confundirse con caracteres ASCII, y las herramientas de procesamiento de texto basadas en ASCII puedan ser usadas con texto UTF-8 siempre y cuando dejen pasar los caracteres de 8 bits sin interpretación. * El caracter nulo El caracter #0 (ASCII NULL) sólo aparece donde se desea un nulo. No puede ser un byte líder o un byte de relleno por ejemplo. * Auto-sincronización para procesamiento de alta velocidad Los patrones de los bits de alto orden desambiguan los límites de los caracteres, y hace fácil saber si un simple byte es un caracter de un solo byte (0xxxxxxx), un byte líder (11yyyyyx) o un byte de relleno (10xxxxxx). Este aspecto es muy importante porque permite que las funciones que procesan caracteres UTF-8 sean más eficientes que las funciones para DBCS de Windows. Por ejemplo, una cadena UTF-8 puede recorrerse hacia atrás y las búsquedas de un caracter multibyte que comience con un byte principal nunca terminarán en un byte de relleno en medio de un caracter multibyte no deseado. Y como el byte líder anuncia la longitud del caracter multibyte, puede contar rápidamente cuántos bytes saltar al recorrer hacia adelante. * Amistoso con el procesador UTF-8 se puede leer y escribir rápidamente con simples operaciones de enmascaramiento y desplazamiento de bits sin multiplicaciones ni divisiones (que son operaciones lentas para el procesador). * Compresión razonable UTF-8 no es tan compacto como las DBCS de Windows, pero para lenguajes occidentales es mejor que Unicode, y en el peor de los casos (idiomas asiáticos) nunca es peor que UCS-4. * Ordenamiento canónico UTF-8 conserva el orden de comparación para rutinas simples de comparación de 8 bits como strcmp (una función estándar del C). * Caracteres centinela Los bytes #$FE y #$FF nunca aparecen, así que los puede usar como centinelas, banderas, señales o para indicar un significado especial (evitando la posibilidad de confundir un centinela con un caracter verdadero). * Detectabilidad Es fácil de detectar una entrada UTF-8 con alta probabilidad si ve la firma #$EF#$BB#$BF ('') o si ve caracteres multibyte UTF-8 válidos dado que es improbable que accidentalmente aparezcan en texto ISO 8859-1 (Latin-1). Codificación UTF-8 ================== Este es el formato general usado para codificar caracteres UCS en UTF-8: Bits Bytes Representación 7 1 0xxxxxxx 11 2 110xxxxx 10xxxxxx 16 3 1110xxxx 10xxxxxx 10xxxxxx 21 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 26 5 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 31 6 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx Nótese que el número de bits 1 en el byte líder es el número de bytes en una secuencia multibyte. El signo de copyright ('©' = #169 = #$A9) en binario es 10101001 y dado que necesita 8 bits, tenemos que usar dos bytes: 110xxxxx 10xxxxxx Tenemos que llenar entonces 11 bits (x), así que le agregamos tres ceros a la izquierda a 10101001: 00010 101001 La representación UTF-8 para el caracter de copyright entonces sería: 11000010 10101001 Podría también representarse con más bytes de lo necesario en secuencias de cadena "extra-largas" (overlong). Por ejemplo con cuatro bytes sería: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 000 000000 000010 101001 ---------------------------------------- 11110000 10000000 10000010 10101001 Las secuencias extra-largas se usan para "camuflajear" caracteres y "engañar" comprobaciones de subcadenas. Por ejemplo, si busca el signo de copyright exactamente como 11000010 10101001 (la codificación más corta posible), entonces no lo encontrará. Longitud de una cadena UTF-8 ============================ En Delphi para Linux, las cadenas largas estarán en formato UTF-8, mientras que las cadenas anchas permanecerán como Unicode de dos bytes, aunque que contarán referencias. Para saber el número real de caracteres almacenados en una cadena UTF-8 podríamos usar una función como la siguiente: function UTF8Length(const s: string): integer; var i, n: integer; c: byte; begin Result := 0; n := Length(s); i := 1; while i <= n do begin inc(Result); c := byte(s[i]); if (c and $80) = 0 then inc(i) else if (c and $E0) = $C0 then inc(i, 2) else if (c and $F0) = $E0 then inc(i, 3) else if (c and $F8) = $F0 then inc(i, 4) else if (c and $FC) = $F8 then inc(i, 5) else if (c and $FE) = $FC then inc(i, 6) else raise Exception.Create('No es una cadena UTF-8!'); end; if i > n + 1 then raise Exception.Create('No es una cadena UTF-8!'); end; Por supuesto esta función debería escribirse usando punteros y algo de assembler para mejorar su velocidad, pero dejaremos eso para los profesionales... :) ________________________________________________________________________ 4. ENLACES * Torry's Delphi Pages http://www.torry.ru * Delphi Programming Source Code http://ssapcs.hispeed.com/index.html * Swiss Delphi Center http://www.swissdelphicenter.ch * Delphi Downloads Web Page http://members.xoom.com/sandbrook/downloads/Download.htm * AlphaCom, Inc. http://alphacom.hypermart.net * Top Delphi Sites http://ssapcs.hispeed.com/topsites/index.html * Advanced Delphi Developer's Guide to ADO http://d5ado.homepage.com * Central Iowa Delphi Users Group http://www.bigcreek.com/delphi * The Delphi Cafe http://www.geocities.com/ResearchTriangle/6201 * Natalia Elmanova http://www.geocities.com/SiliconValley/way/9281 ________________________________________________________________________ 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/descarga/p0003.zip ________________________________________________________________________ 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: eds2004 @ 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) 2000 por Ernesto De Spirito. Todos los derechos reservados ________________________________________________________________________ |
Los ejemplos completos de código fuente de este número están disponibles para descargar.
![]() |
¿Errores? ¿Omisiones? ¿Comentarios? Por favor contáctanos!






