How to Build a Scheduling App Layout with Qt Designer and PyQt6

Plan and design a scheduling application using Qt Designer, QTableView, and model/view architecture
Heads up! You've already completed this tutorial.

I have a scheduling app at work and I'd like to recreate something similar using Qt Designer with a Python backend. The schedule is a grid/table layout showing people, times, and tasks. Where do I even begin designing something like this?

Building a scheduling application might seem like a big project, but it becomes much more manageable once you understand the right tools and approach.

In this article, we'll walk through how to plan and start building a scheduling app layout using Qt Designer and PyQt6, focusing on the QTableView widget and the model/view architecture that makes dynamic data display straightforward.

Start with a Plan on Paper

Before opening Qt Designer or writing any code, grab a piece of paper (or a whiteboard, or a tablet) and sketch out what your application needs. Think about:

  • What widgets do you need? A table for the schedule, buttons for navigation, labels for dates, dropdown menus for filtering?
  • Do you want toolbars, menus, or a status bar?
  • How is the data organized? Rows for people, columns for time slots?

This planning step saves a lot of time. Once you start building layouts in Qt Designer, restructuring things can be tedious. A rough sketch gives you a clear target to work toward.

Understanding the QMainWindow Layout

Qt provides a QMainWindow class that gives you a ready-made structure for application windows. It includes designated areas for:

  • A menu bar at the top (File, Edit, View, etc.)
  • Toolbars below the menu bar (buttons for common actions)
  • Dock widgets on the sides (optional panels that can be moved or hidden)
  • A central widget in the middle (this is where your main content goes)
  • A status bar at the bottom

For a scheduling app, the central widget would contain your schedule table, and you could use toolbars or side panels for controls like date pickers, filters, or navigation buttons.

Setting Up the Layout in Qt Designer

If you haven't used Qt Designer before, take a look at the Qt Designer tutorial to get familiar with the basics.

To set up a scheduling app layout in Qt Designer:

  1. Create a new Main Window form.
  2. From the widget palette, drag a QTableView into the central area. This will be the main schedule display.
  3. Add any other widgets you need around it — QPushButton widgets for "Previous Week" / "Next Week", a QLabel for the current date range, QComboBox for filtering by department, and so on.
  4. Use layouts (vertical, horizontal, grid) to arrange your widgets so they resize properly when the window is resized.
  5. Save the .ui file.

The QTableView widget will appear empty in Qt Designer, and that's expected. The actual schedule data is populated through code at runtime, not through the designer. Qt Designer is for arranging the static structure of your interface.

Why QTableView and the Model/View Architecture?

A scheduling grid is essentially a table — rows might represent employees, columns might represent time slots, and each cell contains a task or status. QTableView is designed exactly for this kind of display.

PyQt6 uses a model/view architecture to separate your data from how it's displayed. Here's how the pieces fit together:

  • The model holds your data and provides it to the view. You create a custom model by subclassing QAbstractTableModel.
  • The view (QTableView) reads from the model and displays it as a table. You don't manually fill cells — the view asks the model for data as needed.
  • An optional delegate controls how individual cells are drawn or edited.

This separation is powerful because your model can pull data from any source — a list of Python objects, a Pandas DataFrame, a database — and the view doesn't need to know or care where the data comes from.

For a deeper dive into table models, check out the QTableView model/view tutorial.

A Minimal Working Example

Let's put this together with a simple example. We'll create a QMainWindow with a QTableView that displays a basic weekly schedule using a custom table model.

python
import sys
from PyQt6.QtCore import Qt, QAbstractTableModel
from PyQt6.QtWidgets import (
    QApplication,
    QMainWindow,
    QTableView,
    QVBoxLayout,
    QHBoxLayout,
    QWidget,
    QLabel,
    QPushButton,
)


DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]

SCHEDULE_DATA = [
    ["Meeting", "Dev work", "Dev work", "Code review", "Planning"],
    ["Training", "Dev work", "Testing", "Dev work", "Retrospective"],
    ["Dev work", "Meeting", "Dev work", "Support", "Dev work"],
    ["Dev work", "Dev work", "Meeting", "Dev work", "Demo"],
]

EMPLOYEES = ["Alice", "Bob", "Carol", "Dan"]


