Arriba

Creando Nuestro Propios Widgets

Una de las ventajas que tiene la programación orientada a objetos es la reusabilidad del código ya hecho. Si creamos una clase, la depuramos exhaustivamente y la dejamos lista, podemos usarla como base para otros programas sin tener que rehacer el código. Una de los puntos a favor del toolkit Qt es que está hecho en C++ brindando todos los beneficios de este lenguaje junto a la programación orientada a objetos; y KDE, estando derivado de Qt también ofrece esta ventaja.

La mejor opción, desde mi punto de vista, al momento de crear interfaces visuales es, precisamente, el crear widgets a nuestra medida o derivar algún widget ya depurado e insertarlo en nuestra aplicación. Un widget es un objeto, que bien diseñado, puede contener cualquier objeto como otros widgets, incluso objetos más complejos como barras de herramientas, layouts, etc. Aprovechando esta facilidad, diseñaremos un widget sencillo con una barra de herramientas y un editor de texto para mostrar una frase. La barra de herramientas la haremos usando un método que nos permitirá ubicarla en cualquier widget, a diferencia del método que vimos en un artículo anterior, que solo se puede ubicar en el área destinada por KMainWindow para este propósito. A la barra de herramientas le colocaremos un ComboBox y un SpinBox para que veamos que la clase KToolBar nos permite colocar otras cosas además de botones y cómo hacer para colocarlos. Además de estos dos componentes, le añadiremos tres botones para recordar el uso de los slots; dos botones para modificar el tamaño de la letra del editor y un botón para terminar la aplicación. Una vez creado nuestro objeto, lo asignaremos como objeto principal de la aplicación para que sea mostrado al correr nuestro programa.

Este artículo será más una prueba de concepto que la realización de un programa con un fin útil. Para este artículo necesitaremos crear cuatro archivos para nuestra aplicación: kapp.pro, include/CWMiWidget.h, src/CWMiWidget.cpp y src/kapp.cpp. Acostumbro a anteponer al nombre del archivo una C para indicar que el archivo es una clase y CW para indicar que es una clase que produce un widget y, como habrán notado al leer otros artículos, no me gusta usar nombres con minúsculas solamente, ni tampoco uso Qt Designer para crear algún widget o alguna interfaz, por lo que si alguien prefiere hacer los widgets con este editor puede buscar en la web algún tutorial al respecto. El contenido de los archivos serían los siguientes:

