viernes, 30 de agosto de 2013

Programación concurrente usando Semáforos con el Framework Qt.

clip_image002

El presente artículo tiene el objetivo de mostrar las bondades de la programación multi-hilo usando el Framework Qt.

Se utilizara la tecnología de Semáforos a través de la clase QSemaphore para controlar el acceso a un búfer circular que comparten un hilo productor y un hilo consumidor.

El productor escribe datos en el búfer hasta que se llega al final del buffer, en cuyo punto se reinicia desde el principio, sobrescribiendo los datos existentes. El hilo consumidor lee los datos a medida que se produce y lo escribe en la salida de error estándar.

Los semáforos hacen posible tener un mayor nivel de concurrencia que los mutex. Si los accesos a la memoria intermedia fueran vigilados por un QMutex, el hilo consumidor no podría acceder a la memoria intermedia al mismo tiempo que el hilo productor. Sin embargo, no hay nada malo en tener dos hilos que trabajan en diferentes partes de la memoria intermedia en el mismo tiempo.

El ejemplo consta de dos clases: productor y consumidor. Ambos heredan de QThread. La memoria intermedia circular utilizado para la comunicación entre estas dos clases y los semáforos que lo protegen son variables globales.

Una alternativa al uso QSemaphore para resolver el problema del productor-consumidor es utilizar QWaitCondition y QMutex.

Variables globales

Vamos a empezar por la revisión del buffer circular y los semáforos asociados:

const int DataSize = 100000;
const int BufferSize = 8192;

char buffer[BufferSize];
QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes;

DataSize es la cantidad que de los datos que el productor va a generar. Para mantener el ejemplo tan simple como sea posible, será una constante. BufferSize es el tamaño del buffer circular. Es menos de DataSize, lo que significa que en algún momento el productor alcanzará el final del buffer y reiniciar desde el principio.

Para sincronizar el productor y el consumidor, necesitamos dos semáforos. El semáforo freeBytes controla el área "libre" del búfer (el área que el productor no ha llenado con los datos todavía o que el consumidor ya ha leído). El semáforo usedBytes controla el área "usado" de la memoria intermedia (el área que el productor ha llenado pero que el consumidor todavía no ha leído).

Juntos, los semáforos garantizan que el productor nunca este más de bufferSize por delante del consumidor, y que el consumidor nunca lee los datos que el productor no ha generado todavía.

El semáforo freeBytes se inicializa con BufferSize, porque al principio todo el buffer está vacío. El semáforo usedBytes se inicializa a 0 (el valor por defecto si no se especifica ninguno).

Clase Productor

Repasemos el código de la clase Producer:

class Producer : public QThread
{
public:
void run()
{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
for (int i = 0; i < DataSize; ++i) {
freeBytes.acquire();
buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
usedBytes.release();
}
}
};

El productor genera DataSize bytes de datos. Antes de que escriba un byte en el buffer circular, se debe adquirir un byte "libre" con el semáforo freeBytes. La llamada al QSemaphore :: adquirir () puede bloquear si el consumidor no ha mantenido el ritmo con el productor.

Al final, el productor libera un byte utilizando el semáforo usedBytes. El byte "libre" con éxito se ha transformado en un byte "usado", listo para ser leído por el consumidor.

Clase del Consumidor

Pasemos ahora a la clase de los consumidores:

class Consumer : public QThread
{
Q_OBJECT
public:
void run()
{
for (int i = 0; i < DataSize; ++i) {
usedBytes.acquire();
fprintf(stderr, "%c", buffer[i % BufferSize]);
freeBytes.release();
}
fprintf(stderr, "\n");
}

signals:
void stringConsumed(const QString &text);

protected:
bool finish;
};

El código es muy similar a la del productor, excepto que esta vez que adquirimos un byte "usado" y liberar un byte "libre", en lugar de lo contrario.

La función main ()

En main (), creamos los dos hilos y llamamos QThread :: wait () para asegurarse de que ambos contextos tienen tiempo para terminar antes de la salida:

int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;
}

Entonces, ¿qué sucede cuando ejecutamos el programa? Inicialmente, el hilo productor es el único que puede hacer cualquier cosa, el consumidor está bloqueado esperando el semáforo usedBytes se libere (a su disposición inicial () count es 0). Una vez que el productor ha puesto un byte en el buffer, freeBytes.available () es BufferSize - 1 y usedBytes.available () es 1. En ese momento, pueden ocurrir dos cosas: o bien el hilo consumidor se hace cargo y dice que el byte, o si el cliente llega a producir un segundo byte.

El modelo productor-consumidor se presenta en este ejemplo permite escribir aplicaciones multiproceso altamente concurrentes. En un equipo con varios procesadores, el programa es potencialmente hasta el doble de rápido que el programa de exclusión mutua basado equivalente, ya que los dos hilos pueden estar activos al mismo tiempo en diferentes partes del buffer.

Tenga en cuenta que estos beneficios no siempre son efectivos. La adquisición y liberación de un QSemaphore tiene un costo. En la práctica, probablemente valdría la pena dividir la memoria intermedia en trozos y para operar en trozos en lugar de bytes individuales. El tamaño del búfer es también un parámetro que se debe seleccionar cuidadosamente, basado en la experimentación.

El código fuente de esta aplicación pueden descargarlo desde aquí.




{ Leer Más }


miércoles, 28 de agosto de 2013

Integrando Spring Security con el Framework ZK

clip_image002

Spring Security es una solución común para el desarrollador para satisfacer las necesidades de seguridad de una aplicación web de Java, que es ampliamente utilizado y es una tecnología probada. Sin embargo, debido a su naturaleza para proteger los recursos por parte de patrones a través de URL, no es obvia para los desarrolladores de aplicaciones para darse cuenta de la Seguridad de Spring se puede adaptar con una petición Ajax mecanismo de manejo específico de un marco de Ajax [1].

Así que en este artículo, voy a presentarles cómo integrar Spring Security con ZK sin problemas por ir a través de la construcción de una aplicación de demostración sencilla (Una publicación del artículo y un sistema de edición).

Recursos para descargar

Puede descargar el código fuente de aquí

El proyecto está basado en Maven, si quieres probar diferentes versiones de ZK o spring, cambie el número de versión en pom.xml

Demostración Detalles de la aplicación

Esta aplicación Demo es un simple artículo publicar y editar sistema que permite tres tipos de usuarios para acceder a:

  • usuario con rol ROLE_USER
  • usuario con rol ROLE_EDITOR
  • usuario anónimo con rol IS_AUTHENTICATED_ANONYMOUSLY

clip_image004

  • El usuario Anónimo puede visitar la página de inicio que contiene la lista de artículos, y se puede ver el contenido de un artículo haciendo clic en el enlace de página de inicio.
  • Al usuario con rol ROLE_USER se le permite publicar artículos nuevos y editar sus propios artículos.
  • El usuario con rol ROLE_EDITOR es el más poderoso, es capaz de editar y borrar cualquier artículo.

Este artículo se basa en los requisitos de implementación de esta aplicación para demostrar la integración de Spring Security con ZK.

Configuración de Spring Security

En primer lugar, vamos a ver cómo configurar nuestro proyecto. Para utilizar Spring Security, tenemos que añadir algunos oyentes y declaraciones de filtro en web.xml

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

