Search code examples
c++qtfocusqpainterqlineedit

Show QWidget as focused


I've got four QLineEdit placed inside of a QLineEdits, where I want the first the parent to look as if it is in focus when any of the containing ones is selected. Note: I don't want the focus to actually change, just the "focus frame" (the thin blue border) to appear on the parent LineEdit.

I've tried to draw a rect, but while it works on Windows I'm running into issues of the drawn rectangle not looking like a proper rectangle on ex. Linux, where it is supposed to be rounded. Is there a way to fix this OR, if possible, just make it draw itself as focused despite focus not being on it?

Here's my attempt at drawing a custom rect, but haven't been able to make it successfully mirror the OS style properly.

            if (childHasFocus) {
            QPainter painter(this);

            QLineEdit textBox;
            QColor color = textBox.palette().color(QPalette::Highlight);

            painter.setPen(color);
            QRect rect;
            rect.setTopLeft(QPoint(0,0));
            rect.setWidth(this->width() - 1);
            rect.setHeight(this->height() - 1);
            painter.drawRect(rect);
        }

EDIT: Added an image of the desired look. Note that I'm trying to get it to look like other LineEdits focusframe independent of OS, so hardcoding a blue rectangle won't work due to ex. Linux having a rounded focusframe.

Desired look:


Solution

  • Here's how to do it. Its a very basic class that draws the focus frame if any of the childs have focus. On focus change, we do an update (which can probably be optimized a bit to avoid unnecessary repaints).

    Screenshot:

    enter image description here

    class IPEdit : public QWidget
    {
    public:
        IPEdit(QWidget *parent = nullptr)
            : QWidget(parent)
        {
            delete layout();
            auto l = new QHBoxLayout(this);
            setFocusProxy(&a);
            setAttribute(Qt::WA_Hover);
            for (auto *w : {&a, &b, &c, &d}) {
                l->addWidget(w);
                w->installEventFilter(this);
            }
        }
    
        bool eventFilter(QObject *o, QEvent *e) override
        {
            if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) {
                update();
            }
            return QWidget::eventFilter(o, e);
        }
    
        void paintEvent(QPaintEvent *e) override
        {
            QStyleOptionFrame opt;
            opt.initFrom(this);
            opt.frameShape = QFrame::StyledPanel;
            opt.state |= QStyle::State_Sunken;
    
            // clear mouseOver and focus state
            // update from relevant widgets
            opt.state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver);
            const auto widgets = {&a, &b, &c, &d};
            for (const QWidget *w : widgets) {
                if (w->hasFocus()) {
                    opt.state |= QStyle::State_HasFocus;
                }
            }
    
            opt.rect = contentsRect();
            QPainter paint(this);
            paint.setClipRegion(e->region());
            paint.setRenderHints(QPainter::Antialiasing);
            style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this);
        }
    private:
        QLineEdit a;
        QLineEdit b;
        QLineEdit c;
        QLineEdit d;
    };