Un principiante en las Artes Gráficas Una experiencia en un sector que nos envuelve… El blog personal de David Pinelo.

20Mar/090

AbanQ: Dando formato a los FLFieldDB numéricos.

Cuando uno trabaja con un ERP, y con AbanQ en particular, hecha de menos que el sistema formetee según la configuración las diferentes cifras con las que se trabaja. Veámoslo:

FLFieldDB sin formato aplicado

FLFieldDB sin formato aplicado

Vale, al menos a mí me pasa, si voy con prisas, si voy con bulla (¿os suena en el curro?), ¿Qué cifra pone ahí? ¿ trece mil o ciento treinta mil?. Veamos lo siguiente:

FLFieldDB visualizando la cifra con Locale.

FLFieldDB visualizando la cifra con Locale.

Se ve un poco mejor, ¿no? Voy a explicaros cómo se programa este pequeño cambio en AbanQ.

Todo el trabajo se va a centrar en los ficheros FLFieldDB.h y FLFieldDB.cpp. Ambos definen una serie de clases para el trabajo con campos asociados a columnas de registros en base de datos. La clase FLFieldDB controla el comportamiento básico de este tipo de campos (que pueden visualizar columnas de texto, booleanas, numéricas, memo, imágenes... ). FLFieldDB define un atributo editor_, que es un puntero a QWidget (la clase base de todos los objetos GUI de Qt). Esto nos indica que FLFieldDB crea un editor diferente por cada tipo de campo: Así para los campos booleanos, crea un widget que presenta un radio button, y para los campos numéricos crea un objeto de tipo FLLineEdit.

Ésta clase, la FLLineEdit, será sobre la que más trabajemos.

¿Qué conseguiremos? Lo siguiente: Todos los FLFieldDB que presentan un número y que NO tienen el foco, presentarán la cantidad con la configuración local que deseemos. Cuando el FLFieldDB recibe el foco, se eliminará automáticamente el formato local y aparecerá el número sin formato (tal y como en la primera imágen para permitir una mejor edición). El usuario edita la cantidad, y cuando pasa a otro campo, el que ha perdido el foco, será formateado de nuevo.

La implementación es relativamente sencilla. Necesitaremos varias cosas:

  • Saber cuándo el FLFieldDB tiene el foco. Una forma sencilla es utilizar los eventos focusOutEvent y focusInEvent. El primero es invocado cuando el control tiene el foco y el segundo cuando lo recibe. Utilizaremos un nuevo atributo privado, tieneFoco_ y modificamos estas funciones tal y como sigue:
void FLLineEdit::focusOutEvent( QFocusEvent * f ) {
  // dpinelo
  tieneFoco_ = false;
  QString s = text();
#ifndef Q_OS_WIN32
  const QValidator *v = validator();
  if ( v ) {
    v->fixup( s );
    setText( s );
  }
#endif
  // dpinelo
  aplicarLocaleNumerico();
  QLineEdit::focusOutEvent( f );
}

void FLLineEdit::focusInEvent( QFocusEvent * f ) {
  // dpinelo
  tieneFoco_ = true;
  eliminarLocaleNumerico();
  if ( selectedText().isEmpty() )
    selectAll();
  QLineEdit::focusInEvent( f );
}

Sólo hemosagregado la información de cuándo se tiene el foco, y se llama a dos funciones, aplicarLocaleNumerico y eliminarLocaleNumerico que se encargarán de quitar y poner el locale.

  • FLFieldDB controla la edición de campos numéricos (double, unsigned int e int) y de cadenas de texto. Pero los números que se presentan pueden tener diferente precisión. El FLLineEdit debe saber con cuánta precisión mostrar los números. Por ello, añadimos un atributo privado: partDecimal de tipo entero. Debemos ajustar además su valor: Ello se hace desde la función initEditor, que crear el widget que hará la edición de la columna asociada al FLFieldDB. Simplemente basta con
        ....
        editor_ = new FLLineEdit( this, "editor" );

        ::qt_cast( editor_ )->setFont( qApp->font() );
        ::qt_cast( editor_ )->type = type;
	// dpinelo
	::qt_cast( editor_ )->partDecimal = partDecimal;
	editor_->installEventFilter( this );
        ....

