Эргономичность в вопросах и ответах

Автор: Jasmin Blanchette
Перевод: Andi Peredri

Эта статья дает ответы на наиболее часто задаваемые вопросы, касающиеся внешнего вида Qt-виджетов. В частности, мы рассмотрим, как изменить внешний вид стандартных Qt-виджетов с помощью наследования от QStyle и как использовать фильтры событий для рисования в контексте другого виджета.

Неофициальный перевод статьи Look 'n' Feel Q & A выполнен с любезного разрешения Trolltech.

Мне бы хотелось изменить способ отображения "горячих" клавиш в меню. Например, для элемента меню выполнения обратного поиска Find Again показать сочетание клавиш Ctrl+Shift+G как [Shift]Ctrl+G. Можно ли как-то изменить способ отображения "горячих" клавиш в выпадающем меню?

Для указания своего текста в элементах меню вы должны использовать символ табуляции ('\t'). Например:

    editMenu->insertItem(tr("F&ind Again\t[Shift]Ctrl+G"));

Если вы используете QAction, то должны написать так:

    findAgainAct->setMenuText(tr("F&ind Again\t[Shift]Ctrl+G"));

Menu

Основная идея реализации задаваемых таким образом "горячих" клавиш заключается в том, что вместо использования классов QAccel или QAction вы должны переопределить метод QWidget::keyEvent().

Я использую Qt на устройстве с сенсорным экраном. Как изменить ширину вертикальных полос прокрутки и высоту горизонтальных полос прокрутки, чтобы стало удобнее пользоваться виджетом QListBox?

Попробуйте воспользоваться функцией QApplication::setGlobalStrut(). Она задает минимальный размер QSize для всех GUI-элементов. Например, после следующего вызова:

    QApplication::setGlobalStrut(QSize(30, 30));

ширина ваших вертикальных полос прокрутки и высота горизонтальных полос прокрутки станут равны, как минимум, 30 пикселам. Также вызов этой функции приведет к изменению размеров других GUI-элементов: кнопок, строк меню и элементов списка.
Scrollview1   Scrollview2

Если вам необходимо изменить размеры лишь элементов QScrollBar, то создайте свой стиль и переопределите метод QStyle::pixelMetric() таким образом, чтобы для параметра QStyle::PM_ScrollBarExtent возвращалось значение 30.

Я попытался изменить внешний вид горизонтального заголовка виджета QTable. Для этого я наследовал от QHeader и переопределил метод paintSection(). Как мне теперь сообщить QTable, чтобы для заголовка он использовал мой класс вместо стандартного QHeader?

К сожалению, в настоящей версии Qt QTable не может быть использован с другими подклассами QHeader. Однако есть два обходных пути:

1. Вы можете создать свой собственный стиль, например, на базе QWindowsStyle, и переопределить метод QStyle::drawPrimitive() следующим образом:

    void MyStyle::drawPrimitive(PrimitiveElement element,
            QPainter *painter, const QRect &rect,
            const QColorGroup &colorGroup, SFlags flags,
            const QStyleOption &opt) const
    {
        if (element == PE_HeaderSection) {
            // код отрисовки
        } else
            QWindowsStyle::drawPrimitive(element, painter, rect, colorGroup, flags, opt);
    }

2. Чтобы самостоятельно отрисовывать горизонтальный заголовок QHeader, вы можете переопределить фильтр событий вашего подкласса QTable:

    bool MyTable::eventFilter(QObject *targetObj, QEvent *event)
    {
        if (targetObj == (QObject *)horizontalHeader() && 
	    event->type() == QEvent::Paint) 
	{
            QPainter painter(horizontalHeader());
            // код отрисовки
            return true;
        } else
            return QTable::eventFilter(targetObj, event);
    }

Возможно, более лучшее решение появится в Qt 4.

Как изменить ширину разделителя QSplitter?

В Qt 3.2 и более поздних версиях вы можете использовать QSplitter::setHandleWidth(). В ранних версиях Qt вам необходимо создать свой собственный стиль и переопределить метод pixelMetric():

    int MyStyle::pixelMetric(PixelMetric metric, const QWidget *widget) const
    {
        if (metric == PM_SplitterWidth) {
            return 6;
        } else {
            return QWindowsStyle::pixelMetric(metric, widget);
        }
    }

