Transmutación de Código VB6 a Lenguajes .NET
Manifiesto de una traducción formal de una clase VB6 a
clases VB.NET y C#
Por Harvey Triana, 8 de Agosto de 2006
Introducción
Muchas veces me he preguntado si realmente alguien
programa VB.NET puro. Sabemos que VB.NET no es una metamorfosis de VB, es una
especificación nueva derivada de otro lenguaje, C#. - ¿Es justo que por un
puñado de palabras reservadas llamemos Visual Basic a VB.NET?, Siendo
realistas, las dos primeras palabras de VB.NET son una mera etiqueta. Pero
no es dramático, ni tengo el propósito de polemizar, VB.NET es un buen
lenguaje y muy elegante.
Percibo que VB.NET puro esta sometido a un segundo plano
por C#. El gran grueso de los ejemplos de VB.NET que encuentras están
escritos por programadores C#, para programadores C#, y traducidos a VB.NET
por un tonto robot. Esto deslegitima aun más a VB.NET como familia de VB. De
otra parte, mucho código de VB.NET, en especial el de los programadores emigrantes
de VB6, usa el nombre de espacios Microsoft.VisualBasic, y creen erróneamente
están produciendo código VB.NET. Esto último es una cuestión a la que
naturalmente huyen los programadores puristas de lenguajes .NET.
Pero entonces ¿A que deberían aspirar los programadores
del Visual Basic Clásico? El objetivo de este articulo es tomar una buena
clase de VB6, traducirla a VB.NET puro (sin el nombre de espacios
Microsoft.VisualBasic) y luego a C#. Quizás se sorprenda de la similitud de
VB.NET con C#, cuando programamos VB.NET puro, y la gran distancia que hay
entre VB6 y VB.NET.
Mucho se ha escrito de la migración de VB6 a VB.NET no
obstante me pregunto si se estará orientando bien a los programadores VB6. Quizás
seria preferible olvidarse que existe VB.NET y comenzar con C#.
El
Clásico Utilitario Números a Palabras
Imagino que por su utilidad, el problema de traducir un número
en sus palabras es tratado desde que existe la programación. La solución del
problema es un buen ejercicio para todos los programadores dedicados.
En este artículo trato el problema de conversión de
números a palabras es su versión en ingles. Escogí la versión en ingles
porque el código es más corto y tiene una lógica más simple de seguir en
cuanto a propósitos didácticos.
Cuando trate este problema, cumplí los siguientes
objetivos: (1) Convertir un entero en palabras, (2) Traducir un número real a
palabras, y (3) Traducir una cantidad monetaria en palabras. Por ejemplo, el
número 123.456 debe producir la siguiente salida:
Función
|
Respuesta
|
Moneda a Palabras
|
One Hundred Twenty Three Dolars
And Forty Six Cents
|
Real a Palabras
|
One Hundred Twenty Three Dot
Four Hundred Fifty Six
|
Entero a Palabras
|
One Hundred Twenty Three
|
Básicamente se trata de una sola función la cual
convierte un número entero a palabras, las demás son derivadas de la misma
con ciertos detalles.
Para propósitos del articulo, he creado tres clases, la
primera en VB6, la segunda en VB.NET, y la tercera en C#.
Solución
en VB.NET
En principio, yo podría tomar la clase escrita en VB6
(puede consultar su código en el anexo 1), importar la librería Microsoft.VisualBasic,
y pegar código. Con unos cuantos ajustes podría hacer funcionar esto en
VB.NET. Con esta estrategia voy a producir un código VB.NET sucio, y poco didáctico
a la hora de aprovechar el potencial real de VB.NET. Tampoco voy a ser
honesto con la eficiencia, ya que voy a colocar una capa de software delante
de VB.NET para hacer funcionar mi código. Si, vamos a programar formalmente
en un lenguaje .NET deberíamos evitar el nombre de espacios Microsoft.VisualBasic.
Usar el nombre de espacios Microsoft.VisualBasic es como
si, por alguna razón de logística, un programador VB.NET que nunca ha escrito
VB6, tuviera que programar en VB6. El, al desconocer VB6, le parecerían
extrañas funciones como Left$(), Right$() o Mid$(). Posiblemente optaría por crea
una clase Global Multiuse con un método SubString(), - o si es mas osado
crearía una clase StringVBNET, y se ahorraría análisis. No obstante estaría
subutilizando VB6 ya que al colocar una capa de software delante de VB6 para
que su código funciones, omite las funciones de la especificación VB6, y
obliga al modulo de ejecución de VB6 a hacer trabajo extra. Esta analogía es
equivalente al programador VB.NET que usa el espacio de nombres
Microsoft.VisualBasic para migrar un código de VB6 a VB.NET.
El código a continuación es VB.NET puro. Hasta la función
IsNumeric fue reemplazada por unas líneas muy eficientes.
VB.NET: Clase N2W.vb
|
Option Strict On '//required if we desired translate to C#
Imports System.Globalization
Public Class CN2W
Private Const ZERO As String = "Zero"
Private IntegerPart As String = ZERO
Private RealPart As String = ZERO
Private m_DecimalSeparator
As String
Private m_GroupSeparator As String
Private aT0() As String = {"One", "Two",
"Three", "Four", "Five",
"Six", _
"Seven", "Eight",
"Nine"}
Private aT1() As String = {"Ten", "Eleven",
"Twelve", "Thirteen", "Fourteen",
_
"Fifteen", "Sixteen",
"Seventeen", "Eighteen", _
"Nineteen"}
Private aT2() As String = {"Twenty", "Thirty",
"Forty", "Fifty", "Sixty",
_
"Seventy", "Eighty",
"Ninety"}
Private aT3() As String = {"", "",
" Thousand ", " Million ", "
Billion ", _
" Trillion ", "", "",
"", ""}
Public Sub New()
m_DecimalSeparator = _
CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator
m_GroupSeparator = _
CultureInfo.CurrentCulture.NumberFormat.CurrencyGroupSeparator
End Sub
Public Function IntegerToWords(ByVal
Number As String)
As String
Call
RealToWords(Number)
Return
IntegerPart
End Function
Public Function RealToWords(ByVal
Number As String)
As String
Dim
DotPos As Integer
= 0
IntegerPart = ZERO
RealPart = ZERO
If
IsNumeric(Number) Then
Number =
Number.Replace(m_GroupSeparator, "")
DotPos =
Number.IndexOf(DecimalSeparator)
If DotPos >= 0 Then
IntegerPart =
ToWords(Convert.ToInt32( _
Number.Substring(0, DotPos)))
RealPart =
ToWords(Convert.ToInt32(
Number.Substring(DotPos
+ 1, Number.Length - DotPos - 1)))
Else
IntegerPart =
ToWords(Convert.ToInt32(Number))
End If
End
If
Return
IntegerPart & " Dot " &
RealPart
End Function
Public Function CurrencyToWords( _
ByVal
Number As String,
_
ByVal
MoneyName As String)
As String
Dim
s As String
= ""
IntegerPart = ZERO
RealPart = ZERO
If
IsNumeric(Number) Then
' round to money number
s =
RealToWords(RoundNumberString(Convert.ToDouble(Number), 2))
End
If
'
add money text
If
IntegerPart = ZERO Then IntegerPart = "No"
Select
Case RealPart
Case ZERO : RealPart = "And
No Cents"
Case "One"
: RealPart = "And One Cent"
Case Else :
RealPart = "And " &
RealPart & " Cents"
End
Select
Return
IntegerPart & " " &
MoneyName & " " &
RealPart
End Function
Private Function ToWords(ByVal
Number As Integer)
As String
Dim
s As String
= ""
Dim
d As String
= ""
Dim
r As String
= ""
Dim
n As Integer
= 0
s = Number.ToString
n = 1
Do
Until s.Length = 0
' convert last 3 digits of s to english words
If s.Length < 3 Then
d =
ConvertHundreds(s)
Else
d =
ConvertHundreds(s.Substring(s.Length - 3, 3))
End If
If d.Length > 0 Then
r = d & aT3(n) & r
If s.Length > 3 Then
' remove last 3 converted digits from s.
s = s.Substring(0,
s.Length - 3)
Else
s = ""
End If
n += 1
Loop
If
r.Length = 0 Then r = ZERO
Return
r.Trim
End Function
Private Function ConvertHundreds(ByVal
pNumber As String)
As String
Dim
rtn As String
= ""
If
Not Convert.ToInt32(pNumber) = 0 Then
' append leading zeros to number.
pNumber = ("000" &
pNumber).Substring(pNumber.Length, 3)
' do we have a hundreds place digit to convert?
If Not
pNumber.Substring(0, 1) = "0" Then
rtn =
ConvertDigit(pNumber.Substring(0, 1)) & "
Hundred "
End If
' do we have a tens place digit to convert?
If pNumber.Length >= 2 Then
If Not pNumber.Substring(1,
1) = "0" Then
rtn &=
ConvertTens(pNumber.Substring(1))
Else
rtn &=
ConvertDigit(pNumber.Substring(2))
End If
End If
Return rtn.Trim
Else
Return ""
End
If
End Function
Private Function ConvertTens(ByVal
pTens As String)
As String
Dim
r As String
= ""
'
is value between 10 and 19?
If
Convert.ToInt32(pTens.Substring(0, 1)) = 1 Then
r =
aT1(Convert.ToInt32(pTens) - 10)
Else
' otherwise it's between 20 and 99.
r =
aT2(Convert.ToInt32(pTens.Substring(0, 1)) - 2) & " "
' convert ones place digit
r &=
ConvertDigit(pTens.Substring(pTens.Length - 1, 1))
End
If
Return
r
End Function
Private Function ConvertDigit(ByVal
pNumber As String)
As String
If
pNumber = "0" Then
Return ""
Else
Return aT0(Convert.ToInt32(pNumber) - 1)
End If
End Function
Private Function RoundNumberString( _
ByVal
Number As Double,
_
ByVal
Decimals As Integer)
As String
Dim
s As String
Dim
r As Double
'
round first
r = 10 ^ Decimals
'
Math.Floor make sure the float round. Suggested by Guillermo Som
r = CType(Math.Floor(Number * r + 0.5), Integer) / r
'
complete with zeros
s = r.ToString
If
s.IndexOf(".") < 0 Then s &= "."
Do
While s.Substring(s.IndexOf(".")).Length <= Decimals
s &= "0"
Loop
Return
s
End Function
'/ Source:
http://aspalliance.com/articleViewer.aspx?aId=80&pId=
'/ Traslate and
review to VB.NET by Harvey Triana
Private Function IsNumeric(ByVal
s As String)
As Boolean
Dim
HasDecimal As Boolean
= False
Dim
i As Integer
= 0
Dim
r As Boolean
= False
Dim
ds As Char
= Convert.ToChar(DecimalSeparator)
Dim
gs As Char
= Convert.ToChar(GroupSeparator)
Do
While i < s.Length
' Check for decimal
If s(i) = ds Then
If HasDecimal Then
r = False
Else ' 1st decimal
' inform loop decimal found and continue
HasDecimal = True
i += 1
Continue Do
End If
End If
' check if number
If Char.IsNumber(s(i))
Or (s(i) = gs) Then
r = True
Else
r = False
Exit Do
End If
i += 1
Loop
Return
r
End Function
Public ReadOnly Property
GroupSeparator() As String
Get
Return m_GroupSeparator
End
Get
End Property
Public ReadOnly Property
DecimalSeparator() As String
Get
Return m_DecimalSeparator
End
Get
End Property
End Class
|
Traducción Eficiente VB6 a VB.NET
El siguiente análisis de la traducción formal de VB6 a
VB.NET pone de manifiesto detalles importantes que se deberían seguir para
una traducción eficiente.
1. No Emplear el Espacio de Nombres
Microsoft.VisualBasic
Omitir el espacio de nombres Microsoft.VisualBasic por
supuesto hace bastante mas difícil la migración del código, pero pone de
presente que vamos a producir un código .NET puro. El espacio de nombres Microsoft.VisualBasic
se agrega de manera predeterminada a proyectos VB.NET, para omitirlo puede
entrar a propiedades, ubicar la ficha Referencias, y desactivar la casilla
correspondiente a Microsoft.VisualBasic.
2. Usar Option Strict On
Esto es particularmente importante si se desea producir
código muy resistente y evita “late bindig” que no percibimos. De otra parte,
facilita el camino para migrar el código VB.NET a C# si se quisiera. Al
obligar a usar conversión formal de tipos, estamos cumpliendo una regla formal
de C#. La ausencia de esta directiva tiende a producir un código .NET no
puro.
3. Usar Métodos de Framework Siempre que sea
Posible
Al omitir el espacio de nombres Microsoft.VisualBasic,
las funciones de cadena clásicas de VB6 como Mid$, Left$, y Rigth$, InStr,
y otras, deben ser reemplazadas por los métodos que suministra el Framework.
En general SubString reemplaza a Mid$, Left$, y Right$, e IndexOf
a InStr. No obstante debe tenerse precaución ya que SubString
por si solo no produce todo el funcionamiento de estas funciones. Si deseemos
podemos escribir dichas funciones en un lenguaje .NET para su uso. Si esta
interesado en la cuestión puede ver
http://200.118.112.230/DV/articles/BasicStrings_DotNET.htm
4. Asignación de Valor de Variables en su Declaración
VB6 no soporta asignación de valor de variables en su
declaración. Es conveniente ahorrar líneas de código en VB.NET y escribir las
líneas pertinentes.
5. Reemplazo de Arreglos Variant
La declaración explicita de los arreglos es más
intuitiva y elegante en VB.NET, además, podemos usar el tipo pertinente, lo
que produce un código de mejor desempeño. Por supuesto, recordar que .NET
inicia los arreglos con índice cero, por favor, no recurrir a artificios .NET
para producir arreglos que no inicien en cero, la potencia de .NET es
opacada. Siempre hay forma de reescribir las formulas que manipulan índices
para ajustarse a la norma de .NET.
6. Usar Métodos de Operador
Ya que VB.NET soporta operadores como “+=”, “&=”,
conviene usarlos. El desempeño es mejor que usar la sintaxis convencional de
VB6.
7. Usar Herencia Si es Posible
Este punto da para un articulo de varias páginas. En realidad
desde una aplicación normal de VB6 es difícil percibir en que nos
beneficiaria la herencia formal. Sin embargo para aplicaciones grandes la
necesidad se puede vislumbrar con facilidad. Otra cuestión que viene a
colación es la herencia que produce Implements de VB6 contra la
herencia formal de lenguajes .NET, o la herencia que produce Implements
de lenguajes .NET. Cuando una aplicación robusta de VB6 que ha usado Implements
masivamente, es preferible no migrarla a lenguajes .NET. Quizás sea preferible
analizar la arquitectura desde un punto de vista .NET y hacer todo de nuevo
si el tiempo lo permite. Es mi caso con algunas de mis aplicaciones.
8. Operaciones con Variables de Cadena
Sabemos que las variables de cadena en .NET son
inmutables, es decir se crea un nuevo espacio de memoria en cada asignación
de una variable tipo String. A raíz de esto, el Framework
suministra la clase StringBuilder para hacer eficiente operaciones con
cadenas. Estuve discutiendo el asunto con varios expertos en los foros de C#
de Microsoft (Jon Skeet, Greg Young, Ignacio Machin). La conclusión es que
si las operaciones con cadena requieren varios ciclos de computación, típico
en un bucle largo, es imperioso usar StringBuilder. Si las iteraciones
son de un numero discreto no muy grande, es permitido el uso directo de String
en operaciones de cadena, sin mucha perdida de desempeño. En el ejemplo del
articulo las operaciones con cadena rara vez superan las ocho iteraciones,
por lo que es eficiente mantener el código si recurrir a StringBuilder.
9. Una función IsNumeric Eficiente
Los lenguajes .NET en su especificación no incorporan IsNumeric,
pero hay varias formas de reproducir la función. El articulo de Ambrose
Little (ver referencias) polemiza el asunto y suministra la función con mejor
desempeño, la cual es la que uso en este articulo, traducida desde C# a
VB.NET. Recomendada.
Para terminar esta discusión, agrego que posiblemente a
Usted se le ocurran otras reglas de traducción VB6 a VB.NET. No obtente he
descrito las que a mi parecer son el principio.
Probando la Clase CN2W de VB.NET
La clase CN2W puede usarse en cualquier contexto .NET. A
continuación muestro una ejemplo extraído de una aplicación de Windows con
VB.NET. Supongo que con los nombres es suficiente para intuir su uso.
Ejemplo de uso de la clase N2W de VB.NET
|
Private Sub btnCurrencyToWords_Click( _
ByVal sender As System.Object, _
ByVal
e As System.EventArgs) Handles btnCurrencyToWords.Click
Dim
n2w As CN2W = New
CN2W()
Me.txtOutput.Text
= n2w.CurrencyToWords(Me.txtNumber.Text, "Dolars")
End Sub
|
En la descarga al final de articulo encuentra la
aplicación N2W_VB completa.
Solución
en C#
Consejo comanche. Es posible traducir una aplicación VB6
a C#, pero es varias veces más dificil que migrar primero a VB.NET y luego a
C#. Los lenguages .NET tiene su asidero en C#, y este deberia ser la opcion
para comenzar una aplicación .NET desde cero. Yo habia abandonado C en 1995
cuando descubri Visual Basic, vaya, uno hacia las cosas en menos de la
tercera parte del tiempo. No obstante la programacion por punteros se olvida
si no se practica, ademas es horrorosa. Asi pues, me case con Visual Basic.
Ahora, VS2005 nos trae un C que se depura como Visual Basic y el Intellisence
es potente ¿Por qué no usarlo?. Ademas, este maldito C# es muy bonito.
El codigo del problema expuesto en este articulo en C#
es el siguiente.
Clase N2W en C#
|
using System;
using System.Globalization;
public class CN2W
{
const
string ZERO = "Zero";
string
IntegerPart = ZERO;
string
RealPart = ZERO;
string
_DecimalSeparator;
string
_GroupSeparator;
string[]
aT0 = new string[]
{"One", "Two", "Three",
"Four", "Five", "Six",
"Seven", "Eight", "Nine"};
string[]
aT1 = new string[]
{"Ten", "Eleven", "Twelve",
"Thirteen",
"Fourteen", "Fifteen", "Sixteen",
"Seventeen",
"Eighteen", "Nineteen"};
string[]
aT2 = new string[]
{"Twenty", "Thirty", "Forty",
"Fifty", "Sixty",
"Seventy", "Eighty", "Ninety"
};
string[]
aT3 = new string[]
{"", "",
" Thousand ", " Million ", "
Billion ",
"Trillion ", "", "", "",
"" };
public
CN2W()
{
_DecimalSeparator =
CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator;
_GroupSeparator =
CultureInfo.CurrentCulture.NumberFormat.CurrencyGroupSeparator;
}
public
string IntegerToWords(string Number)
{
RealToWords(Number);
return IntegerPart;
}
public
string RealToWords(string
Number)
{
int DotPos =
0;
IntegerPart = ZERO;
RealPart = ZERO;
if (IsNumeric(Number))
{
Number =
Number.Replace(_GroupSeparator, "");
DotPos =
Number.IndexOf(_DecimalSeparator);
if (DotPos >= 0)
{
IntegerPart =
ToWords(Convert.ToInt32(
Number.Substring(0, DotPos)));
RealPart =
ToWords(Convert.ToInt32(
Number.Substring(DotPos
+ 1, Number.Length - DotPos - 1)));
}
else
{
IntegerPart =
ToWords(Convert.ToInt32(Number));
}
}
return IntegerPart + "
Dot " + RealPart;
}
public
string CurrencyToWords(string Number, string
MoneyName)
{
string s;
IntegerPart = ZERO;
RealPart = ZERO;
if(IsNumeric(Number))
{
// round to money number
s =
RealToWords(RoundNumberString(Convert.ToDouble(Number),
2));
}
// add money text
if (IntegerPart == ZERO) {IntegerPart = "No";}
switch(RealPart)
{
case ZERO:
RealPart = "And No Cents";
break;
case "One":
RealPart = "And One Cent";
break;
default:
RealPart = "And " + RealPart + " Cents";
break;
}
return IntegerPart + "
" + MoneyName + " "
+ RealPart;
}
private
string ToWords(int
Number)
{
string s = "";
string d = "";
string r = "";
int n;
s = Number.ToString();
n = 1;
while (s.Length != 0)
{
// convert last 3 digits of s to English r.
if (s.Length < 3) { d = ConvertHundreds(s); }
else { d = ConvertHundreds(s.Substring(s.Length -
3, 3)); }
if (d.Length > 0) { r = d + aT3[n] + r; }
if (s.Length > 3) { s = s.Substring(0,
s.Length - 3); }
else { s = "";
}
n++;
}
if (r.Length == 0) { r = ZERO; }
return r;
}
private
string ConvertHundreds(string pNumber)
{
string rtn = "";
if (!(Convert.ToInt32(pNumber)
== 0))
{
// append leading zeros to number
pNumber = ("000" +
pNumber).Substring(pNumber.Length, 3);
// do we have a hundreds place digit to convert?
if (!(pNumber.Substring(0, 1) == "0"))
{
rtn =
ConvertDigit(pNumber.Substring(0, 1)) + "
Hundred ";
}
// do we have a tens place digit to convert?
if (pNumber.Length >= 2)
{
if (!(pNumber.Substring(1, 1) == "0"))
{
rtn +=
ConvertTens(pNumber.Substring(1));
}
else
{
rtn +=
ConvertDigit(pNumber.Substring(2));
}
}
return rtn.Trim();
}
else { return ""; }
}
private
string ConvertTens(string
pTens)
{
string r = "";
// is value between 10 and 19?
if ((Convert.ToInt32(pTens.Substring(0,
1)) == 1))
{
r = aT1[Convert.ToInt32(pTens) - 10];
}
else
{
// otherwise it's between 20 and 99.
r = aT2[Convert.ToInt32(pTens.Substring(0, 1)) - 2] + " ";
// convert ones place digit
r +=
ConvertDigit(pTens.Substring((pTens.Length - 1), 1));
}
return r;
}
private
string ConvertDigit(string pNumber)
{
if (pNumber == "0")
{ return "";
}
else { return
aT0[Convert.ToInt32(pNumber) - 1]; }
}
private
string RoundNumberString(double Number, int
Decimals)
{
string s;
double r;
// round first
r = Math.Pow(10d, Decimals);
// Math.Floor make sure the float round. Suggested by
Guillermo Som
r = (int)(Math.Floor(Number
* r + 0.5d)) / r;
// complete with zeros
s = r.ToString();
if ((s.IndexOf(_DecimalSeparator) < 0))
{
s +=
_DecimalSeparator;
}
while
((s.Substring(s.IndexOf(_DecimalSeparator)).Length <= Decimals))
{
s += "0";
}
return s;
}
//
Source: http://aspalliance.com/articleViewer.aspx?aId=80&pId=
//
Review by Harvey Triana
private
bool IsNumeric(string
s)
{
bool
hasDecimal = false;
bool r = false;
char ds = Convert.ToChar(_DecimalSeparator);
char gs = Convert.ToChar(_GroupSeparator);
for (int i = 0; i
< s.Length; i++)
{
// check for decimal
if (s[i] == ds)
{
if (hasDecimal) // 2nd
decimal
r = false;
else // 1st decimal
{
// inform loop decimal found and continue
hasDecimal
= true;
continue;
}
}
// check if number
if (char.IsNumber(s[i])
|| (s[i] == gs))
r = true;
else
{
r = false;
break;
}
}
return r;
}
public
string GroupSeparator
{
get { return
_GroupSeparator; }
}
public
string DecimalSeparator
{
get { return _DecimalSeparator; }
}
}
|
Si comparacion del Código VB.NET contra el código no hay
mucho que decir. El codigo C# es como un dibujo calcado del codigo VB.NET
puro, o al contrario si lo prefiere. C# produce un código horizontal más
corto, y uno vertical más largo (que es diferente a menor número de líneas de
código).
Aunque si hay un detalle simpatico que se puede
mencionar. El operador “^” de VB.NET no tiene equivalente en C#, es por eso
que usamos Math.Pow(). - Supongo que se conservo el operador “^” en VB.NET
porque ya seria el colmo del olvido con los emigrantes de VB6.
Probando la Clase CN2W de C#
La clase CN2W puede usarse en cualquier contexto .NET. A
continuación muestro una ejemplo extraído de una aplicación de Windows con
C#. Supongo que con los nombres es suficiente para intuir su uso.
Ejemplo de uso de la clase N2W de C#
|
private void btnCurrencyToWords_Click(object sender, EventArgs e)
{
CN2W n2w = new CN2W();
this.txtOutput.Text
= n2w.CurrencyToWords(this.txtNumber.Text, "Dolars");
}
|
En la descarga al final de articulo encuentra la
aplicación N2W_VB completa.
Conclusiones
Si vamos a traducir una aplicación VB6 a VB.NET no
deberíamos usar el espacio de nombres Microsoft.VisualBasic. También,
deberíamos pegarnos a Option Strict On, ya que va a producir un código
con mejor fidelidad y eficiencia.
Escribir VB.NET puro es equivalente a escribir C#, la
diferencia radica en muy poco. Cuando escribes VB.NET puro realmente estas
usando .NET en su verdadero potencial.
Por favor, VB.NET no es la versión moderna de Microsoft
Visual Basic©, salvo por un asunto publicitario.
Nota
Se hizo una corrección el 6 de Agosto de 2006 para dar
soporte al sistema regional Europeo.
Referencias
1. Which IsNumeric method should
you use? Check this article out to find out!, J. Ambrose Little
http://aspalliance.com/articleViewer.aspx?aId=80&pId=
2. Difference between (int),
Int32.Parse and Convert.ToInt32
http://blogs.msdn.com/ianhu/archive/2005/12/19/505702.aspx
3. el Guille
http://www.elguille.info/
Código del Articulo:
http://200.118.112.230/DV/articles/TRANSMUTACION_VB6_DotNET.zip
Anexo
1. Solución VB6
La clase origen CN2W es a siguiente. Un código VB6 que
trabaja a cabalidad, y es eficiente.
VB6: Clase CN2W.cls
|
'
----------------------------------
' Class CN2W_VB6
' By Harvey Triana
'
----------------------------------
Option Explicit
Private Const ZERO As String = "Zero"
Private IntegerPart As String
Private RealPart As String
Private m_DecimalSeparator
As String
Private m_GroupSeparator
As String
Private aT0, aT1, aT2, aT3
Public Function IntegerToWords(ByVal
Number As String)
As String
Call
RealToWords(Number)
IntegerToWords =
IntegerPart
End Function
Public Function RealToWords(ByVal
Number As String)
As String
Dim
DotPos As Long
IntegerPart = ZERO
RealPart = ZERO
If
IsNumeric(Number) Then
DotPos = InStr(Number,
m_DecimalSeparator)
Number =
Replace$(Number, m_GroupSeparator, "")
If DotPos Then
IntegerPart =
ToWords(Int(Left$(Number, DotPos - 1)))
RealPart =
ToWords(Int(Mid$(Number, DotPos + 1)))
Else
IntegerPart =
ToWords(Int(Number))
End If
End
If
RealToWords = IntegerPart
& " Dot " & RealPart
End Function
Public Function CurrencyToWords( _
ByVal
Number As String,
ByVal MoneyName As
String) As String
Dim
s As String
IntegerPart = ZERO
RealPart = ZERO
If
IsNumeric(Number) Then
' round to money number
s =
RealToWords(RoundNumberString(CDbl(Number),
2))
End
If
'
add money text
If
IntegerPart = ZERO Then IntegerPart = "No"
Select
Case RealPart
Case ZERO: RealPart = "And
No Cents"
Case "One":
RealPart = "And One Cent"
Case Else:
RealPart = "And " &
RealPart & " Cents"
End
Select
CurrencyToWords =
IntegerPart & " " &
MoneyName & " " &
RealPart
End Function
Private Function ToWords(ByVal
Number As Long)
As String
Dim
s As String
Dim
d As String
Dim
r As String
Dim
Count As Long
s = CStr(Number)
Count = 1
Do
Until Len(s)
= 0
' convert last 3 digits of s to english words
If Len(s) < 3 Then
d =
ConvertHundreds(s)
Else
d =
ConvertHundreds(Right$(s, 3))
End If
If Len(d) > 0 Then r = d & aT3(Count) & r
If Len(s) > 3 Then
' remove last 3 converted digits from s.
s = Left$(s, Len(s) - 3)
Else
s = ""
End If
Count = Count + 1
Loop
If
Len(r) = 0 Then
r = ZERO
ToWords = Trim$(r)
End Function
Private Function ConvertHundreds(ByVal
pNumber As String)
As String
Dim
r As String
If
Not Int(pNumber)
= 0 Then
' append leading zeros to number.
pNumber = Right$("000" & pNumber, 3)
' do we have a hundreds place digit to convert?
If Not
Left$(pNumber, 1) = "0" Then
r =
ConvertDigit(Left$(pNumber, 1)) & "
Hundred "
End If
' do we have a tens place digit to convert?
If Len(pNumber)
>= 2 Then
If Not
Mid$(pNumber, 2, 1) = "0" Then
r = r &
ConvertTens(Mid$(pNumber, 2))
Else
r = r &
ConvertDigit(Mid$(pNumber, 3))
End If
End If
ConvertHundreds =
Trim(r)
End
If
End Function
Private Function ConvertTens(ByVal
pTens As String)
As String
Dim
r As String
'
is value between 10 and 19?
If
Int(Left$(pTens, 1)) = 1 Then
r = aT1(Int(pTens) - 10)
Else
' otherwise it's between 20 and 99.
r = aT2(Int(Left$(pTens, 1)) - 2) & " "
' convert ones place digit
r = r &
ConvertDigit(Right$(pTens, 1))
End
If
ConvertTens = r
End Function
Private Function ConvertDigit(ByVal
pNumber As String)
As String
If
pNumber = "0" Then
ConvertDigit = ""
Else
ConvertDigit = aT0(Int(pNumber) - 1)
End
If
End Function
Private Function RoundNumberString( _
ByVal
Number As Currency,
ByVal Decimals As
Long) As String
Dim
s As String
Dim
r As Double
'
round first
r = Int(Number * 10 ^ Decimals + 0.5) / (10 ^
Decimals)
'
complete with zeros
s = CStr(r)
If
InStr(s, m_DecimalSeparator) = 0 Then s = s
& m_DecimalSeparator
Do
While Len(Mid$(s,
InStr(s, m_DecimalSeparator))) <= Decimals
s = s & "0"
Loop
RoundNumberString = s
End Function
Private Sub Class_Initialize()
IntegerPart = ZERO
RealPart = ZERO
aT0 = Array("One",
"Two", "Three",
"Four", "Five", "Six",
"Seven", "Eight",
"Nine")
aT1 = Array("Ten",
"Eleven", "Twelve", "Thirteen",
"Fourteen", "Fifteen",
"Sixteen", "Seventeen",
"Eighteen", "Nineteen")
aT2 = Array("Twenty",
"Thirty", "Forty", "Fifty",
"Sixty", "Seventy",
"Eighty", "Ninety")
aT3 = Array("",
"", "
Thousand ", " Million ",
" Billion ", " Trillion ", "",
"", "",
"")
'$GUILLE:
06/Ago/2006 14.53
If
InStr(Format$(1.5, "#.#"), ",") Then
m_DecimalSeparator = ","
m_GroupSeparator = "."
Else
m_DecimalSeparator = "."
m_GroupSeparator = ","
End
If
End Sub
Public Property Get
DecimalSeparator() As String
DecimalSeparator =
m_DecimalSeparator
End Property
Public Property Get
GroupSeparator() As String
GroupSeparator =
m_GroupSeparator
End Property
|
Nota. La declaración de variables en el evento Class_Initialize,
es una manera de asignar variables en VB6 antes de usarlas ¿Alguna duda? No
obstante los arreglos inicializados explícitamente deben ser de tipo Variant,
una limitación de VB6.
Probando la Clase CN2W de VB6
La clase CN2W puede usarse en cualquier contexto COM. A
continuación muestro una ejemplo extraído de una aplicación de Windows con
VB6.
Ejemplo de uso de la clase N2W de VB6
|
Private Sub btnCurrencyToWords_Click()
Dim
n2w As CN2W
Set
n2w = New CN2W
Me.txtOutput.Text
= n2w.CurrencyToWords(Me.txtNumber.Text, "Dolars")
Set
n2w = Nothing
End Sub
|
En la descarga al final de artículo encuentra la
aplicación N2W_VB6 completa.
|