Ok. Prosigamos.

  • Debemos controlar las funciones text() y setText() de FLLineEdit. Por ejemplo, las funciones que utilizan text() desde QSA esperan un valor sin formato local. Por tanto, text() deberá devolver, si el control NO tiene foco, el valor sin formatear. Por otro lado, al hacer setText(), si el control no tiene foco, deberá mostrarse con el valor numérico con el locale aplicado. Así tendremos que sobrecargar estas funciones quedando
// Si el objeto no tiene el foco, devolvemos el texto SIN formato
QString FLLineEdit::text() const
{
	bool ok;
	QString texto = QLineEdit::text();

	if ( !tieneFoco_ && ( type == QVariant::UInt || type == QVariant::Int || type == QVariant::Double ) ) {
		double valor = locale_.toDouble(texto, &ok);
		if ( ok ) {
			texto.setNum( valor, 'f', partDecimal );
		}
	}
	return texto;
}

// Si el objeto NO tiene el foco, y es numérico, le damos formato
void FLLineEdit::setText(const QString & cadena)
{
	bool ok;
	QString texto = cadena;

	if ( !tieneFoco_ && ( type == QVariant::UInt || type == QVariant::Int || type == QVariant::Double ) ) {
		double dValor = cadena.toDouble(&ok);
		if ( ok ) {
			texto = locale_.toString (dValor, 'f', partDecimal );
		}
	}
	QLineEdit::setText(texto);
}

En estas funciones se hace uso de un atributo privado de la clase FLLineEdit "locale_" que será una instancia de un objeto QLocale, el usado por Qt para dar formato. Simplemente, habría que declararlo como atributo privado, ya que el constructor por defecto de QLocale utiliza el locale establecido en el sistema operativo.

  • Sólo nos queda la implementación de las funciones que al recibir y perder el foco el control, aplican o elimina el locale. Estas funciones tienen una particularidad: la utilización de la función blockSignals de Qt. No me gusta esta solución, ya que bloquean la emisión de toda señal del FLLineEdit (Todas incluyen las de dibujado), pero en Qt3 no hay ninguna solución a la siguiente problemática: Siempre que se utiliza setText se dispara textChanged, lo que nos provoca llamadas a los diferentes objetos conectados a esta señal con resultados inesperados. Esto está ya solucionado en Qt4 donde se provee una señal más, textEdited que se dispara sólo en caso de acción "humana".
// dpinelo
void FLLineEdit::aplicarLocaleNumerico()
{
	bool ok;

	// dpinelo: Cogemos el texto, si el campo es de tipo numérico y le aplicamos el locale. Esto se produce cuando
	// se ha perdido el foco
	if ( type == QVariant::UInt || type == QVariant::Int || type == QVariant::Double ) {
		double valor = text().toDouble(&ok);
		if ( ok ) {
			QString texto = locale_.toString (valor, 'f', partDecimal );
			blockSignals(true);
			setText(texto);
			blockSignals(false);
		}
	}
}

// dpinelo
void FLLineEdit::eliminarLocaleNumerico()
{
	bool ok;

	// dpinelo: Ahora si es numérico, eliminamos el formato, ya que el usuario va a editar y es mejor hacerlo con el número en limpio
	if ( type == QVariant::UInt || type == QVariant::Int || type == QVariant::Double ) {
		double valor = locale_.toDouble(text(), &ok);
		if ( ok ) {
			QString texto;
			texto.setNum( valor, 'f', partDecimal );
			blockSignals(true);
			setText (texto);
			blockSignals(false);
		}
	}
}

Nota: La utilización de tanta etiqueta dpinelo no es porque sea egocéntrico, es para así distinguir en el código de AbanQ cuáles son mis cambios, y así, con las nuevas versiones que ofrece InfoSial poder introducirlos fácilmente.

Pues listo: De todas formas, aquí tenéis los archivos cómo quedarían con todos estos cambios. Eah, a disfrutarlo