Archivo del Autor de Albert Mata

Estadística de visitas (200812).

Aquí están como cada mes las estadísticas de visitas del mes cerrado. Este diciembre el ritmo de publicación ha sido más alto -aunque con varios artículos no técnicos, el blog se está convirtiendo en algo mixto reflejando, supongo, lo que son mis propios intereses-. De todos modos las estadísticas este mes son poco representativas porque incluyen un efecto menéame gracias a que Pedro tuvo la bondad de enviar ahí el post Telecinco estafa por navidad (y el resto de cadenas hacen lo mismo) y a que los usuarios lo encontraron interesante subiéndolo a portada…

…poniéndolo entre las noticias más populares de las últimas 24 horas (también 48 horas y semana)…

…y situando este blog como uno de los sitios más votados en cierto momento…

…cosa que me sorprendió gratamente. De todos modos tengo claro que el éxito del artículo no se debe a mis méritos, sino a lo indignada que está la gente con la temática del post en cuestión. A este respecto debo decir que estoy preparando una segunda parte con más evidencias de hasta qué punto llega a ser una estafa lo de los concursos 905 y que voy a mover el tema en organismos varios a ver si se consigue terminar con esta lacra (o al menos comenzar a terminar).

Pero bueno, estas han sido las estadísticas del mes.

Diciembre 2008.

Número de visitas totales:       9899     (+ 281,14%)
Páginas individuales vistas:     11873    (+ 273,07%)
Promedio de tiempo en el sitio:  00:01:18 (+  30,00%)
Porcentaje de rebote:            88,43%   (+   2,55%)
Porcentaje de visitas nuevas:    92,26%   (+   5,52%)

El día con más visitas del mes ha sido el 28 de diciembre con un total de 3.445. Evidentemente los porcentajes de crecimiento son brutales, como lo serán los de decrecimiento el mes que viene… ;-)

Y los tres posts que durante el mes han sido más visitados son estos (el primero es evidente):

1. Telecinco estafa por navidad (y el resto de cadenas hacen lo mismo) (59,33%)
2. Informe en .NET con Crystal Reports y base de datos MySQL. (2,99%)
3. Pasando parámetros al informe en .NET con Crystal Reports. (2,52%)

Insisto que el efecto menéame ha sido bastante acusado y tergiversa tanto las cifras como el gráfico. Y eso que ha sido en plenas vacaciones navideñas y además en fin de semana. Pero si sirve de algo, genial. Por lo pronto, ya ha habido un conocido político catalán con el que he contactado que se ha tomado la molestia de leerse el artículo y contestarme de manera positiva. Espero poder seguir informando sobre este tema.

Nuevo proyecto fotográfico ‘365 photos a year’.

Lo admito. Soy de esos que cuando se va terminando el año suelen pensar en algunos buenos propósitos para el que empieza. Podría ser algo más precavido y pensar en ello unos días o semanas antes y con toda seguridad saldrían ideas más sensatas. Pero procastino hasta en esto y al final, claro, me salen unos propósitos que no van más allá de mediados de enero -cuando llegan-. Pero este año no va a ser así. Este año no voy a incumplir mi lista de buenos propósitos porque no va a haber tal lista.

Solo un pequeño proyecto personal que verá la luz con la llegada del 2009 y que me ilusiona especialmente: ‘365 photos a year’. Se trata de un blog eminentemente fotográfico que inauguro mañana. La idea es bien simple: cada día una fotografía. Algunas pretenderán ser más artísticas. Otras serán de lo más cotidianas y no tendrán otra misión que la de mostrar en una imagen algo que me haya resultado interesante, o gracioso, o conmovedor, o lo que sea cada día. Por lo general la imagen que publique estará tomada ese mismo día, aunque me reservo la posibilidad de crear alguna categoría retrospectica para poder añadir imágenes provenientes de algún proyecto photoblog anterior que quedó en el olvido y que pronto pasará a mejor vida (léase www.pijus.com) o de mi propio archivo fotográfico. Eso sí, las imágenes estarán siempre tomadas por un servidor con mi propia cámara.

La frecuencia de posteo será diaria de media, pero no necesariamente habrá un post todos los días. Cabe la posibilidad de que en algunas ocasiones se acumulen algunas fotografías para cuando tenga unos minutos en que pasarlas de la cámara al portátil y subirlas al blog.

Ni que decir tiene que todo el contenido del proyecto ‘365 photos a year’ está sujeto a una licencia Creative Commons, por lo que si en algún momento alguien encuentra alguna foto que le pueda servir para ilustrar algún post o lo que sea, podrá utilizarla sin problemas.

Y poco más queda por añadir. Simplemente que se trata de un proyecto que me ilusiona porque la fotografía me ha resultado apasionante desde que tuve mi primera (y actual) cámara digital y confío que con esta pequeña obligación autoimpuesta consiga dar más rienda suelta a una de mis principales aficiones.

La dirección web es la fácilmente deducible www.365photosayear.com (o www.365photosayear.net). Básicamente porque barajé varias alternativas para el nombre del proyecto y finalmente escogí la que más me gustaba de entre las que tenían los dominios .com y .net libres. Por supuesto tiene posibilidad de suscribirse vía RSS y en tanto que los posts serán una fotografía y un texto muy breve, no resultará demasiado intrusivo estar suscrito, así que os animo a ello.

Telecinco estafa por navidad (y el resto de cadenas hacen lo mismo).

Aviso a navegantes y lectores habituales varios: este post es largo y no tiene nada que ver ni con VisualBasic.NET ni con MySQL ni con nada técnico, así que tú decides si quedarte a leerlo. De hecho, si tuviera cuenta en Menéame este es el post que creo que me automenearía (aunque creo que uno mismo no puede menearse, pero es una manera de decirlo). Este es uno de los posts de queja que escribo con más indignación, es el post que me gustaría que mucha gente leyera a ver si con un gran eco se pudiera conseguir algo en contra de esta estafa. Pero en fin… entremos de lleno en el tema.

La noche del día de navidad me quedé, como de costumbre, dormido en el sofá y desperté a eso de las cuatro menos cuarto de la madrugada. Telecinco estaba emitiendo uno de esos programas basura de concursos con líneas 905. No suelo hacerles ni caso, pero en esta ocasión me hizo gracia lo farsante que llegaba a ser la presentadora. En la imagen siguiente podemos ver de qué trataba el concurso cuyo enunciado literal era “¡Suma todos los números que hay en la imagen!”:

