jueves, 15 de septiembre de 2011

Convertir un documento Word en un XPS y visualizarlo en un WPF

En esta entrada convertiremos un documento de Word en un documento XPS. ¿Porqué un XPS? este formato de documento es la competencia al PDF de parte de Microsoft. No es tan popular como el primero, ni tampoco está tan presente en las librerías de otros lenguajes, pero cuando se trabaja en .NET Framework, especialmente con interfaces WPF, es una muy buena opción que permite incrustar el documento de forma nativa dentro de la interfaz.

Podemos crear un documento XPS desde Office si instalamos el plugin que hay para esto. Pero como estoy creando un aplicativo cuyos usuarios no necesariamente lo tienen instalado, tuve que buscar la forma de convertir un documento de Word proporcionado por el usuario en un XPS. Esto es lo que se requiere:

Primero debemos importar los ensamblados necesarios:
  • Microsoft.Office.Interop.Word
  • Microsoft.Office.Tools.v9.0
  • Microsoft.Office.Tools.Word.v9.0
  • Microsoft.VisualStudio.Tools.Office.Runtime.v10.0
  • ReachFramework
Para los que no saben de qué hablo, hagan clic derecho sobre la carpeta References de su solución en Visual Studio, y en el menú contextual seleccionen Agregar referencia.... Se abrirá este cuadro donde seleccionarán los ensamblados citados:


Ahora vamos al código. Yo en una ventana XAML he creado un componente DocumentViewer así:

<DockPanel>
   <DocumentViewer Name="docview"/>
</DockPanel>

Ahora en nuestra clase C# importamos los espacios necesarios:

using System.Windows.Xps.Packaging;
using System.IO;

El espacio System.Windows.Xps.Packaging es con el que trabajaremos los documentos XPS. No importaremos el espacio Microsoft.Office.Interop.Word porque también tiene una clase llamada Document que crea ambigüedad con la de XPS.

Ahora en cualquier lugar del código, por ejemplo, dentro de un evento click de un botón, hacemos la conversión así:

Microsoft.Office.Interop.Word.Application
 wa = new Microsoft.Office.Interop.Word.Application();
wa.Documents.Add(rutaDelDoc);
Microsoft.Office.Interop.Word.Document wd = wa.ActiveDocument;
string ruta = rutaDelXPS;
wd.SaveAs(ruta, Microsoft.Office.Interop.Word.WdSaveFormat.wdFormatXPS);
wa.Quit();
xd = new XpsDocument(ruta, FileAccess.Read);

NOTA: he omitido las cláusulas try-catch por facilidad, pero es recomendable capturar los errores que puedan ocurrir.

La variable rutaDelDoc contiene la ruta del documento de Word que queremos transformar. La variable rutaDelXPS contiene la ruta donde guardaremos el documento XPS. Finalmente, en la variable xd tenemos nuestro documento XPS final. Ahora podemos pasarle este documento a nuestro DocumentViewer así:

docview.Document = xd.GetFixedDocumentSequence();

Con esto, al ejecutar nuestro aplicativo WPF, veremos algo como esto:

miércoles, 24 de agosto de 2011

Usar coloreado de sintaxis en Blogger

Desde hace unos días vengo usando en este blog el coloreado de sintaxis según el lenguaje (desconozco el nombre técnico en español). Para esto empleo una librería javascript que encontré en SyntaxisHighlighter. Como Blogger no permite (hasta donde sé) subir archivos de javascript al blog, debo enlazar la librería desde el sitio del autor, lo cual de hecho es mejor ya que, aparte de tener siempre la versión más actualizada, se guarda en él caché del navegador, así si el usuario accede a otro sitio que utiliza el mismo script, el navegador no tiene que volver a cargarlo.

Este es el código que estoy usando en mi plantilla, lo tengo justo antes del cierre de la etiqueta </head>:

<link href='http://alexgorbatchev.com/pub/sh/current/styles/shCore.css' rel='stylesheet' type='text/css'/> 
<link href='http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css' rel='stylesheet' type='text/css'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCSharp.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPhp.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPython.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushBash.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js' type='text/javascript'/>
<script type='text/javascript'> 
  SyntaxHighlighter.config.bloggerMode = true;
  SyntaxHighlighter.all();
</script>

Las dos primeras líneas cargan los archivos de estilos. Luego se carga el archivo javascript principal que es el mismo para todos los lenguajes. A continuación se enlazan los scripts según los lenguajes que se quieran usar. En mi caso son (en orden) C#, PHP, Python, Bash y XML. La lista completa está aquí.

Terminamos creando un bloque script donde llamamos la función SyntaxHighlighter.all(); que activa el coloreado. Como uso Blogger también activo el SyntaxHighlighter.config.bloggerMode.

Cuando necesito mostrar un código con coloreado de sintaxis, escribo lo siguiente:

<pre class="brush: [lenguaje]">
Aquí el código
</pre>

