Hi! Hope you're enjoying this blog. I have a new home at www.goldsborough.me. Be sure to also check by there for new posts <3

Thursday, August 22, 2013

Tutorial: How to make a flat,square and colored button in PyQt

Hi there, today I want to show you how to make a square button in PyQt. You may think this is easily done, but it's not as obvious as it seems.

It will look like this:


I really like the new design Microsoft has come up with, so I decided to try to implement it in PyQt. The problem is, when you set a color for your button using StyleSheets, the button becomes flat and loses everything a user is used to when using a button. What I mean is, when you hover a button, you are used to it changing the color tone a bit, same thing when clicking it, so I had to come up with a way of changing the color manually. Here is how you first of all make a window with a button that is square and colored:

import sys
from PyQt4 import QtGui,QtCore

class Main(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.initUI()

    def initUI(self):
        
        centralwidget = QtGui.QWidget()

        self.button = QtGui.QPushButton("+",self)
        self.button.setStyleSheet("font-size:40px;background-color:#333333;\
        border: 2px solid #222222")
        self.button.setFixedSize(100,100)
        self.button.clicked.connect(self.Button)

        self.grid = QtGui.QGridLayout()

        self.grid.addWidget(self.button,0,0)
        
        centralwidget.setLayout(self.grid)

        self.setCentralWidget(centralwidget)
     
#---------Window settings --------------------------------
        
        self.setGeometry(300,300,500,100)

def Button(self):
        print("Clicked")

def main():
    app = QtGui.QApplication(sys.argv)
    main= Main()
    main.show()

    sys.exit(app.exec_())

if __name__ == "__main__":
    main()


In PyQt we can change many design elements using stylesheets, as one would do when creating a website. Here, I change the button's font-size to 40px, the background color to #333333 and the border to  2px wide solid and its color to #222222. If you don't know about CSS, color is usually displayed using their hexadecimal values, but you can also use their plain names, although there are only limited of those. You can find a nice list of these plain colors and their hexadecimal values here , and if you want to change other elements I recommend you check out w3school's CSS tutorials here.

If you try the above code, you will see that the button does not react to anything you're used to from a GUI button. Of course, everything you are "used to", is just clever use of shadow and change of color tone. So, first, we will have to have a way of checking whether or not the button is being hovered. I've researched many different ways of doing so, and came up with my own.

In PyQt, a QWidget has the attribute underMouse(), which basically checks if the widget is under a cursor. The problem is, we have to have this checked permanently throughout the program. For this, we use a QTimer, that connects via its timeout signal to a function every 0,01 seconds, or 10 milliseconds. In this function, we check if the button is underMouse, or not, and change color tone and shadow accordingly.

So, create a QTimer:

self.timer = QtCore.QTimer(self)
self.timer.start(10)
self.timer.timeout.connect(self.Hover)

As well as a function Hover, to which the QTimer connects once it times out. As described above, in this function, we check if the button is under the cursor and change the tone:

def Hover(self):
        self.timer.start(10)
        if self.button.underMouse() == True:
            self.button.setStyleSheet("font-size:40px;background-color:#444444;\
            border: 2px solid #333333")
        else:
            self.button.setStyleSheet("font-size:40px;background-color:#333333;\
            border: 2px solid #222222")

As you can see, when the button is being hovered I change the color to #444444, that's one tone lighter than the original color (#000000 is black, #111111 is one tone lighter; #FFFFFF is white, #EEEEEE is one tone darker etc.), also the border color is changed to a lighter tone. You see, when the border is darker than the color of the button, it makes the button seem to "stick out". If the button isn't hovered we change it back to the original colors. In the first line, I start the timer again at 10 milliseconds, as to reset it, since it would otherwise just time out the first time and then never again.

Lastly, we also want the button to have a different tone when it is clicked, so inside the Button function, we add another change to the style sheet:

def Button(self):
        print("Clicked")
        self.timer.start(150)
        self.add.setStyleSheet("font-size:40px;background-color:#666666;\
        border: 2px solid #555555")


You may have noticed that I restarted the timer, now timing out at 150 milliseconds. We do this so that the button has the "clicked color" for longer than just 10 milliseconds, which is when the timer would time out normally, thus making the change practically invisible to the eye. When the timer times out this time, it connects to the Hover function again, where we set the timer to start at 10 milliseconds.

There you go, that's more or less how to "design" a colored and flat looking button. You can change the color to any other now, just don't forget to change the tone to one lighter in the Hover function.

6 comments :

  1. Hello, very nice tutorial but is there a way to make it global, instead of specifying a button? Image if you have alot of buttons.
    Typing them all out would be ineffecient.

    ReplyDelete
  2. Yes, certainly. As a matter of fact most of the above code for timing and hover detection is quite redundant (I was new to PyQt when I wrote this). You can set a global stylesheet from a CSS file for your application and put all the CSS in there (recommended if you plan to do more customization and styling), or just set it internally in the program as I do above. QPushButtons and most other QWidgets have a ::hover pseudo-state in CSS which you can use to automatically perform the shading for hovered buttons. This stylesheet can be set either for the Main class, which will then effect the class itself and all of its children, or for the entire QApplication, in case you have other classes derived from QDialog or QWidget or whatever floating around in your application. Something like this should get you started (app is the QApplication in main()): app.setStyleSheet("QPushButton{background: blue} QPushButton::hover{background: red}")

    ReplyDelete
    Replies
    1. For some reason, ::hover works. It's actually :hover (only one colon).

      Delete
    2. Thank you very much!
      A quick question:
      What about the clicked state? Does that remain the same as in your code?

      Delete
    3. Also, now that i got your attention!
      Your blog is really interesting, but will you be posting more Python stuff in the future?
      I want to subscribe, but i'm only interested in the Python/PyQT part! (sadly)

      Thank you for your time..

      Delete
    4. There's a :pressed pseudo-state for CSS which is activated when a button is pressed, you can set the clicked style there.There are many more pseudo-states and other properties you can set. Here's the reference for Qt's stylesheet properties and syntax: http://doc.qt.io/qt-5/stylesheet-reference.html

      You can do almost any styling you want with stylesheets and Qt let's you add custom states and properties so it's very extensible. Moreover, you can easily change the style or "theme" of your application just by changing the CSS file you load the style from. Also, I think it's a good idea to keep the style (the "view") separate from the implementation (the "model"). So it's a really great tool all together.

      Regarding future Python posts: I usually post tutorials or blog posts that relate to what I'm currently working on, which hasn't been PyQt projects for a while, but that may change. Have you had a look at the PyQt Text Editor tutorial series I published on BinPress? You can find it here: https://www.binpress.com/tutorial/building-a-text-editor-with-pyqt-part-one/143

      Delete