Requerimiento

Se solicitó desarrollar una calculadora que permita realizar operaciones básicas como suma, resta, multiplicación y división. Esta debe estar desarrollada en Windows Forms, una conjunto de librerías de .NET que permite crear aplicaciones de escritorio con interfaces gráficas.

Además, se requiere que la calculadora tenga una interfaz gráfica amigable para el usuario en base a lo aprendido en UX/UI de primer año.

En este ejemplo se realizará lo requerido, pero se hará con un enfoque completo, es decir, se aplicarán los conceptos de Programación Orientada a Objetos para estructurar el código de manera eficiente y mantenible.

Contexto

Para mantener la simplicidad en esta instancia inicial, el proyecto se mantendrá en una estructura mínima. Evitaremos separar el código en capas (como Modelos, Vistas y Controladores) para enfocarnos exclusivamente en la lógica de los objetos.

Diseño

Se optó por el siguiente diseño, basado en la calculadora KCalc de KDE, que es una calculadora de código abierto y muy utilizada en sistemas operativos basados en Linux.
Este diseño es simple, intuitivo y fácil de usar, lo que lo hace ideal para este proyecto.

KCalc

KCalc, calculadora de KDE en la que nos basamos para el diseño de nuestra calculadora.

Nuestra Calculadora

Nuestra calculadora, basada en el diseño de KCalc.

Creación

Con el fin de intentar utilizar los mismos colores, empezamos por establecer algunas de las propiedades del Form, como el BackColor y el ForeColor, para que se asemeje al diseño de KCalc.

Propiedades del Form:

Padding: 20; 20; 20; 20

Espaciado interno del Form, para que los controles no estén pegados a los bordes.

Size: 550;520

Tamaño del Form.

Icon: Un ícono de nuestra preferencia.

Esto es opcional, pero le da un toque más profesional a la aplicación.

Text: Calculadora

El título del Form, que se muestra en la barra de título.

ForeColor: White

Color blanco para el texto, para que contraste con el fondo oscuro.

BackColor: 44; 44; 44

Un color oscuro para el fondo, similar al de KCalc.

Name: frmCalc

Nombre descriptivo para identificar el Form.

TableLayoutPanel

Un TableLayoutPanel es un control que permite organizar otros controles en una cuadrícula de filas y columnas. Es ideal para este proyecto, ya que nos permite organizar los botones de la calculadora de manera ordenada y consistente.

Si observamos el diseño de KCalc, podemos ver que los botones están organizados en una cuadrícula de 6 columnas (la 5 y la 6 unidas) y 5 filas:

alt text

Por lo tanto, crearemos un TableLayoutPanel con 6 columnas y 5 filas para organizar los botones de nuestra calculadora.

Para esto, agregamos un TableLayoutPanel al Form y configuramos sus propiedades:

Anchor: Top, Bottom, Left, Right

Esto hace que el TableLayoutPanel se ajuste al tamaño del Form, lo que es importante para mantener un diseño consistente cuando el usuario redimensiona la ventana (Diseño Responsivo).

Rows: Collection -> Agregar 5 filas y que todas usen el 50%.

Esto asegura que todas las filas tengan el mismo alto

Columns: Collection -> Agregar 6 columnas y que todas usen el 50%.

Esto asegura que todas las columnas tengan el mismo ancho

Name: tlpButtons

Nombre descriptivo para identificar el TableLayoutPanel.

Debería quedar algo así hasta ahora:

alt text

Panel de Pantalla

En el diseño de KCalc, hay dos controles en la parte superior que muestran la operación actual y el resultado. Para esto, agregaremos dos TextBox en la parte superior del Form, justo arriba del TableLayoutPanel, para mostrar la operación actual y el resultado, o bien, podríamos crear un nuevo TableLayoutPanel (el cual en este caso llamamos tlpDisplays) con dos filas y una sola columna para organizar estos TextBox de manera ordenada.

alt text

Propiedades de los TextBox:

Dock: Fill

Esto hace que los TextBox usen todo el espacio disponible en su celda del TableLayoutPanel

TextAlign: Right

Esto alinea el texto a la derecha, lo que es común en las calculadoras para que los números se alineen a la derecha a medida que se ingresan.

ReadOnly: True

Esto hace que el usuario no pueda escribir directamente en los TextBox, ya que solo se mostrarán los resultados de las operaciones.

ForeColor: White

Color del texto en los TextBox, para que contraste con el fondo oscuro.

BackColor: 49; 49; 49

Color de fondo de los TextBox.

Font: Arial, 20pt para tbOperacion y Arial, 35pt para tbResultado

Fuente y tamaño del texto en los TextBox.

