Textbox Calculadora Fecha: 27/Sep/2004 (23/09/2004)
|
He utilizado algunas aplicaciones contables y administrativas con anterioridad. Algo que siempre quise cuando cargaba datos era la posibilidad de poder realizar cálculos directamente en los campos de entrada de cada formulario, para evitar tener que utilizar una calculadora, ya sea alguna de escritorio o la de Windows.
Fue una materia pendiente hasta que dejé de utilizar dichas aplicaciones para pasar a desarrollarlas. Ahora que tengo la posibilidad de crear esas aplicaciones por mí mismo voy a hacer realidad mi deseo.
Así es como escribí este artículo, para darme el gusto y porque creo que es un buen ejemplo de algunas nuevas características que trae .Net. Es un breve pero demostrativo modo para mostrar cómo se puede extender las características intrínsecas de un control de Windows para darle nuevas funcionalidades y cómo utilizar una tecnología llamada CodeDom para generar y ejecutar código dinámicamente. (Para más información sobre cómo hacer esto referirse a mi artículo CodeProject - CompilingWithCodeDom, por el momento está en Inglés pero tengo pensado traducirlo para subirlo a este sitio posteriormente).
El resultado es un TextBox en el cual se puede escribir expresiones matemáticas comunes (utilizando la misma sintaxis que en VB) y al presionar Enter o al perder foco, el contenido del TextBox se reemplaza por el valor correspondiente a la expresión.
Abajo se detalla la clase ExpressionEngine que representa el motor de evaluación de expresiones matemáticas. Como miembros a nivel de clase se encuentran todos los objetos del CodeDom necesarios para generar el código para evaluar la expresión. En el constructor de la clase creamos una variable SourceCodeTemplate que contiene la clase que se va a compilar y ejecutar dinámicamente para que nos devuelva el resultado de la operación, luego creamos un objeto para compilar el código e inicializamos los parámetros del mismo a través de la clase VBCompilerParameters. La función EvaluateNumericExpression es el corazón de la clase, la misma recibe la expresión a evaluar y devuelve el resultado de la misma. En esta función reemplazamos la marca "<@Expression@>" por la expresión recibida por parámetros, compilamos el código y luego ejecutamos dinámicamente la función que nos devuelve el valor.
Public Class ExpressionEngine Private SourceCodeTemplate As String 'Objetos para la compilacion dinamica de las expresiones Dim oVBCodeProvider As New VBCodeProvider Dim oVBCodeCompiler As ICodeCompiler Dim oVBCompilerParameters As New CompilerParameters Dim oVBCompilerResults As CompilerResults Dim oVBCompilerError As CompilerError Public Sub New() 'Genero el template de codigo a compilar Dim oCodeSb As New StringBuilder oCodeSb.Append("Public Class ExpressionToEval") : oCodeSb.Append(vbNewLine) oCodeSb.Append(" Public Function Eval() As Double") : oCodeSb.Append(vbNewLine) oCodeSb.Append(" Dim Value As Double") : oCodeSb.Append(vbNewLine) oCodeSb.Append(" Value = <@Expression@>) : oCodeSb.Append(vbNewLine) oCodeSb.Append(" Return Value") : oCodeSb.Append(vbNewLine) oCodeSb.Append(" End Function") : oCodeSb.Append(vbNewLine) oCodeSb.Append("End Class") : oCodeSb.Append(vbNewLine) SourceCodeTemplate = oCodeSb.ToString 'Creo el compilador oVBCodeCompiler = oVBCodeProvider.CreateCompiler 'Seteo los valores de los parametros With oVBCompilerParameters 'Genera un DLL .GenerateExecutable = False .IncludeDebugInformation = False .TreatWarningsAsErrors = False .GenerateInMemory = True End With End Sub' Evalua una expresion numerica Public Function EvaluateNumericExpression(ByVal Expression As String) As Double Dim Source As String Dim GeneratedAssembly As [Assembly] Dim ExpressionToEvalObject As Object 'Genero el codigo fuente Source = SourceCodeTemplate.Replace("<@Expression@>", Expression) 'Compiling process oVBCompilerResults = oVBCodeCompiler.CompileAssemblyFromSource(oVBCompilerParameters, Source) If oVBCompilerResults.Errors.Count > 0 Then Dim ErrorLog As String For Each oVBCompilerError In oVBCompilerResults.Errors ErrorLog += oVBCompilerError.ToString & vbNewLine Console.WriteLine(oVBCompilerError.ToString) Next If oVBCompilerResults.Errors.Count > 0 Then Throw New Exception("The compiler was throwed the following errors:" & vbNewLine & ErrorLog) End If Throw New Exception("The evaluation expression is incorrect") End If 'Ejecuta el codigo compilado utilizando reflection GeneratedAssembly = oVBCompilerResults.CompiledAssembly ExpressionToEvalObject = GeneratedAssembly.CreateInstance("ExpressionToEval") Return GeneratedAssembly.GetType("ExpressionToEval").InvokeMember("Eval", BindingFlags.InvokeMethod, Nothing, ExpressionToEvalObject, Nothing) End Function End ClassLa segunda parte de nuestra tarea es crear un control que herede la funcionalidad del TextBox y adicione la capacidad de calcular expresiones a través de la clase que hemos creado con anterioridad. En el TextBox simplemente creamos un objeto del tipo ExpressionEngine para que nos permita calcular la expresión escrita en el mismo. Obtenemos el DecimalSeparator para utilizar el separador decimal que está definido en la configuración regional del sistema y agregamos dos propiedades al control para que el usuario pueda personalizar el comportamiento del control. La función GetResult utiliza el motor de evaluación para calcular el resultado y lo devuelve. La función Calculate reemplaza el texto de la expresión por su correspondiente resultado. Y luego tenemos sobrecargados los procedimientos OnKeyPress y OnLeave. El primero para filtrar sólo las teclas permitidas en las expresiones a calcular y el segundo para resolver automáticamente la expresión cuando se produce el evento Leave del control.
Public Class TextBoxCalculator Inherits System.Windows.Forms.TextBox 'Motor de evaluacion Protected ExpressionEngine As New ExpressionEngine 'Separador decimal Protected DecimalSeparator As String = CurrentCulture.NumberFormat.NumberDecimalSeparator 'Indica si calculamos automaticamente el resultado al salir del control Protected _CalculateOnLeave As Boolean = False 'Inidica si calculamos automaticamente el resultado al presionar ENTER Protected _CalculateOnPressEnter As Boolean = True #Region " Windows Form Designer generated code " Public Sub New() MyBase.New() 'This call is required by the Windows Form Designer. InitializeComponent() 'Add any initialization after the InitializeComponent() call End Sub 'UserControl1 overrides dispose to clean up the component list. Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub 'Required by the Windows Form Designer Private components As System.ComponentModel.IContainer 'NOTE: The following procedure is required by the Windows Form Designer 'It can be modified using the Windows Form Designer. 'Do not modify it using the code editor. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() components = New System.ComponentModel.Container End Sub #End Region ' Indica si calculamos automaticamente el resultado al presionar ENTER <Browsable(True), Category("Behavior"), DefaultValue(True), _
Description("Calculate expression when the ENTER key is pressed")> _ Public Property CalculateOnPressEnter() As Boolean Get Return _CalculateOnPressEnter End Get Set(ByVal Value As Boolean) _CalculateOnPressEnter = Value End Set End Property ' Indica si calculamos automaticamente el resultado al salir del control <Browsable(True), Category("Behavior"), DefaultValue(False), _
Description("Calculate expression the user lived the control")> _ Public Property CalculateOnLeave() As Boolean Get Return _CalculateOnLeave End Get Set(ByVal Value As Boolean) _CalculateOnLeave = Value End Set End Property ' Calculo el resultado Public Function GetResult() As Double Dim strExpression As String strExpression = Me.Text 'Si no hay ninguna expresion retornamos 0 If strExpression = "" Then Return 0 Else 'Reemplazo el caracter separador decimal del sistema por el . strExpression = strExpression.Replace(CurrentCulture.NumberFormat.NumberDecimalSeparator, ".") Return ExpressionEngine.EvaluateNumericExpression(strExpression) End If End Function ' Calcula el resultado de la expresion Public Sub Calculate() Dim strResultado As Double If Me.Text <> "" Then 'Calculo el resultado strResultado = Me.GetResult 'Reemplazo la formula por el resultado Me.Text = strResultado End If End Sub ' sobreescribimos el evento OnKeyPress para permitir solo caracteres numericos Protected Overrides Sub OnKeyPress(ByVal e As System.Windows.Forms.KeyPressEventArgs) MyBase.OnKeyPress(e) Dim strMask As String strMask = "0123456789+*-/\^E()" & vbBack & CurrentCulture.NumberFormat.NumberDecimalSeparator 'Si el carater es el ENTER realizo el calculo If _CalculateOnPressEnter AndAlso e.KeyChar = vbCr Then Calculate() End If 'Si el caracter presionado está permitido If strMask.IndexOf(e.KeyChar) < 0 Then e.Handled = True End If End Sub ' Sobreescribrimos el evento Leave que se ejecuta cuando salimos del TextBox Protected Overrides Sub OnLeave(ByVal e As System.EventArgs) MyBase.OnLeave(e) 'Si esta activado el calcular en este evento If CalculateOnLeave Then Calculate() End If End Sub End ClassEspero que les sea útil. :-)
Fichero con el código de ejemplo: gbonansea_TextoboxCalculator.zip - Tamaño 20KB