PyQt5:QScrollArea 保持 Pixmap 纵横比

Posted

技术标签:

【中文标题】PyQt5:QScrollArea 保持 Pixmap 纵横比【英文标题】:PyQt5: QScrollArea Keep Pixmap Aspect Ratio 【发布时间】:2018-12-24 13:47:21 【问题描述】:

我基于https://***.com/a/2714554/6859682 使用 QAbstractButton 创建了一组像素图按钮 (PicButton),并希望将它们添加到滚动区域中,以便用户可以水平滚动。但是,我需要

    像素图按钮的纵横比保持不变 像素图按钮应始终占据窗口的整个高度,最高可达 200 像素。

我当前代码的问题是当高度变得太大时像素图按钮会被挤压。

当高度足够小以使所有按钮都适合窗口时,我能够使纵横比保持不变。我在下面附上了我的 PicButton 类。

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.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_())

【问题讨论】:

能否解释一下第二点 是的,我的意思是如果用户将窗口的高度降低到 200 px 以下,像素图的高度也应该降低以适应窗口,宽度应该降低以保持纵横比比率 如果高度大于 200 px,会发生什么?还是应该 200px 是按钮的最大高度? 200 px 应该是最大高度 别担心,因为这很容易解决。 【参考方案1】:

已添加

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_())

很抱歉没有一次嵌入您想要的答案。

QAbstractButton 版本

是的,我没有使用 QAbstractButton。这一直挂在我的头上。 这是 QAbstractButton 版本。您将能够自定义所需的按钮。

从 PyQt5 导入 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_())

因为我接受了cmets,所以我试图详细说明QGraphicsView和QGraphicsScene的场合。

但这只是结果。这段代码可能没有任何人气。 无论如何,我希望你执行这段代码。 希望你喜欢。

如果你想知道细节,请写cmets。

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_())

上一个

我不确定你想要什么,你想这样做吗? 如果不是,我将删除此答案或重写。

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_())

【讨论】:

感谢您的帮助。您的代码部分是我想做的。但是,如果高度减小到 200 像素以下,我希望 PicButtons 的大小(高度和宽度)减小。 @CSforStructuralEngineer 早上好,我明白你想要什么,我认为你应该使用QGraphicsViewQGraphicsScene。你把你的像素图放在它们上面。像素图缩放非常容易。QGraphicsView 也有QScrollAreaQHorizontalScrollBar。我不知道你为什么要使用 QWidgets。要使用 QWidget,你需要将小部件调整大小事件连接到像素图大小。现在,像素图就在小部件,但它们与 QWidget 没有任何关系。你想用 QWidget 做吗? 感谢您的意见。我不致力于使用 QWidget,不管用什么都好。我真的没想过使用 QGraphicsView。 您能否详细说明在这种情况下我将如何使用 QGraphicsView? @CSforStructuralEngineer 我改变了我的代码。我停止了 ResizeEvent。因为当用户处理大小时,总是有时间滞后。所以当用户增加大小时,空白出现了。你可以这样做您将 QRectItem 更改为 QPixmapItem 并可能在遭受一些痛苦后做同样的事情。

以上是关于PyQt5:QScrollArea 保持 Pixmap 纵横比的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 QScrollArea 使滚动条出现

将垂直滚动条添加到嵌入式 matplotlib 画布,同时保持其水平大小占据整个 QScrollArea

使用PyQt5 for Python gui的可滚动标签

pyqt5选项卡区域没有填满整个可用空间

PYQT5 (十八)文件拖放(drag and drop)并获取文件信息

PyQt5 QTableView:如何在保持默认样式/颜色的同时禁用用户交互/选择?