Name: tbOperacion y tbResultado

Nombres descriptivos para identificar cada TextBox.

Botones

Ahora, agregaremos los botones a cada celda del TableLayoutPanel, siguiendo el diseño de KCalc. Para esto, arrastramos y soltamos botones desde la caja de herramientas a cada celda del TableLayoutPanel.

Recomendamos agregar y configurar uno, luego copiarlo y pegarlo para mantener la consistencia en el diseño y las propiedades de los botones, modificando únicamente el texto y el nombre de cada botón.

Propiedades de los botones:

FlatAppearance -> BorderSize: 0

Esto elimina el borde del botón

FlatAppearance tiene otras propiedades que recomendamos echar un vistazo, como el MouseOverBackColor, que permite cambiar el color del botón cuando el usuario pasa el mouse por encima, lo que mejora la experiencia del usuario (UX).

FlatStyle: Flat

Esto hace que el botón tenga un estilo plano, sin bordes ni sombras

Dock: Fill

Esto hace que el botón use todo el espacio disponible en su celda del TableLayoutPanel

Name: Un nombre descriptivo para cada botón (ejemplo: btnSuma, btnResta, etc.).

Esto es importante para identificar cada botón en el código y asignarles eventos de clic.

Text: El texto que corresponda a cada botón.

El texto que se muestra en cada botón, para que el usuario sepa qué función tiene cada uno.

ForeColor: White

Color del texto en los botones, para que contraste con el fondo oscuro.

BackColor: 49; 49; 49

Color de fondo de los botones.

Font: Arial, 15pt

Fuente y tamaño del texto en los botones.

Botones largos

En el diseño de KCalc, los botones C, AC, (, ) y +/- son más largos que los demás, ya que ocupan el espacio de dos columnas. Para lograr esto, debemos configurar la propiedad ColumnSpan de estos botones para que ocupen dos columnas en el TableLayoutPanel en lugar de una. Para esto, seleccionamos cada uno de estos botones, vamos a la propiedad ColumnSpan y la configuramos a 2.

Con ColumnSpan = 1:
alt text
Con ColumnSpan = 2:
alt text

Con todo esto realizado, deberíamos tener una interfaz gráfica similar a la de KCalc, con un diseño limpio y profesional, y lista para ser funcionalizada con código en C#.

Código

Estructura del proyecto

Debemos comprender antes de agregar código, que el framework de Windows Forms ya nos proporciona una estructura básica para nuestra aplicación:

  • Program.cs: Es el punto de entrada de la aplicación (El código que se ejecuta al iniciar la aplicación)
  • frmCalc.Designer.cs: Contiene el código generado automáticamente por el diseñador de Windows Forms para crear la interfaz gráfica.

Form1.cs: Archivo que contiene la clase del formulario principal de la calculadora. En este caso cambiaremos también el nombre del archivo a frmCalc.cs, para que sea más descriptivo y fácil de identificar (igual que la propiedad Name del Form).

alt text

Estructura de la clase frmCalc

Podremos observar el siguiente código en frmCalc.cs:

public partial class frmCalc : Form
    public frmCalc()

Si lo desglosamos, vemos que:

  • public: Es un modificador de acceso que indica que la clase frmCalc es accesible desde cualquier parte del programa.
  • partial: Indica que la definición de la clase frmCalc está dividida en varios archivos. En este caso, la definición de la clase está dividida entre frmCalc.cs y frmCalc.Designer.cs. Esto es común en Windows Forms, ya que el código generado por el diseñador se mantiene separado del código que escribimos manualmente, lo que facilita la organización y el mantenimiento del código.
  • class frmCalc: Define una clase llamada frmCalc, que es el formulario principal de nuestra calculadora. Esta clase hereda de Form, lo que significa que frmCalc es un tipo de formulario de Windows Forms y puede utilizar todas las funcionalidades que ofrece la clase Form.
  • public frmCalc(): Es el constructor de la clase frmCalc. El constructor es un método especial que se ejecuta automáticamente cuando se crea una instancia de la clase. En este caso, el constructor se utiliza para inicializar el formulario y configurar cualquier lógica adicional que necesitemos al iniciar la aplicación.

El código

Constructor

El constructor se ejecuta al crear una instancia del formulario.

  • public frmCalc(): constructor principal del formulario.
  • InitializeComponent(): inicializa y dibuja en pantalla todos los controles creados en el diseñador de Windows Forms.

Constantes

Comenzaremos abriendo el archivo frmCalc.cs. Este será el archivo donde escribiremos la mayoría del código de nuestra calculadora, ya que el archivo frmCalc.Designer.cs está destinado únicamente para el código generado por el diseñador de Windows Forms, y no es recomendable modificarlo manualmente, ya que cualquier cambio que hagamos allí podría ser sobrescrito por el diseñador. Por otra parte, el archivo Program.cs solo contiene el código de inicio de la aplicación, y no es necesario modificarlo para nuestro proyecto.