O sea, que hay que sumar todos los números de la imagen. Bueno, están todos los números del 1 al 11 por duplicado, así que suman 132. Tal vez tengamos que sumar el 11 amarillo del título “11 Amiguitas” y entonces tenemos 143. Incluso puede que tengamos que sumar el 8 de “11 Ami8uitas” que sibilinamente ponen supongo que para crear confusión y poder jugar después con el resultado que les interese. Bueno, pues 151. A partir de aquí podemos considerar que quizá un 10 no es un 10 sino un 1 y un 0 (tendríamos que restar 9). Ídem con los 11. No sé, se pueden buscar trampas mil, pero a priori parecen resultados factibles el 132, el 143 y el 151.

Bueno, vamos a ver un plano general de lo que aparecía en pantalla para ir familiarizándonos con la presentadora:

De paso podemos ver que en la parte inferior nos comentan que hay una vía de participación gratuita a partir de la página web de la productora www.accesogratuito.tv/aquisegana. Vamos a ver qué encontramos en dicha página (las capturas siguientes se pueden ver ampliadas clickando en ellas):

Este es el contenido íntegro de la página. Fascinante. Pero podemos entrar en una subpágina en la que el contenido es este otro:

Es decir, que nos dicen que podemos utilizar el acceso gratuito todos los días pero solo una vez por día. Y después nos preguntan tonterías varias y nos muestran un botón que, atención, no es tal botón. Es un cuadro de texto tal como se aprecia en la imagen siguiente:

No obstante se puede hacer click en dicho cuadro de texto. Imagino que con esta torpe artimaña persiguen que algunos ya no accedan al teléfono gratuito, pero el caso es que sí, se puede obtener un número 900:

Pero solo podemos utilizarlo una vez por día. Este apartado está también recogido de una manera muy curiosa en las bases legales del concurso:

“OPCIÓN 2.- Accediendo gratuitamente a través de Internet en la página web www.accesogratuito.tv/aquisegana , donde se ofrece un número 900, al que el concursante podrá llamar accediendo gratuitamente al mismo sistema de selección de llamadas al que accedería si hubiera optado por llamar al 905. En este caso para evitar abusos y picaresca, los números 900 se irán cambiando de un programa a otro así como sólo se permitirá el uso por una sola persona o número de teléfono al día.”

Menuda desvergüenza. Para evitar abusos y picaresca solo permites participar una vez por día con el número 900. Sin embargo no hay abuso ni picaresca alguna en que estimules a los participantes a llamar infinitas veces a un número 905 cobrándoles en cada ocasión más de un euro. Es increíble. Imagino que lo de la participación gratuíta será por cumplir con algún requisito legal, no lo sé.

Pero ya que estamos con las bases legales, vamos a ver los mejores trozos del documento en formato .pdf disponible en la propia web antes mencionada:

“El espectador-concursante podrá ser elegido para participar en directo en el Concurso a través de las siguientes formas, a entera discreción del productor o del presentador:
a) El simple hecho de llamar a través del número 905 o a través del 900 y escuchar la locución grabada de bienvenida o disuasión supone la participación del concursante en el Concurso. El concursante es consciente de que no necesariamente tiene que llegar a haber hablado en directo con el presentador para considerarse participante en el Concurso. No todos los concursantes logran ser seleccionados para acceder al plató y plantear la respuesta del juego al presentador.”

No todos lo logran. Qué eufemismo tan elegante. ¿Cuántos lo logran? ¿Un diez por ciento? ¿Un uno por ciento? ¿Un uno por mil? ¿Directamente un cero por mil?

“b) La selección de la llamada, y por tanto, futuro concursante en plató, se podrá realizar en cualquier momento que lo estime oportuno el productor en función de la reacción de la audiencia con el juego en cada momento.”

Manera elegante de decir… es mi cortijo y aquí se hace lo que a mí me da la gana sin ningún tipo de transparencia ni darle explicaciones a nadie.

“c) Durante el Concurso el “hot button” del sistema conectará en directo al plató a los llamantes que se encuentren en el sistema en el momento adecuado. El Concurso se representa como juego rápido, con el objetivo de que los llamantes tengan más oportunidad para entrar en directo y concursar. El llamante que encuentre la línea del plató abierta en el momento adecuado tendrá una oportunidad inmediata para concursar. En el caso de que no entre en directo en el momento adecuado, una locución automática le indicará que no ha logrado entrar en directo en el Concurso.”

Claro. Porque por supuesto no debe haber nada del estilo de que solo entren en antena llamadas que no sean del todo reales. Imagino, vamos, ya que eso no sería muy ético. No obstante se hace extraño que entren en antena llamadas que dicen que los números antes mostrados (que, recuerdo, podían sumar 132, 143 o 151) suman 444, 22, 3 (sí, tres) y cosas similares.

“Cualquier decisión tomada por la productora del Concurso respecto a las respuestas a las pruebas de habilidad o destreza, será inapelable.”

Naturalmente. Yo digo que esto es así y es así y punto. No se discute. No se demuestra. Mando yo. Se acabó. Ojo que este punto veremos más adelante que es importante…

“La organización podría decidir el no aceptar llamadas desde números que aparezcan ocultos o similares.”

No termino de ver la razón de esto. Quiero suponer que no tendrá nada que ver con evitar posibles inspecciones de cuerpos de seguridad. Ni idea. Este punto me despista, lo admito.

Bueno, después de haber repasado escuetamente las bases legales del concurso y de haber visto que puedo concursar mediante un número 900 pero solo una vez por día, volvemos a la dinámica del concurso. La presentadora asegura sobre las 4.00am que el concurso se va a terminar en diez minutos exactos y con tal fin muestran un reloj en pantalla. No serán diez minutos exactos como cacarea la chica, puesto que cada vez que entra una llamada detienen o resetean el contador según les place, pero el caso es que finalmente la cuenta atrás llega a su fin.

Pero no importa. Todavía no se ha estafado suficiente dinero a la audiencia, así que la presentadora finge saltarse las normas que le impone producción y decide volver a poner otro contador de otros diez minutos para que la gente tenga más oportunidades de ganar. ¡Que sigan las llamadas al 905333222!

Pasan los siguientes diez minutos y a partir de aquí comienzan con una retahíla de contadores de un minuto y medio. Primero uno, después otro, y otro, y otro… incontables. Sin ningún respeto por la audiencia. Reiniciándolos cuando les apetece. Llego a perder el control de lo que hacen con el tiempo.

