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:
- Create a new Main Window form.
- From the widget palette, drag a
QTableViewinto the central area. This will be the main schedule display. - Add any other widgets you need around it —
QPushButtonwidgets for "Previous Week" / "Next Week", aQLabelfor the current date range,QComboBoxfor filtering by department, and so on. - Use layouts (vertical, horizontal, grid) to arrange your widgets so they resize properly when the window is resized.
- Save the
.uifile.
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.
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.BackgroundRolein your model'sdata()method to return different colors for different task types. - Make cells editable by implementing
setData()andflags()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:
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:
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:
-
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.
-
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.
-
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.uifile in your code. The Qt Designer tutorial covers how to do this. -
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
QStyledItemDelegatefor 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.
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!