Lo primero que haremos es crear algunas constantes en frmCalc.cs que nos van a servir para validar posteriormente, como una lista de operaciones y una lista de números, para asegurarnos de que el usuario solo ingrese caracteres válidos en la calculadora.

Eventos de los botones

Lo normal es que cada botón tenga un evento de clic asociado. Que hagamos doble clic en cada botón en el diseñador de Windows Forms y que genere un método para cada uno de ellos.

El problema de esto es que vamos a terminar con demasiados métodos, uno para cada botón, y el código va a quedar muy desorganizado y difícil de trabajar. Además, muchos de estos métodos van a tener código repetido, lo que no es una buena práctica de programación.

Para evitar esto, podemos asignar el mismo evento de clic a todos los botones que tengan una función similar.

En este caso lo que queremos es que se escriban en la pantalla de la calculadora los números y las operaciones que el usuario ingrese, por lo que podemos asignar el mismo evento de clic a todos los botones numéricos, a los paréntesis, la coma y a las operaciones, ya que todos estos botones van a tener una función similar: agregar su texto al TextBox de la operación actual.

Para esto, seleccionamos los botones mencionados manteniendo presionada la tecla Shift y haciendo clic en cada uno de ellos.

alt text

Luego, vamos a la ventana de propiedades, buscamos el evento Click y asignamos el mismo método para todos los botones seleccionados, por ejemplo, btnAppendTextClick:

alt text

Esto hará que cuando el usuario haga clic en cualquiera de estos botones, se ejecute el mismo método btnAppendTextClick, lo que nos permitirá manejar la lógica de agregar el texto al TextBox de manera centralizada y evitar código repetido.

Método btnAppendTextClick

El método btnAppendTextClick se ejecutará cada vez que el usuario haga clic en cualquiera de los botones numéricos, de operaciones, paréntesis o coma. Este método se encargará de agregar el texto del botón al TextBox de la operación actual.

Para lograr esto, deberíamos agregar el siguiente código al método btnAppendTextClick:

alt text

Desglosemos el código:

  • private void btnAppendTextClick(object sender, EventArgs e): Este es el método que se ejecuta cuando se hace clic en cualquiera de los botones seleccionados. Es un método privado, lo que significa que solo puede ser llamado desde dentro de la clase frmCalc.
    El método no devuelve ningún valor (void) y recibe dos parámetros: sender, que es el objeto que generó el evento (en este caso, el botón que se hizo clic), y e, que contiene información adicional sobre el evento (en este caso, no se utiliza).
  • Button btn = (Button)sender;: Este código convierte el objeto sender (que es el botón que se hizo clic) a un tipo Button, lo que nos permite acceder a sus propiedades, como el texto del botón.
  • tbOperacion.Text += btn.Text;: Este código agrega el texto del botón al TextBox tbOperacion, que es donde se muestra la operación actual. El operador += se utiliza para concatenar el texto del botón al texto existente en el TextBox, lo que permite que el usuario vea la operación completa a medida que ingresa los números y las operaciones.

Hasta ahora, con este código, cada vez que el usuario haga clic en un botón numérico, de operación, paréntesis o coma, el texto de ese botón se agregará al TextBox de la operación actual, permitiendo al usuario construir la operación que desea calcular.

Aunque este método es funcional, no tiene ninguna validación, lo que significa que el usuario podría ingresar una operación con una sintaxis incorrecta, como ++ o 1..2, lo que causaría un error al intentar calcular el resultado. Para evitar esto, es necesario agregar validaciones adicionales para asegurarnos de que la operación ingresada por el usuario tenga una sintaxis correcta antes de intentar calcular el resultado. Esto se puede hacer en el mismo método btnAppendTextClick o en un método separado que se llame antes de calcular el resultado.

Validaciones en btnAppendTextClick

Para evitar sintaxis inválida al escribir operaciones, agregaremos algunas validaciones dentro del mismo método.

alt text

¿Qué validamos?

  • Si el botón presionado es un número, lo agregamos directamente al TextBox y terminamos el método con return.
  • Si el botón presionado es una operación, verificamos si ya hay texto en pantalla.
  • Si ya hay texto, revisamos el último carácter escrito.
  • Si el último carácter también es una operación, reemplazamos ese último carácter por el nuevo operador en lugar de agregar otro.

Con esto evitamos casos como 5++3, 9--2, 4//2, etc.


¿Qué es DataTable.Compute y por qué lo usamos?

