PyQt5: QScrollArea Keep Pixmap Aspect Ratio

て烟熏妆下的殇ゞ 提交于 2020-02-06 11:22:27

问题


I've created a set of pixmap buttons (PicButton) using QAbstractButton based on this https://stackoverflow.com/a/2714554/6859682 and want to add them in a scroll area such that the user can scroll horizontally. However, I need

  1. Aspect ratio of the pixmap buttons to be constant
  2. The pixmap buttons should always occupy the full height of the window upto 200 px.

The issue with my current code is that the pixmap buttons get squeezed when the height becomes too much.

I was able to get the aspect ratio to be constant when the height is small enough for all the buttons to fit in the window. I am attaching my PicButton class below.

from PyQt5 import QtCore, QtGui, QtWidgets

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtGui import QPixmap, QPainter, QPen

def testPixmap(r = 255,g = 0,b = 0, a = 255,size =(200,200)):
    px = QtGui.QPixmap(size[0],size[1])
    color = QtGui.QColor(r,g,b,a)
    px.fill(color) 
    return px

class PicButton(QtWidgets.QAbstractButton):
    checked = QtCore.pyqtSignal(object, QtCore.QRect)
    def __init__(self, name, parent=None, w = 200, h = 200):
        self.w = w; self.h = h;  self.name = name
        super(PicButton, self).__init__(parent)
        pixmap = testPixmap(255,0,0)
        self.resetPixmaps(pixmap); self.pixmap = pixmap
        self.setCheckable(True);   self.setChecked(False)
        self.pressed.connect(self.update)
        self.released.connect(self.blank)

    def resetPixmaps(self, pixmap):
        self.pixmap_hover = testPixmap(20,125,200,128)
        self.pixmap_pressed = testPixmap(30,180,200,128)

    def blank(self):
        self.setChecked(True)    

    def paintEvent(self, event):        
        pix = self.pixmap_hover if self.underMouse() else self.pixmap        
        if self.isChecked():
            self.checked.emit( self.name, event.rect())    
            pix = self.pixmap_pressed
        size = self.size()        
        scaledPix = pix.scaledToHeight(size.height(), Qt.SmoothTransformation)
        # start painting the label from left upper corner
        point = QtCore.QPoint(0,0)
        point.setX((size.width() - scaledPix.width())/2)
        point.setY((size.height() - scaledPix.height())/2)

        painter = QPainter(self)

        painter.drawPixmap(point, scaledPix)

    def otherBoxChecked(self, func, rect):
        if self.isChecked():
            pix = self.pixmap; painter = QPainter(self); painter.drawPixmap(rect, pix)
            self.setChecked(False)            

    def enterEvent(self, event):
        self.update()

    def leaveEvent(self, event):
        self.update()

    def sizeHint(self):
        return QtCore.QSize(self.w, self.h)

In the scenario when the height is too much for all the buttons to fit scenario, I want to activate the scroll bar instead and keep the aspect ratio of the buttons. Any ideas on how to do this? I'm attaching the window code below for completeness

class Window(QtWidgets.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        buttons = ['str(i)' for i in range(10)]
        HB2layout = QtWidgets.QHBoxLayout()
        self.maskButtons = [PicButton(button) for button in buttons]
        for maskButton, mb in zip(self.maskButtons, range(len(self.maskButtons))):
            for maskConnect, mc in zip(self.maskButtons, range(len(self.maskButtons))):
                if mb!=mc:
                    maskButton.checked.connect(maskConnect.otherBoxChecked)

        for button in self.maskButtons:
            HB2layout.addWidget(button) 
        self.scrollArea = QtWidgets.QScrollArea(self)
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scrollArea.setMaximumHeight(200)
        self.scrollArea.setLayout(HB2layout)
        self.scrollArea.show()
        Vlayout = QtWidgets.QVBoxLayout(self)
        Vlayout.addWidget(self.scrollArea)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 800, 200)
    window.show()
    sys.exit(app.exec_())

回答1:


Added

from PyQt5 import QtCore, QtGui, QtWidgets
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtGui import QPixmap, QPainter, QPen




def testPixmap(r = 255,g = 0,b = 0, a = 255,size =(200,200)):
    px = QtGui.QPixmap(size[0],size[1])
    color = QtGui.QColor(r,g,b,a)
    px.fill(color) 
    return px



