Empezando con Qt y QtCreator

Estoy haciendo un programa para el proyecto de fin de carrera (ahora mismo lo tengo por línea de comandos), y me gustaría crearle una interfaz gráfica sencilla.

Miré varias alternativas (GTK+, Qt, wxWidgets...) y al final he optado por Qt, ya que el sistema por clases me pareció a priori más ordenado y dado que mi proyecto está en C/C++ me facilitaba las cosas. Para el programa estaba usando Code::Blocks, pero para hacer la interfaz he optado por el QtCreator, que parece bastante más cómodo. Como no he tocado Qt hasta ahora de momento estoy leyendo y haciendo pruebas con "C++ GUI Programming with Qt 4" de Jasmin Blanchette y Mark Summerfield, y hasta ahora he conseguido diseñarme una ventana principal (directamente con código y luego con Qt Designer) y un par de cosas con signals y slots. Ahora, mi duda es: Si tengo una ventana principal y selecciono una opción del menú (por ejemplo que aplique un filtro en una imagen) quiero que aparezca otra ventana en la que se detallen los parámetros de dicho filtro y que tras aceptarlo se cierre esa ventana y se aplique.

¿Cómo puedo hacer eso? Si lo he entendido bien, debería crearme una clase que herede de QDialog e implementar ahí la ventana con sus parámetros etc, después crearía esa ventana como respuesta a un signal en mi clase principal y luego de alguna manera pasaría los parámetros que he personalizado en mi ventana de opciones al algoritmo correspondiente, que se encargaría de aplicar el filtro, cerraría la ventana de opciones y volvería a la principal a esperar nuevos eventos. El problema es que a nivel de código no se cómo hacerlo. Ahora mismo tengo mi ventana principal en mainwindow.cpp y mainwindow.h que se han generado automáticamente con QtCreator al hacer mi GUI (mainwindow.ui) con el QtDesigner, pero ahora no se como crear otra ui y añadirla al proyecto.

Para que se entienda mejor, en la imagen adjunta pongo mi ventana principal: Lo que quiero es que si pulso por ejemplo en "LSB Simple" me aparezca una ventana (que ahora mismo no tengo creada ni se cómo) donde pueda elegir algunos parámetros que necesito, y una vez que pulse en "Aceptar" cierre mi ventana de opciones y llame a una función que tengo ya implementada en una clase aparte que reciba una imagen y "x" parámetros y aplique un algoritmo. (Si se le pudiese poner una barra de progreso que he visto que hay sería ya perfecto).

Si alguien ha manejado Qt o mejor aún el QtCreator y me puede echar un cable (o enlazarme algún tutorial de QtCreator en español o inglés) se lo agradezco.

Adjuntos

Por ejemplo en python puedes hacerlo así:

def .....(self:)
...bla bla bla...
self.connect(self.button, QtCore.SIGNAL('clicked()'), self.showDialog)
self.connect(self.button, QtCore.SIGNAL('clicked()'), QtCore.SLOT('close()'))

def showDialog(self):
self.dialogodescargas=DialogoDescargas()
self.dialogodescargas.show()


Un saludo.
Gracias por la ayuda
No acabo de entender el código que has puesto, a ver :