Archivo CWMiWidget.h
#ifndef CWMIWIDGET_H #define CWMIWIDGET_H // Librerías Qt #include <qwidget.h> class KComboBox; class KIntSpinBox; class KTextBrowser; class QGridLayout; class CWMiWidget : public QWidget { Q_OBJECT public: CWMiWidget(QWidget *parent = 0, const char *name = 0); private: KComboBox *cmb; KIntSpinBox *sp; KTextBrowser *tb; QGridLayout *Layout; // Funciones privadas void CrearToolbar(void); private slots: void slotComboBox (int index); void slotSpinBox (int num); }; #endif
Archivo CWMiWidget.cpp
#include "CWMiWidget.h" // Librerías KDE #include <kcombobox.h> #include <kiconloader.h> #include <knuminput.h> #include <ktextbrowser.h> // Librerías Qt #include <qdockarea.h> #include <qlayout.h> #include <qtoolbar.h> #include <qtoolbutton.h> /****************************************************************************** * Constructor del Widget. * ******************************************************************************/ CWMiWidget::CWMiWidget(QWidget *parent, const char *name) : QWidget(parent, name, Qt::WDestructiveClose) { Layout = new QGridLayout(this, 2, 1, 5, 5, "CWMiWidget_Layout"); tb = new KTextBrowser(this, "CWMiWidget_tb"); CrearToolbar(); Layout->addWidget(tb, 1, 0); setGeometry(0,0,400,400); setCaption("CWMiWidget"); show(); } /****************************************************************************** * Crea y configura la barra de herramientas * ******************************************************************************/ void CWMiWidget::CrearToolbar(void) { // Creamos el DockArea y la barra de herramientas QDockArea *qda = new QDockArea(Qt::Horizontal, QDockArea::Normal, this, "CWMiWidget_qda"); QToolBar *qtb = new QToolBar ("ToolBar", NULL, qda, FALSE, "CWMiWidget_qtb"); // Creamos y añadimos los widgets a la barra de herramientas // Primero creamos el ComboBox cmb = new KComboBox(FALSE, qtb, "CWMiWidget_cmb"); // LLenamos con algo el ComboBox for (int i = 1; i < 11; i++) cmb->insertItem(QString::number(i)); // Ahora creamos el SpinBox sp = new KIntSpinBox(1, 10, 1, 1, 10, qtb, "CWMiWidget_sp"); // Creamos los tres botones y los añadimos a la barra de herramientas qtb->addSeparator(); new QToolButton(BarIcon("viewmag+.png"), "Aumentar tamaño de fuente", "", tb, SLOT(zoomIn()), qtb, "tlbZoomIn"); new QToolButton(BarIcon("viewmag-.png"), "Disminuir tamaño de fuente", "", tb, SLOT(zoomOut()), qtb, "tlbZoomOut"); qtb->addSeparator(); new QToolButton(BarIcon("exit.png"), "Cerrar ventana", "", this, SLOT(close()), qtb, "tlbZoomOut"); // Añadimos el DockArea con la barra de herramientas al Layout de CWMiWidget Layout->addWidget(qda, 0, 0); // Creamos las conexiones a los slots connect(cmb, SIGNAL(activated(int)), this, SLOT(slotComboBox(int))); connect(sp, SIGNAL(valueChanged(int)), this, SLOT(slotSpinBox(int))); } /****************************************************************************** * Cambia el texto según el item seleccionado en el combo box * ******************************************************************************/ void CWMiWidget::slotComboBox(int index) { tb->setText(QString("Seleccionado el item %1 en el ComboBox").arg(index + 1)); } /****************************************************************************** * Cambia el texto según el item seleccionado en el spin box * ******************************************************************************/ void CWMiWidget::slotSpinBox(int num) { tb->setText(QString("Seleccionado el item %1 en el SpinBox").arg(num)); }

En el código de este ejemplo no hay mucho que explicar pues ya algunas cosas las hemos visto; lo único nuevo interesante es el uso del DockArea. Este elemento nos permite ubicar componentes acoplables, como el caso de las barras de herramientas, en cualquier parte de nuestro widget personalizado. Si se arrastra la barra de herramientas fuera del área de acoplamiento, se observa como se convierte en una ventanita; si se hace doble clic sobre el título de la ventanita entonces se vuelve a acoplar al DockArea. Por medio de este elemento podemos ubicar la barra de herramientas en cualquier widget que lo necesite. Luego que creamos el DockArea, creamos la barra de herramientas pero teniendo como padre al DockArea. Esto tiene que ser así porque la clase QToolBar necesita saber el área donde será acoplado el objeto luego de ser creado. Ya teniendo la barra de herramientas vamos creando los objetos que queremos añadirle, poniéndoles como padre la barra de herramientas donde queremos que se inserten, en nuestro caso un KComboBox, un KIntSpinBox y tres QToolButton; una vez creados todos los componentes que van en la barra de herramientas, la añadimos al layout de nuestro widget. Es de notar que el orden de creación de los componentes altera el orden de visualización de los controles en la barra de herramientas. Por último, creamos las conexiones de los controles con los slots que recibirán las señales.

Nuestro archivo principal también hay que modificarlo pues ahora debemos instanciar un objeto de la clase CWMiWidget y asignarlo como objeto principal de nuestra aplicación.