Наше приложение поддерживает платформы Windows, Linux, Solaris и Mac OS X, и при этом использует стили, свойственные каждой из этих систем. Однако мы хотели бы немного изменить внешний вид виджета QTabWidget и сделать его одинаковым на всех платформах. Как это можно сделать без наследования от всех стандартных классов стилей (QWindowsStyle, QMotifStyle и т.д.) ?

Это зависит от того, что именно вы хотите изменить. Одним из предложенных Qt-разработчиками решений является использование класса ProxyStyle, унаследованного непосредственно от Style и перенаправляющего вызовы виртуальных функций на соответствующие вызовы стандартных классов стилей:

    class ProxyStyle : public QStyle
    {
    public:
        ProxyStyle(const QString &baseStyle) 
	    { style = QStyleFactory::create(baseStyle); }

        void polish(QWidget *w)   { style->polish(w); }
        void unPolish(QWidget *w) { style->unPolish(w); }
        int pixelMetric(PixelMetric metric, QWidget *widget) const
            { return style->pixelMetric(metric, widget); }
        ...
    
    private:
        QStyle *style;
    };

Затем достаточно наследовать ProxyStyle и реализовать класс с желаемым поведением:

    class MyStyle : public ProxyStyle
    {
    public:
        MyStyle(const QString &baseStyle);
    
        int pixelMetric(PixelMetric metric, const QWidget *widget) const;
    };
    
    int MyStyle::pixelMetric(PixelMetric metric, const QWidget *widget) const
    {
        if (metric == PM_SplitterWidth)
            return 6;
        return ProxyStyle::pixelMetric(metric, widget);
    }

Впоследствии этот класс используется следующим образом:

    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
        app.setStyle(new MyStyle(app.style().name()));
        ...
        return app.exec();
    }

Благодаря прокси-технике, необходимое поведение реализуется лишь в одном классе MyStyle. Далее объект этого класса используется вместо стандартных стилей, и все нереализованные в MyStyle методы подменяются соответствующими методами из стандартных стилей.

Styles

Таким образом, прокси-техника в некоторой степени компенсирует ограничения языка: в C++ можно наследовать только от классов, а не от объектов. Подробнее прокси-классы рассмотрены в Design Patterns (ISBN 0-201-63361-2).

При использовании прокси-классов нужно помнить об одной тонкости. Некоторые виртуальные функции стандартных стилей Qt реализованы посредством других виртуальных функций, например, метод QWindowsStyle::drawComplexControl() для отрисовки стрелочек виджета QComboBox вызывает метод QWindowsStyle::drawPrimitive(). Если в классе MyStyle (который унаследован от ProxyStyle) вы переопределите метод drawPrimitive(), то он будет проигнорирован в классе QWindowsStyle, так как последний будет использовать свою функцию drawPrimitive(). (Это объясняется тем, что MyStyle не наследует QWindowsStyle, а лишь включает его.) В зависимости от того, какой именно виджет вы захотите изменить, вам также может понадобиться переопределить метод drawComplexControl().

У меня проблема со стилем Windows XP. При настройке палитры и выборе зеленого цвета для компоненты Button не происходит смена цвета кнопки QPushButton. Но в то же время смена цвета осуществляется корректно после смены текущей темы Windows на "Windows Classic". Как это исправить? Это ошибка в стиле?

Стиль Windows XP в Qt использует Windows-механизм тем. Для отрисовки кнопок и других компонентов тема XP использует изображения, игнорируя при этом цвета палитры. Такое поведение "зашито" в код Windows XP, и Qt здесь бессильна. (В Mac OS X наблюдается такая же проблема со стилем Mac.)
Button2   Button1

Если вам действительно нужна кнопка зеленого цвета, вы всегда можете сменить ее стиль на QWindowsStyle с помощью QWidget::setStyle(). Хотя при этом она будет отличаться от других кнопок в вашем приложении.

Я использую Qt/Mac, и хотел бы поинтересоваться, в чем отличия между стилями QAquaStyle и QMacStyle?

Впервые стиль QAquaStyle появился в Qt 3.0, когда стала поддерживаться платформа Qt/Mac. Он эмулировал Apple-тему "Aqua". В Qt 3.1 ему на смену пришел стиль QMacStyle, который стал использовать для отрисовки Appearance Manager. Сейчас стиль QAquaStyle поставляется вместе с Qt/Mac лишь для совместимости и может быть удален в новых версиях Qt.