Как сохранить соотношение сторон виджетов в Qt?

Как можно поддерживать соотношение сторон виджетов в Qt и как насчет центрирования виджета?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
34
0
37 058
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вызов resize() изнутри resizeEvent() никогда не работал для меня хорошо - в лучшем случае это вызовет мерцание, когда размер окна изменяется дважды (как у вас), в худшем случае - бесконечный цикл.

Я думаю, что «правильный» способ сохранить фиксированное соотношение сторон - это создать собственный макет. Вам нужно будет переопределить всего два метода: QLayoutItem::hasHeightForWidth() и QLayoutItem::heightForWidth().

Это кажется правильным ответом, но мне нужно разобраться в нем, когда я на работе, и, возможно, дать более подробный ответ после того, как я это сделаю.

Bleadof 18.01.2009 14:23

Правильный ответ - создать свой собственный менеджер по расположению. Это возможно путем создания подкласса QLayout.

Методы для реализации при создании подкласса QLayout

void addItem (QLayoutItem * item);
Добавляет элемент в макет.
int count () const;
Возвращает количество предметов.
QLayoutItem * itemAt (int index) const;
Возвращает ссылку на элемент по индексу или 0, если его нет.
QLayoutItem * takeAt (int index);
Принимает и возвращает элемент из макета из индекса или возвращает 0, если его нет.
Qt :: Orientations expandingDirections () const;
Возвращает направления раскрытия макетов.
bool hasHeightForWidth () const;
Сообщает, обрабатывает ли макет высоту для расчета ширины.
QSize minimumSize () const;
Возвращает минимальный размер макетов.
void setGeometry (const QRect & rect);
Устанавливает геометрию макета и элементов внутри него. Здесь вам нужно сохранить соотношение сторон и выполнить центрирование.
QSize sizeHint () const;
Возвращает предпочтительный размер макета.

дальнейшее чтение

Вы говорите о создании подкласса QLayout, но цитируемые вами функции взяты из QLayoutItem, который просто вызывает соответствующие функции из QWidget. Так что же наследование QLayout может дать вам такого, чего не может дать сам QWidget?

Marc Mutz - mmutz 28.07.2009 23:02

также все ссылки мертвы.

Tomáš Zato - Reinstate Monica 16.12.2015 12:58
Ответ принят как подходящий

Вам не нужно реализовывать свой собственный менеджер по расположению. Вы можете унаследовать QWidget и переопределить

int QWidget::heightForWidth( int w ) { return w; }

оставаться квадратным. Однако heightForWidth() не работает с окнами верхнего уровня на X11, поскольку, очевидно, протокол X11 этого не поддерживает. Что касается центрирования, вы можете передать Qt::AlignCenter в качестве третьего параметра для QBoxLayout::addWidget() или пятого параметра для QGridLayout::addWidget().

Примечание: По крайней мере, в более новых версиях Qt QWidget больше не имеет heightForWidth или widthForHeight (поэтому они не могут быть переопределены) и, следовательно, setWidthForHeight(true) или setHeightForWidth(true)имеют эффект только для потомков QGraphicsLayout.

Да. Вам нужно установить sizePolicy, а не hasHeightForWidth() == true, и макет часто дает виджету большую ширину или высоту, которые он запрашивает, но этот макет пытается удовлетворить ограничения. Вы получите тот же эффект с решением QLayoutItem, поскольку они эквивалентны (см. QWidgetLayoutItem).

Marc Mutz - mmutz 28.07.2009 22:59

@ MarcMutz-mmutz: Конечно, X11 поддерживает это: посмотрите XSizeHints tronche.com/gui/x/xlib/ICC/client-to-window-manager/…, которые позволяют сообщать оконному менеджеру соотношение сторон окна.

datenwolf 22.04.2013 16:24

Что, если я хочу вместо этого установить widthForHeight? QWidget не имеет метода widthForHeight.

Neptilo 15.11.2014 04:53

Итак, как это сделать в новых версиях Qt?

Tomáš Zato - Reinstate Monica 16.12.2015 12:57

Ммм ... как для Qt 5.7 doc.qt.io/qt-5/qwidget.html#hasHeightForWidth

cbuchart 06.10.2016 14:22

