Wiki

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

Автор: 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.