Obviamente se reemplaza [lenguaje] por el lenguaje del código a mostrar. Recordemos que para usar el coloreado de sintaxis de un lenguaje debo enlazar su correspondiente script. También se debe tener presente usar &lt; y &gt; en vez de < y > respectivamente, dentro del código que se quiera mostrar.

Edit: parece que el término "coloreado de sintaxis" es correcto, o al menos así está en la Wikipedia.

Programar rápidamente un navegador web con PyQt

En esta entrada haremos un navegador web muy sencillo y rápido de programar, usando la librería QtWebKit y el componente QWebView de PyQt. Para quien no sabe de lo que hablo, puede leer mis anteriores entradas sobre PyQt donde creamos un Hola Mundo tradicional y luego lo mejoramos con la ayuda del QtDesigner.

Para esta entrada, y en adelante, seguiremos usando el QtDesigner que es la mejor forma de diseñar interfaces gráficas para Qt. También dije que haremos uso de la librería QtWebKit de Qt para el renderizado de las páginas. Con el nombre se deduce que esta librería usa el motor WebKit que, para quien no lo sabe, es el motor que usan Google Chrome y Safari.

Empecemos abriendo el QtDesigner y seleccionando un proyecto nuevo de tipo Main Window


Ahora arrastremos a nuestro formulario un Grid Layout


Hacemos clic derecho sobre el formulario y luego seleccionamos en el menú contextual Lay Out > Lay Out in a Grid. Hemos agregado un layout de tipo grid a nuestro formulario. Ahora arrastramos un Line Edit (campo de texto) y un Push Button (botón) así:


Debajo de estos dos componentes, arrastramos nuestro Web View, que es el componente navegador:


Nombramos nuestros componentes así: al Line Edit lo nombramos leURL, al Push Button btIr y al Web View wvNavegador. También al Push Button le cambiamos el texto por "Ir".

Ahora necesitaremos una función en nuestro formulario, la cual hará que el navegador cargue la URL que escribamos en el Line Edit. Para ello hacemos clic derecho sobre el espacio vacío del formulario y en el menú contextual seleccionamos Change signals/slots para que nos abra el editor de señales. Ya habíamos visto este procedimiento en las anteriores entradas así que debe ser familiar. Agregamos un nuevo slot en nuestra ventana llamado navegar:


Ahora necesitamos conectar nuestro botón Ir a este nuevo slot. Para ello hacemos clic en el botón de edición de señales ubicado en la barra de herramientas:


Así entramos en modo de edición de señales. Hacemos clic sostenido sobre el botón Ir y sin soltar arrastramos el mouse hasta un lugar vacío del formulario. Si se hizo correctamente aparecerá este cuadro de diálogo:


Allí seleccionamos la señal click del botón y el slot navegar del formulario. Con esto ahora cuando se haga clic en el botón, se llamará al slot (función) navegar de nuestro formulario. Esta función la escribiremos en python más abajo.

Ahora, guardemos nuestro diseño con el nombre navegador.ui, y ya estamos listos para exportarlo a python. Recordemos el procedimiento:
  1. Abrimos una ventana de terminal (o consola) en el directorio donde guardamos el archivo Ui.
  2. Ejecutamos la orden pyuic4 navegador.ui -o navegador_gui.py. Si no funciona es que necesita configurar el pyuic4 como lo describo en esta entrada, o bien, puede llamarlo desde su ubicación completa (sólo en Windows), por ejemplo en mi caso: C:\Python25\Lib\site-packages\PyQt4\bin\pyuic4 navegador.ui -o navegador_gui.py (porque uso Python 2.5).
Ya tenemos un archivo navegador_gui.py que contiene el diseño ya exportado. Luego creamos nuestro script principal que llamaremos navegador.py. Lo abrimos para escribir lo siguiente:

from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4.QtWebKit import *
from navegador_gui import Ui_MainWindow
import sys

class Navegador(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self,None)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
    def navegar(self):
        sUrl = str(self.ui.leURL.text())
        if not sUrl.startswith('http://'):
            sUrl = 'http://'+sUrl
        url = QUrl(sUrl)
        self.ui.wvNavegador.load(url)

app = QApplication(sys.argv)
nav = Navegador()
nav.show()
sys.exit(app.exec_())

El código es muy similar al que ya hicimos en la entrada anterior. Como vemos, hemos agregado la función navegar que se corresponde con el slot que agregamos al formulario y que asociamos al botón Ir. Allí pondremos el código que se ejecutará cuando se hace clic en dicho botón.

Entre lo nuevo podemos ver que hemos importado las librerías QtWebKit y QtCore. La función __init__ es igual a nuestro anterior proyecto. Lo diferente es la función navegar que agrega lo siguiente:

#Obtenemos la dirección que escribió el usuario en
#el Line Edit
sUrl = str(self.ui.leURL.text())
#Si la drección no empieza con http:// se lo agregamos
if not sUrl.startswith('http://'):
    sUrl = 'http://'+sUrl
#Creamos un QUrl con la dirección...
url = QUrl(sUrl)
#Y la cargamos en el navegador
self.ui.wvNavegador.load(url)