class ScheduleModel(QAbstractTableModel):
    def __init__(self, schedule_data, employees, days):
        super().__init__()
        self._data = schedule_data
        self._employees = employees
        self._days = days

    def rowCount(self, parent=None):
        return len(self._data)

    def columnCount(self, parent=None):
        return len(self._days)

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            return self._data[index.row()][index.column()]
        if role == Qt.ItemDataRole.TextAlignmentRole:
            return Qt.AlignmentFlag.AlignCenter
        return None

    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                return self._days[section]
            if orientation == Qt.Orientation.Vertical:
                return self._employees[section]
        return None


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Schedule App")
        self.resize(700, 300)

        # Header with label and navigation buttons
        self.date_label = QLabel("Week of August 17, 2020")
        self.date_label.setAlignment(Qt.AlignmentFlag.AlignCenter)

        prev_button = QPushButton("← Previous Week")
        next_button = QPushButton("Next Week →")

        nav_layout = QHBoxLayout()
        nav_layout.addWidget(prev_button)
        nav_layout.addWidget(self.date_label)
        nav_layout.addWidget(next_button)

        # Table view
        self.table_view = QTableView()
        model = ScheduleModel(SCHEDULE_DATA, EMPLOYEES, DAYS)
        self.table_view.setModel(model)

        # Stretch columns to fill available space
        header = self.table_view.horizontalHeader()
        for col in range(model.columnCount()):
            header.setSectionResizeMode(
                col,
                header.ResizeMode.Stretch,
            )

        # Main layout
        layout = QVBoxLayout()
        layout.addLayout(nav_layout)
        layout.addWidget(self.table_view)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

When you run this, you'll see a window with a navigation bar at the top and a table below it showing a weekly schedule for four employees. The columns stretch to fill the window width, and the row and column headers display employee names and days of the week.

What This Example Gives You

This is a starting point — a skeleton you can build on. From here, you could:

  • Connect the navigation buttons to update the model with data for different weeks.
  • Connect to a database backend by modifying the model to fetch data from your database instead of a hardcoded list.
  • Add color coding by handling the Qt.ItemDataRole.BackgroundRole in your model's data() method to return different colors for different task types.
  • Make cells editable by implementing setData() and flags() in your model.
  • Use a custom delegate to control how cells are rendered — for example, drawing colored blocks with task names inside them, similar to what you'd see in a professional scheduling app.

Adding Color to Schedule Cells

To give you a taste of how you can enhance the display, here's an updated version of the model's data() method that adds background colors based on the task type:

python
from PyQt6.QtGui import QColor


TASK_COLORS = {
    "Meeting": QColor("#FFDDC1"),
    "Dev work": QColor("#C1E1FF"),
    "Testing": QColor("#D4EDDA"),
    "Code review": QColor("#FFF3CD"),
    "Training": QColor("#E2D9F3"),
    "Support": QColor("#F8D7DA"),
    "Planning": QColor("#D1ECF1"),
    "Retrospective": QColor("#E2D9F3"),
    "Demo": QColor("#FFF3CD"),
}


class ScheduleModel(QAbstractTableModel):
    def __init__(self, schedule_data, employees, days):
        super().__init__()
        self._data = schedule_data
        self._employees = employees
        self._days = days

    def rowCount(self, parent=None):
        return len(self._data)

    def columnCount(self, parent=None):
        return len(self._days)

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            return self._data[index.row()][index.column()]
        if role == Qt.ItemDataRole.TextAlignmentRole:
            return Qt.AlignmentFlag.AlignCenter
        if role == Qt.ItemDataRole.BackgroundRole:
            task = self._data[index.row()][index.column()]
            return TASK_COLORS.get(task)
        return None

    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                return self._days[section]
            if orientation == Qt.Orientation.Vertical:
                return self._employees[section]
        return None

With this change, each cell gets a pastel background color based on the task it contains, making the schedule much easier to read at a glance.

Complete Working Example with Colors

Here's the full application with color-coded cells included:

python
import sys
from PyQt6.QtCore import Qt, QAbstractTableModel
from PyQt6.QtGui import QColor
from PyQt6.QtWidgets import (
    QApplication,
    QMainWindow,
    QTableView,
    QVBoxLayout,
    QHBoxLayout,
    QWidget,
    QLabel,
    QPushButton,
)


DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]

SCHEDULE_DATA = [
    ["Meeting", "Dev work", "Dev work", "Code review", "Planning"],
    ["Training", "Dev work", "Testing", "Dev work", "Retrospective"],
    ["Dev work", "Meeting", "Dev work", "Support", "Dev work"],
    ["Dev work", "Dev work", "Meeting", "Dev work", "Demo"],
]

EMPLOYEES = ["Alice", "Bob", "Carol", "Dan"]

TASK_COLORS = {
    "Meeting": QColor("#FFDDC1"),
    "Dev work": QColor("#C1E1FF"),
    "Testing": QColor("#D4EDDA"),
    "Code review": QColor("#FFF3CD"),
    "Training": QColor("#E2D9F3"),
    "Support": QColor("#F8D7DA"),
    "Planning": QColor("#D1ECF1"),
    "Retrospective": QColor("#E2D9F3"),
    "Demo": QColor("#FFF3CD"),
}


class ScheduleModel(QAbstractTableModel):
    def __init__(self, schedule_data, employees, days):
        super().__init__()
        self._data = schedule_data
        self._employees = employees
        self._days = days

    def rowCount(self, parent=None):
        return len(self._data)

    def columnCount(self, parent=None):
        return len(self._days)

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            return self._data[index.row()][index.column()]
        if role == Qt.ItemDataRole.TextAlignmentRole:
            return Qt.AlignmentFlag.AlignCenter
        if role == Qt.ItemDataRole.BackgroundRole:
            task = self._data[index.row()][index.column()]
            return TASK_COLORS.get(task)
        return None

    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                return self._days[section]
            if orientation == Qt.Orientation.Vertical:
                return self._employees[section]
        return None


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Schedule App")
        self.resize(700, 350)

        # Header with label and navigation buttons
        self.date_label = QLabel("Week of August 17, 2020")
        self.date_label.setAlignment(Qt.AlignmentFlag.AlignCenter)

        prev_button = QPushButton("← Previous Week")
        next_button = QPushButton("Next Week →")

        nav_layout = QHBoxLayout()
        nav_layout.addWidget(prev_button)
        nav_layout.addWidget(self.date_label)
        nav_layout.addWidget(next_button)

        # Table view
        self.table_view = QTableView()
        model = ScheduleModel(SCHEDULE_DATA, EMPLOYEES, DAYS)
        self.table_view.setModel(model)

        # Stretch columns to fill available space
        header = self.table_view.horizontalHeader()
        for col in range(model.columnCount()):
            header.setSectionResizeMode(
                col,
                header.ResizeMode.Stretch,
            )

        # Main layout
        layout = QVBoxLayout()
        layout.addLayout(nav_layout)
        layout.addWidget(self.table_view)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

Where to Go from Here

You now have a solid foundation: a QMainWindow with a navigation bar and a color-coded QTableView powered by a custom model. The next steps to turn this into a full scheduling application would be:

  1. Connect to your database. Modify the model to load schedule data from your backend instead of a hardcoded list. The beauty of the model/view architecture is that the view doesn't change at all — you only update the model.

  2. Add interactivity. Wire up the Previous/Next buttons to change the displayed week. Add click handlers on cells to let users create or edit schedule entries.

  3. Design the UI in Qt Designer. Now that you understand which widgets you need (QTableView, QPushButton, QLabel), you can arrange them visually in Qt Designer and load the .ui file in your code. The Qt Designer tutorial covers how to do this.

  4. Explore custom delegates. If you want cells that look more like the colored blocks in a professional scheduling tool (with rounded corners, icons, or multi-line text), look into QStyledItemDelegate for custom cell rendering.

Starting with a clear plan, using QTableView with a custom model, and building up features incrementally is a reliable path to a polished scheduling application.

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

Create GUI Applications with Python & Qt6 by Martin Fitzpatrick

(PyQt6 Edition) The hands-on guide to making apps with Python — Over 15,000 copies sold!

More info Get the book

Martin Fitzpatrick

How to Build a Scheduling App Layout with Qt Designer and PyQt6 was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.