/WEB-INF/applicationContext.xml

/WEB-INF/applicationContext-security.xml

</param-value>

</context-param>

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

<listener>

<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>

</listener>

<listener>

<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>

</listener>

<filter><!-- the filter-name must be preserved, do not change it! -->

<filter-name>springSecurityFilterChain</filter-name>

<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

</filter>

<filter-mapping>

<filter-name>springSecurityFilterChain</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>


Como se puede ver, a pesar de los oyentes de contexto ordinarios de Spring (RequestContextListener y ContextLoaderListener), declaramos HttpSessionEventPublisher y springSecurityFilterChain de Spring Security. Aquí HttpSessionEventPublisher es opcional y está diseñada para Spring Security para hacer el control de sesión simultánea detallada, springSecurityFilterChain es el gancho principal de toda la funcionalidad de Spring Security de la aplicación, es necesario y debe ser nombrado springSecurityFilterChain.

applicationContext-security.xml

Aquí, en este proyecto, separamos el fichero applicationContext.xml de Spring en dos archivos, el original applicationContext.xml es para el backend bean y el service bean declarations y el adicional applicationContext-security.xml es sólo para la configuración de Spring Security.

En applicationContext-security.xml, hay dos elementos importantes que tenemos que configurar, que son el elemento <http> y el elemento <authentication-manager>.

Configuración del elemento Http

El elemento <http> es para decirle a Spring qué tipo de recursos necesita ser asegurado, el puerto que será utilizado por contenedor para conexiones http y https, y qué tipo de inicio de sesión en solución se utiliza en esta aplicación web.

<http auto-config="true">

<port-mappings>

<port-mapping http="8080" https="8443"/>

</port-mappings>

<intercept-url pattern="/zkau/**" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="any"/>

<intercept-url pattern="/login.zul" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https" />

<intercept-url pattern="/newArticle.zul" access="ROLE_USER" requires-channel="https" />

<intercept-url pattern="/j_spring_security_check" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https" />

<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="any" />

<session-management session-fixation-protection="none" />

<form-login login-page="/login.zul"

authentication-failure-url="/login.zul?login_error=1"

login-processing-url="/j_spring_security_check"/>

<logout logout-success-url="/index.zul" invalidate-session="true" />

</http>


Configuración del Authentication Manager

La declaración de elemento <authentication-manager> está diseñado para gestionar authentication-provider, que es el proveedor de la instancia de autenticación de Spring y hará el trabajo real de la autenticación. Puede declarar múltiples authentication-providers, con el fin de aprovechar distintas fuentes de los usuarios (por ejemplo: openidAuthenProvider o LDAPAuthenProvider), en este proyecto se extiende UserDetailsService del authentication-provider predeterminado:

<authentication-manager>

<authentication-provider user-service-ref="myUserDetailsService">

<password-encoder hash="md5" />

</authentication-provider>

</authentication-manager>

<beans:bean id="myUserDetailsService"

class="org.zkoss.demo.springsec.model.MyUserDetailsService"/>

La protección de una solicitud de página

Después de la configuración de Spring Security, ahora, vamos a ver cómo utilizarla para proteger a una solicitud de archivo zul.

Escenario: cuando el usuario solicita un recurso limitado

En nuestro proyecto de demostración, NewArticle.zul es un recurso limitado que sólo pueden acceder los usuarios que ya se ha iniciado sesión, lo que es natural, porque para poder hacer un artículo, tenemos que saber quién es el autor. En Spring Security, para restringir un recurso para cierto usuario (que tiene suficiente permiso) para tener acceso es muy sencillo, simplemente declaramos elemento <intercept-url> bajo <http> en applicationContext-security.xml:

<intercept-url pattern="/newArticle.zul" access="ROLE_USER" requires-channel="https" />

Aquí el atributo patrón se utiliza para determinar qué solicitud esta configuración tendrá efecto, y si la dirección URL de una solicitud coincide con este patrón, el atributo de acceso se utilizará para comprobar autorizaciones del usuario actual.

Como podemos ver, la solicitud de protección de página en Spring Security es muy sencilla e intuitiva, todo se basa en la comparación de patrones de solicitar la ruta url.

Log-in Page Implementation in ZK


Now, after the newArticle.zul has been secured, the log-in page(login.zul) which is required for authentication process has to be implemented for user to perform log in. Here let's see how to implement a log-in page in ZUL:

Implementación de la Página Log-in en ZK

Ahora, después de que el newArticle.zul ha sido asegurado, la página de log-in (login.zul) que se requiere para proceso de autenticación tiene que ser implementada para que usuario pueda acceder:

<html:form id="f" name="f" action="j_spring_security_check" method="POST"

xmlns:html="native">

<grid>

<rows>

<row>User: <textbox id="u" name="j_username"/></row>

<row>Password: <textbox id="p" type="password" name="j_password"/></row>

<row spans="2">

<hbox>

<html:input type="reset" value="Reset"/>

<html:input type="submit" value="Submit Query"/>

<button type="submit" label="Accedi_trendy" mold="trendy"/>

<button type="submit" label="Accedi_os" />

</hbox>

</row>

</rows>

</grid>

</html:form>


  1. Un elemento HTML <form> tiene que ser declarado fuera de las entradas de nombre de usuario y contraseña.
  2. El formulario debe contener dos elementos de entrada denominados j_username y j_passoword. Para aplicar un efecto ajax y las características de estos insumos, se puede utilizar el componente <textbox> en su lugar.
  3. El Atributo action Form tiene que asignar el valor del atributo login-processing-url del elemento <form-login> en applicationContext-security.xml

Si un usuario anónimo, hace clic en el botón Nuevo Artículo en página de inicio, será re-direccionado a nuestro login.zul.

Asegurar Vista parcial mediante el uso de EL en ZUL

Con el fin de asegurar parte de la vista parcial de la página web, Spring Security tiene su propio taglib que brinda soporte básico para el acceso a la información de seguridad y la aplicación de restricciones de seguridad en las páginas JSP. Y aquí la seguridad en ZUL, marco ZK también proporciona métodos para definir la nueva biblioteca de etiquetas. En nuestra aplicación de ejemplo, para el uso de Spring Security a través de EL hicimos nuestra propia biblioteca de etiquetas.

Biblioteca de etiquetas Zul para Spring Security.

Aunque ZK Developer Reference proporciona información muy detallada sobre lo que podemos hacer en un taglib personalizado de ZK, aquí sólo nos centramos en lo que queremos tener para Spring Security.

En primer lugar, vamos a crear un security.tld bajo / WEB-INF /. Sin embargo, para los proyectos más serios, se puede hacer la ruta de clases: Método metainfo / tld / config.xml como se menciona en la Referencia del programador.

En segundo lugar, se definen las funciones de EL / WEB-INF/security.tld, aquí está el ejemplo:

<taglib>

<uri>http://www.zkoss.org/demo/integration/security</uri>

<description>

Methods and actions for ZK + Spring Security

</description>

<function>

<name>isAllGranted</name>

<function-class>org.zkoss.demo.springsec.SecurityUtil</function-class>

<function-signature>