Debemos tener presente que los componentes con texto en PyQt (como el Line Edit del ejemplo) no devuelven un str sino un QString, por ello tenemos que convertirlo a un string de python con str(elQString).

Con este sencillo código ya tenemos un navegador funcional. Ejecutémoslo con la línea de comando python navegador.py. Escribimos una dirección en el campo de texto (el Line Edit) y pulsamos el botón Ir:


Sólo para Windows:


Si vamos a la siguiente dirección: http://www.adobe.com/software/flash/about/ veremos una página sin animación Flash:


Tampoco podremos ver videos en Youtube :p . Esto es porque nuestro navegador no está cargando los plugins de WebKit. Para que los cargue (entre ellos el Flash Player) debemos agregar lo siguiente después del self.ui.setupUi(self):

self.ui.wvNavegador.settings().setAttribute(QWebSettings.PluginsEnabled,True)

Hay más opciones para explorar en la clase QWebSettings. El código completo queda así:

from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4.QtWebKit import *
from navegador_gui import Ui_MainWindow
import sys

class Navegador(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self,None)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.wvNavegador.settings().setAttribute(QWebSettings.PluginsEnabled,True)
    def navegar(self):
        sUrl = str(self.ui.leURL.text())
        if not sUrl.startswith('http://'):
            sUrl = 'http://'+sUrl
        url = QUrl(sUrl)
        self.ui.wvNavegador.load(url)

app = QApplication(sys.argv)
nav = Navegador()
nav.show()
sys.exit(app.exec_())

Al ejecutarlo:


Voilà! Nuestro navegador carga animaciones Flash (y otros plugins) sin problemas.

En próximas entradas extenderemos un poco este navegador, agregándole nuevas funcionalidades y más adelante conectaremos a Python con Javascript y Flash.

domingo, 14 de agosto de 2011

Usar QtDesigner para actualizar nuestro QHolaMundo

En esta entrada expliqué cómo comenzar con PyQt creando un sencillo QHolaMundo. Ahora voy a explicar cómo usar QtDesigner para recrear nuestro QHolaMundo.

Voy a asumir que ya se tiene instalado Python y PyQt, como lo expliqué en el primer post. En Windows el instalador de PyQt ya trae todas las herramientas necesarias. Para tener las herramientas de edición en Linux debemos instalar los siguientes paquetes:
  • pyqt4-dev-tools
  • designer-qt4
Ya con las herramientas, comenzamos abriendo el QtDesigner:

Seleccionamos la opción Main Window para obtener esto:
Así podremos comenzar a editar nuestra interfaz gráfica. Lo primero que necesitamos (para emular el QHolaMundo original) es un QBoxLayout horizontal. Vamos a las herramientas de la izquierda y arrastramos un Horizontal Layout a nuestra ventana:
Ahora debemos indicarle a nuestra ventana que este layout será su layout por defecto. En la esquina superior derecha del editor encontramos el Object Inspector
Allí seleccionamos nuestra ventana principal (MainWindow) y hacemos clic derecho sobre ella. Aparece un menú contextual en el cual haremos clic en Lay out > Lay out horizontally.

Ahora pondremos los widgets que necesitamos dentro de nuestro layout. En el QHolaMundo original eran un QLineEdit y un QPushButton. Ambos se encuentran en el panel de herramientas:
En el panel de propiedades de la derecha, le pondremos nombres a nuestros componentes

Para preservar los nombres del QHolaMundo original, al campo de texto lo llamaremos leMiNombre y al botón btHola. Ahora viene lo qué hace de QtDesigner tan buen editor: vamos a crear el evento de saludar y lo conectaremos al botón. En QtDesigner todo es visual, hasta esto.

Comencemos con crear un slot. Un slot en Qt es una función que puede ser llamada por un evento (signal). Podemos decir que en Qt los eventos se llaman signals y los métodos que ejecutan éstos son slots. Todo widget puede tener sus signals y slots personalizados. Lo que haremos es crearle un slot a nuestra ventana que se encargará de saludar al usuario. Para ello volvamos al Object Inspector
Volvemos a hacer clic derecho sobre MainWindow, en el menú contextual hacemos clic en Change signals/slots para abrir este diálogo:
Como vemos en la imagen, hacemos clic en el símbolo + verde de los slots para que se agregue un nuevo slot. Lo renombraremos como salude() (debe incluir los paréntesis, esto indica que el slot no recibe parámetros). Aceptamos y ahora nos vamos al modo de edición Signals/Slots ubicado en el toolbar superior
El primer botón de este toolbar es el diseño de componentes estándar. El segundo es el diseño de Signals/Slots. El tercero y el cuarto no lo usaremos por ahora. Al activar el modo de edición de Signals/Slots, haremos clic sostenido sobre el botón y arrastraremos el mouse sobre el espacio vacío de la ventana y allí soltaremos el botón del mouse. Al hacerlo correctamente aparecerá el siguiente cuadro:
Lo que hemos hecho (según muestra el conector rojo), es asociar una señal (signal) del botón a un método (slot) en la ventana. Ahora debemos seleccionar clicked() como la señal a esperar y salude() como nuestro slot receptor, y aceptamos.