def me supongo que es el equivalente a class en python, asi que en principio se define una clase llamada self (que supondo que heredará de QDialog o similar), se pones 2 connects para que al pulsar en un boton ejecute el metodo showDialog y se cierre (lo de que se cierre no estoy seguro, porque a priori no tiene porque ejecutarse antes el primer evento que el segundo no?

y showDialog() lo que creo que hace es abrir otra ventana, lo que no se es dónde y cómo se define esa ventana y si hay que usar varios ficheros y en ese caso cuales son los includes que habría que hacer. En el libro que estoy leyendo venía que lo correcto era un *.cpp y un *.h por clase para cada clase que se definiese. Pero no me llega a compilar.
La idea general es correcta, lo único que no me ha quedado claro es si quieres diseñar la ventana de diálogo gráficamente o si planeas hacerlo mediante código.

Voy a suponer lo primero, por lo tanto, debes crear un nuevo fichero en tu proyecto yendo a File, New... y eligiendo Qt Designer Form en el cuadro de diálogo que aparece. Hecho esto, eliges de nuevo qué tipo de diálogo quieres y por último le das un nombre (dialog.h en este ejemplo) y una ruta al fichero y terminas. Lo que sigue es añadir widgets al diálogo hasta conseguir lo que quieras.

Para utilizar ese diálogo luego desde c++ has de crear (por ejemplo) ajustes.h con estos contenidos:

//Documentación
#ifndef __AJUSTES_H__
#define __AJUSTES_H__

#include "ui_dialog.h"
//Resto de includes

class ajustes : public QDialog, private Ui::Dialog
{
Q_OBJECT
//Resto de detalles de la clase


Las partes importantes de lo anterior son
#include "ui_dialog.h" que te incluye el fichero de definición del diálogo que has creado visualmente. Este fichero lo crea automáticamente al compilar el proyecto, pero si tu proyecto está creado fuera de Qt Creator supongo que deberás añadir la forma de crearlo al Makefile o lo que uses para compilar. Ahora, en esto no me hagas mucho caso porque al automatizarse por el entorno no lo recuerdo bien, creo que para obtener el ui_dialog.h había que utilizar la herramienta moc de Qt.

class ajustes : public QDialog, private Ui::Dialog es lo que permite que tu clase herede de la ventana de diálogo que has creado y que puedas acceder a sus widgets. En Ui::Dialog, Dialog se corresponde con la propiedad objectName del QObject, la primera que se ve al seleccionar en el diálogo.

Luego tendrás que implementar los métodos de la clase en su cpp correspondiente, añadir un include a este dialog.h en las clases en las que quieras llamarlo y crear una instancia de la clase cuando se pinche en el menú. Por ejemplo:

fichero mainwindow.h
#include "dialog.h"
...
método de respuesta al click en el menú
{
...
ajustes1 = new Ajustes(this);
ajustes1->showDialog();
}


Ya te digo que, además de esta, hay otras formas de trabajar con un diseño .ui pero yo prefiero esta. Espero que te sea de ayuda.
Antes de nada, gracias por responder.

Sí, quiero diseñar la ventana gráficamente en la medida de lo posible, y estoy usando QtCreator (Versión 1.2.1)

Hasta ahora, para la prueba, tengo mi ventana principal diseñada en mainwindow.ui. Después tengo los ficheros mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui/QMainWindow>

namespace Ui
{
    class MainWindow;
    class Dialog;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

private slots:
   void on_action_Usando_el_asistente_triggered();
   void on_pushButton_3_clicked();
    void on_pushButton_2_clicked();
    void on_action_Iniciar_asistente_triggered();
    void on_actionAcerca_de_Hyde_triggered();
    void on_actionT_cnicas_de_estegoan_lisis_triggered();
    void on_actionExtrayendo_mensajes_triggered();
    void on_actionOcultando_mensajes_triggered();
    void on_action_ndice_triggered();
    void on_actionOcultar_bits_aleatorios_triggered();
    void on_actionAn_lisis_RS_triggered();
    void on_actionAn_lisis_de_Histograma_triggered();
    void on_actionAtaque_Chi_Cuadrado_triggered();
    void on_actionAtaque_Visual_triggered();
    void on_actionJSteg_2_triggered();
    void on_actionSSB_5_triggered();
    void on_actionEZ_Stego_2_triggered();
    void on_actionDistance_Sort_2_triggered();
    void on_actionLuminance_Sort_2_triggered();
    void on_actionLSB_Adaptativo_2_triggered();
    void on_actionLSB_Fuerte_2_triggered();
    void on_actionLSB_Simple_2_triggered();
    void on_actionJSteg_triggered();
    void on_actionSSB_4_triggered();
    void on_actionEZ_Stego_triggered();
    void on_actionDistance_Sort_triggered();
    void on_actionLuminance_Sort_triggered();
    void on_actionLSB_Fuerte_triggered();
    void on_actionLSB_Adaptativo_triggered();
    void on_actionLSB_Simple_triggered();
    void on_pushButton_clicked();
};

#endif // MAINWINDOW_H


Que me he supuesto que contendría la definicion de mi ventana principal, y mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "lsb_simple_params.h"
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    ui->textBrowser->clear();
}

void MainWindow::on_actionLSB_Simple_triggered()
{
    ui->textBrowser->append("Has seleccionado esconder un mensaje usando LSB Simple");
}

void MainWindow::on_actionLSB_Adaptativo_triggered()
{
    ui->textBrowser->append("Has seleccionado esconder un mensaje usando LSB Adaptativo");
}

void MainWindow::on_actionLSB_Fuerte_triggered()
{
    ui->textBrowser->append("Has seleccionado esconder un mensaje usando LSB Fuerte");
}

void MainWindow::on_actionLuminance_Sort_triggered()
{
    ui->textBrowser->append("Has seleccionado esconder un mensaje usando Luminance Sort");
}

void MainWindow::on_actionDistance_Sort_triggered()
{
    ui->textBrowser->append("Has seleccionado esconder un mensaje usando Distance Sort");
}

void MainWindow::on_actionEZ_Stego_triggered()
{
    ui->textBrowser->append("Has seleccionado esconder un mensaje usando Ez-Stego");
}

void MainWindow::on_actionSSB_4_triggered()
{
    ui->textBrowser->append("Has seleccionado esconder un mensaje usando SSB-4");
}

void MainWindow::on_actionJSteg_triggered()
{
    ui->textBrowser->append("Has seleccionado esconder un mensaje usando JSteg");
}

void MainWindow::on_actionLSB_Simple_2_triggered()
{
    ui->textBrowser->append("Has seleccionado extraer un mensaje usando LSB Simple");
}

void MainWindow::on_actionLSB_Fuerte_2_triggered()
{
    ui->textBrowser->append("Has seleccionado extraer un mensaje usando LSB Fuerte");
}

void MainWindow::on_actionLSB_Adaptativo_2_triggered()
{
    ui->textBrowser->append("Has seleccionado extraer un mensaje usando LSB Adaptativo");
}

void MainWindow::on_actionLuminance_Sort_2_triggered()
{
    ui->textBrowser->append("Has seleccionado extraer un mensaje usando Luminance Sort");
}

void MainWindow::on_actionDistance_Sort_2_triggered()
{
    ui->textBrowser->append("Has seleccionado extraer un mensaje usando Distance Sort");
}

void MainWindow::on_actionEZ_Stego_2_triggered()
{
    ui->textBrowser->append("Has seleccionado extraer un mensaje usando EZ-Stego");
}

void MainWindow::on_actionSSB_5_triggered()
{
    ui->textBrowser->append("Has seleccionado extraer un mensaje usando SSB-4");
}

void MainWindow::on_actionJSteg_2_triggered()
{
    ui->textBrowser->append("Has seleccionado extraer un mensaje usando JSteg");
}

void MainWindow::on_actionAtaque_Visual_triggered()
{
    ui->textBrowser->append("Has seleccionado realizar un ataque visual a la imagen");
}

void MainWindow::on_actionAtaque_Chi_Cuadrado_triggered()
{
    ui->textBrowser->append("Has seleccionado realizar un ataque Chi-Cuadrado a la imagen");
}

void MainWindow::on_actionAn_lisis_de_Histograma_triggered()
{
    ui->textBrowser->append("Has seleccionado realizar un análisis de histograma a la imagen");
}

void MainWindow::on_actionAn_lisis_RS_triggered()
{
    ui->textBrowser->append("Has seleccionado realizar un análisis RS a la imagen");
}

void MainWindow::on_actionOcultar_bits_aleatorios_triggered()
{
    ui->textBrowser->append("Has seleccionado rellenar los LSB de la imagen con bits aleatorios");
}

void MainWindow::on_action_ndice_triggered()
{
    ui->textBrowser->append("Has seleccionado el índice de la ayuda");
}

void MainWindow::on_actionOcultando_mensajes_triggered()
{
    ui->textBrowser->append("Has seleccionado capítulo \"Ocultar mensajes\" de la ayuda");
}

void MainWindow::on_actionExtrayendo_mensajes_triggered()
{
    ui->textBrowser->append("Has seleccionado capítulo \"Extraer mensajes\" de la ayuda");
}

void MainWindow::on_actionT_cnicas_de_estegoan_lisis_triggered()
{
    ui->textBrowser->append("Has seleccionado capítulo \"Técnicas de estegoanálisis\" de la ayuda");
}

void MainWindow::on_actionAcerca_de_Hyde_triggered()
{
    ui->textBrowser->append("Hyde. Un programa de esteganografía y estegoanálsis en imágenes digitales");
    QMessageBox msgBox;
    msgBox.setWindowTitle("Acerca de Hyde");
    msgBox.setText("Hyde. Un programa de esteganografía y estegoanálsis en imágenes digitales");
    msgBox.exec();
}

void MainWindow::on_action_Iniciar_asistente_triggered()
{
    ui->textBrowser->append("Has seleccionado el asistente");
}

void MainWindow::on_pushButton_2_clicked()
{
    ui->textBrowser->append("Has seleccionado limpiar la imagen");
}

void MainWindow::on_pushButton_3_clicked()
{
    ui->textBrowser->append("Has seleccionado cargar una imagen");
}

void MainWindow::on_action_Usando_el_asistente_triggered()
{
   ui->textBrowser->append("Has seleccionado capítulo \"Usando el asistente\" de la ayuda");
}


Que contiene lo que hago en los para cada señal (por ahora lo único que he hecho es que me muestre un mensaje un un textBrowser que tengo a modo de consola).

Por último tengo un main.cpp:

#include <QtGui/QApplication>
#include <QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;

    w.show();
    return a.exec();
}