Mientras tanto (son ya las 5.00am) la presentadora va soltando perlas del estilo de “porque es navidad y esta navidad es mágica” cuando suben el premio hasta los 8000 euros. La verdad es que a esta chica debería darle vergüenza dedicarse a esto. ¿Habrá estudiado periodismo o es solo una cara bonita (bueno, supuestamente)? ¿Le compensa hacer esto? ¿No le da vergüenza colaborar en estafar a la gente? ¿No se siente una ladrona? ¿No tiene remordimientos? ¿Cómo se puede tener tan poca integridad personal y profesional? La verdad es que a mí me merece infinitamente más respeto una chica que se dedique incluso a la prostitución que quien se presta a engañar y quitar el dinero a la gente incauta de esta manera… Pero cada cual decide, está claro.

Durante el programa ocurren cosas ante las que uno no sabe si reir o llorar. Algunos concursantes entran en antena más de una y de dos veces. Por ejemplo un tal Salvador o un tal Sito. A Salvador la presentadora ya le saluda con un “buenas noches de nuevo Salvador, a ver si tienes más suerte esta vez”. Si Salvador es realmente un concursante real es sin duda para llorar que el pobre esté siendo víctima de esta estafa. Si es un cebo, si no es un concursante real, es para reir (por no llorar).

Otra cosa curiosa es que a algunos concursantes ni se les entiende el número que dicen (yo al menos no). Pero la chica le dice directamente que no es correcto sin siquiera pedirle que repitan el número. Imagino que tendrá una capacidad auditiva fuera de lo común… Pero eso sí, después de decir su números, cuelgan ipsofacto las llamadas a todos los concursantes sin siquiera despedirse ni dar ocasión al concursante (perdón, al estafado) a decir nada. No fuera caso que alguno dijera algo fuera de tono.

La presentadora se decide a dar algunas pistas: la solución es un número impar, menor que 900 y mayor que 18. Gracias. Llama Rosario y dice que la solución es 340… a continuación otro concursante pronostica 131 y la presentadora le dice “¡No! Pero mejor que el anterior porque me has dicho un número impar y esto me gusta”. ¿En serio no le da vergüenza a la chica dedicarse a esto?

Vamos con el desenlace final. Recuerdo que se traba de sumar todos los números de la imagen inicial “11 Ami8uitas” y que los resultados plausibles eran 132, 143 o 151. Podían hacernos alguna jugarreta por ahí para colarnos algún otro resultado, y de hecho era evidente que iban a hacérnosla porque esos tres resultados ya los habían dicho algunos concursantes a los que les habían dicho que no era esa la cifra correcta.

Son las 05:12am. Recuerdo que llevan con esto desde antes de las 04:00am. Creí que sería lento pero no tanto, pardiez. Pero decido quedarme hasta las seis que es cuando en la programación de Telecinco dice que se termina este espacio. Y me quedo porque empiezo a tener dudas de si alguien se llevará el premio (no lo creo) y de si abrirán realmente el sobre. Espero que no tengan la poca vergüenza de terminar el programa sin abrir el sobre.

El caso es que anuncian que van a entrar en antena los últimos 5 concursantes. Y es curioso porque estaban entrando llamadas sin parar a un ritmo de una cada 5-10 segundos y es decir “cinco últimos concursantes” y ahora pasan minutos y minutos entre un concursante y otro.

A las 05:38am faltan dos concursantes…

A las 05:42am falta un último concursante…

A las 05:49am entra la última llamada…

Pero habrá otra reúltima llamada, decide la presentadora…

Y ahí me teníais a mí cámara en mano a las 6:00am. Dos horas y media después de que comenzara este concurso (bueno, fraude). Después de haber contado así a bote pronto más de 200 llamadas en antena (no quiero ni pensar cuántas no habrán entrado en antena y a cuánto habrá ascendido el importe que los televidentes se pueden haber dejado en este atraco a mano armada) sin que ninguna diera el resultado correcto (cosa que estadísticamente ya resulta cuanto menos complicado). Pero ahí estoy yo, decía, preparado con la cámara para no perderme el gran momento en que la presentadora abre el sobre y nos ilustra sobre cuál es la cifra correcta y de dónde sale dicha suma. Como prueba de que realmente estaba atento, algunas imágenes:

   
   

Pues bien. No tengo la imagen del resultado final… ¿Por qué? Porque después de haber estado todo el programa cacareando que la respuesta estaba en ese sobre azul con la palabra “SOLUCIÓN” escrita delante, la presentadora ha tenido la poca vergüenza de abrir el sobre, sacar una hoja de papel con el resultado escrito, mostrarlo un brevísimo (creedme, brevísimo) espacio de tiempo en pantalla, ni siquiera decirlo en voz alta e inmediatamente apartarlo de la cámara. Insisto: no ha dicho el resultado. Solo lo ha mostrado durante unas décimas de segundo. Tan breve que ni siquiera he tenido tiempo de sacarle la foto. La última de las fotos anteriores es la que pretendía capturar ese momento, pero en ella ya no aparece el papel con el resultado. Se ve solo a la presentadora en una actitud que parece reflejar lo que todo el concurso es en realidad: una gran mofa. Fijaos en la actitud de la chica, en serio, solo le falta un “jojojo que tontos sois”.

¿Y el resultado era…? Sí, lo tengo. Afortunadamente soy más rápido que mi cámara. Sujetaos bien… el resultado era 517. Me niego a intentar hacer combinaciones extrañísimas con los números de la imagen…

…para que terminen sumando 517. Probablemente no exista dicha combinación, no lo sé. En cualquier caso es un timo descarado. He buscado el video en Youtube pero no lo he encontrado (lástima, ¿quizá por esto Vasile no quiere nada de Telecinco subido a YouTube? ¿para que no resulten evidentes las estafas a las que se somete a la audiencia?). Pero no creo que para un juez fuera complicado conseguir una cinta con la copia del programa solicitándosela a Telecinco o a la empresa productora del programa. ¿Por qué ningún juez lo hará? ¿Por qué no se persigue esta estafa?

En este punto es bueno recordar algún punto de las bases que rezaba:

“Cualquier decisión tomada por la productora del Concurso respecto a las respuestas a las pruebas de habilidad o destreza, será inapelable.”

Pues nada, la productora decide que la respuesta a la pregunta es 517 y se acabó. Es inapelable. Y falso. Pero inapelable.