Con esto es suficiente de QtDesigner para nuestro sencillo QHolaMundo. Guardamos el archivo en la carpeta que desee. Nos dirigimos a ella y abrimos una ventana de terminal (recordemos que en Windows Vista/7 se abre  haciendo clic derecho+Shift sobre la carpeta y luego Abrir ventana de comandos aquí).

Ahora haremos uso de la consola para convertir nuestro archivo GUI guardado (que debe tener extensión ui si no la cambió) en un script Python. Para ello usaremos la herramienta pyuic4 que viene en la instalación de Windows. En Windows ejecutamos en la consola:

C:\Python25\Lib\site-packages\PyQt4\bin\pyuic4 QHolaMundo.ui -o QHolaMundoGui.py

Esto porque yo tengo Python 2.5, si instaló Python 2.6 se reemplaza Python25 por Python26, y lo mismo con el 2.7. En Linux es más fácil ya que pyuic4 ya está en el path del sistema:

pyuic4 QHolaMundo.ui -o QHolaMundoGui.py

Ahora tendrá un script de python en la misma carpeta donde guardó el ui, llamado QHolaMundoGui.py. A continuación necesitamos ejecutar este aplicativo. Vamos a crear un nuevo script de python en la misma carpeta llamado QHolaMundo.py con el siguiente contenido:

from PyQt4.QtGui import *
import sys
from QHolaMundoGui import Ui_MainWindow

class QHolaMundo(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self,None)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
    def salude(self):
        miNombre = self.ui.leMiNombre.text()
        saludo = 'Hola %s'%(miNombre)
        QMessageBox.information(self,'Saludo',saludo)

app = QApplication(sys.argv)
qhm = QHolaMundo()
qhm.show()
sys.exit(app.exec_())

Encontremos las diferencias con el QHolaMundo original (las he resaltado). Para comenzar, creamos la clase heredada de QMainWindow y no de QWidget, esto porque en el QtDesigner creamos un Main Window. Además hemos reemplazado la creación de los widgets por dos líneas:

self.ui = Ui_MainWindow()
self.ui.setupUi(self)

Lo que hacemos es crear en la propiedad ui de nuestra clase el constructor de widgets (Ui_MainWindow que es la clase que creó pyuic4 en QHolaMundoGui.py). Luego le indicamos que inicialice los widgets en nuestra ventana y listo, ahora nuestra ventana tiene la apariencia que le dimos en QtDesigner. La diferencia con hacerlo directamente (como el QHolaMundo original) es que ahora los widgets no están en la misma ventana, sino dentro de la propiedad ui, es decir, ahora no llamamos al campo de texto con self.leMiNombre, sino con self.ui.leMiNombre. Algo para nada molesto si tenemos en cuenta que nos ha ahorrado código.

Además vemos ahora que no hay que hacer conexiones con signals y slots. QtDesigner ya lo hizo por nosotros, lo único que necesitamos (de no hacerlo saltan errores al ejecutar) es crear en nuestra clase un método por cada slot que hayamos creado en QtDesigner. En este ejemplo sólo fue uno: salude() y como vemos, no recibe parámetros.

Al ejecutar:
Como el original pero más fácil y divertido. Hay muchas más cosas que explorar con QtDesigner, como el exportador de recursos (pyrcc4), una opción muy interesante. En otra oportunidad haremos un navegador usando las librerías WebKit (las de Google Chrome y Safari) que vienen incluidas en PyQt, con QtDesigner.

TIP: si se trabaja mucho con QtDesigner resultará molesto en Windows estar llamando a pyuic4 y pyrcc4 desde su ubicación cada que se desea exportar un archivo ui. Hay soluciones como hacer el comando de exportación en un archivo bat, pero yo prefiero agregar la ubicación del pyuic4 (en mi equipo es C:\Python25\Lib\site-packages\PyQt4\bin) al path del sistema (como en Linux). Así desde donde quiero llamo a pyuic4 por su nombre, sin la dirección completa.

Mi primer Hola Mundo con PyQt

Hasta hace poco para desarrollar aplicativos de escritorio en Python usaba la confiable librería wxPython. Se trata de una librería GUI que usa las ventanas nativas de Windows o GTK, según el sistema donde se corra. Como dije, es confiable y está muy desarrollada. Sin embargo, en mi opinión, se ha desarrollado un poco desordenado y esto hace que algunos componentes no se comunique bien con otros.

Hace un par de años, buscando un reemplazo fiable a wxPython, instalé PyQt. Cuando lo probé me impresionó su editor gráfico, el QtDesigner, que viene en el archivo de instalación para Windows. En aquella época trabajaba mucho Flash y necesitaba un GUI que me permitiera embeber un reproductor Flash como un Activex. PyQt tiene una versión comercial que permite hacer eso, pero por desgracia, la versión libre no lo permite. Así que lo deseché.

