Search code examples
qtpyqtpyqt5composition

PyQt5 - encapsulate / make a component reusable - example: QTimer/QLabel implementation outside of parent class


I implement a label that displays the current time in several of my PyQt5 applications. Here is a MRE:

import sys 
import logging

from PyQt5 import QtWidgets, QtCore, QtGui

__log__ = logging.getLogger()


class App(QtWidgets.QApplication):
    
    def __init__(self, sys_argv):
        super(App, self).__init__(sys_argv)
        self.main_view = MainView()
        self.main_view.show()


class MainView(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setObjectName("MreUi")
        self.resize(300, 100)
        self.setWindowTitle('MreUi')
        self.label = QtWidgets.QLabel()
        self.setCentralWidget(self.label)
        config_clock(self.label)
        self.start_clocks()
        
    def start_clocks(self):
        timer = QtCore.QTimer(self)
        timer.timeout.connect(self.show_time)  
        timer.start(1000)
        
    def show_time(self):
        current_time = QtCore.QTime.currentTime()
        clock_label_time = current_time.toString('hh:mm:ss')
        self.label.setText(clock_label_time)


def config_clock(label):
    label.setAlignment(QtCore.Qt.AlignCenter)
    font = QtGui.QFont('Arial', 24, QtGui.QFont.Bold)
    label.setFont(font)
        
        
if __name__ == '__main__':
    logging.basicConfig()
    app = App(sys.argv)
    try:
        sys.exit(app.exec_())
    except Exception as e:
        __log__.error('%s', e)

As I implemented a similar clock in several of my PyQt apps, I thought it would be nice to implement it as a component / encapsulate it. First I thought of doing this by calling a config_clock function from any QWidget, and have that function do ~ALL of the work implementing the clock for the specified label. This would avoid having to repeat myself in multiple applications from writing/calling start_clocks and show_time instance methods of MainView. but as I started to code that ...

# from inside my QWidget:
config_clock(self.label)

# this function would live outisde the class, thus reusable by diff Qt apps:
def config_clock(label):
   # start the clock
   # set default font, etc for label
   # instantiate QtCore.QTimer
   # # but that's when I realized I've always passed self to QtCore.QTimer and that maybe encapsulating this isn't as trivial as I thought.

Should I create some kind of ClockLabel() object of my own that gets passed a QtWidget's label and can also be an instance attribute of each QtWidget that might need it? That smells kind of clunky to me. But surely there must be a way to make a 'reusable component' in PyQt, I just don't know how...

I also am not certain if the MainView(QtWidgets.QMainWindow) could rightly be referred to as the 'parent class' if I were to pass it as a parameter to a ClockLabel() class I write or a config_clock function whose signature could look like:

def config_clock(label, parent_qtwidget):
# also feels clunky and not sure if parent would be the right term

Thanks


Solution

  • With QtWidgets it is normal to specialize widgets by inheritance. Here is an example of how you might rearrange your code to produce a reusable widget:

    class ClockLabel(QtWidgets.QLabel):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.setAlignment(QtCore.Qt.AlignCenter)
            font = QtGui.QFont('Arial', 24, QtGui.QFont.Bold)
            self.setFont(font)
    
            self._timer = QtCore.QTimer(self)
            self._timer.timeout.connect(self._show_time)
    
        def start(self):
            self._timer.start(1000)
    
        def stop(self):
            self._timer.stop()
    
        def _show_time(self):
            current_time = QtCore.QTime.currentTime()
            clock_label_time = current_time.toString('hh:mm:ss')
            self.setText(clock_label_time)