class PicButton(QtWidgets.QAbstractButton):
    checked = QtCore.pyqtSignal(object, QtCore.QRect)
    def __init__(self, name, parent=None, w = 200, h = 200):
        self.w = w; self.h = h;  self.name = name
        super(PicButton, self).__init__(parent)
        pixmap = testPixmap(255,0,0)
        self.resetPixmaps(pixmap); self.pixmap = pixmap
        self.setCheckable(True);   self.setChecked(False)
        self.pressed.connect(self.update)
        self.released.connect(self.blank)

    def resetPixmaps(self, pixmap):
        self.pixmap_hover = testPixmap(20,125,200,128)
        self.pixmap_pressed = testPixmap(30,180,200,128)

    def blank(self):
        self.setChecked(True)    

    def paintEvent(self, event):        
        pix = self.pixmap_hover if self.underMouse() else self.pixmap        
        if self.isChecked():
            self.checked.emit( self.name, event.rect())    
            pix = self.pixmap_pressed
        size = self.size()        
        scaledPix = pix.scaledToHeight(size.height(), Qt.SmoothTransformation)
        # start painting the label from left upper corner
        point = QtCore.QPoint(0,0)
        point.setX((size.width() - scaledPix.width())/2)
        point.setY((size.height() - scaledPix.height())/2)

        painter = QPainter(self)

        painter.drawPixmap(point, scaledPix)

    def otherBoxChecked(self, func, rect):
        if self.isChecked():
            pix = self.pixmap; painter = QPainter(self); painter.drawPixmap(rect, pix)
            self.setChecked(False)            

    def enterEvent(self, event):
        self.update()

    def leaveEvent(self, event):
        self.update()

    def sizeHint(self):
        return QtCore.QSize(self.w, self.h)


class View(QtWidgets.QGraphicsView):
    def __init__(self):
        super(View,self).__init__()        
        self.pic_scene = QtWidgets.QGraphicsScene()
        self.neighborhood = 200
        buttons = ['str(i)' for i in range(10)]

        self.maskButtons = [PicButton(button) for button in buttons]
        for maskButton, mb in zip(self.maskButtons , range(len(self.maskButtons ))):
            for maskConnect, mc in zip(self.maskButtons , range(len(self.maskButtons ))):
                if mb!=mc:
                    maskButton.checked.connect(maskConnect.otherBoxChecked)

        self.pic_scene.setSceneRect(0,0,10000,200)
        for i,item in enumerate(self.maskButtons ):
            item.setGeometry(self.neighborhood*i,item.geometry().y(),item.geometry().width(),item.geometry().height())            
            self.pic_scene.addWidget(item)

        self.setScene(self.pic_scene)
        self.setMaximumHeight(200)
        self.setGeometry(500,500,5000,200)
    def paintEvent(self,event):
        for k,i in enumerate(self.maskButtons ):

            rect = i.geometry()         
            #eventually,width = height
            rect.setSize(QtCore.QSize(self.height(),self.height()))   
            self.neighborhood = self.height()               
            rect.setX(self.height()*k)
            rect.setY(rect.y())
            i.setGeometry(rect)   
            self.pic_scene.addWidget(i)     
        return QtWidgets.QGraphicsView.paintEvent(self,event)



if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = View()
    window.setGeometry(500, 300, 800, 200)   
    window.show()
    sys.exit(app.exec_())

I'm sorry for not enbedding answer you want at one time.

QAbstractButton ver.

Yes, I didn't use QAbstractButton.This has being hung on my head. Here is the QAbstractButton version.You will be able to customize the buttons you want.

from PyQt5 import QtCore, QtGui, QtWidgets

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtGui import QPixmap, QPainter, QPen

class PicButton(QtWidgets.QAbstractButton):
    def __init__(self,x=0,y=0,width=200,height=200):
        super(PicButton,self).__init__()
        self.setGeometry(x,y,width,height)

    def paintEvent(self,event):        
        painter = QtGui.QPainter()
        if not painter.isActive():
            painter.begin(self)
        brush = QtGui.QBrush()     
        brush.setColor(QtGui.QColor(Qt.red))
        brush.setStyle(Qt.SolidPattern)        
        painter.setBrush(brush)
        painter.drawRect(QtCore.QRect(0,0,200,200))
        painter.end()


class View(QtWidgets.QGraphicsView):
    def __init__(self):
        super(View,self).__init__()        
        self.pic_scene = QtWidgets.QGraphicsScene()
        self.neighborhood = 200
        self.rect_items = [PicButton() for i in range(10)]
        self.pic_scene.setSceneRect(0,0,10000,200)
        for i,item in enumerate(self.rect_items):
            item.setGeometry(self.neighborhood*i,item.geometry().y(),item.geometry().width(),item.geometry().height())            
            self.pic_scene.addWidget(item)

        self.setScene(self.pic_scene)
        self.setMaximumHeight(200)
        self.setGeometry(500,500,5000,200)
    def paintEvent(self,event):
        for k,i in enumerate(self.rect_items):

            rect = i.geometry()         
            #eventually,width = height
            rect.setSize(QtCore.QSize(self.height(),self.height()))   
            self.neighborhood = self.height()               
            rect.setX(self.height()*k)
            rect.setY(rect.y())
            i.setGeometry(rect)   
            self.pic_scene.addWidget(i)     
        return QtWidgets.QGraphicsView.paintEvent(self,event)



if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = View()
    window.setGeometry(500, 300, 800, 200)   
    window.show()
    sys.exit(app.exec_())

New

Because I accepted the comments, so I tried to elaborate the occasion of QGraphicsView & QGraphicsScene.

But this is only the result.This code may not have any popularity. At any rate, I want to you to execute this code. I hope you like it.

If you want to know the detail,please write comments.

from PyQt5 import QtCore, QtGui, QtWidgets

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtGui import QPixmap, QPainter, QPen

