Son muchos los momentos en los que es necesario cambiar detalles específicos de nuestra interfaz gráfica en Qt (usualmente la apariencia de un control). Qt ofrece muchas opciones al respecto:
- Usar las propiedades del widget. Desafortunadamente no siempre están expuestas todos los parámetros de renderizado del control.
- Heredar el control y especiar sus métodos de dibujado. Normalmente es la más complicada de las opciones.
- Heredar y exponer las variables protegidas. Acá dos problemas: primero, que al no formar parte del API es más fácil que en futuras versiones de Qt se rompa la compilación; segundo, que muchos de los controles usan pimpl para ocultar los detalles internos.
- Usar hojas de estilo (stylesheets): habitualmente es mi opción preferida, aunque tiene sus limitaciones: la interacción entre la hoja de estilos y el estado actual de la aplicación es limitado, y si usamos un
QStyle
puede que haya elementos no soportados. Hablaré más sobre las hojas de estilo en Qt en otro momento. - Crear un estilo propio (
QStyle
), pero que, para cambios puntuales, es normalmente bastante trabajoso. - Usar un
QProxyStyle
, que trae un poco lo mejor de varios mundos y que es el centro de este artículo.
Como mencioné antes, soy muy amigo de las hojas de estilo, pero hay momentos en los cuales simplemente no es posible realizar ciertos cambios con ellas. Es cuando los QProxyStyle
entran en acción.
Un clase que herede QProxyStyle
simplemente tiene que indicar un estilo base y reimplementar los métodos que necesite, y el proxy se encargará de redirigir todos los demás al estilo padre. Si bien es necesario saber qué parte del estilo reimplementar, no es difícil conseguir esa información desde la documentación de los estilos (estos ejemplos están muy bien para empezar) o incluso examinando el código fuente ([https://code.qt.io/cgit/qt/]) para casos más extremos.
Ejemplo: QMessageBox
con icono personalizado
El siguiente código muestra un caso de uso: cambiar el icono de los QMessageBox
(usaremos los warning para simplificar el ejemplo). Así luce un message box con el estilo por defecto (en Windows 10) y con el estilo fusion:
Comencemos con la definición del proxy:
class MyProxyStyle : public QProxyStyle {
public:
explicit MyProxyStyle(const QString& name)
: QProxyStyle(name), // definimos acá el estilo padre
m_warning_icon(":/resources/warning.png") {
}
public:
virtual QIcon standardIcon(StandardPixmap standard_icon,
const QStyleOption* option,
const QWidget* widget) const override {
switch (standard_icon) { // cambiamos sólo el icono del Warning MB
case SP_MessageBoxWarning: return m_warning_icon;
}
// Solicitamos el comportamiento por defecto
return QProxyStyle::standardIcon(standard_icon, option, widget);
}
private:
QIcon m_warning_icon; // guardamos el icono en caché para no tener que cargarlo cada vez
};
A continuación, simplemente fijamos el estilo de nuestra aplicación en algún momento antes de usar los message boxes:
int main(int argc, char* argv[]) {
QApplication a(argc, argv);
// ...
qApp->setStyle(new MyProxyStyle("fusion")); // usamos "fusion" como estilo padre
// ...
}
¡Listo! A partir de este momento todos los QMessageBox::warning(...)
mostrarán nuestro icono en lugar del por defecto.
El proyecto completo (VS) de este ejemplo está disponible en GitHub.
Otro ejemplo: quitar el cuadro de foco de todos los controles
Para esto necesitaremos reimplementar QProxyStyle::drawPrimitive
y evitar el dibujado de los elementos PE_FrameFocusRect
:
void MyProxyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption* option,
QPainter* painter, const QWidget* widget) const
{
if (element != PE_FrameFocusRect) { // dibujamos cualquier cosa que no sea el foco
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
}
Como se ve, los proxy de estilo son técnicas sencillas de extender la forma en la que se renderiza el interfaz gráfico en Qt.