Que lo único que hace es crear la ventana principal, mostrarla y esperar algún evento.

Ahora quería hacer un QDialog (por ejemplo) de manera que cuando pulse en una opción de mi ventana principal, se muestre el diálogo (que es donde voy a recoger algunos parámetros para un algoritmo), y quiero colocar un botón aceptar en dicho diálogo, para que cuanto lo pulse se cierre esa ventana, se ejecute el algoritmo, y vuelva a la pantalla principal (mainwindow.ui).

Así que le he dado a archivo/nuevo/clase del formulario de Qt Designer/Dialog without buttons con los nombres de diálogo y clase por defecto (clase Dialog, encabezado dialog.h, fuentes dialog.cpp formulario dialog.ui y ruta la de mi proyecto), dejo marcada la opción de agregar al proyecto y empiezo a diseñar mi ventana con el Qt Designer. Al acabar compilo y sin problemas.

Ahora, mis archivos dialog.h y dialog.cpp están así:

#ifndef DIALOG_H
#define DIALOG_H

#include <QtGui/QDialog>

namespace Ui {
    class Dialog;
}

class Dialog : public QDialog {
    Q_OBJECT
public:
    Dialog(QWidget *parent = 0);
    ~Dialog();

protected:
    void changeEvent(QEvent *e);

private:
    Ui::Dialog *m_ui;
};