Archivo kapp.cpp
#include "CWMiWidget.h" #include <kapp.h> int main( int argc, char **argv ) { KApplication kaplicacion(argc, argv, "kapp_CWMiWidget"); CWMiWidget *mw = new CWMiWidget(0, "CWMiWidget"); kaplicacion.setMainWidget(mw); return kaplicacion.exec(); }

El archivo de proyecto también debe ser modificado para incluir los archivos del objeto creado.

kapp.pro
# Opciones de compilación CONFIG = qt INCLUDEPATH = /usr/include/kde ./include LIBS = -lkdeui -L/usr/lib/ TEMPLATE = app TARGET = kaplicacion DEPENDPATH = . # Archivos con código fuente HEADERS += include/CWMiWidget.h SOURCES += src/CWMiWidget.cpp src/kapp.cpp # Directorios de destino DESTDIR = bin MOC_DIR = moc OBJECTS_DIR = obj

Una vez modificado se ejecuta en una cónsola:

qmake && make && bin/kaplicacion

Y tendremos nuestro programa funcionando y mostrando una apariencia similar a ésta:

Creando Nuestro Propio Widget

Aunque los puristas de la programación orientada a objetos dicen que si un objeto no va a ser instanciado más de una vez, entonces no es necesario el uso de una clase para ese objeto, en cuanto al uso de widgets me parece más útil encapsularlos en una clase para poder reusarlo en otro momento. Usando este enfoque podemos disponer de nuestro widget en cualquier otra aplicación. Por ejemplo, en esta imagen se muestra una aplicación que hace uso de dos pestañas y en cada pestaña se usa nuestro widget.

Creando Nuestro Propio Widget  Creando Nuestro Propio Widget

Esto fue hecho simplemente modificando el archivo principal para demostrar el uso de nuestro widget personalizado. El archivo principal quedaría así:

Archivo kapp.cpp
#include "CWMiWidget.h" // Encabezados KDE #include <kapp.h> #include <klocale.h> // Encabezados Qt #include <qtabwidget.h> int main( int argc, char **argv ) { KLocale::setMainCatalogue("kdelibs"); KApplication kaplicacion(argc, argv, "kapp_CWMiWidget"); QTabWidget *tab = new QTabWidget(0, "tab"); tab->addTab(new CWMiWidget(tab, "CWMiWidget_1"), "CWMiWidget #1"); tab->addTab(new CWMiWidget(tab, "CWMiWidget_2"), "CWMiWidget #2"); tab->show(); kaplicacion.setMainWidget(tab); return kaplicacion.exec(); }
Arriba

Creando Un StatusBar

Una barra de estado permite mostrar de manera disimulada información referente a nuestra aplicación. Si nuestro programa hace alguna actividad en segundo plano, el usuario puede ser notificado viendo la barra de estado; si el archivo ha sido modificado y debe ser guardado, la barra de estado puede indicarlo; en un editor de textos también puede mostrar información como la línea y la columna actual. En este artículo tomaremos el código desarrollado en Un Editor Sencillo De Documentos y le añadiremos la barra de estado para que muestre el nombre del archivo abierto por poner algo, y un widget de la clase KProgress que puede ser usado para mostrar el avance al cargar el archivo a editar (no se incluirá el código para esta función dejándoselo de tarea al lector); cualquier otra cosa la puede añadir el lector usando el mismo procedimiento que con la barra de progreso.

La barra de estado es manejada por la clase KStatusBar la cual corresponde a la biblioteca libkdeui. Si el widget principal de la aplicación es derivado de KMainWindow ya disponemos de una barra de estado automáticamente, a la cual tenemos acceso mediante el método statusbar() que devuelve un puntero a dicha barra perteneciente a la ventana principal. Si queremos usar otra barra u otro método, se debe ubicar de forma manual, por ejemplo usando un layout. En nuestro caso, como dijimos, tenemos un objeto principal derivado de KMainWindow, así que esta clase hará el trabajo de ubicarla por nosotros. El código de nuestra clase principal quedaría así:

Archivo KMiMainWindow.cpp
#include "KMiMainWindow.h" // Encabezados KDE #include <kaction.h> #include <kmenubar.h> #include <kpopupmenu.h> #include <kstdaction.h> #include <ktoolbar.h> #include <kfiledialog.h> #include <ktextedit.h> // Encabezados Qt #include <qfile.h> #include <qtextstream.h> // Encabezados añadidos #include <kprogress.h> #include <kstatusbar.h> /****************************************************************************** * Constructor de la clase. * ******************************************************************************/ KMiMainWindow::KMiMainWindow(QWidget *parent, const char *name) : KMainWindow(parent, name) { setGeometry(0, 0, 400, 400); setCaption("MiMainWindow"); CrearStatusBar(); CrearAcciones(); CrearMenu(); CrearToolbar(); txtEditor = new KTextEdit(this, "txtEditor"); setCentralWidget(txtEditor); } /****************************************************************************** * Procedimiento encargado de crear las acciones que usaremos en el programa * ******************************************************************************/ void KMiMainWindow::CrearAcciones(void) { actAbrir = KStdAction::open (this, SLOT(slotAbrir()), actionCollection()); actCopiar = KStdAction::copy (this, SLOT(slotCopiar()), actionCollection()); actCortar = KStdAction::cut (this, SLOT(slotCortar()), actionCollection()); actGuardar = KStdAction::save (this, SLOT(slotGuardar()), actionCollection()); actPegar = KStdAction::paste(this, SLOT(slotPegar()), actionCollection()); actSalir = KStdAction::quit (this, SLOT(slotSalir()), actionCollection()); actAbrir->setToolTip ("Abre un archivo en el editor"); actCopiar->setToolTip ("Copia el texto seleccionado al portapapeles"); actCortar->setToolTip ("Elimina el texto seleccionado y lo copia al portapapeles"); actGuardar->setToolTip("Guarda el texto actual en un archivo"); actPegar->setToolTip ("Pega el texto del portapapeles"); actSalir->setToolTip ("Termina el programa"); actionCollection()->setHighlightingEnabled(true); connect(actionCollection(), SIGNAL(actionStatusText(const QString &)), statusBar(), SLOT (message(const QString &))); connect(actionCollection(), SIGNAL(clearStatusText()), statusBar(), SLOT (clear())); } /****************************************************************************** * Procedimiento para crear el menú y añadir las acciones * ******************************************************************************/ void KMiMainWindow::CrearMenu(void) { mb = new KMenuBar(this, "MenuBar"); p = new KPopupMenu(this, "PopupArchivo"); actAbrir->plug(p); actGuardar->plug(p); p->insertSeparator(); actSalir->plug(p); mb->insertItem("&Archivo", p); p = new KPopupMenu(this, "PopupEdicion"); actCopiar->plug(p); actCortar->plug(p); actPegar->plug(p); mb->insertItem(QString::fromUtf8("&Edición"), p); } /****************************************************************************** * Procedimiento para crear la barra de herramientas y añadir las acciones. * ******************************************************************************/ void KMiMainWindow::CrearToolbar(void) { KToolBar *tb = new KToolBar(this, QMainWindow::Top, false, "Toolbar", true, false); actAbrir->plug(tb); actGuardar->plug(tb); tb->insertLineSeparator(); actCopiar->plug(tb); actCortar->plug(tb); actPegar->plug(tb); tb->insertLineSeparator(); actSalir->plug(tb); } /****************************************************************************** * Procedimiento para crear la barra de estado. * ******************************************************************************/ void KMiMainWindow::CrearStatusBar(void) { statusBar()->show(); pbar = new KProgress(this, "ProgressBar"); pbar->setFixedHeight(18); // Añadimos la etiqueta para indicar lo que hace el programa statusBar()->insertItem("Archivo en blanco", 0, 1); // Insertamos la barra de progreso en la barra de estado statusBar()->addWidget(pbar); statusBar()->setItemAlignment(0, Qt::AlignLeft | Qt::AlignVCenter); } /****************************************************************************** * SLOT para procesar la acción Abrir * ******************************************************************************/ void KMiMainWindow::slotAbrir(void) { // Primero obtenemos el nombre del archivo a abrir. Archivo = KFileDialog::getOpenFileName(); QFile ArchEntr(Archivo); // Lo abrimos en modo lectura if (ArchEntr.open(IO_ReadOnly)) { // Lo leemos QTextStream ts(&ArchEntr); // Llenamos el txtEditor con el contenido del archivo txtEditor->setText(ts.read()); statusBar()->changeItem(Archivo, 0); setCaption("MiMainWindow - " + Archivo); } } /****************************************************************************** * SLOT para procesar la acción Copiar * ******************************************************************************/ void KMiMainWindow::slotCopiar(void) { txtEditor->copy(); } /****************************************************************************** * SLOT para procesar la acción Cortar * ******************************************************************************/ void KMiMainWindow::slotCortar(void) { txtEditor->cut(); } /****************************************************************************** * SLOT para procesar la acción Guardar * ******************************************************************************/ void KMiMainWindow::slotGuardar(void) { // Abrimos el archivo en modo escritura QFile ArchSal(Archivo); if (ArchSal.open(IO_WriteOnly)) { // Asignamos el stream de salida al archivo QTextStream ts(&ArchSal); // Guardamos en el archivo el contenido de txtEditor ts << txtEditor->text(); } } /****************************************************************************** * SLOT para procesar la acción Pegar * ******************************************************************************/ void KMiMainWindow::slotPegar(void) { txtEditor->paste(); } /****************************************************************************** * SLOT para procesar la acción Salir * ******************************************************************************/ void KMiMainWindow::slotSalir(void) { close(); }
Archivo KMiMainWindow.h
#include <kmainwindow.h> class KAction; class KMenuBar; class KPopupMenu; class KProgress; class KTextEdit; class QString; class KMiMainWindow : public KMainWindow { Q_OBJECT public: KMiMainWindow(QWidget *parent = 0, const char *name = 0); private: // Variables privadas KAction *actAbrir; KAction *actCopiar; KAction *actCortar; KAction *actGuardar; KAction *actPegar; KAction *actSalir; KMenuBar *mb; KPopupMenu *p; KProgress *pbar; KTextEdit *txtEditor; QString Archivo; // Funciones Privadas void CrearAcciones(void); void CrearMenu(void); void CrearStatusBar(void); void CrearToolbar(void); private slots: void slotAbrir(void); void slotCopiar(void); void slotCortar(void); void slotGuardar(void); void slotPegar(void); void slotSalir(void); };

Esta vez hemos añadido el método CrearStatusBar() para manejar lo referente a la inicialización de la barra de estado. Durante la creación de las acciones, se conectan las señales emitidas por los KAction a través de actionCollection() con los slots de la barra de estado. Con ésto logramos que cada vez que se seleccione un menú y se resalte un item, se muestre en la barra de estado el tooltip asociado con el item resaltado. A continuación los pantallazos de nuestra aplicación recién creada.

Creando Un StatusBar  Creando Un StatusBar

Podemos observar en el primero cómo se muestra en la barra de estado el tooltip referente a la acción seleccionada Abrir, mientras que el segundo muestra el tooltip como tal al posicionar el mouse sobre el botón de la barra de herramientas que corresponde con la acción Abrir. Esta vez hemos usado un nuevo método llamado QString::fromUtf8() el cual permite mostar un texto escrito en UTF-8 independientemente de la configuración del sistema donde se ejecute nuestro programa, a partir de ahora todo nuestro código será escrito en formato UTF-8 por lo que haremos uso de esta función constantemente.