Insisto una vez más: abrió el sobre, mostró el papel durante solo un instante, no lo leyó y despidió el programa en cosa de dos o tres segundos cortando inmediatamente la emisión y pasando Telecinco a emitir el siguiente programa.

¿Por qué permite el gobierno esta estafa a los ciudadanos más incautos? ¿Por qué esta tolerancia? ¿Tanto pesan las operadoras telefónicas y sus ingresos? ¿Tanto pesan los medios de comunicación y sus ingresos? ¿Mucha crisis pero se permite que un hatajo de piratas roben a los ciudadanos más crédulos cada noche en un montón de cadenas? ¿Es más importante seguir por ejemplo con la guerra del canon y tratar de piratas a todos los ciudadanos en lugar de perseguir y directamente no permitir actuar a los auténticos piratas de la televisión nocturna? En serio, que soliciten el video de este programa. Que comprueben el resultado que se da por solución oficial (517) y persigan a los que estafan de esta manera. ¿Por qué no lo hacen?

PD. Ignoro si el resto de televisiones autonómicas también estafan a sus espectadores o no, pero al menos me siento orgulloso de que TV3 nunca haya entrado en el juego de burlarse de sus televidentes de esta manera.

Mobuzz versus Wikipedia.

Es curioso. Hace apenas mes y medio asistimos en España al triste espectáculo del cierre de Mobuzz. Y no digo triste porque me apenara que Mobuzz cerrara. No. En realidad no me apenó. Pero tampoco me alegró. Hasta entonces no conocía qué era Mobuzz y por tanto poco vínculo afectivo podía tener con ellos. Tampoco me parecieron argumentos suficientes eso de que es una pena que una start-up española tenga que cerrar. Soy de los que pensó que su modelo de negocio no debía ser válido. En realidad tiendo a pensar que en el mundo de los negocios hay cosas que son dos más dos. Puedes tener un sistema analítico formado por mil ratios, pero en el fondo para saber si tu empresa va a mejor o a peor te basta con ver si estás facturando más o menos. Después hay muchos otros factores, sí, pero vamos… facturas más, bien, facturas menos, mal. Y en el caso de Mobuzz y por mucho que Enrique Dans opinara y asegurara lo contrario y pese a admitir que él podía conocer los números de Mobuzz y yo no, está claro que si tuvieron que cerrar es que su modelo de negocio no era el apropiado, bien sea por dimensionamiento, por coyuntura económica o por lo que sea.

No obstante, dije triste porque en el caso de Mobuzz pudimos ver en acción la más pura carroña ibérica. Y todo porque Mobuzz, antes de echar el cierre, pasó la gorra entre sus usuarios y les solicitó donaciones voluntarias para intentar subsistir. Repito, era voluntario. Yo mismo no doné un céntimo porque no me sentí nada involucrado con la causa. Pero tampoco me pareció mal que los que quisieran colaboraran. ¿Por qué iba a parecerme mal?

Pues a muchos les pareció la ocasión ideal para entrar a verter toda su bilis. Como ejemplo, los comentarios en el post del propio Enrique Dans donde se hacía eco de la solicitud de Mobuzz. Enrique tuvo que filtrar y eliminar cientos de comentarios terriblemente ofensivos, lo que dio lugar al siguiente post y a que, de hecho, desde entonces los comentarios en su blog estén moderados a priori en lugar de a posteriori como ocurría hasta entonces.

Insisto. Yo mismo no simpaticé nada con esa iniciativa y no me causó la más mínima conmoción que Mobuzz cerrara. ¿No consiguieron hacer su negocio suficientemente rentable? Pues a otra cosa. Pero… ¿atacar tan salvajemente su iniciativa?

¿Qué ocurre entonces ahora con Wikipedia y su solicitud de aportaciones por parte de su fundador Jimmy Wales? ¿Puede Wikipedia solicitar aportaciones a sus usuarios y es una actitud comprensible -cuando no loable- pero no puede hacerlo Mobuzz -o tú, o yo-? ¿Por algún motivo concreto?

Me he ahorrado el añadir links a diestro y siniestro hacia muchos de los que criticaron abiertamente tanto la iniciativa de Mobuzz como el posicionamiento de los Enrique Dans, Ricardo Galli, Martin Varsavsky y compañía. Pero el que quiera es muy fácil encontrar dichos links tirando de Google. Y conste que algunos de estos nombres que enlazo y de los que se considera su entorno común no son para nada santos de mi devoción. En absoluto. Pero esta actitud tan de aquí de criticar por criticar, con maldad, por envidia, con un clarísimo doble rasero… nunca la entenderé.

TextBox mejorado en .NET.

Cuando desarrollamos en .NET es habitual utilizar controles personalizados que mejoran los preexistentes. Habitualmente podremos hacer uso de librerías públicas que podemos obtener en internet (liberadas o de pago). Otras veces podremos crearnos nosotros mismos nuestras propias librerías de controles. Yo suelo hacer esto último cuando no se trata de algo demasiado complejo, ya que termino antes que buscando componentes de terceros y peleándome después con sus correspondientes licencias. Entre otras cosas porque cualquier tipo de licencia “share alike” no me sirve.

Uno de los controles que me ahorran mucho trabajo en la creación de formularios es este que presento hoy y que en su día llamé TextBoxFocused (en adelante TBF). Se trata de un TextBox que cambia de color al recibir y perder el foco (de ahí su nombre -por cierto, sé que debería ser FocusedTextBox, pero quería mantener lo de TextBox al inicio-) y permite controlar a priori y a posteriori qué se puede introducir en él.

Un formulario con algunos TBF se vería así:

Como se puede apreciar, sin tener que añadir código alguno al formulario, el TBF aparecerá sombreado cuando tenga el foco. Además cuando se esté introduciendo un texto se mostrará en rojo, y cuando ya esté validado quedará en verde, tal como se aprecia en las siguientes imágenes:

Nada realmente espectacular, pero queda bonito. Sin embargo en donde el TBF se muestra útil es en el control del contenido introducido. Este control se realiza de una doble manera: a priori y mediante una propiedad del control se establece qué tipo de introducciones se admitirán, a posteriori -si se ha seleccionado- se controla que lo que se ha introducido realmente fuera lo que se debía introducir, impidiendo abandonar el TBF en caso contrario.

Para ello, en la ventana de propiedades del control nos aparecen dos nuevas creadas para la ocasión, tal como se muestra a continuación:

En las propiedades de cosecha propia siempre añado el prefijo “am” para localizarlas todas juntas. En este caso amAllow permite fijar qué introducciones se permitirán (cualquier cosa, nada de nada, sólo enteros positivos, dobles negativos, etc.) y amPostVerification decide si se aplicará verificación posterior o no.

Esto de amPostVerification tiene un sentido claro. Y es que podemos decirle al control que admita números dobles negativos y así sólo permitirá introducir números, el signo negativo y el signo decimal. Pero es evidente que con esos caracteres se pueden crear expresiones que no se correspondan con un número doble negativo (p.ej. -30-3.8..-25). Con este control a posteriori verificamos que efectivamente lo sea.

A nivel de código el control a posteriori se podría haber implementado a base de cástings y control de errores, pero no ha sido la opción que he escogido. Queda al antojo de cada cual modificar el código como le plazca.

Para su implementación en una aplicación, basta con añadir una clase TextBoxFocused.vb, copiar todo el código que se adjunta a continuación, compilar la aplicación y a partir de entonces tendremos el control disponible para añadirlo en cualquier formulario. O también se puede añadir a una librería de controles personalizados (que es lo que hago yo) y mantenerlo un poquito más organizado.

Código completo (de libre uso como todo lo que aparece publicado en este blog):

'--------------------------------------------------------------------
' Author:             Albert Mata (www.albertmata.net)
' Last time modified: 2008-11-13
' Description:        Acts like a standard TextBox but with some 
'                     better features like changing color when gets 
'                     or losts focus and controlling allowed 
'                     introductions.
'--------------------------------------------------------------------
Imports System.Drawing

Public Class TextBoxFocused
    Inherits System.Windows.Forms.TextBox

#Region "Constants"

    '----------------------------------------------------------------
    ' Constants for colors.
    '----------------------------------------------------------------
    Private COLOR_FOCUSED As Color = Color.FromArgb(255, 240, 157)
    Private COLOR_NON_FOCUSED As Color = Color.White
    Private COLOR_VALIDATED As Color = Color.Green
    Private COLOR_NON_VALIDATED As Color = Color.Red

    '----------------------------------------------------------------
    ' Constants for special keys.
    '----------------------------------------------------------------
    Private ASC_BACKSPACE As Integer = 8
    Private ASC_SUPPRESS As Integer = 127
    Private ASC_DASH As Integer = 45
    Private ASC_COMMA As Integer = 44

#End Region

#Region "Enumerations"

    '----------------------------------------------------------------
    ' Enumerations.
    '----------------------------------------------------------------
    Public Enum Introduction
        NothingAtAll
        PositiveInteger
        NegativeInteger
        PositiveDouble
        NegativeDouble
        OnlyLetter
        OnlyLetterOrDigit
        Password
        Everything
        EverythingNonCasing
    End Enum

#End Region

#Region "Attributes&Properties"

    '----------------------------------------------------------------
    ' Attributes.
    '----------------------------------------------------------------
    Private aAllow As Introduction = Introduction.Everything
    Private aPostVerification As Boolean = True

    '----------------------------------------------------------------
    ' Public properties to be shown in design time.
    '----------------------------------------------------------------
    Public Property amAllow() As Introduction
        Get
            Return Me.aAllow
        End Get
        Set(ByVal value As Introduction)
            Me.aAllow = value
        End Set
    End Property
    Public Property amPostVerification() As Boolean
        Get
            Return Me.aPostVerification
        End Get
        Set(ByVal value As Boolean)
            Me.aPostVerification = value
        End Set
    End Property

#End Region

