Как сохранить соотношение сторон виджетов в Qt?
Как можно сохранить соотношение сторон виджетов в Qt и как насчет центрирования виджета?
4 ответа
Вам не нужно реализовывать свой собственный менеджер макета. Вы можете сделать с наследованием 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.
Правильный ответ - создать свой собственный менеджер раскладки. Это возможно путем создания подкласса 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;
- Сообщает, обрабатывает ли макет высоту для расчета ширины.
- QSizeimumSize () const;
- Возвращает макеты минимального размера.
- void setGeometry (const QRect & rect);
- Устанавливает геометрию макета и элементов внутри него. Здесь вы должны сохранить соотношение сторон и выполнить центрирование.
- QSize sizeHint () const;
- Возвращает предпочтительный размер для макета.
дальнейшее чтение
Призвание resize()
изнутри resizeEvent()
никогда не работал хорошо для меня - в лучшем случае это вызовет мерцание, так как размер окна изменяется в два раза (как у вас), в худшем - бесконечный цикл.
Я думаю, что "правильный" способ сохранить фиксированное соотношение сторон - это создать собственный макет. Вам придется переопределить только два метода, QLayoutItem::hasHeightForWidth()
а также QLayoutItem::heightForWidth()
,
Я тоже пытался добиться запрошенного эффекта: виджет, который сохраняет фиксированное соотношение сторон, оставаясь центрированным в выделенном пространстве. Сначала я попробовал другие ответы на этот вопрос:
- реализация
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++ или на выбранном вами языке.)
В моем случае переопределение heightForWidth() не работает. И для кого-то может быть полезно получить рабочий пример использования события resize.
Сначала подкласс 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 и устанавливать правильную высоту при изменении размера.