boolean isAllGranted(java.lang.String authorities) {

</function-signature>

<description>

Return true if the authenticated principal is granted authorities

of ALL the specified roles.

</description>

</function>

...

Como podemos ver, el documento debe comenzar con un elemento raíz: <taglib> con un elemento <uri> en ella. A continuación, se puede declarar funciones. En la declaración de la función, mapeamos un método (isAllGranted ()) de org.zkoss.demo.springsec.SecurityUtil a nuestra función EL isAllGranted, sobre la SecurityUtil, la aplicación de la misma se basa en SecurityContextHolder de Spring Security.

Ahora vamos a ver cómo utilizar taglib personalizado Spring Security en ZUL.

Modo de Uso: Protección de Acción

En nuestro proyecto de demostración, de acuerdo con los casos de uso más arriba, tenemos una función "eliminar artículo" que sólo usuario con rol: ROLE_EDITOR puede acceder. Ahora, con nuestras bibliotecas de etiquetas personalizadas para Spring Security, podemos establecer algunas limitaciones de esta manera:

<?taglib uri="/WEB-INF/security.tld" prefix="sec"?>

...

<button id="deleteBtn" label="Delete"

if="${sec:isAllGranted('ROLE_USER')}"

disabled="${not sec:isAllGranted('ROLE_EDITOR')}"/>

...

Atributos Especiales del componente ZK para la Seguridad

En el código de ejemplo anterior, se ha utilizado el atributo personalizado EL + disabled del botón de ZK para proteger el clic del usuario, ZK también ofrece otras características que pueden ser aplicables para la restricción de seguridad tales como visible y de sólo lectura (para combobox). Estos atributos que se basan en efectos html son muy útiles para cumplir con los requisitos de la escalada de permiso para acceder a la interfaz de usuario.

Protegiendo la solicitud de ZK Ajax

En un marco como el Ajax ZK, hay dos tipos de solicitudes. Ahora, vamos a hablar de la parte más difícil de esta integración es decir, cómo hacer frente a la petición de Ajax ZK usando Spring Security?

En primer lugar, tenemos que echar un vistazo a cómo proteger una solicitud de página de una manera más programática.

Haga Security Check en Listener de ZK

En algunas situaciones, puede ser que necesite el usuario para hacer control de seguridad en el código java no en ZUL, por ejemplo, en nuestro accessDeniedExTest.zul, tenemos un iniciador que hacer el registro de seguridad en el método public void doInit(Page page, Map<String, Object> args) throws Exception.

public class AccessDeniedExInit extends GenericInitiator {

public void doInit(Page page, Map<String, Object> args) throws Exception {

if(SecurityUtil.isNoneGranted("ROLE_EDITOR")){

throw new AccessDeniedException("this is a test of AccessDeniedException!");

}

}

}

El AccessDeniedException es clave para la cadena de filtros de Spring Security para manejar, y todo funciona bien en una situación de solicitud de la página, pero lo que si tiramos la excepción en una acción que dice EventListener de ZK?

Chequeo de Seguridad en Acciones de ZK.

Lanzando AccessDeniedException durante una solicitud Ajax no activar el proceso de autenticación de Spring Securit. Esta autorización se debe a la perspectiva del ZK AU Engine, que es imposible para cualquier otro outsider de org.zkoss.zk.au.http. DHtmlUpdateServlet saber cómo componer una respuesta significativa y correctamente estructurada para el motor del cliente en el navegador. Por lo que cualquier excepción que debe ser manejado dentro de ZK AU Engine.

Para hacer esto tenemos que convertir petición Ajax ZK a una petición de una página normal, así que podemos dejar Spring Security para manejar la AccessDeniedException en una solicitud de página normal. la puesta en práctica de esta idea incluye 3 pasos:

PASO 1: Convertir Ajax Request to Request

En primer lugar, tenemos que decirle ZK queremos encargo del mecanismo de control de errores para AccessDeniedException. Para ello es muy simple, en zk.xml añadimos esta sección:

<error-page>

<exception-type>org.springframework.security.access.AccessDeniedException</exception-type>

<location>security_process.zul</location>

</error-page>

Manejo de errores de ZK es muy sencillo, se asigna un tipo de error a una página ZUL, la página ZUL se analizará con base en el escritorio actual, si ocurre tal error. En nuestro security_process.zul simplemente tenemos un iniciador dentro de manejar AccessDeniedException:

<?init class="org.zkoss.demo.springsec.ui.error.SpringSecurityHandleInit"?>

<zk>

<!-- DO NOTHING! -->

</zk>

Luego, en SpringSecurityHandleInit, podemos comprobar si se trata de una petición Ajax, almacenamos todos los datos necesarios en la sesión y le pedimos al motor del cliente para hacer el cambio de dirección de nuevo a este security_process.zul nuevo.

if(exec.isAsyncUpdate(null) ){

//STEP 1: convert Ajax Request to Page Request(Error Handling Page Request)

System.out.println(">>>> Security Process: STEP 1");

if(ex instanceof AccessDeniedException){

sess.setAttribute(VAR_DESKTOP_REQ_URI, getOriginalDesktopUri());// for login-success-url

sess.setAttribute(VAR_SPRING_SECURITY_ERROR, ex);

Executions.sendRedirect(toSecurityProcessUrl((AccessDeniedException) ex));// GOTO STEP 2 by redirection.

}else{

throw new IllegalArgumentException(

"How come an unexpected Exception type will be mapped to this handler? please correct it in your zk.xml");

}

PASO 2: Lanzar AccessDeniedException de redirección para adaptarse Proceso de seguridad Resorte

El cambio de dirección será de nuevo a este security_process.zul nuevo, y porque esta vez estamos dentro de una solicitud de página, se puede obtener la excepción de regresar de HttpSession y tírelo a la basura para que la cadena de filtros de Spring Security para manejarlo.

Exception err = (Exception) sess.getAttribute(VAR_SPRING_SECURITY_ERROR);

String dtPath = (String) sess.getAttribute(VAR_DESKTOP_REQ_URI);

if(err!=null){

//STEP 2: throw Error in Error Handling Page Request.

System.out.println(">>>> Security Process: STEP 2");

sess.removeAttribute(VAR_SPRING_SECURITY_ERROR);

throw err;// we suppose Spring Security Error Filter Chain will handle this properly.

}

Ahora, Spring Security comprobará principio del usuario actual y hacer el proceso de autenticación o autorización. En el proceso de autenticación, el usuario será redirigido a la login.zul como lo hemos configurado, y si log-in es el éxito, el usuario será redirigido a la ubicación original donde se produjo esta excepción y la ubicación aquí será nuestra security_process.zul que es no es el sitio original en el que la petición Ajax causó el error. Así que tenemos el tercer paso para manejar esta situación.

PASO 3: Maneje el Login Success redirección

Vamos a volver a ver el código de ejemplo de paso 1, el código

sess.setAttribute(VAR_DESKTOP_REQ_URI, getOriginalDesktopUri());

reservado la ruta de solicitud original del ZUL que generó el escritorio, y que se prepara la información para STEP 3, la aplicación de la forma de recuperar la ruta original del escritorio (impl de getOriginalDesktopUri ()) es la siguiente:

private static String getOriginalDesktopUri(){

// developer may implement this part to adapt to PushState or any other Page based Framework, that might have interference to request URI.

String str = Executions.getCurrent().getDesktop().getRequestPath();

String qs = Executions.getCurrent().getDesktop().getQueryString();

System.out.println(">>>security Process: Desktop path= "+str);

return str+"?"+qs;

}

Y ahora en el PASO 3, simplemente redirigir usuarios de nuevo a nuestro camino escritorio:




else if(dtPath!=null){

System.out.println(">>>> Security Process: STEP 3");

//STEP 3: if Spring Security Authentication was triggered at STEP 2,

//then we need STEP 3 to redirect back to original URI the very first desktop belongs to.

sess.removeAttribute(VAR_DESKTOP_REQ_URI);

exec.sendRedirect(dtPath);

}

Para más detalles, por favor, eche un vistazo al código fuente de SpringSecurityHandleInit.

Uso: Proteja botón Abrir Editor

Después de tejer ZK petición Ajax al Spring Security, vamos a ver cómo aplicar restricción de seguridad a la acción del usuario en nuestro proyecto de demostración, en primer lugar, en uno de nuestros casos de uso del usuario escenario se le permite editar un artículo si es el autor o tiene 'ROLE_EDITOR , por lo que el botón de editor abierto en ArticleContentViewCtrl para articleContent.zul se implementa como esto:

@Listen("onClick=#openEditorBtn")

public void edit(){

//ownership & permission check.

if(!isOwner() && SecurityUtil.isNoneGranted("ROLE_EDITOR")){

throw new AccessDeniedException(

"The user is neither the author, nor a privileged user.");

}

ArticleEditor editor = new ArticleEditor();

editor.setParent(container);

editor.doHighlighted();

}

Como se muestra, tiramos AccessDeniedException directamente en un detector de eventos, y podemos hacer lo mismo en la acción ZK Ver Modelo:

public class TestVModel {

...

@Command

@NotifyChange("fullName")

public void doChange(){

if(SecurityUtil.isNoneGranted("ROLE_EDITOR")){

throw new AccessDeniedException("you are not an editor!");

}

}

}

Si está utilizando un bean de Spring con anotación @ Secured etiquetado de algunos métodos, cuando el usuario no pasa el control de seguridad, el AccessDeniedException lanzado será tratada de la misma manera.

Resumen

En este artículo, hablamos de cómo utilizar Spring Security en una aplicación web ZK, y también mostró una solución de adaptar petición Ajax ZK de cadena de filtros de Spring Security.

Esperamos que le sea útil y quedamos al tanto de sus comentarios.




{ Leer Más }


martes, 20 de agosto de 2013

Generación de códigos de barras con CodeIgniter

Recientemente tuve la necesidad de generar códigos de barra, como el de la figura 1. En este artículo comentaré la solución que pude darle a este problema.

clip_image001

Fig. 1 Un código de barra alfanumérico de tipo code39.

Los códigos de barra son un tipo de tecnología para identificación automática y colección de datos (en inglés Automatic Identification and Data Collection, AIDC), estructurados para contener información específica de un producto en forma de barras y espacios. Permiten reducir la intervención humana en la entrada y colección de datos, reduciendo así los errores y el tiempo de trabajo.

Puedes encontrar un artículo muy completo al respecto en [1], donde por ejemplo explican que puede haber códigos de barra unidimensionales (1D) como en la figura 1 y bidimensionales (2D) como en la figura 2. En este artículo proponen además una biblioteca para la generación de códigos de barra.

clip_image002

Fig. 2 Código de barras bidimensional (2D).

En el sitio [2] puedes ver algunos tipos y estándares de códigos de barra, clasificados en sólo-numéricos, alfanuméricos (que contienen números y letras) y bidimensionales. Muchas bibliotecas libres en internet para la generación de códigos de barras, con PHP u otros lenguajes, dan soporte a varios de estos estándares. Es algo que debes tener en cuenta para seleccionar una para trabajar, considerando qué estándar usa tu cliente, después de todo el código que generes seguramente se va a imprimir y a pegar en algún objeto como su identificador o su precio, para luego ser leído con un escáner-pistola que trae implementado determinados estándares por hardware, así que debemos conocer para qué equipos se está trabajando.

Bueno, mi primera opción para implementar mi generador de códigos de barra, fue incluir en mi CodeIgniter (CI) la biblioteca Zend Framework (ZF), pero esto me trajo algunos inconvenientes que realmente no pude resolver en el poco tiempo que tenía para esta tarea. En muchos foros incluso no encontré soluciones claras a lo que me pasaba, probablemente se deba a una incompatibilidad de versiones ZF-CI.

Zend Framework trae consigo muchas funcionalidades, no solamente la de generar códigos de barra, aunque se puede utilizar (copiar hacia application/libraries) solamente las carpetas necesarias para lograr esto. En [3] puedes encontrar un tutorial muy completo de la creación de códigos de barra con ZF (independiente, sin CodeIgniter).

El ZF lo puedes descargar aquí [4], en algunos foros algunos recomiendan la versión 1 para el tema de los códigos de barra. Para acoplar el ZF al CI, básicamente copias la carpeta Zend dentro de application/libraries, y en esta misma carpeta libraries debes crear un fichero (zend.php) cuyo contenido varía según los tutoriales pero básicamente hace lo mismo. Puedes ver ejemplos de tutoriales en [5] , [6] y [7] y puedes descargar un fichero zend.php en [8], que es un link que brinda el tutorial [6].

Cuando haces esta instalación probablemente tengas errores como que no aparece la clase “Zend_Barcode” (Fatal error: Class 'Zend_Barcode' not found o Non-existent class: Barcode). En el fuente del ZF 2.2.2 que fue el que descargué, la clase se llama “Barcode”, al cambiar el nombre en la llamada a la clase también da error. Puedes ver foros relacionados en [9] y [10], en este último usan el Factory de ZF, ya que no se puede instanciar directamente la clase Barcode, pero esto me genera el error Fatal error: Class 'Zend\Barcode\ObjectPluginManager' not found (ver [11]). Muchos de estos errores no están respondidos y al resolver algunos se me generaban otros. Siendo nula mi experiencia con ZF decidí buscar variantes más sencillas.

Finalmente opté por la biblioteca brindada en PHPClasses [12], bajo licencia GPL. Esta clase permite generar códigos de barras en imágenes y ficheros PDF, usando las bibliotecas GD (toma un recurso de imagen de GD y renderea un código de barras en ella) y la FPDF. En la versión 2.0.1 de septiembre del 2010 que puedes descargar en [12], se da soporte a los formatos codabar, code 11, code 39, code 93, code 128, ean 8, ean 13, standard 2 of 5, interleaved 2 of 5, msi y datamatrix.

El compactado descargado trae tres ficheros: la biblioteca en Barcode.php y dos ejemplos: sample-gd.php y sample-fpdf.php, para GD y PDF respectivamente. El fichero Barcode.php lo pones en application/libraries, y como siempre recomiendo, puedes hacer una clase que actúe como interfaz entre tu aplicación y esta biblioteca.

Usando el ejemplo sample-gd.php creé la clase Barcode_manager dentro de un fichero barcode_manager.php que puse también en application/libraries.

En el constructor de mi clase cargo la biblioteca load->library('barcode'), y adicioné una función create_barcode que recibe la cadena alfanumérica para codificar, la posición donde se ubicará el centro de la imagen, las dimensiones de la imagen y el alto y ancho de las barras. Internamente especifiqué el tipo 'code39' y el ángulo 0 para la imagen para que salga en posición horizontal:

function create_barcode($code, $center_x, $center_y, $width, $height, $bars_width, $bars_height);

El contenido de esta función es básicamente el mismo de sample-gd.php.

Finalmente, desde mis controladores ejecuto la siguiente secuencia de código:


$this->load->library('barcode_manager');

$code = $this->obtener_codigo_desde_bd(id_del_producto);

$center_x = 220;

$center_y = 50;

$height = 100;

$width = 440;

$bars_height = 50;

$bars_width = 2;

$this->barcode_manager->create_barcode($code, $center_x, $center_y, $width, $height, $bars_width, $bars_height);

El código de barras lo genero a partir de un código alfanumérico que mantengo en la base de datos para cada producto, que a su vez generé con uniqid() en el momento de la inserción. Así, genero la imagen del código de barras en cualquier momento a partir del código alfanumérico del producto y no necesito guardar las imágenes de las barras en el servidor.


Bueno, te he ejemplificado el uso de una de las tantas bibliotecas que existen para el propósito de generar códigos de barras con PHP. Cualquiera de ellas que uses recuerda tener en cuenta el estándar de bar_code que usa el cliente, y haz una clase interfaz que te permita adaptar la biblioteca que estés usando a las características de tu producto.


Referencias:

[1] Generating barcode using php:

http://bmpradeep.wordpress.com/2013/01/29/generating-barcode-using-php/

[2] Different Types of Barcodes:

http://www.mecsw.com/specs/speclist.html

[3] Barcode creation using Zend\Barcode\Barcode class

http://framework.zend.com/manual/2.0/en/modules/zend.barcode.creation.html

[4] Descarga del Zend Framework:

http://framework.zend.com/downloads/archives

http://framework.zend.com/downloads/latest#ZF1

[5] Coupling Zend Framework with CodeIgniter.htm

http://www.masnun.com/2010/06/16/coupling-zend-framework-with-codeigniter.html

[6] Zend Barcodes in CodeIgniter:

http://ghostbilt.com/blog/zend-barcodes-codeigniter/

[7] How to generate barcode image in Codeigniter

http://pastebin.com/jXD1hJN5

[8] Descarga del zend.php:

http://ghostbilt.com/wp-content/resources/Zend.phps

[9] Integrate Zend Framework Barcode Library2 in Codeigniter 2.1.3

http://ellislab.com/forums/viewthread/235473/

[10] Codeigniter + zend barcode class

http://stackoverflow.com/questions/13243656/codeigniter-zend-barcode-class

[11] Codeigniter 2.1.3 Zend Framework 2.2 barcode use

http://stackoverflow.com/questions/16863832/codeigniter-2-1-3-zend-framework-2-2-barcode-use

[12] DEMONTE, Jean-Baptist: Barcode PHP, Generate barcode in GD and PDF

http://www.phpclasses.org/browse/package/6520.html?download=zip

[13] How To Create Barcodes in PHP

http://davidscotttufts.com/2009/03/31/how-to-create-barcodes-in-php/

{ Leer Más }


lunes, 12 de agosto de 2013

Tratamiento de imágenes con CodeIgniter.

En mi artículo “Reglas propias para validación de formularios con CodeIgniter”, te comenté cómo adicionar reglas y mensajes propios al FormValidation de Codeigniter. Además usé como ejemplo la validación de la carga de archivos, llamando con un callback una función de validación, que a su vez utilizaba la biblioteca upload de Codeigniter para cargar (o intentar hacerlo) un fichero. Especificamos requisitos como el tipo permitido (allowed_types) y el tamaño en bytes (max_size) y si es una imagen el ancho y alto (max_width y max_height).

En este artículo te hablaré sobre el uso de otra biblioteca de CodeIgniter, la image_lib, que te permite formatear las imágenes según tus necesidades, con un código como el siguiente:

$this->load->library('image_lib');

$config_img = array();

$config_img['image_library'] = 'gd2';

$config_img['source_image'] = './uploads/path/my_image.jpg;

$config_img['create_thumb'] = FALSE;

$config_img['maintain_ratio'] = FALSE;

$config_img['width'] = 250;

$config_img['height'] = 250;

$this->image_lib->initialize($config_img);

$this->image_lib->resize();

En este ejemplo cargas la biblioteca antes mencionada, especificas la imagen a transformar, y le das el nuevo ancho y alto, pasas los parámetros con initialize y llamas la función de cambiar el tamaño. Si pones create_thumb en TRUE creará una nueva imagen llamada my_image_thumb.jpg con el nuevo tamaño, sino transformará la imagen original. El maintain_ratio permite transformar la imagen manteniendo la razón de aspecto ancho/alto, en este caso aplica el width especificado y calcula internamente el height manteniendo las proporciones de la imagen.


Con eso es suficiente para ajustar las imágenes a los tamaños deseados sin tener que obligar a los usuarios del sitio a que la ajusten antes de subirla.

Esta funcionalidad también es útil para crear thumbs de las imágenes y conservar ambas versiones, la grande y la chiquita, así puedes mostrar en tu sitio las vistas previas o thumbs con un menor tamaño, lo cual es muy conveniente porque el navegador en el cliente no tiene que tardarse ajustando la imagen a los parámetros width y height del tag <img…>, sino que escribes un <img> sin width ni height. Entonces, al dar clic en la imagen para aumentar pues muestras la imagen original, con algún efecto o en otra página, como más te guste. Lo mismo es útil para descargar imágenes.

Bien, si tienes un sitio en el que es común subir imágenes desde diferentes vistas, y estas imágenes tienen requerimientos diferentes, por ejemplo, en una página los usuarios suben fotos de 40X40 para su perfil, y en otra pueden subir fotos de sus vacaciones con un tamaño mayor, y en otra… pues se hace necesario unificar el código relacionado con el tratamiento de estas imágenes para su reutilización.

Ante un caso similar, hice una biblioteca como interfaz entre mi aplicación y la biblioteca image_lib. Recuerda que una biblioteca y un helper de CodeIgniter son similares, con la diferencia de que la biblioteca se programa orientada a objetos (con clases) y el helper es una colección de funciones. Por lo que para hacer una interfaz con una biblioteca es mejor programarla orientada a objetos, y para esto tienes dos opciones, crear una biblioteca que incluye a la otra, o crear una biblioteca que reimplemente funciones de la otra, y este caso se llamaría My_image_lib. Yo opté por la primera variante pues no me interesaba reimplementar nada sino llamar a las funciones de image_lib.

Primero, creé la clase image_manager que puse en un fichero image_manager.php en la carpeta libraries de CodeIgniter. En el constructor de esta clase cargué la biblioteca (load->library('image_lib')), y puse tres funciones por el momento, upload, resize y delete.

La función delete llama a unlink($image) para borrar una imagen del directorio de uploads (el borrado de la tabla en la base de datos la hago en el modelo correspondiente).

La función resize básicamente hace el código que te puse más arriba, especificando como argumentos de la función los parámetros $filename, $thumb_width, $thumb_height y $maintain_ratio.

La función upload hace lo que puse dentro de la función valid_file en el artículo “Reglas propias para validación de formularios con CodeIgniter”, sólo que aquí recibe los argumentos $user_filename, $upload_path, $thumb_width = 0, $thumb_height = 0 y $create_thumb, para especificar el nombre del componente visual de tipo file (se usa dentro de upload->do_upload($user_filename)) y la carpeta donde se va a subir la imagen en el servidor. Si $thumb_width y $thumb_height son distintos de cero asumo que se quiere cambiar el tamaño de la imagen que se está subiendo, así que internamente llamo a resize con estos últimos tres parámetros; sino, la dejo como viene. No obstante hago una restricción de una tamaño máximo de la imagen para no colgar el servidor.

En esta función upload en lugar de devolver TRUE o FALSE devuelvo un mensaje de error, vacío si se subió la imagen, sino devuelvo el valor de $this->upload->display_errors().

¿Cómo se utilizaría estos en los controladores? Mi función de validar quedaría ahora así:

function valid_image()
{
$this->load->library('image_manager');
$filename = $_FILES['userfile']['name'];
if (!$filename)
{
$this->form_validation->set_message('valid_image',
'La foto es obligatoria…');
return FALSE;
//si no es obligatorio subir una imagen simplemente se retorna TRUE
}
else
{
$error_message = $this->image_manager->upload('userfile',
'./uploads/users/', 40, 40, FALSE);
if ($error_message == '')
return TRUE;
else
{
$this->form_validation->set_message('valid_image',
$error_message);
return FALSE;
}
}
}

Y en la función que recibe los posts de la vista adicionas la regla callback_valid_image.

Un detalle final: en las vistas de inserción pones sencillamente un input de tipo file para subir la imagen, pero en las vistas de modificación de los datos deberías permitir sustituir la imagen, o eliminarla si no es obligatoria, así que te recomiendo diseñar visualmente componentes que le permitan al usuario (puede ser con checkboxes, etc) cambiar la imagen o eliminarla.

En este caso en el controlador debes revisar todas las variantes, y si se cambió la foto o se pidió eliminarla, hacer las correspondientes actualizaciones en la base de datos y en el sistema de ficheros del servidor. Para esto te recomiendo hacer una función privada aparte en el mismo controlador, se puede llamar input_image, que se encargue de todas estas verificaciones y devuelva el nombre definitivo de la imagen, ya sea el de la misma imagen que había antes, o una nueva, o vacío si se eliminó. Este nombre es el que se pasará al update a la BD.

De esta forma la función que recibe el post quedaría aproximadamente así:

function insert()
{
$this->form_validation->set_rules('name', 'Nombre', 'required');

$this->form_validation->set_rules('userfile', 'Foto',
'callback_valid_image');

if ($this->form_validation->run() == FALSE)
$this->form_insert();
else
{
$name = $this->input->post('name');

$image_name = $this->input_image();

$this->users_model->insert($name, …, $image_name);
}
}

La función de actualizar quedaría muy parecida, quizás debas pasarle a input_image algún parámetro que indique que se está haciendo una actualización en vez de una inserción, para así preguntar por los respectivos componentes visuales.


En conclusión, hemos visto que la biblioteca image_lib de Codeigniter puede resultar muy útil para ajustar nuestras imágenes a los tamaños requeridos o crear versiones de diferentes tamaños y thumbs. Combinada con la biblioteca upload y el FormValidation, permite hacer un correcto manejo y validación de las imágenes subidas a nuestro servidor, lo cual nos permitirá mantener la integridad de nuestro sitio y los servicios que estemos prestando.

{ Leer Más }


viernes, 9 de agosto de 2013

CodeIgniter + Active Record

Hola comunidad, en el artículo anterior abordamos un poco del patrón Modelo Vista Controlador, de CodeIgniter y el patrón Active Record que usa CodeIgniter para desarrollar sus consultas a las base de datos, es aquí donde vamos a detenernos un poco más hoy en este nuevo artículo y citarle mediante un grupo de ejemplos como es que podemos hacer las diferentes consultas usando el nombrado patrón.

Como ya mencionamos en el artículo anterior CodeIgniter usa una versión modificada del Patrón de Base de Datos Active Record. Este patrón permite obtener, insertar y actualizar información en tu base de datos con mínima codificación. En algunos casos, sólo una o dos líneas de código son necesarias para realizar una acción de base de datos. CodeIgniter no requiere que cada tabla de la base de datos sea un propio archivo de clase. Se permite una interface más simplificada

Más allá de la simplicidad, un beneficio mayor de usar la Active Record es que te permite crear una aplicación independiente de la base de datos que usa, ya que la sintaxis de consulta es generada por cada adaptador de base de datos. También permite consultas más seguras.

Ya en artículo anterior pusimos algunos pequeños ejemplos en este vamos a profundizar más y complejizar un poco más las consultas.

Seleccionando Datos

Permite escribir una porción JOIN de la consulta:

image

Método simple de Clave / Valor

image

Si utiliza múltiples llamadas a la función, ellos serán encadenados juntos con un AND entre ellos:

image

Permite establecer una cláusula de ORDER BY. El primer parámetro contiene el nombre de la columna por la que desea ordenar. El segundo parámetro establece la dirección del resultado. Las opciones son asc or desc, or random.

image

Insertando Datos

A continuación vamos a mostrar algunos ejemplos de inserción:

Genera una cadena de inserción basado en los datos que se suministren, y ejecuta la consulta. Se puede pasar un arreglo o un objeto a la función, este ejemplo es usando un arreglo:

image

Y el que viene a continuación es un ejemplo usando un objeto:

image

La función Set permite establecer valores para actualizar e insertar:

image

Actualizando Datos

Genera una cadena de actualización y corre la consulta basado en los datos suministrados. Puede pasar un arreglo o un objeto a la función, en este ejemplo lo hacemos pasando un arreglo.

image


Borrando Datos

Genera una cadena de eliminación SQL y ejecuta la consulta.

image

El primer parámetro es el nombre de la tabla, el segundo la cláusula WHERE. También puede usar las funciones where() o or_where() en vez de pasar los datos como segundo parámetro de la función:

image

Como ven amigos es muy sencillo hacer consultas usando el Active Record sobre su CodeIgniter, les recomendamos que siga el manual de ayudas de CodeIgniter para seguir aumentando en conocimiento y poder desarrollar sus proyectos de manera rápida y profesional.

Referencia Bibliográfica: Manual de ayuda CodeIgniter.

{ Leer Más }


lunes, 5 de agosto de 2013

Introducción al diseño e implementación de un proceso con la herramienta BPMS Bonita. VI

clip_image002

En la primera entrada de esta serie con el BPMS Bonita habíamos visto como modelar el proceso de la figura anterior.

En la segunda entrada vimos cómo crear tanto variables globales como locales para actividades en particular.

En la tercera entrada vimos cómo definir condiciones y transiciones que nos permitían dar un mejor sentido a las posibles variantes que el proceso puede seguir en función de las decisiones que se tomen.

En la cuarta entrada vimos cómo definir los roles participantes en el proyecto y ejecutar el proyecto visualizándolo desde una aplicación web ofrecida por Bonita.

En la quinta entrada vimos como iniciar la mejora de las interfaces gráficas de los formularios.

En esta 6 entrega veremos cómo seguir la mejora del resto de las UI, User Interfaces o Interfaces Gráficas, realizando cambios en dependencia del formulario en que nos encontremos.

Comencemos.!!!!

Como se puede apreciar en la imagen inicial de esta entrada, tenemos varias actividades que llevan intervención humana. Para continuar nuestro ejemplo modificaremos la UI par la actividad “Revisión por ventas”. Los pasos iniciales son los mismos que los de la entrada anterior, así que nos lo saltaremos y ya estamos en el diseño de la nueva UI.

Lo primero es dejar el diseño igual al de la entrada anterior, por un tema de uniformidad.

Luego debemos notar que este formulario no es para entrar información, si no para que el vendedor revise la información y tome una decisión por lo que los Widgets deberían ser de solo lectura.

Para hacer esto es realmente sencillo:

  • Los campos que son de tipo “Caja de Texto” deben ser cambiado a “Texto”.
  • En el caso del Widget de los productos pues debemos eliminar el Widget y agregar uno de la paleta de componentes que sea de tipo texto. Lo importante aquí es indicarle de donde obtendrá la información. Los pasos son los siguientes:
  1. Eliminar el Widget.
  2. Agregar una nueva fila en la misma posición.
  3. Seleccionar el componente Texto de la paleta de componentes y arrastrarlo a la fila recién creada.
  4. Ponerle como nombre “productos”.
  5. Y como etiqueta “Productos Seleccionados”.
  6. Manteniendo seleccionado este Widget vamos a la opción “Datos” y como valor inicial seleccionamos “productos” tal y como se muestra en la siguiente imagen.

clip_image004

Para probar lo hecho ejecutamos el proceso.

clip_image006

Y al enviar la solicitud vemos lo siguiente:

clip_image008

Como se puede apreciar los campos no son seleccionables y se nos muestran las posibles decisiones a seleccionar.

Aprovechando esta entrada les comento otras mejoras que se le han realizado a las UI:

  1. Como se podía apreciar en las imágenes, los campos estaban muy para la izquierda. La solución más rápida es en el diseño del formulario agregar una columna a la izquierda de la columna en la que tenemos los widgets.
  2. El otro problema era que las cajas de texto eran muy grandes. Así que seleccionando un Widget, vamos a la pestaña de “Apariencia” y ahí podemos modificar las propiedades. En este caso en particular cada Widget tiene esta definición de su ancho.

clip_image010

Ahora se ve mucho mejor, aunque se le pueden seguir haciendo mejoras.

clip_image011

Llegado a este punto algunos señalamientos:

  • La vista previa en nuestro caso no se corresponde luego de hacer los últimos cambios con lo que se muestra una vez ejecutado el proceso.
  • Existe la necesidad de agregar una nueva columna para que los widgets se vean en el medio. Puede ser que exista alguna propiedad que no se ha encontrado aún.

Posibles mejoras a incluir:

  • Poder seleccionar varios productos en una misma orden de compra.
  • Cargar los datos de los productos ofertados desde una BD, y no como se hace hoy que son estáticos.

Estas mejoras las estaremos viendo en futuras entrada.


{ Leer Más }


jueves, 1 de agosto de 2013

Desarrollo de una Tabla caché, usando el Framework Qt.

clip_image002

En la presente entrada se explica la implementación, de una Tabla caché, que muestra una vista de tabla, se puede utilizar para acceder a una base de datos, y realiza el almacenamiento en caché de cualquier cambio en los datos hasta que el usuario haga clic en el botón “Submit”. Este ejemplo nos permitirá comprobar las potencialidades del Framework Qt, para el trabajo con Bases de Datos y el lenguaje SQL [1].

clip_image003

La implementación consta de una sola clase, TableEditor, que es un widget diálogo personalizado que permite al usuario modificar los datos almacenados en una base de datos. Primero vamos a revisar la definición de la clase y luego vamos a echar un vistazo a la implementación.

La definición de la clase TableEditor

La clase TableEditor hereda QDialog haciendo el widget editor de tablas de una ventana de diálogo de alto nivel.

class TableEditor : public QWidget
{
Q_OBJECT

public:
explicit TableEditor(const QString &tableName, QWidget *parent = 0);

private slots:
void submit();

private:
QPushButton *submitButton;
QPushButton *revertButton;
QPushButton *quitButton;
QDialogButtonBox *buttonBox;
QSqlTableModel *model;
};

El constructor TableEditor toma dos argumentos: el primero es un puntero al widget padre y se pasa al constructor de la clase base. El otro argumento es una referencia a la tabla de base de datos donde operará el objeto TableEditor.

Tenga en cuenta la declaración de la variable QSqlTableModel: Como veremos en este ejemplo, la clase QSqlTableModel puede ser utilizada para proporcionar datos que pueden ser visualizados por la clase QTableView. La clase QSqlTableModel proporciona un modelo de datos editable por lo que es posible leer y escribir los registros de base de datos de una sola tabla. Está construido en la parte superior de la clase de nivel inferior QSqlQuery que proporciona los medios para la ejecución y manipulación de sentencias SQL.

También vamos a mostrar como una vista de tabla se puede utilizar para almacenar en caché los cambios a los datos, hasta que el usuario solicita expresamente que los presente. Por eso tenemos que declarar un slot denominado show () que sirva al modelo y a los botones del editor.

Conexión a una base de datos

Antes de poder utilizar la clase TableEditor, tenemos que crear una conexión con la base de datos que contiene la tabla que queremos modificar:

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
if (!createConnection())
return 1;

TableEditor editor("person");
editor.show();
return app.exec();
}

La función CreateConnection () es una función auxiliar para comodidad del lector, que se define en el archivo connection.h. Dicha función es utilizada para conectarse a una base de datos.

static bool createConnection()
{
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(":memory:");
if (!db.open()) {
QMessageBox::critical(0, qApp->tr("Cannot open database"),
qApp->tr("Unable to establish a database connection.\n"
"This example needs SQLite support. Please read "
"the Qt SQL driver documentation for information how "
"to build it.\n\n"
"Click Cancel to exit."), QMessageBox::Cancel);
return false;
}

QSqlQuery query;
query.exec("create table person (id int primary key, "
"firstname varchar(20), lastname varchar(20))");
query.exec("insert into person values(101, 'Danny', 'Young')");
query.exec("insert into person values(102, 'Christine', 'Holand')");
query.exec("insert into person values(103, 'Lars', 'Gordon')");
query.exec("insert into person values(104, 'Roberto', 'Robitaille')");
query.exec("insert into person values(105, 'Maria', 'Papadopoulos')");

query.exec("create table offices (id int primary key,"
"imagefile int,"
"location varchar(20),"
"country varchar(20),"
"description varchar(100))");
query.exec("insert into offices "
"values(0, 0, 'Oslo', 'Norway',"
"'Oslo is home to more than 500 000 citizens and has a "
"lot to offer.It has been called \"The city with the big "
"heart\" and this is a nickname we are happy to live up to.')");
query.exec("insert into offices "
"values(1, 1, 'Brisbane', 'Australia',"
"'Brisbane is the capital of Queensland, the Sunshine State, "
"where it is beautiful one day, perfect the next. "
"Brisbane is Australia''s 3rd largest city, being home "
"to almost 2 million people.')");
query.exec("insert into offices "
"values(2, 2, 'Redwood City', 'US',"
"'You find Redwood City in the heart of the Bay Area "
"just north of Silicon Valley. The largest nearby city is "
"San Jose which is the third largest city in California "
"and the 10th largest in the US.')");
query.exec("insert into offices "
"values(3, 3, 'Berlin', 'Germany',"
"'Berlin, the capital of Germany is dynamic, cosmopolitan "
"and creative, allowing for every kind of lifestyle. "
"East meets West in the metropolis at the heart of a "
"changing Europe.')");
query.exec("insert into offices "
"values(4, 4, 'Munich', 'Germany',"
"'Several technology companies are represented in Munich, "
"and the city is often called the \"Bavarian Silicon Valley\". "
"The exciting city is also filled with culture, "
"art and music. ')");
query.exec("insert into offices "
"values(5, 5, 'Beijing', 'China',"
"'Beijing as a capital city has more than 3000 years of "
"history. Today the city counts 12 million citizens, and "
"is the political, economic and cultural centre of China.')");

query.exec("create table images (locationid int, file varchar(20))");
query.exec("insert into images values(0, 'images/oslo.png')");
query.exec("insert into images values(1, 'images/brisbane.png')");
query.exec("insert into images values(2, 'images/redwood.png')");
query.exec("insert into images values(3, 'images/berlin.png')");
query.exec("insert into images values(4, 'images/munich.png')");
query.exec("insert into images values(5, 'images/beijing.png')");

return true;
}

La función CreateConnection abre una conexión a una base de datos SQLITE en memoria y crea una tabla de prueba. Si desea utilizar otra base de datos, sólo tiene que modificar el código de esta función.

Clase de implementación TableEditor

La implementación de la clase se compone de sólo dos funciones, el constructor y el “slot” show (). En el constructor creamos y personalizamos el modelo de datos y los distintos elementos de la ventana:

TableEditor::TableEditor(const QString &tableName, QWidget *parent)
: QWidget(parent)
{
model = new QSqlTableModel(this);
model->setTable(tableName);
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->select();

model->setHeaderData(0, Qt::Horizontal, tr("ID"));
model->setHeaderData(1, Qt::Horizontal, tr("First name"));
model->setHeaderData(2, Qt::Horizontal, tr("Last name"));

En primer lugar vamos a crear el modelo de datos y establecer la tabla de base de datos SQL, con la cual queremos que el modelo funcione. Tenga en cuenta que la función QSqlTableModel :: setTable () no selecciona datos de la tabla, sino que sólo obtiene su información de campo. Por eso llamamos más tarde a la función QSqlTableModel :: select (), poblando el modelo con los datos de la tabla. La selección se puede personalizar mediante la especificación de los filtros y las condiciones de ordenación.

También establecemos la estrategia de edición del modelo. La estrategia de edición dicta cuando los cambios realizados por el usuario en la vista, en realidad se aplican a la base de datos. Dado que queremos almacenar en caché los cambios en la vista de tabla (es decir, en el modelo) hasta que el usuario los decida enviar expresamente, elegimos la estrategia QSqlTableModel::OnManualSubmit. Las alternativas son QSqlTableModel :: OnFieldChange y QSqlTableModel :: OnRowChange.

Por último, hemos creado las etiquetas que aparecen en la cabecera de vista con la función setHeaderData () que el modelo hereda de la clase QSqlQueryModel.

QTableView *view = new QTableView;
view->setModel(model);
view->resizeColumnsToContents();

Luego creamos una vista de tabla. La clase QTableView proporciona una implementación de modelo / vista predeterminada de una vista de tabla, es decir, se aplica una vista de tabla que muestra elementos de un modelo. También permite al usuario editar los artículos, el almacenamiento de los cambios en el modelo. Para crear una vista de sólo lectura, establezca el indicador adecuado mediante la propiedad editTriggers la vista hereda de la clase QAbstractItemView.

Para que la vista presente nuestros datos, pasamos nuestro modelo a la vista mediante la función setModel ().

    submitButton = new QPushButton(tr("Submit"));
submitButton->setDefault(true);
revertButton = new QPushButton(tr("&Revert"));
quitButton = new QPushButton(tr("Quit"));

buttonBox = new QDialogButtonBox(Qt::Vertical);
buttonBox->addButton(submitButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(revertButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);

Los botones de la interfaz TableEditor son objetos QPushButton regulares. Se le añade una caja de botones para asegurar de que los botones se presentan en un diseño que sea adecuado para el estilo del widget actual. La razón de esto es que los cuadros de diálogo y cuadros de mensaje normalmente presentan botones en una disposición que se ajusta a las directrices de la interfaz para la plataforma. Invariablemente, diferentes plataformas tienen diferentes diseños para sus diálogos. QDialogButtonBox permite a los desarrolladores agregar botones a la misma y se utilizará automáticamente el diseño adecuado para el entorno de escritorio del usuario.

La mayoría de los botones para un diálogo siguen ciertos roles. Al agregar un botón a una caja de botones con la función addButton (), la función del botón debe ser especificado usando la enumeración QDialogButtonBox :: ButtonRole. Alternativamente, QDialogButtonBox proporciona varios botones estándar (por ejemplo, Aceptar, Cancelar, Guardar) que se pueden utilizar.

    connect(submitButton, SIGNAL(clicked()), this, SLOT(submit()));
connect(revertButton, SIGNAL(clicked()), model, SLOT(revertAll()));
connect(quitButton, SIGNAL(clicked()), this, SLOT(close()));

Conectamos el botón Quit para con el “slot” close () que permite cerrar el editor de tablas, y el botón de envío (Submit) a la “slot” show (). Este último slot se hará cargo de las transacciones de datos. Por último, conectamos el botón Revert con el slot RevertAll () de nuestro modelo, que permite revertir todos los cambios pendientes (es decir, la restauración de los datos originales).

QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(view);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);

setWindowTitle(tr("Cached Table"));
}

Al final se añade la caja de botones de la vista de tabla a un diseño, se instala la distribución en el widget editor de tablas, y se establece el título de la ventana del editor.

void TableEditor::submit()
{
model->database().transaction();
if (model->submitAll()) {
model->database().commit();
} else {
model->database().rollback();
QMessageBox::warning(this, tr("Cached Table"),
tr("The database reported an error: %1")
.arg(model->lastError().text()));
}
}

El slot show () se llama cada vez que los usuarios hacen clic en el botón Submit para guardar los cambios.

En primer lugar, comenzamos una transacción en la base de datos utilizando la función transaction () de QSqlDatabase. Una transacción de base de datos es una unidad de interacción con un sistema de gestión de base de datos o sistema similar que se trata de una manera coherente y fiable independiente de otras transacciones. Un puntero a la base de datos utilizada puede ser obtenido usando la función QSqlTableModel ().

Entonces, tratamos de presentar todos los cambios pendientes, es decir, los elementos modificados del modelo. Si no se produce ningún error, nos comprometemos la transacción a la base de datos utilizando la función (tenga en cuenta que en algunas bases de datos, esta función no funcionará si hay un QSqlQuery activo en la base de datos) QSqlDatabase :: commit (). En caso contrario se realiza un rollback de la transacción utilizando la función QSqlDatabase :: rollback () y publicaremos un aviso al usuario.

El código fuente de esta aplicación pueden descargarlo desde aquí.


{ Leer Más }


IconIconIcon