Hace unos meses le dí una nueva oportunidad y encontré una librería muy rica en componentes y posibilidades, y lo mejor, muy ordenada y coherente. Aún no puedo embeber un Flash, al menos no hasta que pague la licencia que no es extremadamente costosa (350 libras), pero hay que evaluar si vale la pena pagarla sólo para embeber Flash. Por fortuna hace unos días descubrí como usar Flash en PyQt sin comprar ni quebrantar la licencia (sólo en Windows), pero eso será tema de otra entrada.

Ahora veremos cómo hacer un Hola Mundo muy sencillo en PyQt sin usar el diseñador de interfaces. Lo primero que necesitamos son los ingredientes. Yo uso Python 2.5 que, en mi opinión, es la mejor versión de Python para Windows:
  • Python (recomiendo la 2.5). Para evitar complicaciones en Windows, es mejor usar el instalador que ofrece Activestate, es más fácil de instalar y trae la librería Win32.
  • PyQt (última versión). La versión de Windows trae el QtDesigner y un demo.
En Linux es más fácil, Python ya viene instalado (la versión 2.7 es perfecta para este SO y es la que trae Ubuntu), y PyQt se encuentra en los repositorios.

Una vez se instalen los ingredientes, creamos un nuevo script de python y lo llamamos QHolaMundo.py. Lo primero que hacemos es importar la librería PyQt:

from PyQt4.QtGui import *
from PyQt4.QtCore import *

También importamos el módulo sys:

import sys

Luego creamos una ventana sencilla:

class QHolaMundo(QWidget):
    def __init__(self):
        QWidget.__init__(self,None)
        layout = QBoxLayout(QBoxLayout.LeftToRight)
        self.leMiNombre = QLineEdit(self)
        self.btHola = QPushButton('Hola...',self)
        layout.addWidget(self.leMiNombre)
        layout.addWidget(self.btHola)
        self.setLayout(layout)

La ventana hereda de QWidget que es el componente básico de Qt. Creamos un QBoxLayout que será el que organice nuestros componentes dentro de la ventana. Creamos un QLineEdit que es el campo de texto donde introduciremos nuestro nombre. Luego un QPushButton (un botón común y corriente) que hará la acción de saludar. Estos dos componentes (widgets) se los agregamos al layout y luego asociamos el layout a nuestra ventana.

Por ahora esta ventana no hace nada, pero ejecutémosla para probar si funciona. Para ello necesitamos agregar el siguiente código al final del módulo:

app = QApplication(sys.argv)
qhm = QHolaMundo()
qhm.show()
sys.exit(app.exec_())

Esto lo que hace es crear un QApplication, que administrará la ejecución de PyQt. Le debemos pasar los argumentos del sistema, para ello necesitábamos al módulo sys.

Todo el código completo queda así:

from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys

class QHolaMundo(QWidget):
    def __init__(self):
        QWidget.__init__(self,None)
        layout = QBoxLayout(QBoxLayout.LeftToRight)
        self.leMiNombre = QLineEdit(self)
        self.btHola = QPushButton('Hola...',self)
        layout.addWidget(self.leMiNombre)
        layout.addWidget(self.btHola)
        self.setLayout(layout)

app = QApplication(sys.argv)
qhm = QHolaMundo()
qhm.show()
sys.exit(app.exec_())

Finalmente ejecutamos desde la línea de comandos con: python QHolaMundo.py y este es el resultado:
Si hacemos clic en el botón, aún no hace nada, y es que necesitamos hacer la función que salude y conectarla al botón. Nuestra función de saludo (dentro de la clase QHolaMundo):

def salude(self):
        miNombre = self.leMiNombre.text()
        saludo = 'Hola %s'%(miNombre)
        QMessageBox.information(self,'Saludo',saludo)

Este método toma lo que haya escrito el usuario en el campo de texto, lo adiciona al saludo ('Hola %s' donde %s se reemplaza por miNombre) y lo muestra en un cuadro de mensaje típico.

Ahora hacemos que el botón llame a esta función así (dentro de la función __init__ de la clase QHolaMundo):

QObject.connect(self.btHola,SIGNAL('clicked()'),self.salude)

De esta forma hacemos que btHola ejecute el método self.salude, cuando se de la señal 'clicked()' (clic en el botón). El código completo actualizado es este:

from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys

class QHolaMundo(QWidget):
    def __init__(self):
        QWidget.__init__(self,None)
        layout = QBoxLayout(QBoxLayout.LeftToRight)
        self.leMiNombre = QLineEdit(self)
        self.btHola = QPushButton('Hola...',self)
        layout.addWidget(self.leMiNombre)
        layout.addWidget(self.btHola)
        self.setLayout(layout)
        QObject.connect(self.btHola,SIGNAL('clicked()'),self.salude)
    def salude(self):
        miNombre = self.leMiNombre.text()
        saludo = 'Hola %s'%(miNombre)
        QMessageBox.information(self,'Saludo',saludo)

app = QApplication(sys.argv)
qhm = QHolaMundo()
qhm.show()
sys.exit(app.exec_())

Al ejecutarlo y presionar el botón:
Así de simple. Para más información sobre las clases se puede consultar la documentación en la página oficial de PyQt.

