Растягивающаяся фоновая картинка в PyQt5 с сохранением пропорций
Есть простое приложение, главное окно QMainWindow и в нем несколько QDockWidget. Цель поставить фоновую картинку на это окно, да так, чтобы она занимало всё место (растягивалась вместе с окном) и при этом сохраняла пропорции. Желательно через self.setStyleSheet.
Я пробовал background-image: url("background.png"); и border-image: url("background.png") 0 0 0 0;.
В первом случае картинка сохраняла свой размер, если картинка, скажем, размером 1280x720 - и при окне 200x200 она такой будет, обрезая себе края, и при окне 1920x1080, либо повторяясь, либо оставляя пустое место, если я добавлял background-repeat: no-repeat;.
Во втором случае картинка растягивалась во всё окно, однако пропорции не сохранялись.
Насколько я понял никаких background-size: 100%; в QSS нет.
Важно то что на пустом QMainWidget размещается несколько QDockWidget. Находил способ через создания QLabel, установки в нем QPixMap и размещения этого виджета как центрального в QMainWidget, однако в этом случае картинки не видно за QDockWidget, даже если к ним применено background-color: transparent;.
Есть варианты как решить это?
Пример кода:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class DockDemo(QMainWindow):
def __init__(self,parent=None):
super(DockDemo, self).__init__(parent)
layout=QHBoxLayout()
self.setStyleSheet("""
QMainWindow {
background-image: url("background.png");
}
""")
self.items=QDockWidget('Dockable',self)
self.items2=QDockWidget('Dockable2',self)
self.listWidget=QListWidget()
self.listWidget.addItem('Item1')
self.listWidget.addItem('Item2')
self.listWidget.addItem('Item3')
self.listWidget.addItem('Item4')
self.listWidget2=QListWidget()
self.listWidget2.addItem('Item1')
self.listWidget2.addItem('Item2')
self.listWidget2.addItem('Item3')
self.listWidget2.addItem('Item4')
self.items.setWidget(self.listWidget)
self.items2.setWidget(self.listWidget)
self.items.setFloating(False)
self.items2.setFloating(False)
self.addDockWidget(Qt.RightDockWidgetArea,self.items)
self.addDockWidget(Qt.RightDockWidgetArea,self.items2)
self.items.setStyleSheet("background-color: transparent;")
self.items2.setStyleSheet("background-color: transparent;")
self.setLayout(layout)
self.setWindowTitle('Dock')
if __name__ == '__main__':
app=QApplication(sys.argv)
demo=DockDemo()
demo.show()
sys.exit(app.exec_())
Пример того, что я имею ввиду (gif). Белая рамка - само окно, картинка в нем - фон, и эта картинка занимает всё место, и поверх неё уже крепятся QDockWidget.
Ответы (2 шт):
Обратите внимание [Qt Main Window Framework]1
Главное окно обеспечивает основу для создания пользовательского интерфейса приложения.
Qt имеет QMainWindow и связанные с ним классы для управления главным окном.
QMainWindow имеет свой собственный layout, в который вы можете добавить
QToolBars, QDockWidgets, QMenuBar и QStatusBar.
В макете есть центральная область, которую можно занять любым виджетом.
Вы можете увидеть изображение макета ниже.
Попробуйте так:
import sys
from PyQt5.Qt import *
from PyQt5 import QtCore, QtGui, QtWidgets
class ImageLabel(QtWidgets.QGraphicsView):
def __init__(self, *args, **kwargs):
super(ImageLabel, self).__init__(*args, **kwargs)
self.setScene(QtWidgets.QGraphicsScene())
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
def setImage(self, filename):
self.setPixmap(QtGui.QPixmap(filename))
def setPixmap(self, pixmap):
item = QtWidgets.QGraphicsPixmapItem(pixmap)
item.setTransformationMode(QtCore.Qt.SmoothTransformation)
self.scene().addItem(item)
def resizeEvent(self, event):
r = self.scene().itemsBoundingRect()
self.fitInView(r, QtCore.Qt.KeepAspectRatio)
super(ImageLabel, self).resizeEvent(event)
class DockDemo(QMainWindow):
def __init__(self,parent=None):
super(DockDemo, self).__init__(parent)
self.centralWidget = QWidget() # +++
self.setCentralWidget(self.centralWidget) # +++
layout = QHBoxLayout(self.centralWidget) # + centralWidget
self.image = ImageLabel() # +++
layout.addWidget(self.image) # +++
self.image.setImage("boy.jpg") # установите свое изображение !!!
self.items = QDockWidget('Dockable 1', self)
self.items2 = QDockWidget('Dockable 2', self)
self.listWidget = QListWidget()
self.listWidget.addItems(['Item 1', 'Item 2', 'Item 3', 'Item 4', ])
self.listWidget2 = QListWidget()
self.listWidget2.addItems(['Item-1', 'Item-2', 'Item-3', 'Item-4', ])
self.items.setWidget(self.listWidget)
self.items2.setWidget(self.listWidget2) # listWidget2 !!!
self.items.setFloating(False)
self.items2.setFloating(False)
self.addDockWidget(Qt.RightDockWidgetArea,self.items)
self.addDockWidget(Qt.RightDockWidgetArea,self.items2)
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = DockDemo()
demo.setWindowTitle('Dock')
demo.show()
sys.exit(app.exec_())
Что ж, я смог. Перед тем как писать решение, надо упомянуть что у меня в проекте есть вот такая функция
def resource_path(relative_path):
try:
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
Она мне нужна чтобы загружать картинку (в моем случае background.png) в Exe файл для pyinstaller с флагом --onefile.
Теперь само решение:
background_image = resource_path("background.png").replace('\\','/')
pix = QPixmap(background_image) #Фон
def main_resize(event): #Ресайз основного окна
pal = self.palette()
if pix.width()>=self.width() and pix.height()>=self.height(): #Если размер окна слишком мал
pal.setBrush(self.backgroundRole(), QBrush(pix.scaled(pix.size(),Qt.KeepAspectRatioByExpanding,Qt.SmoothTransformation)))
else:
pal.setBrush(self.backgroundRole(), QBrush(pix.scaled(self.size(),Qt.KeepAspectRatioByExpanding,Qt.SmoothTransformation)))
self.setPalette(pal)
self.resizeEvent = main_resize
Вообще можно было обойтись только кодом в блоке else, однако я не хочу чтобы картинка сужалась и была слишком мала, поэтому если размер окна меньше размера загруженной картинки и по ширине, и по высоте - она не трансформируется, а если нет - растягивается с сохранение пропорций.



