Textbox Calculadora
Textbox que calcula expresiones matemáticas

Fecha: 27/Sep/2004 (23/09/2004)
Autor: Gustavo Bonansea gustavobonansea@yahoo.com.ar

 


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 Class

La 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 Class

 

Espero que les sea útil. :-)

 


ir al índice

Fichero con el código de ejemplo: gbonansea_TextoboxCalculator.zip - Tamaño 20KB