En posteriores entradas explicaré un poco más sobre PyQt, el uso del QtDesigner y cómo conectarse con Flash Player.

sábado, 13 de agosto de 2011

Crear un cargador de imágenes dinámico y asíncrono en ASP .NET

Hace unos días ando trasteando un poco con ASP .NET. Debo reconocer que al principio no me llamaba mucho la atención, pues la tecnología es similar a Java y prefiero éste como lenguaje. Sin embargo, estoy trabajando un sitio en Visual Wev Developer 2010 Express y me he llevado una gran sorpresa con el Framework y el IDE de Microsoft, especialmente en lo que se refiere a trabajo asíncrono.

En esta entrada vamos a hacer una página que cargue dinámicamente cualquier imagen ubicada en una ruta remota (en otro servidor), asíncronamente y sin escribir una sola línea de Javascript. Cuando hablo de "dinámicamente" me refiero a que el usuario decide la ruta, no está predefinida en el aplicativo, y cuando me refiero a "asíncronamente" quiero decir que la página no se refresca, el elemento se carga sin hacer un refresco de la página, algo habitual desde hace ya varios años con Ajax.

Todo esto no es nuevo para nada, como dije, se puede hacer lo mismo desde hace años. Lo que me sorprende es la facilidad con que se puede hacer y sin escribir (nosotros, el Framework sí) una sóla línea de Javascript. Empecemos:

Creamos un proyectos nuevo
Al iniciar nos encontramos con este código, al cual le borraremos el fragmento seleccionado:
Al moverse a la pestaña Diseño veremos el resultado, un contenido vacío:
Ahora comenzaremos a arrastrar componentes al área vacía. También podemos agregar los mismos componentes a código, pero prefiero el método visual.

Lo primero que necesitaremos será un ScriptManager:
Este componente no es visual, es decir, no se mostrará en el resultado final, pero es necesario para trabajar con paneles asíncronos. Ahora necesitamos un UpdatePanel:
Todo lo que ubiquemos dentro de este panel se comportará asíncronamente, es decir, no recargará la página completa, los componentes se actualizarán a través de Javascript. Ahora pondremos una imagen dentro del UpdatePanel:
Esta será la imagen que cambiaremos dinámicamente. Ahora pondremos una imagen que se cargará al principio como imagen predeterminada. Por ejemplo, esta imagen:
Para agregarla al proyecto debemos abrir las propiedades del mismo y buscar la pestaña Recursos. Luego hacemos clic en el menú Agregar Recurso > Agregar Archivo Existente
Buscamos nuestra imagen de muestra y la agregamos como recurso. Este es el resultado:
Guardamos. Ahora seleccionamos nuestro componente imagen y en las propiedades modificamos el ImageURL. Podemos abrir el cuadro de selección de recurso:
Nuestro recurso estará en la carpeta Resources. Hacemos clic en Aceptar para ver la imagen actualizada en el diseñador. En las propiedades de la imagen le cambiamos la propiedad Width de forma que al cargar una imagen siempre conserve un ancho fijo. A mi imagen le puse 500px. Ahora, debajo de la imagen, agregamos un campo de texto y un botón:
Hacemos dobleclic en el botón para que se abra el código donde pondremos la función de cargar la imagen que el usuario escriba en el cuadro de texto:
Como vemos, es una sola línea de código (línea 18). Cuando compilamos y ejecutamos, éste es el resultado:
Ahora escribimos la URL de una imagen cualquiera en el cuadro de texto, pulsamos el botón y:
Nuestra imagen cargada dinámicamente y sin refrescar la página. Ya sé que esto se puede hacer con Javascript en cualquier tecnología (PHP, Java, etc.), lo sorprendente aquí es que lo tenemos usando sólo componentes visuales y escribiendo una sola línea de código en C#, nada en Javascript. El Framework hace el resto del trabajo.

Un ejercicio adicional es ponerle al aplicativo un mensaje que indique que se está cargando la imagen, esto para cuando la imagen es muy grande y tarda en cargarse. Para hacerlo podemos usar el componente UpdateProgress:
Ahora vamos a sus propiedades y le asociamos nuestro UpdatePanel:
Finalmente ponemos el componente que queramos mostrar mientras se carga la imagen:
Aquí, un Label que dice "Cargando imagen". El UpdateProgress es un div que solo se mostrará mientras se esté ejecutando una función asíncrona. Al terminarse de ejecutar, el div se oculta nuevamente.

Esto es todo por ahora, mientras vaya aprendiendo nuevas cualidades de este Framework las iré escribiendo aquí.

Leer archivos remotos en C#

Creando un sitio en ASP .NET me he enfrentado a la necesidad de leer archivos remotos, es decir, ubicados en un servidor diferente al que ejecuta el aplicativo. Esto para cargar parte de él en un UpdatePanel, que es un contenedor que se actualiza de forma asíncrona. Leyendo y probando, encontré los siguientes métodos según el origen y tipo del archivo:

Local
El más simple, se lee como cualquier archivo local en C#:

using System.IO;
...
protected string LeerArchivoLocal(string rutaLocal)
{
 StreamReader sr = new StreamReader(rutaLocal);
 string linea = "";
 string res = "";
 while (linea != null)
 {
  linea = sr.ReadLine();
  res += linea + "\n";
 }
 sr.Close();
 return res;
}

Xml remoto
Acceder a un XML remotamente es demasiado fácil:

using System.Xml;
...
protected XmlDocument LeerXmlRemoto(string url)
{
 XmlDocument doc = new XmlDocument();
 doc.Load(url);
 return doc;
}

Otro archivo remoto
Realmente este era el que necesitaba: leer cualquier tipo de archivo ubicado remotamente. En Stackoverflow encontré la respuesta:


using System.Net;
...
protected string LeerArchivoDeTextoRemoto(string url)
{
 WebClient wc = new WebClient();
 return wc.DownloadString(url);
}

DownloadString es el método de lectura como String. La clase tiene además otros métodos que permite leer el archivo como un arreglo de bytes o descargarlo a una ruta local.

jueves, 11 de agosto de 2011

Instalando Apache+PHP+MySQL+Python+Herramientas en (K)Ubuntu

En mi anterior blog ya había publicado una entrada de cómo instalar Apache+PHP+MySQL+Userdir en Ubuntu. Ese post me ayudó muchas veces para poner a funcionar un servidor HTTP en Ubuntu rápidamente. Sin embargo, ha llegado la hora de actualizarlo.

Resulta que el soporte de Userdir, en mi opinión, se ha venido desmejorando en las últimas versiones de Ubuntu (desconozco si en todas las distros), por lo que al instalarlo ahora hay que configurar y toquetear más cosas. Por ejemplo, la última vez que lo instalé no funcionaba el PHP, había que configurarlo. Por esto ahora prefiero programar directamente en el /var/www.

Adicional a esto, he trabajado mucho más en python como CGI, por lo que se me ha vuelto una necesidad tener una guía de cómo configurarlo.

Comencemos

Instalando todo
Lo primero, instalar todo  lo que necesitamos. Ahí nos ayudamos con el Synaptic, o en mi caso, el KPackageKit, o el apt-get de toda la vida. Los ingredientes son:

  • apache2
  • php5
  • mysql-server
  • php5-mysql
  • python-mysqldb
  • mysql-query-browser

Deberían instalarse todas las dependencias. En la instalación nos debería aparecer un formulario donde se nos pregunta la contraseña de MySQL. Se escribe dos veces y se hace clic en Aceptar. Debemos recordarla porque la vamos a necesitar. Listo, podemos probar Apache en http://localhost, debería aparecer:



El último aplicativo de la lista (mysql-query-browser) sirve para acceder a las bases de datos de MySQL. Se encuentra en el grupo de programas "Desarrollo". Al ejecutarlo nos pide los datos de conexión:



Normalmente el nombre del servidor es localhost, el usuario root y la contraseña es la que introdujimos cuando instalamos MySQL. Algunos pueden preferir PhpMyAdmin que es una interfaz web (corriendo en nuestro servidor local) de administración de MySQL, que se instala con el paquete phpmyadmin. Una vez instalado se accede a través de http://localhost/phpmyadmin.

Creando el directorio de trabajo

Como mencioné, ya no uso Userdir, así que debemos trabajar directamente sobre el /var/www. El problema es que sólo el root tiene permisos sobre él y no está en nuestro home, que sería mucho más cómodo para acceder. Esto se resuelve dando permisos de usuario (desde el root) a la carpeta y luego creando un enlace simbólico. En mi caso, prefiero no trabajar sobre el /var/www mismo, sino sobre una subcarpeta, pero el procedimiento es igual.




Creo mi carpeta de trabajo (se lo pueden saltar si trabajan con la carpeta predeterminada): no soy un purista de la consola, así que prefiero el Nautilus o el Dolphin para estas tareas, pero debo abrirlos como root. En Ubuntu se abre con gksu nautilus y aunque desconozco la alternativa a gksu (abre una aplicación gráfica como root) en KDE, sudo dolphin funciona muy bien. Busco la carpeta y creo allí la mía (proyectos, es decir: /var/www/proyectos).

Doy permisos de usuario: ahora con el Nautilus o Dolphin en modo root, seleccionamos la carpeta con la que vamos a trabajar y hacemos clic derecho > Propiedes > Permisos. Ahora le decimos que el propietario de la carpeta es [mi nombre de usuario] y el grupo igual ([nombre de usuario]). En KDE:



Aceptar y ya tenemos los permisos para escribir en la carpeta. También lo pueden hacer desde consola con chmod.

Creo el enlace en mi home: es preferible, en mi opinión, tener la carpeta donde se trabaja en el home por varias razones (facilidad con los accesos directos, inicio del terminal, etc). Por ello es mejor que nos traigamos la carpeta de trabajo para nuestro home con un enlace simbólico. Para el novato en linux: esto es lo mismo que los accesos directos de Windows, pero con una maravillosa diferencia: se comportan como el original.