#endif // DIALOG_H


#include "dialog.h"
#include "ui_dialog.h"

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    m_ui(new Ui::Dialog)
{
    m_ui->setupUi(this);
}

Dialog::~Dialog()
{
    delete m_ui;
}

void Dialog::changeEvent(QEvent *e)
{
    QDialog::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        m_ui->retranslateUi(this);
        break;
    default:
        break;
    }
}


Y aquí es cuando me pierdo: Se supone que esos archivos son equivalentes al "ajustes.h" que me has comentado? porque el dialog.h sólo incluye <QtGui/QDialog>, pero dialog.cpp sí que incluye ui_dialog.h.

He puesto en uno de los eventos de mi mainwindow.cpp:
void MainWindow::on_actionLSB_Simple_triggered()
{
    ui->textBrowser->append("Has seleccionado esconder un mensaje usando LSB Simple");
   Dialog* ajustes1;
   ajustes1 = new Dialog(this);
   ajustes1->show();
}


Y parece que funciona sin problemas (he cambiado el método showDialog por show a secas, que supongo que será lo mismo pero el showDialog no lo encontraba. Pero quiero saber si lo he hecho bien o me he dejado algo.

EDITO:

Pues algo me he tenido que dejar o hacer mal, porque si quiero editar algún slot (como uso Qt Designer marco con el ratón el widget que quiero y con click derecho selecciono la opción "Go to slot...") me da un mensaje de error "La definición de la clase Ui::Dialog no pudo ser encontrada en ......./dialog.cpp", con lo cual no puedo crear slots. He intentado crear otro Dialog limpio (sin tocar nada) y me da el mismo error así que supongo que tengo que añadir algo a dialog.cpp, pero ya tiene un #include "ui_dialog.h" Alguna idea?

EDITO 2:
Pues misteriosamente al cerrar y volver a abrir QtCreator me funcionan perfectamente los slots XD. Ahora lo que estoy intentando hacer es que al cerrar el Dialog me muestre un mensaje en la ventana principal, pero de momento esto:

void MainWindow::on_actionLSB_Simple_triggered()
{
    ui->textBrowser->append("Has seleccionado esconder un mensaje usando LSB Simple");
   Dialog* ajustes1;
   ajustes1 = new Dialog(this);
   ajustes1->show();
   ui->textBrowser->append("Has ejecutado el algoritmo LSB Simple");
}


No me funciona como quiero, porque muestra la ventana e inmediatamente muestra el mensaje, cuando lo que yo quiero es que no muestre el mensaje hasta que yo pulse en el botón "aceptar" del Dialog. Supongo que tendré que mirar como hacer que mi ventana principal responda a un evento de la otra ventana.
Los def en python son funciones, y en mi ejemplo se encuentran en un class. He identado el bloque para la explicación, pero el foro me los ha quitado :(

Creo que Gooler podrá ayudarte mejor en esto que yo, ya que yo solo lo he experimentado en Python. Bueno, mucha suerte ante todo ;-)

Un saludo.
Creo que tienes que llamar a showModal() o algo así, en C++Builder de Borland había que usar ShowModal(); al llamar a un Dialog, eso hace que el programa se pare (pierda el foco) y espere a que salgas del Dialog (como al llamar a Herramientas - Opciones de muchos programas).

En cambio el Show() es para abrir nuevas ventanas, pero todas normales y siguen funcionando (como tener varias imágenes abiertas en un programa de edición gráfica).

Edito: En vez de usar show();, usa exec(); eso hace que el programa se quede parado mientras ejecutas la ventana Dialog.

if (ajustes1->exec() == true) // sólo se ejecuta si pulsas un botón aceptar (sale con slot accept(); o algo así)
ui->textBrowser->append("Has ejecutado el algoritmo LSB Simple");
Saludos.
Con exec() funciona, gracias. Se atenúa la ventana principal y cuando le doy a aceptar y cierro el dialog me imprime la frase.

Ya que estamos, me gustaría que la ventana principal se bloquease, es decir, que no pueda hacer click en ninguna de las opciones, botones, etc de mi ventana principal mientras está el dialog abierto. Ahora mismo y usando ajustes1->exec(); puedo seguir tocando opciones de la ventana principal.

capitanquartz, gracias también. Para que la próxima vez te mantenga la indentación del código ponlo en un bloque [code][/code] :)
Korso, lo que buscas es hacer que tu ventana sea modal ( http://doc.qtsoftware.com/4.5/qdialog.h ... al-dialogs ), pero me descoloca que puedas interactuar con el padre porque exec() lanza el diálogo como modal por defecto: http://doc.qtsoftware.com/4.5/qdialog.html#exec

Lo que comentas es propio del comportamiento de show()...
Vale, ahora lo he pillado y era fallo mío, ya que en mi código, primero llamaba a show() y acto seguido a exec(), quitando show ya hace lo que tiene que hacer.

Y ya que estoy una última duda: A la hora de usar mi programa para que funcione en cualquier pc me pide varias librerías dll (estoy trabajando en windows). Necesito copiarlas a la misma carpeta del ejecutable o se puede crear uno con todo lo necesario incluído en el exe? Y a la hora de compilarlo para linux?
Estupendo, algo raro había :D

Respecto a las dlls, sí, van en la carpeta en la que tengas el ejecutable (http://doc.trolltech.com/4.5/deployment-windows.html ). Para GNU/Linux, como no hay un modo "estandar" de distribuir aplicaciones tendrías que crear un paquete con las dependencias correspondientes para la distribución para la que quieras preparar la aplicación.

Y no hace falta que sea la última pregunta, cualquier duda que te surja coméntala que además de ayudarte a tí puede ayudar a otros :D.
Pues me lo estoy leyendo y a ver si lo consigo, parece que hay que hacer más cosas aparte de meter las dll en la misma carpeta. Lo de la última pregunta era porque tengo que dejar "aparcado" el proyecto hasta después de los exámenes de septiembre para estudiarlos, así que cuando lo retome ya volveré a preguntar. :)
En Windows yo programaba antes con Borland C++ Builder y el sistema era decirle al compilador las dll que tenía que añadir al EXE, así el exe podía medir 50 Kb sin librerías o 300 Kb con ellas (depende del tamaño de tu código y las librerías a usar).

Pero en GNU/Linux lo correcto, si usas librerías GPL o similar y éstas se pueden encontrar en los repositorios, es que el instalador sea un paquete .DEB o .RPM y tenga información de las librerías que necesita (sus dependencias). Este sistema es mucho mejor, porque así se evita duplicar el espacio de usar una librería por varios programas distintos.

Si optas por añadir las dll a tu ejecutable, supongo que habrá que añadirlas al llamar al make (qmake en linux, en windows no sé porque no lo tengo ya).

Yo no puedo ayudarte más porque no he realizado todavía ningún programa con QtCreator, simplemente estoy dándole un vistazo para pasarme a programar a GNU/Linux definitivamente.
Una pregunta:

Si quiero añadir librerías de C++ adicionales (como tinyXML, por ejemplo) ¿Como lo hago en Windows y en Linux? Será editar el qmake o añadirlo mediante alguna opción por ahí, que no veo.
Es que en casa usaré el Qt Creator en Linux, pero en el curro lo tengo en Windows.

Gracias.

EDITO:

Creo que he encontrado una forma de añadir librerías, pero antes estoy comprobando que el qt creator me compila bien los proyectos.
He creado un proyecto en Windows y al compilar me sale este error:

Ejecutando los pasos para construir el proyecto Pruebas_001...
Iniciando: C:/Qt/2009.03/qt/bin/qmake.exe C:/Programacion/Proyectos/Pruebas_Qt_Creator/Pruebas_001/Pruebas_001.pro -spec win32-g++ -r
Error processing project file: C:/Programacion/Proyectos/Pruebas_Qt_Creator/Pruebas_001/Pruebas_001.pro
Finalizado retornando 3.
Error durante la construcción del proyecto Pruebas_001
Mientras se ejecutaba la etapa 'QMake'


¿Es algún problema de QMake? ¿Sabéis como solucionarlo?
14 respuestas