DataTable.Compute es un método de .NET que permite evaluar expresiones escritas como texto (por ejemplo, "(3+2)*4") y devolver el resultado.

En otras palabras: recibe una cadena con una operación matemática y la calcula sin que tengamos que programar un parser matemático desde cero.

¿Por qué lo usamos en esta calculadora?

  • Será nuestra herramienta principal para evaluar las operaciones que el usuario ingresa en la calculadora.
  • Simplificará mucho la implementación del botón =.
  • Evita tener que desarrollar manualmente la precedencia de operadores (*, /, +, -, paréntesis, etc.).
  • Es una solución rápida y eficiente para el propósito de esta calculadora básica.

Botón de igual (=)

Ahora vamos a crear la lógica del botón igual para evaluar la expresión.

Como en el resto de los casos, asignamos el evento Click del botón = a un método, por ejemplo: btnEqualClick.

alt text
Método btnEqualClick

Este método se debe encargar de:

  • Limpiar el resultado anterior.
  • Preparar la expresión para que tenga una sintaxis válida.
  • Transformar formatos que DataTable.Compute no entiende directamente (como ciertos porcentajes).
  • Evaluar la expresión.
  • Mostrar el resultado o un mensaje de error de sintaxis.
alt text

Desglosemos los puntos importantes:

  1. Bloque try/catchEncerramos la evaluación en un try/catch porque cualquier error de formato o sintaxis lanzará una excepción. Así evitamos que la app se cierre inesperadamente.
  2. Paréntesis sin cerrarAntes de evaluar, verificamos si existe ( y no existe ). En ese caso:
    • Si la expresión termina en (, se limpia el último carácter usando el botón de borrado.
    • Si no termina en (, se agrega automáticamente ) para cerrar la expresión.
  3. Conversión de porcentajeDataTable.Compute no interpreta directamente una expresión como 50%100 con el significado "50 por ciento de 100".Por eso usamos Regex.Replace para transformarlo a:Ese formato sí puede evaluarse correctamente.
    • Entrada: 50%100
    • Salida: (100*50)/100
  4. Decimales con comaEl evaluador espera punto decimal en lugar de comas, por lo que reemplazamos , por . antes de calcular.
  5. Evaluación de la expresiónSe crea un DataTable y se usa el método Compute para evaluar el string matemático.
  6. Manejo de erroresSi ocurre una DataException, escribimos el error técnico en consola de depuración con Debug.WriteLine y mostramos al usuario el texto definido en la constante SYNTAX_ERROR.

Botón de borrado (C)

Este botón elimina el último carácter de la operación actual.

Asignamos su evento Click al método btnClearClick.

alt text
Método btnClearClick
alt text

Lógica:

  • Si no hay texto en pantalla, no hace nada.
  • Si hay texto, recorta el último carácter con Substring.

Esto permite corregir rápidamente la operación sin limpiar todo.


Botón de limpieza total (AC)

Este botón reinicia la calculadora visualmente, limpiando operación y resultado.

Asignamos su evento Click al método btnClearAllClick.

alt text
Método btnClearAllClick
alt text

Lógica:

  • tbCalc.Text = string.Empty;
  • tbResult.Text = string.Empty;

Con esto dejamos la calculadora en estado inicial.


Botón de negación (+/-)

Este botón cambia el signo del resultado actual.

Asignamos su evento Click al método btnNegateClick.

alt text
Método btnNegateClick
alt text

Lógica:

  1. Primero llama a btnEqualClick para asegurarse de que la expresión sea válida y obtener un resultado actualizado.
  2. Si el resultado es Error de sintaxis, no hace nada.
  3. Si el resultado no empieza con -, le agrega - al inicio.
  4. Si el resultado sí empieza con -, elimina ese primer carácter para volverlo positivo.

Asignación final de eventos

Al terminar, deberíamos tener asignados los eventos de esta forma:

  • Botones numéricos, coma, paréntesis y operadores: btnAppendTextClick
  • Botón =: btnEqualClick
  • Botón C: btnClearClick
  • Botón AC: btnClearAllClick
  • Botón +/-: btnNegateClick

Resultado final

Con esto, la calculadora ya permite:

  • Escribir operaciones matemáticas básicas.
  • Corregir errores de ingreso rápidamente.
  • Evaluar expresiones y mostrar resultados.
  • Manejar errores de sintaxis sin romper la aplicación.
  • Negar el resultado cuando sea necesario.
alt text

Cerramos con esto el tutorial de la calculadora basada en KCalc, utilizando Windows Forms y aplicando conceptos de Programación Orientada a Objetos para estructurar el código de manera eficiente y mantenible.