В моем случае переопределение heightForWidth () не работает. А кому-то может быть полезно получить рабочий пример использования события изменения размера.

Сначала создайте подкласс qObject, чтобы создать фильтр. Подробнее о фильтрах событий.

class FilterObject:public QObject{
public:
    QWidget *target = nullptr;//it holds a pointer to target object
    int goalHeight=0;
    FilterObject(QObject *parent=nullptr):QObject(parent){}//uses QObject constructor
    bool eventFilter(QObject *watched, QEvent *event) override;//and overrides eventFilter function
};

Затем функция eventFilter. Его код должен быть определен вне определения FilterObject, чтобы предотвратить предупреждение. Благодаря этому ответу.

bool FilterObject::eventFilter(QObject *watched, QEvent *event) {
    if (watched!=target){//checks for correct target object.
        return false;
    }
    if (event->type()!=QEvent::Resize){//and correct event
        return false;
    }

    QResizeEvent *resEvent = static_cast<QResizeEvent*>(event);//then sets correct event type

    goalHeight = 7*resEvent->size().width()/16;//calculates height, 7/16 of width in my case
    if (target->height()!=goalHeight){
        target->setFixedHeight(goalHeight);
    }

    return true;
};

Затем в основном коде создайте FilterObject и установите его как прослушиватель EventFilter для целевого объекта. Благодаря этому ответу.

FilterObject *filter = new FilterObject();
QWidget *targetWidget = new QWidget();//let it be target object
filter->target=targetWidget;
targetWidget->installEventFilter(filter);

Теперь фильтр будет получать все события targetWidget и устанавливать правильную высоту при изменении размера.

Чтобы избежать мерцания - как упоминалось в ответе выше, не добавляйте виджет в макет, просто сделайте его дочерним по отношению к родительскому, а затем установите размер вручную.

tunafish24 13.12.2019 19:27

Я тоже пытался добиться запрошенного эффекта: виджет, который сохраняет фиксированное соотношение сторон, оставаясь центрированным в выделенном пространстве. Сначала я попробовал другие ответы на этот вопрос:

  • реализация heightForWidth и hasHeightForWidth, предложенная marc-mutz-mmutz, просто не сработала для меня.
  • Я кратко рассмотрел реализацию настраиваемого менеджера компоновки, но все ссылки Bleadof были мертвы, и когда я нашел документация и прочитал его, это выглядело слишком сложным для того, чего я пытался достичь.

В итоге я создал собственный виджет, который реагирует на resizeEvent и использует setContentsMargin для установки полей таким образом, чтобы оставшаяся область содержимого сохраняла желаемое соотношение.

Я обнаружил, что мне также пришлось установить политику размера виджета на QSizePolicy::Ignored в обоих направлениях, чтобы избежать нечетных проблем с изменением размера, возникающих из-за запросов размера дочерних виджетов - конечный результат состоит в том, что мой виджет принимает любой размер, который ему присваивает родительский элемент (а затем устанавливает его поля, как описано выше, чтобы сохранить желаемое соотношение сторон в области содержимого).

Мой код выглядит так:

from PySide2.QtWidgets import QWidget, QSizePolicy


class AspectWidget(QWidget):
    '''
    A widget that maintains its aspect ratio.
    '''
    def __init__(self, *args, ratio=4/3, **kwargs):
        super().__init__(*args, **kwargs)
        self.ratio = ratio
        self.adjusted_to_size = (-1, -1)
        self.setSizePolicy(QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored))

    def resizeEvent(self, event):
        size = event.size()
        if size == self.adjusted_to_size:
            # Avoid infinite recursion. I suspect Qt does this for you,
            # but it's best to be safe.
            return
        self.adjusted_to_size = size

        full_width = size.width()
        full_height = size.height()
        width = min(full_width, full_height * self.ratio)
        height = min(full_height, full_width / self.ratio)

        h_margin = round((full_width - width) / 2)
        v_margin = round((full_height - height) / 2)

        self.setContentsMargins(h_margin, v_margin, h_margin, v_margin)

(Очевидно, что этот код написан на Python, но его должно быть просто выразить на C++ или на вашем выбранном языке.)

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

Lorenzo Castellino 04.03.2021 13:27

Другие вопросы по теме