c++ qt

c++ - ¿Cómo se acoplan realmente las señales/ranuras Qt a los elementos en un archivo.ui?



(2)

Mi pregunta es: ¿por qué y cómo? ¿Cómo se acoplan exactamente mis acciones en Qt Designer (un programa que genera un XML para el uic) a mi QObject en Qt Creator? Más específicamente, ¿cómo sabe a qué QObject agregar esta ranura?

La respuesta corta es: magia.

La respuesta real es que, en realidad, no tiene nada que ver con sus acciones en Designer, al menos no directamente. En realidad, ni siquiera es una cosa específica de la interfaz de usuario, es solo que el diseñador hace un buen uso de ella. Lo que pasa es esto:

En primer lugar, cada vez que compile un archivo que haga uso de la macro Q_OBJECT , esto activa la herramienta Q_OBJECT Qt para que se ejecute durante la compilación (los "disparadores" no son realmente la descripción más precisa: más precisamente, cuando se ejecuta qmake para generar archivos make). , agrega reglas de compilación para ejecutar Q_OBJECT cuando se detecta Q_OBJECT en un encabezado, pero eso no Q_OBJECT al Q_OBJECT ). Los detalles están un poco fuera del alcance de esta respuesta, pero para moc_*.cpp última instancia, se generan esos archivos moc_*.cpp que verá después de la compilación, que contienen una gran cantidad de información meta compilada. La parte importante de esto es que genera información accesible en tiempo de ejecución sobre las diversas señales y ranuras que ha declarado. Verás cómo eso es importante aquí abajo.

La otra cosa importante es que todos los QObject s tienen un nombre . Este nombre es accesible en tiempo de ejecución. El nombre que le da a sus widgets en el diseñador se establece como el nombre de objeto del widget. La razón por la que esto es importante también se aclarará a continuación.

Y lo último importante es que los QObject tienen una estructura jerárquica; cuando creas un QObject puedes establecer su padre. Todos los widgets en su formulario finalmente tienen el formulario en sí (por ejemplo, su clase derivada de QMainWindow ) como su padre.

OK entonces...

Notará que en el código fuente que Qt genera para sus ventanas, siempre tiene esa ui->setupUi(this) en el constructor. La implementación de esa función es generada por la herramienta uic de Qt durante la compilación, por ejemplo, en ui_mainwindow.h . Esa función es donde realmente se configuran todas las propiedades que configura en el diseñador, almacenadas en el archivo .ui, incluidos los nombres de objetos de los widgets. Ahora, la última línea de la implementación de setupUI() generada es la clave:

void setupUi(QMainWindow *MainWindow) { ... // Object properties, including names, are set here. This is // generated from the .ui file. For example: pushButton = new QPushButton(centralWidget); pushButton->setObjectName(QString::fromUtf8("pushButton")); pushButton->setGeometry(QRect(110, 70, 75, 23)); ... // This is the important part for the signal/slot binding: QMetaObject::connectSlotsByName(MainWindow); }

Esa función, connectSlotsByName , es donde ocurre la magia para todas esas señales de widgets.

Básicamente esa función hace lo siguiente:

  1. Iterar a través de todos sus nombres de ranura declarados, lo que puede hacer porque todo estaba empaquetado en cadenas accesibles en tiempo de ejecución por moc en la información del objeto meta.
  2. Cuando encuentra una ranura cuyo nombre coincide con el patrón on_<objectname>_<signalname> , busca recursivamente la jerarquía de objetos para un objeto cuyo nombre es <objectname> , por ejemplo, uno de sus widgets con nombre, o cualquier otro objeto con nombre que pueda tener creado. Luego conecta la señal de los objetos, <signalname> a la ranura que encontró (básicamente, automatiza las llamadas a QObject::connect(...) ).

Y eso es.

Entonces, lo que esto significa es que, en realidad, no pasa nada especial en el diseñador cuando vas a una ranura allí. Todo lo que sucede es que el diseñador inserta una ranura denominada on_widgetName_signalName, con los parámetros apropiados, en su encabezado y genera un cuerpo de función vacío para él. (Thuga ha añadido una gran explicación de esto). Esto es suficiente para que QMetaObject::connectSlotsByName encuentre en el tiempo de ejecución y configure la conexión.

Las implicaciones aquí son muy convenientes:

  • Puede eliminar el controlador de señal de un widget sin hacer nada especial en el diseñador. Simplemente elimine la ranura y su definición de su archivo fuente, todo es completamente autónomo.
  • Puede agregar manejadores de señales de widgets a mano sin tener que pasar por la interfaz del diseñador, lo que puede ahorrarle algo de tedio a veces. Todo lo que tienes que hacer es crear una ranura en tu ventana llamada eg on_lineEdit1_returnPressed() y voila, funciona. Solo debe tener cuidado, como nos recuerda Hyde en un comentario: si comete un error aquí, no obtendrá un error en tiempo de compilación, solo obtendrá una advertencia en tiempo de ejecución impresa en stderr y la conexión no ser creado; por lo que es importante prestar atención a la salida de la consola si realiza cambios aquí.

La otra implicación aquí es que esta funcionalidad no está realmente limitada a los componentes de la interfaz de usuario. moc ejecuta para todos sus QObject s, y connectSlotsByName siempre está disponible. Por lo tanto, cualquier jerarquía de objetos que cree, si nombra los objetos y luego declara los espacios con los nombres adecuados, puede llamar a connectSlotsByName para configurar las conexiones automáticamente; da la casualidad de que uic pega esa llamada al final de setupUi , porque es un lugar conveniente para ponerlo. Pero la magia no es específica de la interfaz de usuario, y no se basa en uic , solo en la información meta. Es un mecanismo generalmente disponible. Es bastante limpio

Como QMetaObject::connectSlotsByName aparte: puse un ejemplo rápido de QMetaObject::connectSlotsByName en GitHub .

Actualmente estoy trabajando en un proyecto Qt y estoy algo confundido con el mecanismo de señales y ranuras. Sin embargo, siento que tengo un buen entendimiento entre las diferencias entre un formulario QObject y la interfaz de usuario.

Un formulario de interfaz de usuario (descrito por un archivo .ui) se alimenta en el compilador de interfaz de usuario (uic) y produce un archivo de encabezado asociado. Este archivo de encabezado no solo contiene información de la interfaz, sino que, en su lugar, contiene los detalles de la implementación en un QObject que se debe formatear. Un QObject, por otro lado, es la clase base en la que se construye gran parte del marco Qt. Las señales y los sistemas de ranura se basan completamente en el QObject.

Al extender la clase QObject (o de una clase derivada), en realidad está definiendo un objeto en el que puede producir señales y ranuras. Para formatear este objeto para que se parezca a la interfaz de usuario que acaba de diseñar en Qt Designer, cree una instancia de la clase ui (a través del encabezado ui generado por el uic). Llamas al método de configuración en esta clase y le das el nuevo QObject que estás creando.

Todo esto parece tener sentido.

Sin embargo, lo que no tiene sentido es cuando comienzas a definir señales. Desde con Qt Designer, tengo la opción de hacer clic derecho y seleccionar una ''señal''. Cuando selecciono una señal (ya sea un clic con el mouse en un botón o un cambio en una barra de progreso), termina agregando eso a la sección de señales de mis QObjects. Mi pregunta es: ¿por qué y cómo? ¿Cómo se acoplan exactamente mis acciones en Qt Designer (un programa que genera un XML para el uic) a mi QObject en Qt Creator? Más específicamente, ¿cómo sabe a qué QObject agregar esta ranura?

Esta pregunta puede ser algo confusa, así que daré un ejemplo aquí.

Digamos, por ejemplo, que quiero crear una nueva pantalla interactiva de algún tipo. Llamémoslo ''MyScreen''. Para crear esta interfaz, es probable que tenga 3 archivos: ''myscreen.h'', ''myscreen.cpp'' y ''myscreen.ui''. ''myscreen.h'' es responsable de declarar las propiedades y métodos de QObjects, así como las señales y ranuras. ''myscreen.cpp'' es responsable de definir los métodos, las señales y las ranuras. ''myscreen.ui'' es repsonible para crear el diseño de la interfaz de usuario que se usará para formatear una instancia de MyScreen.

Pero debido a lo que dije anteriormente, diciendo que la interfaz de usuario solo se usa para generar un encabezado, ¿por qué exactamente está acoplada a ''myscreen.cpp''? Es decir, puedo hacer clic con el botón derecho en el diseño .ui, crear un nuevo tipo de señal y ese tipo de señal se agregará a myscreen.h y myscreen.cpp. ¿Como sucedió esto? ¿Cómo se acopla? ¿Funciona Qt de modo que siempre debería haber 3 archivos (xxx.h, xxx.cpp, xxx.ui) que deberían existir?

Así que espero que eso te dé un poco de contexto en lo que estoy confundido. No parece haber un documento bien escrito (que haya encontrado al menos), que describa a fondo la relación subyacente entre todos estos elementos.

TLDR: ¿Cómo se enlaza la señal / ranura de .h y .cpp a los elementos definidos en el archivo .ui?


Lo que sucede cuando agrega una ranura desde el elemento del menú contextual Go to slot... es que pasa por su proyecto y busca cualquier archivo que incluya ui_myclass.h . Cuando encuentra este archivo, luego se asegura de que el objeto Ui::MyClass se declare en ese archivo o en los archivos incluidos directamente. Estos serían tus archivos myclass.cpp y myclass.h . Si encuentra estos archivos, agregará la declaración de ranura y la definición en esos archivos.

Puede probar esto eliminando la declaración Ui::MyClass object ( usualmente llamada ui ) de su archivo myclass.h , y luego agregando una ranura usando la función Go to slot... en el diseñador. Debería recibir un mensaje de error que indica que no pudo encontrar la declaración del objeto Ui::MyClass . También puede intentar eliminar la inclusión de myclass.cpp su archivo myclass.cpp . Si intenta agregar una ranura del diseñador ahora, recibirá un mensaje de error que indica que no se pudo encontrar ui_myclass.h incluido en ninguna parte, o algo así.

Si define todo dentro de su archivo myclass.h y elimina myclass.cpp , debería myclass.cpp un mensaje de error que indica que no se puede agregar la definición de la ranura.

Puede inspeccionar cómo funciona con más detalle mirando esta parte del código fuente .

Pero al final, lo que realmente importa es que declare y defina sus métodos de clase en archivos .h y .cpp separados, y que ui_xxx.h esté incluido y que el objeto Ui::xxx haya sido declarado en esos archivos. Por supuesto, esto es lo que Qt Creator hace por usted automáticamente cuando agrega una nueva clase de formulario, por lo que no tiene que preocuparse por eso.