Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QComboBox and QPushButton in a grid layout do not line up properly in macOS

QComboBox and QPushButton in a grid layout do not line up properly in macOS

Scheduled Pinned Locked Moved Solved General and Desktop
mac-osgridlayoutbuttoncombobox
15 Posts 7 Posters 6.5k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • G Offline
    G Offline
    Guy Gizmo
    wrote on 30 Jan 2018, 03:15 last edited by
    #1

    I'm working on a Qt 5.9.1 app in macOS 10.13.2. I have an issue in one of my windows where a QPushButton and a QComboBox are in the same column of a grid layout, but their left sides do not line up properly. Here is a screen shot:

    Picture of widgets not lining up properly

    It's a subtle issue, but the left side of the button is one pixel too far to the right.

    I try very hard to make my interfaces professional looking, which includes having the widgets line up nicely. However I don't know what I can do to address this. If I use a style sheet on either widget to try and adjust its margins then they will no longer render as standard macOS UI elements.

    Why is this happening? And what can I do? Any help is much appreciated!

    1 Reply Last reply
    0
    • S Offline
      S Offline
      sanojsubran
      wrote on 30 Jan 2018, 03:54 last edited by
      #2

      Hi,

      Have you tried the same with QVBoxLayout and QHBoxLayout? I have always felt much control over the layout items when I use those rather than grid layout.

      Regards,
      San

      1 Reply Last reply
      0
      • G Offline
        G Offline
        Guy Gizmo
        wrote on 30 Jan 2018, 04:02 last edited by
        #3

        The screenshot only shows a small portion of my UI. There are more controls not pictured that require me to use a grid layout so that everything stays in proper alignment.

        If necessary I could just a combination of many nested horizontal and vertical layouts to accomplish the same thing, but it would be a lot of work.

        I'm more curious why Qt doesn't automatically line up these two widgets properly, when nearly every other widget does line up. Perhaps this is a Qt bug?

        1 Reply Last reply
        0
        • M Offline
          M Offline
          Mr Aloof
          wrote on 30 Jan 2018, 04:43 last edited by
          #4

          i cannot understand what you say, can you draw me a picture to show the correct layout what you want.
          i want to help you.

          1 Reply Last reply
          0
          • S Offline
            S Offline
            SGaist
            Lifetime Qt Champion
            wrote on 30 Jan 2018, 08:24 last edited by
            #5

            Hi,

            Are you using a QGridLayout ?

            Did you already took a look at QFromLayout which seems to be more in line with the UI you are showing ?

            Interested in AI ? www.idiap.ch
            Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

            1 Reply Last reply
            0
            • G Offline
              G Offline
              Guy Gizmo
              wrote on 30 Jan 2018, 15:50 last edited by
              #6

              To all: here is another screenshot that further demonstrates the extent of this problem, plus why it is necessary for my to use QGridLayout:

              0_1517327056357_Screen Shot 2018-01-30 at 10.41.59 AM.png

              First, note that I have several different UI elements whose left and/or right sides are aligned throughout various columns. In order to maintain a good and clean look for this UI, it is important that I use a QGridLayout, as otherwise these elements would not be aligned. Furthermore, you can see how using a QFormLayout does not work.

              And it's not just QPushButton that's out of alignment. In order for these elements to be properly aligned given the spacing I supplied the QGridLayout, the following adjustments need to be made:

              1. QComboBoxes need to be moved two pixels to the right
              2. QCheckBoxes need to be moved one pixel to the right.

              That only applies on a retina display, though. On a non-retina display, the QComboBoxes need to be moved one pixel to the right.

              I'm thinking this is a Qt bug and that I'll need to make a bug report, but if there's any good way to work around this in the meantime, I'd love to hear it.

              M 1 Reply Last reply 31 Jan 2018, 05:13
              0
              • M Offline
                M Offline
                mrjj
                Lifetime Qt Champion
                wrote on 30 Jan 2018, 16:20 last edited by
                #7

                Hi
                Its not a bug. Its just drawn that way.
                alt text

                Both are PushButtons but red one, is filling whole area with custom paintEvent.
                So its just visually they are off. The actual clientrect is not.

                G 1 Reply Last reply 30 Jan 2018, 17:04
                0
                • M mrjj
                  30 Jan 2018, 16:20

                  Hi
                  Its not a bug. Its just drawn that way.
                  alt text

                  Both are PushButtons but red one, is filling whole area with custom paintEvent.
                  So its just visually they are off. The actual clientrect is not.

                  G Offline
                  G Offline
                  Guy Gizmo
                  wrote on 30 Jan 2018, 17:04 last edited by
                  #8

                  @mrjj Well I would say that it's not so much that the bug is in QGridLayout but rather the drawing of these widgets. QGridLayout is indeed laying out everything in proper alignment. But the widgets themselves are not drawing in proper alignment.

                  Ordinarily I could fix this easily using a style sheet but just adding something like a pixel or two of negative margins to shift the element over. But as soon as you apply a style sheet to these widgets in macOS, they stop rendering as standard macOS elements are revert to Qt's default style that looks more like elements in X11 from fifteen years ago.

                  M 1 Reply Last reply 30 Jan 2018, 17:26
                  0
                  • G Guy Gizmo
                    30 Jan 2018, 17:04

                    @mrjj Well I would say that it's not so much that the bug is in QGridLayout but rather the drawing of these widgets. QGridLayout is indeed laying out everything in proper alignment. But the widgets themselves are not drawing in proper alignment.

                    Ordinarily I could fix this easily using a style sheet but just adding something like a pixel or two of negative margins to shift the element over. But as soon as you apply a style sheet to these widgets in macOS, they stop rendering as standard macOS elements are revert to Qt's default style that looks more like elements in X11 from fifteen years ago.

                    M Offline
                    M Offline
                    mrjj
                    Lifetime Qt Champion
                    wrote on 30 Jan 2018, 17:26 last edited by mrjj
                    #9

                    @Guy-Gizmo
                    Hi
                    I am assuming its the same as in windows and macOS that the QStyle
                    used subtract the pixel before drawing the buttons/checkbox.

                    Yeah, sadly is stylesheet all or nothing but all is not lost yet its only 2 widgets that you seems to have this issue with and you could do

                    #ifndef FLATBUT_H
                    #define FLATBUT_H
                    
                    #include <QPainter>
                    #include <QPushButton>
                    #include <QStyleOptionButton>
                    #include <QStylePainter>
                    
                    class Flatbut : public QPushButton { // sorry about the name :)
                      Q_OBJECT
                    public:
                      explicit Flatbut(QWidget* parent = nullptr) : QPushButton(parent) {}
                    protected:
                      virtual void paintEvent(QPaintEvent* event) override {
                        QStylePainter p(this);
                        QStyleOptionButton option;
                        initStyleOption(&option);
                        option.rect = rect().adjusted(-1, 0, -1, 0);
                        p.drawControl(QStyle::CE_PushButton, option);
                      }
                    };
                    #endif // FLATBUT_H
                    

                    And make it draw how you want.
                    alt text
                    (top one is the red one from last picture with a new custom paint (stolen from QPushbutton))

                    The downside is that you must promote all your buttons and checkboxes to apply the fix.
                    There might be a way via QProxyStyle if the QStyle has a setting for it. (the offset)( didnt check yet)

                    1 Reply Last reply
                    0
                    • M Offline
                      M Offline
                      mrjj
                      Lifetime Qt Champion
                      wrote on 30 Jan 2018, 18:26 last edited by
                      #10

                      Ah
                      Found out why it reservers the pixels.
                      Its for the "default" feature and if you change the offset, it breaks visually.
                      alt text
                      So if you use this feature, customPaint might be a no go.

                      1 Reply Last reply
                      0
                      • G Guy Gizmo
                        30 Jan 2018, 15:50

                        To all: here is another screenshot that further demonstrates the extent of this problem, plus why it is necessary for my to use QGridLayout:

                        0_1517327056357_Screen Shot 2018-01-30 at 10.41.59 AM.png

                        First, note that I have several different UI elements whose left and/or right sides are aligned throughout various columns. In order to maintain a good and clean look for this UI, it is important that I use a QGridLayout, as otherwise these elements would not be aligned. Furthermore, you can see how using a QFormLayout does not work.

                        And it's not just QPushButton that's out of alignment. In order for these elements to be properly aligned given the spacing I supplied the QGridLayout, the following adjustments need to be made:

                        1. QComboBoxes need to be moved two pixels to the right
                        2. QCheckBoxes need to be moved one pixel to the right.

                        That only applies on a retina display, though. On a non-retina display, the QComboBoxes need to be moved one pixel to the right.

                        I'm thinking this is a Qt bug and that I'll need to make a bug report, but if there's any good way to work around this in the meantime, I'd love to hear it.

                        M Offline
                        M Offline
                        Mr Aloof
                        wrote on 31 Jan 2018, 05:13 last edited by
                        #11

                        @Guy-Gizmo

                        i think the reason is that, some pixel is transparent.

                        for exzample:

                        a button , which the size is 90x90, may be when it display at mac os , have only 86x86.
                        in the top\left\right\buttom have 2 pixel which display nothing.

                        so the only method to solved this problem is use custom stylesheet.(or send a bug to digia).
                        do not use spacer or margin to fix it, because maybe in the different version of mac os , you could saw different graphics result.

                        J.HilkJ 1 Reply Last reply 31 Jan 2018, 06:05
                        0
                        • M Mr Aloof
                          31 Jan 2018, 05:13

                          @Guy-Gizmo

                          i think the reason is that, some pixel is transparent.

                          for exzample:

                          a button , which the size is 90x90, may be when it display at mac os , have only 86x86.
                          in the top\left\right\buttom have 2 pixel which display nothing.

                          so the only method to solved this problem is use custom stylesheet.(or send a bug to digia).
                          do not use spacer or margin to fix it, because maybe in the different version of mac os , you could saw different graphics result.

                          J.HilkJ Offline
                          J.HilkJ Offline
                          J.Hilk
                          Moderators
                          wrote on 31 Jan 2018, 06:05 last edited by
                          #12

                          @Mr-Aloof I believe, this may be due to the Focus-Rectangle that MacOs wants to apply to QPushButtons.
                          IIRC the F-Rectangle turns the border (1px wide) blue and extends 1 px out too

                          You could use a QToolButton and see if it changes anything.


                          Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


                          Q: What's that?
                          A: It's blue light.
                          Q: What does it do?
                          A: It turns blue.

                          1 Reply Last reply
                          0
                          • G Offline
                            G Offline
                            Guy Gizmo
                            wrote on 2 Feb 2018, 00:13 last edited by
                            #13

                            Okay, after lots of work and a fair amount of hair pulling, I finally got all of my widgets lining up properly in a QGridLayout.

                            @mrjj, I essentially used the solution you proposed, but with a few tweaks to improve the rendering of widgets beyond just their alignment. Fortunately in macOS, unlike Windows (and probably Linux too) there is lots of margins around various macOS widgets, meaning there's no issue with rendering them one or two pixels to the side of where they normally would be. So this solution won't work in anything other than macOS, but as far as I know, the problem doesn't exist anywhere other than macOS!

                            So for posterity and such, here is the code I wrote to fix the issue:

                            // QtAdjustedWidgets.h
                            
                            #include <QComboBox>
                            #include <QPushButton>
                            #include <QRadioButton>
                            #include <QLineEdit>
                            #include <QSpinBox.h>
                            #include <QDoubleSpinBox.h>
                            #include <QPixmap>
                            #include <QMarginsF>
                            
                            class AdjustedWidgetHelper {
                            public:
                                AdjustedWidgetHelper() : pixmap(nullptr) {};
                                ~AdjustedWidgetHelper();
                                QRectF calculateDrawRect(QWidget *widget, const QMarginsF &nonRetinaAdjustment, const QMarginsF &retinaAdjustment);
                                QPixmap * setupPixmap(QRectF drawRect, int devicePixelRatio);
                            protected:
                                QPixmap *pixmap;
                            };
                            
                            class QtAdjustedComboBox : public QComboBox {
                                Q_OBJECT
                            public:
                                explicit QtAdjustedComboBox(QWidget *parent = nullptr) : QComboBox(parent) {};
                            protected:
                                void paintEvent(QPaintEvent *);
                                AdjustedWidgetHelper helper;
                                static QMarginsF nonRetinaAdjustment;
                                static QMarginsF retinaAdjustment;
                            };
                            
                            class QtAdjustedPushButton : public QPushButton {
                                Q_OBJECT
                            public:
                                explicit QtAdjustedPushButton(QWidget *parent = nullptr) : QPushButton(parent) {};
                            protected:
                                void paintEvent(QPaintEvent *);
                                AdjustedWidgetHelper helper;
                                static QMarginsF nonRetinaAdjustment;
                                static QMarginsF retinaAdjustment;
                            };
                            
                            class QtAdjustedRadioButton : public QRadioButton {
                                Q_OBJECT
                            public:
                                explicit QtAdjustedRadioButton(QWidget *parent = nullptr) : QRadioButton(parent) {};
                            protected:
                                void paintEvent(QPaintEvent *);
                                static QMarginsF nonRetinaAdjustment;
                                static QMarginsF retinaAdjustment;
                            };
                            
                            class QtAdjustedLineEdit : public QLineEdit {
                                Q_OBJECT
                            public:
                                explicit QtAdjustedLineEdit(QWidget *parent = nullptr) : QLineEdit(parent) {};
                            protected:
                                void paintEvent(QPaintEvent *);
                                AdjustedWidgetHelper helper;
                                static QMarginsF nonRetinaAdjustment;
                                static QMarginsF retinaAdjustment;
                            };
                            
                            class QtAdjustedSpinBox : public QSpinBox {
                                Q_OBJECT
                            public:
                                explicit QtAdjustedSpinBox(QWidget *parent = nullptr) : QSpinBox(parent) {};
                            protected:
                                void paintEvent(QPaintEvent *);
                                AdjustedWidgetHelper helper;
                                static QMarginsF nonRetinaAdjustment;
                                static QMarginsF retinaAdjustment;
                            };
                            
                            class QtAdjustedDoubleSpinBox : public QDoubleSpinBox {
                                Q_OBJECT
                            public:
                                explicit QtAdjustedDoubleSpinBox(QWidget *parent = nullptr) : QDoubleSpinBox(parent) {};
                            protected:
                                void paintEvent(QPaintEvent *);
                                AdjustedWidgetHelper helper;
                                static QMarginsF nonRetinaAdjustment;
                                static QMarginsF retinaAdjustment;
                            };
                            
                            // QtAdjustedWidgets.cpp
                            #include <QPainter>
                            #include <QStylePainter>
                            #include "QtAdjustedWidgets.h"
                            
                            QMarginsF QtAdjustedComboBox::nonRetinaAdjustment(-1.0, 0.0, 0.0, 0.0);
                            QMarginsF QtAdjustedComboBox::retinaAdjustment(-0.5, 0.0, 0.5, 0.0);
                            
                            QMarginsF QtAdjustedPushButton::nonRetinaAdjustment(0.0, 0.0, 0.0, 0.0);
                            QMarginsF QtAdjustedPushButton::retinaAdjustment(0.5, 0.0, 0.5, 0.0);
                            
                            QMarginsF QtAdjustedRadioButton::nonRetinaAdjustment(-1.0, 0.0, 1.0, 0.0);
                            QMarginsF QtAdjustedRadioButton::retinaAdjustment(-1.0, 0.0, 1.0, 0.0);
                            
                            QMarginsF QtAdjustedLineEdit::nonRetinaAdjustment(0.0, 0.0, 0.0, 0.0);
                            QMarginsF QtAdjustedLineEdit::retinaAdjustment(-1.5, 0.0, -1.5, 0.0);
                            
                            QMarginsF QtAdjustedSpinBox::nonRetinaAdjustment(1.0, 0.0, 1.0, 0.0);
                            QMarginsF QtAdjustedSpinBox::retinaAdjustment(1.5, 0.0, 1.5, 0.0);
                            
                            QMarginsF QtAdjustedDoubleSpinBox::nonRetinaAdjustment(1.0, 0.0, 1.0, 0.0);
                            QMarginsF QtAdjustedDoubleSpinBox::retinaAdjustment(1.5, 0.0, 1.5, 0.0);
                            
                            AdjustedWidgetHelper::~AdjustedWidgetHelper()
                            {
                                if (pixmap) {
                                    delete pixmap;
                                }
                            }
                            
                            QRectF AdjustedWidgetHelper::calculateDrawRect(QWidget *widget, const QMarginsF &nonRetinaAdjustment, const QMarginsF &retinaAdjustment)
                            {
                                if (widget->devicePixelRatio() == 2) {
                                    return QRectF(widget->rect()) + retinaAdjustment;
                                } else {
                                    return QRectF(widget->rect()) + nonRetinaAdjustment;
                                }
                            }
                            
                            QPixmap * AdjustedWidgetHelper::setupPixmap(QRectF drawRect, int devicePixelRatio)
                            {
                                QSize pixmapSize(drawRect.width() * devicePixelRatio, drawRect.height() * devicePixelRatio);
                                
                                if (!pixmap || pixmap->size() != pixmapSize || pixmap->devicePixelRatio() != devicePixelRatio) {
                                    if (pixmap) {
                                        delete pixmap;
                                    }
                                    
                                    pixmap = new QPixmap(pixmapSize);
                                    pixmap->setDevicePixelRatio(devicePixelRatio);
                                }
                                
                                pixmap->fill(Qt::transparent);
                                
                                return pixmap;
                            }
                            
                            void QtAdjustedComboBox::paintEvent(QPaintEvent *)
                            {
                                QRectF drawRect = helper.calculateDrawRect(this, nonRetinaAdjustment, retinaAdjustment);
                                QPixmap *pixmap = helper.setupPixmap(drawRect, devicePixelRatio());
                                QPainter painter(this);
                                QStylePainter stylePainter(pixmap, this);
                                QStyleOptionComboBox options;
                                
                                initStyleOption(&options);
                                options.rect = QRect(0, 0, drawRect.width(), drawRect.height());
                                stylePainter.setPen(palette().color(QPalette::Text));
                                stylePainter.drawComplexControl(QStyle::CC_ComboBox, options);
                                stylePainter.drawControl(QStyle::CE_ComboBoxLabel, options);
                                
                                painter.drawPixmap(drawRect, *pixmap, QRectF(pixmap->rect()));
                            }
                            
                            void QtAdjustedPushButton::paintEvent(QPaintEvent *)
                            {
                                QRectF drawRect = helper.calculateDrawRect(this, nonRetinaAdjustment, retinaAdjustment);
                                QPixmap *pixmap = helper.setupPixmap(drawRect, devicePixelRatio());
                                QPainter painter(this);
                                QStylePainter stylePainter(pixmap, this);
                                QStyleOptionButton options;
                                
                                initStyleOption(&options);
                                options.rect = QRect(0, 0, drawRect.width(), drawRect.height());
                                stylePainter.setPen(palette().color(QPalette::Text));
                                stylePainter.drawControl(QStyle::CE_PushButton, options);
                                
                                painter.drawPixmap(drawRect, *pixmap, QRectF(pixmap->rect()));
                            }
                            
                            void QtAdjustedRadioButton::paintEvent(QPaintEvent *)
                            {
                                // For some odd reason, stylePainter does not render the label of QRadioButton correctly when
                                // rendering into a QPixmap -- the font is slightly too heavy. Fortunately, we don't need to
                                // adjust it by a single pixel on a retina display so we don't need the QPixmap.
                                QStylePainter stylePainter(this);
                                QStyleOptionButton options;
                                initStyleOption(&options);
                                
                                if (devicePixelRatio() == 2) {
                                    options.rect = QRectF(options.rect).marginsAdded(retinaAdjustment).toRect();
                                } else {
                                    options.rect = QRectF(options.rect).marginsAdded(nonRetinaAdjustment).toRect();
                                }
                                
                                stylePainter.drawControl(QStyle::CE_RadioButton, options);
                            }
                            
                            void QtAdjustedLineEdit::paintEvent(QPaintEvent *event)
                            {
                                QLineEdit::paintEvent(event);
                                
                                // Qt renders the lighter rectangle on the outside of QLineEdit's frame, so in that case
                                // we'll render our own frame with the right colors.
                                if (devicePixelRatio() == 2) {
                                    QPainter painter(this);
                                    
                                    painter.setPen(QPen(QBrush(QColor(177, 177, 177)), 0.5));
                                    painter.setBrush(Qt::transparent);
                                    painter.drawRect(QRectF(rect()).adjusted(0, 0, -0.5, -0.5));
                                    
                                    painter.setPen(QPen(QBrush(QColor(240, 240, 240)), 0.5));
                                    painter.drawRect(QRectF(rect()).adjusted(0.5, 0.5, -1, -1));
                                }
                            }
                            
                            void QtAdjustedSpinBox::paintEvent(QPaintEvent *)
                            {
                                QRectF drawRect = helper.calculateDrawRect(this, nonRetinaAdjustment, retinaAdjustment);
                                QPixmap *pixmap = helper.setupPixmap(drawRect, devicePixelRatio());
                                QPainter painter(this);
                                QStylePainter stylePainter(pixmap, this);
                                QStyleOptionSpinBox options;
                                
                                initStyleOption(&options);
                                options.rect = QRect(0, 0, drawRect.width(), drawRect.height());
                                
                                // Qt leaves a gap between the frame a line edit control when rendering a spin box, so we'll
                                // fill it in with its background color:
                                QRectF fieldRect = QRectF(style()->subControlRect(QStyle::CC_SpinBox, &options, QStyle::SC_SpinBoxEditField)).adjusted(-1, -1, 1, 1);
                                stylePainter.setPen(palette().color(QPalette::Base));
                                stylePainter.setBrush(palette().color(QPalette::Base));
                                stylePainter.drawRect(fieldRect);
                                
                                stylePainter.drawComplexControl(QStyle::CC_SpinBox, options);
                                painter.drawPixmap(drawRect, *pixmap, QRectF(pixmap->rect()));
                            }
                            
                            void QtAdjustedDoubleSpinBox::paintEvent(QPaintEvent *)
                            {
                                QRectF drawRect = helper.calculateDrawRect(this, nonRetinaAdjustment, retinaAdjustment);
                                QPixmap *pixmap = helper.setupPixmap(drawRect, devicePixelRatio());
                                QPainter painter(this);
                                QStylePainter stylePainter(pixmap, this);
                                QStyleOptionSpinBox options;
                                
                                initStyleOption(&options);
                                options.rect = QRect(0, 0, drawRect.width(), drawRect.height());
                                
                                // Qt leaves a gap between the frame a line edit control when rendering a spin box, so we'll
                                // fill it in with its background color:
                                QRectF fieldRect = QRectF(style()->subControlRect(QStyle::CC_SpinBox, &options, QStyle::SC_SpinBoxEditField)).adjusted(-1, -1, 1, 1);
                                stylePainter.setPen(palette().color(QPalette::Base));
                                stylePainter.setBrush(palette().color(QPalette::Base));
                                stylePainter.drawRect(fieldRect);
                                
                                stylePainter.drawComplexControl(QStyle::CC_SpinBox, options);
                                painter.drawPixmap(drawRect, *pixmap, QRectF(pixmap->rect()));
                            }
                            
                            

                            Admittedly I'm not the biggest fan of how Qt often requires you to subclass everything in order to get customized behavior, and I tried a number of different alternative approaches, including something that could be applied automatically to any set of widgets. But in the end, subclassing was the best way to go.

                            In case anyone is wondering why the objects render to QPixmaps, the reason is because some of the widgets required being moved over a fraction of a point, which would be one pixel on a retina display. When using QRect as opposed to QRectF, there's no way to accomplish this and is consequently a limitation of QStylePainter / QStyleOption. The workaround is to render into a QPixmap with a device pixel ratio of 2 and then compositing it into the widget at the correct retina pixel coordinates.

                            M 1 Reply Last reply 2 Feb 2018, 01:15
                            0
                            • G Guy Gizmo
                              2 Feb 2018, 00:13

                              Okay, after lots of work and a fair amount of hair pulling, I finally got all of my widgets lining up properly in a QGridLayout.

                              @mrjj, I essentially used the solution you proposed, but with a few tweaks to improve the rendering of widgets beyond just their alignment. Fortunately in macOS, unlike Windows (and probably Linux too) there is lots of margins around various macOS widgets, meaning there's no issue with rendering them one or two pixels to the side of where they normally would be. So this solution won't work in anything other than macOS, but as far as I know, the problem doesn't exist anywhere other than macOS!

                              So for posterity and such, here is the code I wrote to fix the issue:

                              // QtAdjustedWidgets.h
                              
                              #include <QComboBox>
                              #include <QPushButton>
                              #include <QRadioButton>
                              #include <QLineEdit>
                              #include <QSpinBox.h>
                              #include <QDoubleSpinBox.h>
                              #include <QPixmap>
                              #include <QMarginsF>
                              
                              class AdjustedWidgetHelper {
                              public:
                                  AdjustedWidgetHelper() : pixmap(nullptr) {};
                                  ~AdjustedWidgetHelper();
                                  QRectF calculateDrawRect(QWidget *widget, const QMarginsF &nonRetinaAdjustment, const QMarginsF &retinaAdjustment);
                                  QPixmap * setupPixmap(QRectF drawRect, int devicePixelRatio);
                              protected:
                                  QPixmap *pixmap;
                              };
                              
                              class QtAdjustedComboBox : public QComboBox {
                                  Q_OBJECT
                              public:
                                  explicit QtAdjustedComboBox(QWidget *parent = nullptr) : QComboBox(parent) {};
                              protected:
                                  void paintEvent(QPaintEvent *);
                                  AdjustedWidgetHelper helper;
                                  static QMarginsF nonRetinaAdjustment;
                                  static QMarginsF retinaAdjustment;
                              };
                              
                              class QtAdjustedPushButton : public QPushButton {
                                  Q_OBJECT
                              public:
                                  explicit QtAdjustedPushButton(QWidget *parent = nullptr) : QPushButton(parent) {};
                              protected:
                                  void paintEvent(QPaintEvent *);
                                  AdjustedWidgetHelper helper;
                                  static QMarginsF nonRetinaAdjustment;
                                  static QMarginsF retinaAdjustment;
                              };
                              
                              class QtAdjustedRadioButton : public QRadioButton {
                                  Q_OBJECT
                              public:
                                  explicit QtAdjustedRadioButton(QWidget *parent = nullptr) : QRadioButton(parent) {};
                              protected:
                                  void paintEvent(QPaintEvent *);
                                  static QMarginsF nonRetinaAdjustment;
                                  static QMarginsF retinaAdjustment;
                              };
                              
                              class QtAdjustedLineEdit : public QLineEdit {
                                  Q_OBJECT
                              public:
                                  explicit QtAdjustedLineEdit(QWidget *parent = nullptr) : QLineEdit(parent) {};
                              protected:
                                  void paintEvent(QPaintEvent *);
                                  AdjustedWidgetHelper helper;
                                  static QMarginsF nonRetinaAdjustment;
                                  static QMarginsF retinaAdjustment;
                              };
                              
                              class QtAdjustedSpinBox : public QSpinBox {
                                  Q_OBJECT
                              public:
                                  explicit QtAdjustedSpinBox(QWidget *parent = nullptr) : QSpinBox(parent) {};
                              protected:
                                  void paintEvent(QPaintEvent *);
                                  AdjustedWidgetHelper helper;
                                  static QMarginsF nonRetinaAdjustment;
                                  static QMarginsF retinaAdjustment;
                              };
                              
                              class QtAdjustedDoubleSpinBox : public QDoubleSpinBox {
                                  Q_OBJECT
                              public:
                                  explicit QtAdjustedDoubleSpinBox(QWidget *parent = nullptr) : QDoubleSpinBox(parent) {};
                              protected:
                                  void paintEvent(QPaintEvent *);
                                  AdjustedWidgetHelper helper;
                                  static QMarginsF nonRetinaAdjustment;
                                  static QMarginsF retinaAdjustment;
                              };
                              
                              // QtAdjustedWidgets.cpp
                              #include <QPainter>
                              #include <QStylePainter>
                              #include "QtAdjustedWidgets.h"
                              
                              QMarginsF QtAdjustedComboBox::nonRetinaAdjustment(-1.0, 0.0, 0.0, 0.0);
                              QMarginsF QtAdjustedComboBox::retinaAdjustment(-0.5, 0.0, 0.5, 0.0);
                              
                              QMarginsF QtAdjustedPushButton::nonRetinaAdjustment(0.0, 0.0, 0.0, 0.0);
                              QMarginsF QtAdjustedPushButton::retinaAdjustment(0.5, 0.0, 0.5, 0.0);
                              
                              QMarginsF QtAdjustedRadioButton::nonRetinaAdjustment(-1.0, 0.0, 1.0, 0.0);
                              QMarginsF QtAdjustedRadioButton::retinaAdjustment(-1.0, 0.0, 1.0, 0.0);
                              
                              QMarginsF QtAdjustedLineEdit::nonRetinaAdjustment(0.0, 0.0, 0.0, 0.0);
                              QMarginsF QtAdjustedLineEdit::retinaAdjustment(-1.5, 0.0, -1.5, 0.0);
                              
                              QMarginsF QtAdjustedSpinBox::nonRetinaAdjustment(1.0, 0.0, 1.0, 0.0);
                              QMarginsF QtAdjustedSpinBox::retinaAdjustment(1.5, 0.0, 1.5, 0.0);
                              
                              QMarginsF QtAdjustedDoubleSpinBox::nonRetinaAdjustment(1.0, 0.0, 1.0, 0.0);
                              QMarginsF QtAdjustedDoubleSpinBox::retinaAdjustment(1.5, 0.0, 1.5, 0.0);
                              
                              AdjustedWidgetHelper::~AdjustedWidgetHelper()
                              {
                                  if (pixmap) {
                                      delete pixmap;
                                  }
                              }
                              
                              QRectF AdjustedWidgetHelper::calculateDrawRect(QWidget *widget, const QMarginsF &nonRetinaAdjustment, const QMarginsF &retinaAdjustment)
                              {
                                  if (widget->devicePixelRatio() == 2) {
                                      return QRectF(widget->rect()) + retinaAdjustment;
                                  } else {
                                      return QRectF(widget->rect()) + nonRetinaAdjustment;
                                  }
                              }
                              
                              QPixmap * AdjustedWidgetHelper::setupPixmap(QRectF drawRect, int devicePixelRatio)
                              {
                                  QSize pixmapSize(drawRect.width() * devicePixelRatio, drawRect.height() * devicePixelRatio);
                                  
                                  if (!pixmap || pixmap->size() != pixmapSize || pixmap->devicePixelRatio() != devicePixelRatio) {
                                      if (pixmap) {
                                          delete pixmap;
                                      }
                                      
                                      pixmap = new QPixmap(pixmapSize);
                                      pixmap->setDevicePixelRatio(devicePixelRatio);
                                  }
                                  
                                  pixmap->fill(Qt::transparent);
                                  
                                  return pixmap;
                              }
                              
                              void QtAdjustedComboBox::paintEvent(QPaintEvent *)
                              {
                                  QRectF drawRect = helper.calculateDrawRect(this, nonRetinaAdjustment, retinaAdjustment);
                                  QPixmap *pixmap = helper.setupPixmap(drawRect, devicePixelRatio());
                                  QPainter painter(this);
                                  QStylePainter stylePainter(pixmap, this);
                                  QStyleOptionComboBox options;
                                  
                                  initStyleOption(&options);
                                  options.rect = QRect(0, 0, drawRect.width(), drawRect.height());
                                  stylePainter.setPen(palette().color(QPalette::Text));
                                  stylePainter.drawComplexControl(QStyle::CC_ComboBox, options);
                                  stylePainter.drawControl(QStyle::CE_ComboBoxLabel, options);
                                  
                                  painter.drawPixmap(drawRect, *pixmap, QRectF(pixmap->rect()));
                              }
                              
                              void QtAdjustedPushButton::paintEvent(QPaintEvent *)
                              {
                                  QRectF drawRect = helper.calculateDrawRect(this, nonRetinaAdjustment, retinaAdjustment);
                                  QPixmap *pixmap = helper.setupPixmap(drawRect, devicePixelRatio());
                                  QPainter painter(this);
                                  QStylePainter stylePainter(pixmap, this);
                                  QStyleOptionButton options;
                                  
                                  initStyleOption(&options);
                                  options.rect = QRect(0, 0, drawRect.width(), drawRect.height());
                                  stylePainter.setPen(palette().color(QPalette::Text));
                                  stylePainter.drawControl(QStyle::CE_PushButton, options);
                                  
                                  painter.drawPixmap(drawRect, *pixmap, QRectF(pixmap->rect()));
                              }
                              
                              void QtAdjustedRadioButton::paintEvent(QPaintEvent *)
                              {
                                  // For some odd reason, stylePainter does not render the label of QRadioButton correctly when
                                  // rendering into a QPixmap -- the font is slightly too heavy. Fortunately, we don't need to
                                  // adjust it by a single pixel on a retina display so we don't need the QPixmap.
                                  QStylePainter stylePainter(this);
                                  QStyleOptionButton options;
                                  initStyleOption(&options);
                                  
                                  if (devicePixelRatio() == 2) {
                                      options.rect = QRectF(options.rect).marginsAdded(retinaAdjustment).toRect();
                                  } else {
                                      options.rect = QRectF(options.rect).marginsAdded(nonRetinaAdjustment).toRect();
                                  }
                                  
                                  stylePainter.drawControl(QStyle::CE_RadioButton, options);
                              }
                              
                              void QtAdjustedLineEdit::paintEvent(QPaintEvent *event)
                              {
                                  QLineEdit::paintEvent(event);
                                  
                                  // Qt renders the lighter rectangle on the outside of QLineEdit's frame, so in that case
                                  // we'll render our own frame with the right colors.
                                  if (devicePixelRatio() == 2) {
                                      QPainter painter(this);
                                      
                                      painter.setPen(QPen(QBrush(QColor(177, 177, 177)), 0.5));
                                      painter.setBrush(Qt::transparent);
                                      painter.drawRect(QRectF(rect()).adjusted(0, 0, -0.5, -0.5));
                                      
                                      painter.setPen(QPen(QBrush(QColor(240, 240, 240)), 0.5));
                                      painter.drawRect(QRectF(rect()).adjusted(0.5, 0.5, -1, -1));
                                  }
                              }
                              
                              void QtAdjustedSpinBox::paintEvent(QPaintEvent *)
                              {
                                  QRectF drawRect = helper.calculateDrawRect(this, nonRetinaAdjustment, retinaAdjustment);
                                  QPixmap *pixmap = helper.setupPixmap(drawRect, devicePixelRatio());
                                  QPainter painter(this);
                                  QStylePainter stylePainter(pixmap, this);
                                  QStyleOptionSpinBox options;
                                  
                                  initStyleOption(&options);
                                  options.rect = QRect(0, 0, drawRect.width(), drawRect.height());
                                  
                                  // Qt leaves a gap between the frame a line edit control when rendering a spin box, so we'll
                                  // fill it in with its background color:
                                  QRectF fieldRect = QRectF(style()->subControlRect(QStyle::CC_SpinBox, &options, QStyle::SC_SpinBoxEditField)).adjusted(-1, -1, 1, 1);
                                  stylePainter.setPen(palette().color(QPalette::Base));
                                  stylePainter.setBrush(palette().color(QPalette::Base));
                                  stylePainter.drawRect(fieldRect);
                                  
                                  stylePainter.drawComplexControl(QStyle::CC_SpinBox, options);
                                  painter.drawPixmap(drawRect, *pixmap, QRectF(pixmap->rect()));
                              }
                              
                              void QtAdjustedDoubleSpinBox::paintEvent(QPaintEvent *)
                              {
                                  QRectF drawRect = helper.calculateDrawRect(this, nonRetinaAdjustment, retinaAdjustment);
                                  QPixmap *pixmap = helper.setupPixmap(drawRect, devicePixelRatio());
                                  QPainter painter(this);
                                  QStylePainter stylePainter(pixmap, this);
                                  QStyleOptionSpinBox options;
                                  
                                  initStyleOption(&options);
                                  options.rect = QRect(0, 0, drawRect.width(), drawRect.height());
                                  
                                  // Qt leaves a gap between the frame a line edit control when rendering a spin box, so we'll
                                  // fill it in with its background color:
                                  QRectF fieldRect = QRectF(style()->subControlRect(QStyle::CC_SpinBox, &options, QStyle::SC_SpinBoxEditField)).adjusted(-1, -1, 1, 1);
                                  stylePainter.setPen(palette().color(QPalette::Base));
                                  stylePainter.setBrush(palette().color(QPalette::Base));
                                  stylePainter.drawRect(fieldRect);
                                  
                                  stylePainter.drawComplexControl(QStyle::CC_SpinBox, options);
                                  painter.drawPixmap(drawRect, *pixmap, QRectF(pixmap->rect()));
                              }
                              
                              

                              Admittedly I'm not the biggest fan of how Qt often requires you to subclass everything in order to get customized behavior, and I tried a number of different alternative approaches, including something that could be applied automatically to any set of widgets. But in the end, subclassing was the best way to go.

                              In case anyone is wondering why the objects render to QPixmaps, the reason is because some of the widgets required being moved over a fraction of a point, which would be one pixel on a retina display. When using QRect as opposed to QRectF, there's no way to accomplish this and is consequently a limitation of QStylePainter / QStyleOption. The workaround is to render into a QPixmap with a device pixel ratio of 2 and then compositing it into the widget at the correct retina pixel coordinates.

                              M Offline
                              M Offline
                              Mr Aloof
                              wrote on 2 Feb 2018, 01:15 last edited by
                              #14

                              @Guy-Gizmo said in QComboBox and QPushButton in a grid layout do not line up properly in macOS:

                              options

                              congratulations!

                              if i do this , i'll use custom stylesheet

                              QPushButton
                              {
                              padding: 0px;
                              margin:0px;
                              image:url(xxx);
                              image-position:center;
                              border-image: url(xxxxxx) 2 2 2 2;
                              }

                              maybe fix it.
                              i suggest that if you use qt, use stylesheet to redraw all the controls.
                              because qt draw every control itself, maybe undefined behavior happened.

                              but at last, congratulations for u!

                              1 Reply Last reply
                              0
                              • MrKozmonM Offline
                                MrKozmonM Offline
                                MrKozmon
                                wrote on 28 Oct 2019, 16:24 last edited by
                                #15

                                Checkout the answer I have given here:
                                https://forum.qt.io/topic/105191/why-isn-t-a-qcombobox-positioned-correctly-in-a-layout

                                1 Reply Last reply
                                0

                                • Login

                                • Login or register to search.
                                • First post
                                  Last post
                                0
                                • Categories
                                • Recent
                                • Tags
                                • Popular
                                • Users
                                • Groups
                                • Search
                                • Get Qt Extensions
                                • Unsolved