Tutorial WINAPI C++ 1.6 (Creación del ObjetoMarcador)
Categorías : Windows, Programación, C y C++.

A partir de todo lo que vimos en el capitulo anterior la cosa esta en echarle imaginación. Ahora nos toca hacer un marcador para la calculadora que nos mostrara las operaciones y el resultado. La idea es hacer una parte para ver las operaciones que se están evaluando, y otra parte para mostrar el resultado actual de la operación.
Ademas este objeto sera el encargado de apuntar y resolver las operaciones de la calculadora, por lo que tambien se mostrara como responder a pulsaciones del teclado.
Al querer mostrar las operaciones independientemente del resultado nos va a hacer falta ingeniar un sistema para almacenar dichas operaciones con el mismo orden en que se han introducido.
// Enumeracion de los tipos de datos para la calculadoraenum ObjetoMarcador_TipoDatos {TipoDatos_INDEFINIDO = -1,TipoDatos_Valor = 0,TipoDatos_Suma = 1,TipoDatos_Resta = 2,TipoDatos_Multiplicacion = 3,TipoDatos_Division = 4};
Lo siguiente será crear un objeto que simbolice tanto un valor como una operación, pero ojo solo puede ser o un valor, o una operación. La idea es tener una lista de objetos que se irá ampliando a medida que se añadan valores o operaciones. De esta forma será fácil recrear la operación que se está evaluando en la calculadora.
// Contenedor de una porción de datos de la calculadoraclass ObjetoMarcador_Datos {public: /////////////////////////// Miembros publicos// -Constructor por defectoObjetoMarcador_Datos(void) {_Tipo = TipoDatos_INDEFINIDO;_Valor = 0.0f;};// -Constructor asignador de datosObjetoMarcador_Datos(const ObjetoMarcador_TipoDatos nTipo,const double nValor = 0.0f) {_Tipo = nTipo;_Valor = nValor;};// -Destructor~ObjetoMarcador_Datos(void) { };// -Función que retorna el tipoinline ObjetoMarcador_TipoDatos Tipo(void) const { return _Tipo; };// -Función que retorna el valorinline double Valor(void) const { return _Valor; };private : ///////////////////////// Miembros privados// -Valordouble _Valor;// -Tipo de datoObjetoMarcador_TipoDatos _Tipo;};
Esta clase tiene un constructor para indicarle que tipo de operación es, o en el caso de que sea un valor especificaremos el tipo correctamente y le pasaremos el valor en el segundo parámetro. Por lo demás todos sus miembros serán privados y solo podremos acceder a ellos para lectura.
Ahora que tenemos una implementación para almacenar los datos de la calculadora podemos proceder a crear la clase que va a contener el objeto marcador. Este objeto no requerirá eventos de mouse ni de teclado, aunque necesitaremos re-emplazar el Evento_Pintar , hacer unas funciones Pintar y Repintar, hacer unas funciones para que desde la ventana principal de la calculadora podamos añadir valores y operaciones, definir una lista que contenga todos los valores y operaciones, y hacer una función que calculara las operaciones y mostrara el resultado.
// Clase que hereda ObjetoControl y se convierte en el marcador para nuestra calculadoraclass ObjetoMarcador : public ObjetoControl {public : /////////////////////////////// Miembros publicos// -ConstructorObjetoMarcador(void);// -Destructor~ObjetoMarcador(void);// -Función para crear el controlHWND CrearMarcador( HWND hWndParent, const int cX, const int cY,const int cAncho, const int cAlto,COLORREF nColor_Texto = RGB(255, 255, 255),COLORREF nColor_DegradadoSuperior = RGB(180, 180, 180),COLORREF nColor_DegradadoInferior = RGB( 60, 60, 60),COLORREF nColor_BordeExterno = RGB(128, 128, 128),COLORREF nColor_BordeInterno = RGB(190, 190, 190) );/////////////////////////////////////// Funciones de pintado// -Función que enlaza con el evento WM_PAINTLRESULT Evento_Pintar(HDC hDC, PAINTSTRUCT &PS);// -Función que pinta todo el controlvoid Pintar(HDC hDC);// -Función que repinta el control si es necesariovoid Repintar(void);/////////////////////////////////////// Funciones para los datos// -Función que agrega un numero al valor actualvoid AgregarNumero(const TCHAR Numero);// -Función que agrega una coma decimal al valor actualvoid AgregarPunto(void);// -Función que borra el último caracter introducidovoid BorrarCaracter(void);// -Función que valida el valor actual, y agrega una operacion a la listavoid AgregarOperacion(const ObjetoMarcador_TipoDatos nTipo);// -Función que imprime el resultado y borra la listavoid Resultado(void);// -Función que borra la lista de operaciones y el valor actualvoid CE(void);/////////////////////////////////////// Aspecto grafico// -Macro que crea funciones para asignar y obtener el COLORREF _Color_TextoAGREGAR_COLOR(Color_Texto);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_DegradadoSuperiorAGREGAR_COLOR(Color_DegradadoSuperior);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_DegradadoInferiorAGREGAR_COLOR(Color_DegradadoInferior);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_BordeExternoAGREGAR_COLOR(Color_BordeExterno);// -Macro que crea funciones para asignar y obtener el COLORREF _Color_BordeInternoAGREGAR_COLOR(Color_BordeInterno);// -Macro que crea la función Fuente_Desactivada, y la variable HFONT _Fuente_OperacionesAGREGAR_FUENTE(Fuente_Operaciones);// -Macro que crea la función Fuente_Desactivada, y la variable HFONT _Fuente_ResultadoAGREGAR_FUENTE(Fuente_Resultado);protected : //////////////////////////// Miembros privados// -Función para quitar ceros que sobren a la izquierdasize_t _FormatearStr(TCHAR *String);// -Función que borra todos los datos de la listavoid _BorrarLista(void);// -Vector que contiene los valores y operacionesstd::vector<ObjetoMarcador_Datos *> _Lista;// -Valor que indica si se ha introducido la coma decimal en el valor actualbool _Punto;// -Valor actual en stringTCHAR _ValorActual[256];// -Tamaño del valor actual en caracteres.size_t _TamValorActual;};
Pensad que los valores los introduciremos vía teclado o con los botones, y la única forma de saber cuándo se ha terminado de introducir un valor es cuando se añade una operación.
Por ejemplo pulsamos el botón 1, el botón 2, el botón 3, y el botón +. Hasta que no se ha pulsado el botón + no podemos saber si el valor esta completo, en vez de pulsar el botón + se podría pulsar el botón 4, con lo que el valor en vez de ser 123 seria 1234. Por ello tenemos la función AgregarNumero la cual ira almacenando los números que se pulsen, la función AgregarPunto que agregara una coma decimal, y la función AgregarOperacion que añadirá el valor y la operación a la lista.
En el tutorial anterior creamos y pintamos el control de una forma similar, así que omitiré las clases para colores y fuentes, la función CrearMarcador y las funciones de repintado.
Ahora pasemos a ver el núcleo de las operaciones para la calculadora, empezaremos por la función AgregarNumero :
// Función que agrega un numero al valor actualvoid ObjetoMarcador::AgregarNumero(const TCHAR Numero) {if (Numero == TEXT('0') && _TamValorActual == 0) return;_ValorActual[_TamValorActual] = Numero;_TamValorActual ++;_ValorActual[_TamValorActual] = TEXT('\0');Repintar();};
Esta función básicamente agrega un numero al string que contiene el valor actual, lo único destacable es que si el numero que se va a agregar es 0 y no hay caracteres en el valor actual, este valor no se añadirá ya que los ceros a la izquierda no nos sirven de nada. Ahora veamos la función AgregarPunto :
// Funcion que agrega una coma decimal al valor acutalvoid ObjetoMarcador::AgregarPunto(void) {if (_Punto == true) {MessageBeep(-1);return;}// Si no hay nada agregamos un ceroif (_TamValorActual == 0) {_ValorActual[_TamValorActual] = TEXT('0');_TamValorActual ++;}_ValorActual[_TamValorActual] = TEXT('.');_TamValorActual ++;_ValorActual[_TamValorActual] = TEXT('\0');_Punto = true;Repintar();};
En esta función lo primero que hacemos es mirar el valor _Punto, ya que si esta en true quiere decir que ya hay una coma decimal en el valor actual, y por lo tanto no debemos añadir más. En segundo lugar, si no hay ningún carácter añadiremos un cero al valor actual. Y por último se añade un punto al valor, y se asigna _Punto a true. Veamos la función AgregarOperacion :
// Función que agrega una operaciónvoid ObjetoMarcador::AgregarOperacion(const ObjetoMarcador_TipoDatos nTipo) {// Convertimos el _ValorActual a un numero decimaldouble nValor = _wtof(_ValorActual);// Si el valor es 0 no agregamos nadaif (nValor == 0.0f) return;// Añadimos el valor a la listaObjetoMarcador_Datos *nDatosValor = new ObjetoMarcador_Datos(TipoDatos_Valor, nValor);_Lista.push_back(nDatosValor);// Añadimos la operacion a la listaObjetoMarcador_Datos *nDatosOperacion = new ObjetoMarcador_Datos(nTipo);_Lista.push_back(nDatosOperacion);_ValorActual[0] = TEXT('0');_ValorActual[1] = TEXT('\0');_TamValorActual = 0;_Punto = false;Repintar();};
Lo primero es determinar si el valor no es cero para añadirlo a la lista. Si el valor es válido creamos una clase ObjetoMarcador_Datos en la que almacenamos el valor actual, luego creamos otra clase ObjetoMarcador_Datos en la que añadimos la operación que se va a realizar. Por último borramos el contenido de _ValorActual, y asignamos _Punto a false, de forma que podamos empezar a añadir un nuevo valor actual.
Ahora veamos la función BorrarCaracter :
// Función que borra el último caractervoid ObjetoMarcador::BorrarCaracter(void) {// Hay un caracter en el valor actualif (_TamValorActual == 1) {_ValorActual[0] = TEXT('0');_ValorActual[1] = TEXT('\0');_TamValorActual = 0;_Punto = false;}// Hay caracteres en el valor actualelse if (_TamValorActual > 1) {_TamValorActual --;if (_ValorActual[_TamValorActual] == TEXT('.')) _Punto = false;_ValorActual[_TamValorActual] = TEXT('\0');}else { // Sin caracteres que borrar en el valor actualsize_t TamLista = _Lista.size();// Miramos si hay mas valores en la lista de valoresif (TamLista > 1) {_TamValorActual = swprintf_s(_ValorActual, 256, TEXT("%f"), _Lista[TamLista - 2]->Valor());_TamValorActual = _FormatearStr(_ValorActual);delete _Lista[TamLista -1];delete _Lista[TamLista -2];_Lista.resize(TamLista -2);_Punto = false;// Miramos si el nuevo valor tiene una coma decimalfor (size_t n = 0; n < _TamValorActual; n++) {if (_ValorActual[n] == TEXT('.')) _Punto = true;}}else { // No hay mas valores en la lista, reafirmamos el valor 0_ValorActual[0] = TEXT('0');_ValorActual[1] = TEXT('\0');_TamValorActual = 0;}}Repintar();};
Lo primero que miramos en esta función es si el tamaño de valor actual es 1, y en ese caso asignamos un cero a valor actual y a su tamaño. En caso de que el valor actual sea mas grande que uno, borramos el ultimo carácter y nos fijamos si era un punto para asignar correctamente el valor _Punto.
Y si llegamos al caso de que valor actual es 0 quiere decir que no tenemos mas caracteres en el valor actual, pero puede que tengamos valores y operaciones en la lista, así que miramos el tamaño de la lista. Si el tamaño de la lista es mas grande que 1 lo que hacemos es asignar el ultimo valor de la lista al valor actual, y eliminar tanto la operación como el ultimo valor de la lista.
Por último nos queda ver la función Resultado :
// Función que calcula el resultado, y reinicia todos los valores.void ObjetoMarcador::Resultado(void) {double Op1 = 0.0f;double Op2 = 0.0f;double nValor = _wtof(_ValorActual);size_t i;if (nValor == 0.0f) return;// Añadimos el valor a la listaObjetoMarcador_Datos *nDatosValor = new ObjetoMarcador_Datos(TipoDatos_Valor, nValor);_Lista.push_back(nDatosValor);// Realizamos toda la serie de operacionesOp1 = _Lista[0]->Valor();for (i = 1; i < _Lista.size() -1; i += 2) {Op2 = _Lista[i + 1]->Valor();switch (_Lista[i]->Tipo()) {case TipoDatos_Suma : Op1 = Op1 + Op2; break;case TipoDatos_Resta : Op1 = Op1 - Op2; break;case TipoDatos_Multiplicacion : Op1 = Op1 * Op2; break;case TipoDatos_Division : Op1 = Op1 / Op2; break;}}// Formateamos el resultado y lo introducimos en _ValorActualswprintf_s(_ValorActual, 256, TEXT("%f"), Op1);_TamValorActual = _FormatearStr(_ValorActual);_Punto = false;for (i = 0; i < _TamValorActual; i++) {if (_ValorActual[i] == TEXT('.')) _Punto = true;}Repintar();_BorrarLista();}
Para mostrar correctamente toda la operación lo primero que hacemos es añadir el valor actual a la lista, luego hacemos un bucle que revisa todos los valores a partir de la segunda posición de la lista. La lista contiene los datos de la siguiente forma : VALOR, OPERACION, VALOR, OPERACION, VALOR, etc.. asi que si partimos del primer valor, sabemos que luego siempre vendrá una operación y un segundo valor. Por último formateamos correctamente el resultado, miramos si contiene una coma decimal para asignar el valor _Punto, repintamos el control, y borramos la lista de operaciones para comenzar una nueva lista.
Llegados a este punto ya hemos visto lo más importante a la hora de crear nuestro marcador. Si deseáis ver el marcador funcionando podéis descargar los ejemplos de este tutorial y echar un vistazo al ejemplo numero 6 :
La siguiente y última parte del tutorial será el punto y final para la calculadora, y consistirá en enlazar todo lo hecho anteriormente para ensamblar la aplicación : 1.7 - Terminando la calculadora
Descargar tutorial WinAPI completo | Calculadora compilada |