Un ejemplo vale más que mil explicaciones: voy a crear un enlace en mi home con ruta /home/[nombre de usuario]/proyectos que apunta a /var/www/proyectos. En Linux hay dos formas: con enlaces fuertes (que no voy a tratar aquí) y simbólicos. Creo mi enlace simbólico con:

ln -s /var/www/proyectos /home/[mi nombre de usuario]/proyectos

También se puede usar Dolphin llendo al home, clic derecho sobre el espacio vacío, y en el menú contextual Crear Nuevo>Enlace básico a archivo o carpeta:



Mucho más fácil.

Como mencioné antes, a diferencia de Windows, nuestro enlace es como si la carpeta /var/www/proyectos estuviera en nuestro home, ya que puedo accederla a través de cd /home/[mi nombre de usuario]/proyectos, listarla con ls, etc.

Probando PHP

Hasta el momento sólo hemos instalado el servidor y creado un directorio de trabajo en nuestro home. Ahora hay que probar el funcionamiento de PHP con un sencillo script dentro de la carpeta de trabajo. Llamémoslo test.php. Ahora lo abrirmos para escribir:

<?php
phpinfo();
?>

Lo guardamos. Si la carpeta de trabajo que enlazó es /var/www, puede acceder a este archivo con http://localhost/test.php. Yo creé una subcarpeta llamada proyectos (asumiré esta opción en adelante), así que debo acceder a ella con http://localhost/proyectos/test.php. Debe mostrar esto:



Si es así, funcionó. Si no (me ha pasado) deben reiniciar Apache con sudo service apache2 restart.

Configurando y probando Python

Debo reconocer que esta parte es la razón por la que escribo este post, es la que más olvido y, digamos, la más compleja. Lo que vamos a hacer es habilitar el uso de python como motor CGI. Debemos movernos a la carpeta /etc/apache2/sites-available ya sea con consola (cd ...) o con Nautilus o Dolphin en modo root como ya expliqué antes. Mi método preferido (no soy amigo de la consola) es abrir Kate o Gedit como root con gksu gedit (Ubuntu) o sudo kate (Kubuntu). Me abre el editor de textos de cada uno y con él busco, edito y guardo los archivos que necesito, con permisos de root.

En la carpeta que mencioné, debemos abrir el archivo default para editarlo (OJO: como root). Buscamos donde diga <Directory /var/www/> y agregamos lo siguiente:

  • Donde dice Options agregar al final (separado por espacio) +ExecCGI.
  • Agregar la línea (o reemplazarla si ya existe) AddHandler por: AddHandler cgi-script cgi py pl.

Finalmente, yo suelo cambiar todos los AllowOverride None del archivo por AllowOverride All. Esto no tiene ninguna funcionalidad para lo que estamos haciendo en este post, pero me sirve para cuando uso .htaccess.

A mí me quedó así (OJO, es sólo el fragmento del archivo):

...
<Directory /var/www/>
	Options Indexes FollowSymLinks MultiViews +ExecCGI
	AddHandler cgi-script cgi py pl
	AllowOverride All
	Order allow,deny
	allow from all
</Directory>
...

Guardamos el archivo y reiniciamos Apache con sudo service apache2 restart.

Ahora probemos python. Creamos un archivo en nuestra carpeta de trabajo, con el nombre test.py. Escribimos en él:

#!/usr/bin/python

print 'Content-type: text/plain'
print ''

for i in range(5):
  print 'Hola %i veces'%i

print 'chao'

Más adelante, en otros posts, hablaré más de python. Quien esté familiarizado con el entorno CGI de python puede escribir el script que desee, pero recuerde: debe dejar una línea vacía después del Content-type.

Guardamos y con Nautilus o Dolphin (ya no es necesario que sea como root porque tenemos permisos de usuario en la carpeta de trabajo) hacemos clic derecho sobre el archivo > Propiedades > Permisos e indicamos que es un ejecutable. En KDE:



NOTA: es muy frecuente olvidar hacer este paso de definir el archivo como ejecutable. Me he pasado amargos minutos revisando una y otra vez un script que falla, cuando lo que falta es esto.

Aceptar y ahora a probarlo: http://localhost/proyectos/test.py. Debería aparecer:



Ahora ya tenemos Apache con PHP, MySQL y Python configurado en (K)Ubuntu. Para configurar en Windows es similar, sólo que se edita el archivo httpd.conf. También se puede configurar de la misma forma para usar Perl u otro lenguaje para CGI.

Primer post

Este blog se abre, principalmente, con el fin de almacenar mis experiencias en desarrollo de software, en forma de fragmentos de códigos y procedimientos que me servirán como memoria auxiliar, ya que al parecer en mi cabeza tengo un diskette por memoria.

Me disculpo si el lenguaje es parco e impersonal, pero como expliqué antes, la finalidad es recordarme procedimientos que no uso frecuentemente, y que me harté de buscar en mis apuntes. Mejor si lo tengo en la web, y además le sirve a alguien más, y mejor aún, si puede aportar.

No siendo más...