0

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 Issue

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_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241

1 Answers1

2

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_())
Haru
  • 1,884
  • 2
  • 12
  • 30
  • Thanks for your help. Your code is partially what I wanted to do. However, if the height is reduced below 200 px, I wanted the size of the PicButtons (height and width) to reduce. – CSforStructuralEngineer Dec 24 '18 at 15:39
  • @CSforStructuralEngineer Good morning,I understood what you want,I think you should use `QGraphicsView` and `QGraphicsScene`.You put your pixmap on them.Pixmap Scalings are very easy.`QGraphicsView` also has `QScrollArea` and `QHorizontalScrollBar`.I don't know why you want to do it with QWidgets.For doing it with `QWidget`,you need to connect the widget resize events to pixmap size.Now, the pixmaps are just on the widget,but they have no connection with `QWidget`.Do you want to do it with QWidget? – Haru Dec 25 '18 at 01:57
  • Thanks for your input. I'm not committed to using QWidget, whatever works is fine. I didn't really think of using QGraphicsView. – CSforStructuralEngineer Dec 25 '18 at 02:03
  • Could you elaborate a bit more on how I would use QGraphicsView in this case? – CSforStructuralEngineer Dec 25 '18 at 03:23
  • @CSforStructuralEngineer I updated.Please look and give it a try. – Haru Dec 25 '18 at 03:51
  • Thanks for your help. While your new code removes the button functionality I had initially implemented, a combination of your codes together gives me some ideas to try to implement this. – CSforStructuralEngineer Dec 25 '18 at 04:12
  • 1
    @CSforStructuralEngineer I changed my code.I stopped ResizeEvent.Because when users handle the size,there is always time lag.So when the user increments the size,the blank comes up.You can do it if you change the QRectItem to QPixmapItem and do the same thing probably after some suffer. – Haru Dec 25 '18 at 04:15
  • @CSforStructuralEngineer I updated.QAbstractButton ver. – Haru Dec 26 '18 at 06:24