#Region "GraphicalControls"

    '----------------------------------------------------------------
    ' Event Me.GotFocus.
    '----------------------------------------------------------------
    Private Sub TextBoxFocused_GotFocus(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles Me.GotFocus
        MyBase.BackColor = COLOR_FOCUSED
        MyBase.ForeColor = COLOR_NON_VALIDATED
    End Sub

    '----------------------------------------------------------------
    ' Event Me.LostFocus.
    '----------------------------------------------------------------
    Private Sub TextBoxFocused_LostFocus(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles Me.LostFocus
        If Me.CheckText() Or Not Me.aPostVerification Then
            MyBase.BackColor = COLOR_NON_FOCUSED
            MyBase.ForeColor = COLOR_VALIDATED
        Else
            Me.Focus()
        End If
    End Sub

    '----------------------------------------------------------------
    ' Event Me.KeyPress.
    '----------------------------------------------------------------
    Private Sub TextBoxFocused_KeyPress(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.KeyPressEventArgs) _
    Handles Me.KeyPress
        Select Case Me.aAllow
            Case Introduction.NothingAtAll
                'Allowing no character.
                e.Handled = True
                If AscW(e.KeyChar) = ASC_BACKSPACE Then
                    MyBase.Text = ""
                End If
            Case Introduction.PositiveInteger
                'Allowing just digits.
                If Not Char.IsDigit(e.KeyChar) _
                And AscW(e.KeyChar) <> ASC_BACKSPACE _
                And AscW(e.KeyChar) <> ASC_SUPPRESS Then
                    e.Handled = True
                End If
            Case Introduction.NegativeInteger
                'Allowing just digits and '-' character.
                If Not Char.IsDigit(e.KeyChar) _
                And AscW(e.KeyChar) <> ASC_BACKSPACE _
                And AscW(e.KeyChar) <> ASC_SUPPRESS _
                And AscW(e.KeyChar) <> ASC_DASH Then
                    e.Handled = True
                End If
            Case Introduction.PositiveDouble
                'Allowing just digits and ',' character.
                If Not Char.IsDigit(e.KeyChar) _
                And AscW(e.KeyChar) <> ASC_BACKSPACE _
                And AscW(e.KeyChar) <> ASC_SUPPRESS _
                And AscW(e.KeyChar) <> ASC_COMMA Then
                    e.Handled = True
                End If
            Case Introduction.NegativeDouble
                'Allowing just digits and '-' and ',' characters.
                If Not Char.IsDigit(e.KeyChar) _
                And AscW(e.KeyChar) <> ASC_BACKSPACE _
                And AscW(e.KeyChar) <> ASC_SUPPRESS _
                And AscW(e.KeyChar) <> ASC_DASH _
                And AscW(e.KeyChar) <> ASC_COMMA Then
                    e.Handled = True
                End If
            Case Introduction.OnlyLetter
                'Allowing only letters.
                If Not Char.IsLetter(e.KeyChar) _
                And AscW(e.KeyChar) <> ASC_BACKSPACE _
                And AscW(e.KeyChar) <> ASC_SUPPRESS Then
                    e.Handled = True
                Else
                    e.KeyChar = Char.ToUpper(e.KeyChar)
                End If
            Case Introduction.OnlyLetterOrDigit
                'Allowing only letters and digits and upper casing 
                'letters.
                If Not Char.IsLetterOrDigit(e.KeyChar) _
                And AscW(e.KeyChar) <> ASC_BACKSPACE _
                And AscW(e.KeyChar) <> ASC_SUPPRESS Then
                    e.Handled = True
                Else
                    e.KeyChar = Char.ToUpper(e.KeyChar)
                End If
            Case Introduction.Password
                'Allowing only letters and digits but not upper 
                'casing letters.
                If Not Char.IsLetterOrDigit(e.KeyChar) _
                And AscW(e.KeyChar) <> ASC_BACKSPACE _
                And AscW(e.KeyChar) <> ASC_SUPPRESS Then
                    e.Handled = True
                End If
            Case Introduction.Everything
                'Allowing everything upper casing letters.
                e.KeyChar = Char.ToUpper(e.KeyChar)
            Case Introduction.EverythingNonCasing
                'Allowing everything not upper casing letters (so 
                'doing nothing).
        End Select
    End Sub

#End Region

#Region "PrivateMethods"

    '----------------------------------------------------------------
    ' Checks introduced text according to Me.amAllow property and 
    ' returns True only when introduced text respects desired 
    ' Me.amAllow property.
    '----------------------------------------------------------------
    Private Function CheckText() As Boolean
        Select Case Me.aAllow
            Case Introduction.NothingAtAll
                'Checking no character has been introduced.
                Return Me.Text = ""
            Case Introduction.PositiveInteger
                'Checking a right positive integer has been 
                'introduced.
                Return Me.IsPositiveInteger(Me.Text) Or Me.Text = ""
            Case Introduction.NegativeInteger
                'Checking a right negative integer has been 
                'introduced.
                Return Me.IsNegativeInteger(Me.Text) _
                    Or Me.IsPositiveInteger(Me.Text) _
                    Or Me.Text = ""
            Case Introduction.PositiveDouble
                'Checking a right positive double has been 
                'introduced.
                Return Me.IsPositiveDouble(Me.Text) _
                    Or Me.IsPositiveInteger(Me.Text) _
                    Or Me.Text = ""
            Case Introduction.NegativeDouble
                'Checking a right negative double has been 
                'introduced.
                Return Me.IsNegativeDouble(Me.Text) _
                    Or Me.IsPositiveDouble(Me.Text) _
                    Or Me.IsNegativeInteger(Me.Text) _
                    Or Me.IsPositiveInteger(Me.Text) _
                    Or Me.Text = ""
            Case Introduction.OnlyLetter
                'Checking only letters have been introduced.
                Return Me.HasOnlyLetters(Me.Text) Or Me.Text = ""
            Case Introduction.OnlyLetterOrDigit, _
                 Introduction.Password
                'Allowing only letters and digits have been 
                'introduced.
                Return Me.HasOnlyLettersOrDigits(Me.Text) _
                    Or Me.Text = ""
            Case Introduction.Everything, _
                 Introduction.EverythingNonCasing
                'Allowing everything.
                Return True
        End Select
    End Function

    '----------------------------------------------------------------
    ' Checks a right positive integer has been introduced.
    '----------------------------------------------------------------
    Private Function IsPositiveInteger(ByVal S As String) As Boolean
        Dim R As Boolean
        If Not String.IsNullOrEmpty(S) Then
            Dim IT As IEnumerator = S.GetEnumerator()
            Dim C As Char
            R = True
            While IT.MoveNext And R
                C = DirectCast(IT.Current, Char)
                R = Char.IsDigit(C)
            End While
        Else
            R = False
        End If
        Return R
    End Function

    '----------------------------------------------------------------
    ' Checks a right negative integer has been introduced.
    '----------------------------------------------------------------
    Private Function IsNegativeInteger(ByVal S As String) As Boolean
        Dim R As Boolean
        If Not String.IsNullOrEmpty(S) Then
            Dim IT As IEnumerator = S.GetEnumerator()
            Dim C As Char
            R = True
            While IT.MoveNext And R
                C = DirectCast(IT.Current, Char)
                R = Char.IsDigit(C) OrElse C = ChrW(ASC_DASH)
            End While
        Else
            R = False
        End If
        'Checking:
        '  - first character is a dash character,
        '  - length must be at least 2 characters.
        Return R And S.LastIndexOf(ChrW(ASC_DASH)) = 0 _
                 And S.Length >= 2
    End Function

    '----------------------------------------------------------------
    ' Checks a right positive double has been introduced.
    '----------------------------------------------------------------
    Private Function IsPositiveDouble(ByVal S As String) As Boolean
        Dim R As Boolean
        If Not String.IsNullOrEmpty(S) Then
            Dim IT As IEnumerator = S.GetEnumerator()
            Dim C As Char
            R = True
            While IT.MoveNext And R
                C = DirectCast(IT.Current, Char)
                R = Char.IsDigit(C) OrElse C = ChrW(ASC_COMMA)
            End While
        Else
            R = False
        End If
        'Checking:
        '  - there's one comma character and it's not the first one,
        '  - there's exactly only one comma character,
        '  - the comma character it's not the last one.
        Return R And S.IndexOf(ChrW(ASC_COMMA)) > 0 _
                 And S.IndexOf(ChrW(ASC_COMMA)) = _
                     S.LastIndexOf(ChrW(ASC_COMMA)) _
                 And S.LastIndexOf(ChrW(ASC_COMMA)) < S.Length - 1
    End Function

    '----------------------------------------------------------------
    ' Checks a right negative double has been introduced.
    '----------------------------------------------------------------
    Private Function IsNegativeDouble(ByVal S As String) As Boolean
        Dim R As Boolean
        If Not String.IsNullOrEmpty(S) Then
            Dim IT As IEnumerator = S.GetEnumerator()
            Dim C As Char
            R = True
            While IT.MoveNext And R
                C = DirectCast(IT.Current, Char)
                R = Char.IsDigit(C) OrElse C = ChrW(ASC_DASH) _
                                    OrElse C = ChrW(ASC_COMMA)
            End While
        Else
            R = False
        End If
        'Checking:
        '  - first character is a dash character,
        '  - length must be at least 4 characters,
        '  - there's one comma character and it's not the first or 
        '    second one (-0,0),
        '  - there's exactly only one comma character,
        '  - the comma character it's not the last one.
        Return R And S.LastIndexOf(ChrW(ASC_DASH)) = 0 _
                 And S.Length >= 4 _
                 And S.IndexOf(ChrW(ASC_COMMA)) > 1 _
                 And S.IndexOf(ChrW(ASC_COMMA)) = _
                     S.LastIndexOf(ChrW(ASC_COMMA)) _
                 And S.LastIndexOf(ChrW(ASC_COMMA)) < S.Length - 1
    End Function

    '----------------------------------------------------------------
    ' Checks only letters have been introduced.
    '----------------------------------------------------------------
    Private Function HasOnlyLetters(ByVal S As String) As Boolean
        Dim R As Boolean
        If Not String.IsNullOrEmpty(S) Then
            Dim IT As IEnumerator = S.GetEnumerator()
            Dim C As Char
            R = True
            While IT.MoveNext And R
                C = DirectCast(IT.Current, Char)
                R = Char.IsLetter(C)
            End While
        Else
            R = False
        End If
        Return R
    End Function

    '----------------------------------------------------------------
    ' Checks only letters and digits have been introduced.
    '----------------------------------------------------------------
    Private Function HasOnlyLettersOrDigits(ByVal S As String) _
    As Boolean
        Dim R As Boolean
        If Not String.IsNullOrEmpty(S) Then
            Dim IT As IEnumerator = S.GetEnumerator()
            Dim C As Char
            R = True
            While IT.MoveNext And R
                C = DirectCast(IT.Current, Char)
                R = Char.IsLetterOrDigit(C)
            End While
        Else
            R = False
        End If
        Return R
    End Function

#End Region

End Class

Feliz navidad (o algo así).

Ayer por la mañana tuve ocasión de ver un rato el programa matutino de noticias de Josep Cuní en TV3, ya que tenía cita en Hacienda para un par de temas sin importancia y me quedó un rato libre muerto por ahí. Cielos, es infinitamente mejor trabajar sin parar y no enterarte de lo que pasa en el mundo que ver como la estupidez humana se va concentrando cada vez más en la clase política que nos gobierna. Creo que Albert Einstein ya dijo algo acerca de cosas infinitas y estupidez varia (consúltalo aquí si no lo leíste nunca)…

Motivo para alucinar/llorar número 1.

En Barcelona tenemos (sufrimos) desde hace ya muchos meses una limitación de la velocidad a 80km/h en todos los tramos de autopistas y autovías cercanas a la capital. Supuestamente con ello se reduce la contaminación y los accidentes. Es una estupidez porque basta ir por la carretera para ver que nadie respeta dichas limitaciones. Lo único que consiguen es que todo el mundo sepa perfectamente donde están los radares y se produzcan las típicas frenadas y posteriores acelerones antes y después de éstos. Pero molesta una barbaridad esa limitación. Además dudo que se contamine menos, porque al menos yo me veo forzado a ir en cuarta marcha en lugar de en quinta, ya que de lo contrario el coche “se me aceleraría solo” y multa que me caería. Y a 80km/h en cuarta probablemente se consuma más que a 100km/h en quinta… Pues bien, a partir de ahora la limitacion será variable en función del momento del día y de otros factores tan importantísimos como la contaminación(*), pudiendo llegar a limitar a 60km/h e incluso 40km/h (pero nunca a 120km/h por mucho que sean las dos de la madrugada y conduzcas solo por la autopista). Y para montar esta sistema de limitaciones variables, la infrastructura necesaria nos costará 2,6 millones de euros. Toma crisis. Total, lo vamos a pagar a base de multas, qué más les da. Menuda panda de sinvergüenzas.

(*) Lamento la ironía, pero si realmente les importara la contaminación tendríamos un sistema de transporte público decente en lugar del patético que tenemos.

Motivo para alucinar/llorar número 2.

En la Unión Europea se votaba ayer (finalmente se rechazó, pero da igual, la sola propuesta ya es indignante) una proposición para aceptar la jornada laboral de 65 horas semanales. Claro que sí. ¿Crisis? Trabajemos más. No seamos más productivos, no, trabajemos más horas. En plan monos estúpidos. Después nos hablarán de conciliar vida profesional con vida familiar. Sí, pues como no sea beneficiándote a la administrativa de tu empresa ya me dirás tú. Patético. Para esto les pagamos, desde luego… Menuda panda de sinvergüenzas. ¿Y los sindicatos? Ah, ésos… andarán cacareando tonterías en cualquier manifestación que corte, cómo no, alguna que otra ronda.

Motivo para alucinar/llorar número 3.

En EEUU han tenido la brillante idea de bajar los tipos de interés al 0% - 0,25%. Con un par. No hemos aprendido realmente nada con la que nos ha caído. La crisis, digo. Supuestamente la medida intenta evitar la deflación. Para el que no esté puesto en economía, al igual que el incremento de los precios se denomina inflación, el decremento (o incremento negativo si no te chirrian los dientes al leer eufemismos estúpidos) de los precios se denomina deflación. Y uno podría pensar… ¡genial, bajan los precios! Pues no, la deflación tiende a ser algo negativo. Cuando el escenario es el habitual de que los precios van subiendo, la gente consume con cierta normalidad porque no tiene ningún aliciente para no consumir hoy y hacerlo mañana. Ante un escenario de precios bajando, el consumo se ve frenado porque el consumidor tiene un claro aliciente para no consumir hoy y sí mañana (el precio será más bajo). Esto lleva a que se consuma menos, por tanto se produzca menos… y por tanto tengamos recesión (decrecimiento o crecimiento negativo de la economía), mayor desempleo y todo lo que ahora estamos teniendo. Y con el fin de evitarlo en los EEUU han tenido la brillante idea de bajar los tipos de interés, prácticamente anulándolos, para fomentar el consumo. Todavía la crisis no nos ha mordido con ganas (apenas un par de amagos) y ya hemos olvidado que el consumo desmesurado por encima de nuestras posibilidades nos ha metido en esto. Es alucinante que el mundo gire alrededor de cuatro iluminados que deciden en base a no se sabe qué. Menuda panda de sinvergüenzas.

Voy a centrarme de nuevo en mis desarrollos. Pese a todo, cualquier aplicación informática tiene infinidad menos de bugs que la clase política mundial (lástima que a esta clase no se le pueda aplicar un .Dispose, un =Nothing o algún .Destroy o .Kill personalizado…).

Por cierto, este post es mi versión del ‘Feliz navidad’ para los que de vez en cuando pasáis por este blog. Como muestra de ello, la imagen con la que lo encabezo, que viene a ser un arbolito de navidad adaptado para la ocasión (lo encontré por internet y me hizo gracia). Así que lo dicho, felices fiestas para todos, trabajad mucho y ganad unos quilitos… :-)

Decidir clase en tiempo de ejecución en .NET.

En ocasiones puede resultarnos imprescindible no determinar la clase de la que un objeto va a ser instancia hasta que estemos en tiempo de ejecución. Para ello podemos valernos de la clase Activator y de su método CreateInstance, que nos permiten pasarle una clase para que nos devuelva un objeto de esa clase.

Podemos por ejemplo crearnos una función sencilla como esta…

'----------------------------------------------------------------
' Creates an instance of specified class and returns that object.
'----------------------------------------------------------------
Public Function CreateInstance(ByVal T As Type) As Object
    Return Activator.CreateInstance(T)
End Function

…que podríamos tener encapsulada como método de alguna clase que nos permitiera hacer determinadas acciones con objetos, esto ya a gusto de cada cual.

Una vez tenemos esta función podemos llamarla de varias maneras. La primera, si tenemos otro objeto de la misma clase de la que ahora queremos obtener una instancia:

Dim OBJ As Object

'Option 1. We already have an object of that class.
Dim C1 As New Class1()
OBJ = Me.CreateInstance(C1.GetType())

La segunda, si no tenemos ningún objeto pero conocemos el nombre de la clase en cuestión:

Dim OBJ As Object

'Option 2. We haven't any object of that class.
OBJ = Me.CreateInstance(Type.GetType("AlbertMata.Class2"))

'But be careful because this would fail:
'OBJ = Me.CreateInstance(Type.GetType("Class2"))

Como aparece en el código, conviene remarcar que el nombre de la clase debe incorporar el espacio de nombres, de lo contrario provocará una excepción.

En ambos casos, si una vez creado el objeto -que si nos fijamos lo habíamos declarado como Object y lo habíamos instanciado a través de un método que también devolvía un Object- consultamos su tipo exacto…

'Checking exact type for object.
Debug.Print(OBJ.GetType.ToString)

…obtendremos…

AlbertMata.Class2

Particularmente este sistema me ha venido bien para alguna travesura que quería hacer con formularios, pero creo que en más ocasiones podrá serme útil.

Distancia de Levenshtein en VisualBasic.NET.

Hace un rato se me ha planteado un problema que quien más quien menos habrá tenido en alguna ocasión. Necesitaba poder comparar dos cadenas y obtener algún indicador de hasta qué punto podría tratarse de la misma cadena escrita de maneras ligeramente diferentes. He estado dándole vueltas sobre cómo podría jugar con los distintos métodos de la clase String para conseguir algún valor. Me he planteado utilizar análisis de frecuencias, generar números ponderados en función de carácter y posición dentro de la cadena… Pero nada me convencía, así que he insistido un poco más en buscar si ya alguien había desarrollado alguna función parecida y para mi mayúscula sorpresa me he dado de bruces con la Distancia de Levenshtein.

Resulta que esto que andaba buscando es exactamente esta distancia. Y tal como reza la Wikipedia… “se llama Distancia de Levenshtein o distancia de edición al número mínimo de operaciones requeridas para transformar una cadena de caracteres en otra. Se entiende por operación, bien una inserción, eliminación o la substitución de un carácter. Esta distancia recibe ese nombre en honor al científico ruso Vladimir Levenshtein, quien se ocupara de esta distancia en 1965. Es útil en programas que determinan cuán similares son dos cadenas de caracteres, como es el caso de los correctores de ortografía.”

Y para mayor regocijo, en la propia página de la Wikipedia aparece el algoritmo en pseudocódigo de la función:

int LevenshteinDistance(char str1[1..lenStr1], char str2[1..lenStr2])
   // d is a table with lenStr1+1 rows and lenStr2+1 columns
   declare int d[0..lenStr1, 0..lenStr2]
   // i and j are used to iterate over str1 and str2
   declare int i, j, cost
 
   for i from 0 to lenStr1
       d[i, 0] := i
   for j from 0 to lenStr2
       d[0, j] := j
 
   for i from 1 to lenStr1
       for j from 1 to lenStr2
           if str1[i] = str2[j] then cost := 0
                                else cost := 1
           d[i, j] := minimum(
                                d[i-1, j] + 1,     // deletion
                                d[i, j-1] + 1,     // insertion
                                d[i-1, j-1] + cost   // substitution
                            )
 
   return d[lenStr1, lenStr2]

Incluso viene la implementación en algunos lenguajes de programación (C++, Java, Perl, Python, Ruby, Delphi y ColdFusion). Lástima que no viniera también en VisualBasic.NET que es el lenguaje en el que yo desarrollo… pero bueno, como codificar un algoritmo que ya nos viene dado en pseudocódigo tampoco es tarea infernal, a ello me he dedicado:

'--------------------------------------------------------------------
' Author:      Albert Mata (www.albertmata.net)
' Date:        20081212
' Description: Calculates Levenshtein distance between two different 
'              strings.
'--------------------------------------------------------------------
Public Function LevenshteinDistance(ByVal STR1 As String, _
ByVal STR2 As String) As Integer
    'Variables to iterate (i, j) and get cost for any needed 
    'operation.
    Dim i As Integer
    Dim j As Integer
    Dim Cost As Integer

    'Creating array with string's lengths as bounds.
    Dim D(STR1.Length, STR2.Length) As Integer

    'Initializing array's values.
    For i = 0 To STR1.Length
        D(i, 0) = i
    Next
    For j = 0 To STR2.Length
        D(0, j) = j
    Next

    'Calculating Levenshtein distance.
    For i = 1 To STR1.Length
        For j = 1 To STR2.Length
            If STR1(i - 1) = STR2(j - 1) Then
                Cost = 0
            Else
                Cost = 1
            End If
            'First compared element: deletion.
            'Second compared element: insertion.
            'Third compared element: substitution.
            D(i, j) = Math.Min(Math.Min(D(i - 1, j) + 1, _
                                        D(i, j - 1) + 1), _
                                        D(i - 1, j - 1) + Cost)
        Next
    Next

    'Returning result.
    Return D(STR1.Length, STR2.Length)
End Function

Creo que está bien. Y el resultad