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.
  • 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