class PicRectItem(QtWidgets.QGraphicsRectItem):
    def __init__(self,x=0,y=0,width=200,height=200):
        super(PicRectItem,self).__init__()
        self.setRect(x,y,width,height)
        brush = QtGui.QBrush()        
        brush.setColor(QtGui.QColor(Qt.red))
        brush.setStyle(Qt.SolidPattern)        
        self.setBrush(brush)
class View(QtWidgets.QGraphicsView):
    def __init__(self):
        super(View,self).__init__()        
        self.pic_scene = QtWidgets.QGraphicsScene()
        self.neighborhood = 200
        self.rect_items = [PicRectItem() for i in range(10)]
        for i,item in enumerate(self.rect_items):
            item.setRect(self.neighborhood*i,item.y(),item.rect().width(),item.rect().height())
        for i in self.rect_items:
            self.pic_scene.addItem(i)
        self.setScene(self.pic_scene)
        self.setMaximumHeight(200)
    def paintEvent(self,event):
        for k,i in enumerate(self.rect_items):
            rect = i.rect()
            #eventually,width = height
            rect.setSize(QtCore.QSizeF(self.height(),self.height()))   
            self.neighborhood = self.height()               
            i.setRect(rect)                 
            self.pic_scene.addItem(i)           
            rect = i.rect()
            rect.setX(self.height()*k)
            rect.setY(rect.y())
            i.setRect(rect)        
        return QtWidgets.QGraphicsView.paintEvent(self,event)



if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = View()
    window.setGeometry(500, 300, 800, 200)   
    window.show()
    sys.exit(app.exec_())

Previous

I'm not sure about what you want,do you want to do this? If it is not,I will delete this answer or rewrite.

from PyQt5 import QtCore, QtGui, QtWidgets

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtGui import QPixmap, QPainter, QPen

def testPixmap(r = 255,g = 0,b = 0, a = 255,size =(200,200)):
    px = QtGui.QPixmap(size[0],size[1])
    color = QtGui.QColor(r,g,b,a)
    px.fill(color) 
    return px

class PicButton(QtWidgets.QAbstractButton):
    checked = QtCore.pyqtSignal(object, QtCore.QRect)
    def __init__(self, name, parent=None, w = 200, h = 200):
        self.w = w; self.h = h;  self.name = name
        super(PicButton, self).__init__(parent)
        pixmap = testPixmap(255,0,0)
        self.resetPixmaps(pixmap); self.pixmap = pixmap
        self.setCheckable(True);   self.setChecked(False)
        self.pressed.connect(self.update)
        self.released.connect(self.blank)

    def resetPixmaps(self, pixmap):
        self.pixmap_hover = testPixmap(20,125,200,128)
        self.pixmap_pressed = testPixmap(30,180,200,128)

    def blank(self):
        self.setChecked(True)    

    def paintEvent(self, event):        
        pix = self.pixmap_hover if self.underMouse() else self.pixmap        
        if self.isChecked():
            self.checked.emit( self.name, event.rect())    
            pix = self.pixmap_pressed
        size = self.size()        
        scaledPix = pix.scaledToHeight(size.height(), Qt.SmoothTransformation)
        # start painting the label from left upper corner
        point = QtCore.QPoint(0,0)
        point.setX((size.width() - scaledPix.width())/2)
        point.setY((size.height() - scaledPix.height())/2)

        painter = QPainter(self)

        painter.drawPixmap(point, scaledPix)

    def otherBoxChecked(self, func, rect):
        if self.isChecked():
            pix = self.pixmap; painter = QPainter(self); painter.drawPixmap(rect, pix)
            self.setChecked(False)            

    def enterEvent(self, event):
        self.update()

    def leaveEvent(self, event):
        self.update()

    def sizeHint(self):
        return QtCore.QSize(self.w, self.h)
class Window(QtWidgets.QWidget):
    def __init__(self):
        super(Window, self).__init__()

        buttons = ['str(i)' for i in range(10)]
        HB2layout = QtWidgets.QHBoxLayout()

        self.maskButtons = [PicButton(button) for button in buttons]
        for maskButton, mb in zip(self.maskButtons, range(len(self.maskButtons))):
            for maskConnect, mc in zip(self.maskButtons, range(len(self.maskButtons))):
                if mb!=mc:
                    maskButton.checked.connect(maskConnect.otherBoxChecked)

        for button in self.maskButtons:
            HB2layout.addWidget(button) 
        self.scrollChildArea = QtWidgets.QWidget()
        self.scrollChildArea.setLayout(HB2layout)
        self.scrollArea = QtWidgets.QScrollArea()
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scrollArea.setMaximumHeight(200)

        self.scrollArea.setWidget(self.scrollChildArea)
        self.scrollArea.show()
        Vlayout = QtWidgets.QVBoxLayout()
        Vlayout.addWidget(self.scrollArea)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 800, 200)

    sys.exit(app.exec_())


来源:https://stackoverflow.com/questions/53914267/pyqt5-qscrollarea-keep-pixmap-aspect-ratio

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!