Skip to content

bgriffen/Python3GUITemplate

Repository files navigation

Python3 GUI Template

A minimal, working starting point for scientific desktop apps built with Traits, TraitsUI, Qt5 and matplotlib.

CI Python Qt License

The application on launch

Python2 is dead. Long live Python3.

This template is an upgrade of my Python2 template, stripped back to the simplest useful combination of Traits/TraitsUI and matplotlib — no Mayavi. The figure embedding leans on Pierre Haessig's gist, ported here to Qt5.

Copy the repository, rename it, and start replacing the example tab with your own.

Features

  • An embedded matplotlib canvas with the standard navigation toolbar (pan, zoom, save), wired into TraitsUI as a reusable editor.
  • A split layout — the shared figure on the left, a tabbed panel of controls on the right — that scales to as many tabs as you need.
  • Live plot styling: marker shape, size and colour controls that act on the plotted data immediately.
  • A worked example of the common widgets — sliders, checkboxes, dropdowns, text fields, a directory picker, buttons, read-only outputs, and a group that enables itself conditionally.
  • Headless tests and CI, so you can refactor the template without wondering what you broke.

Prerequisites

  • Python 3.9 or newer
  • A Qt binding. requirements.txt installs PyQt5; PySide2 and PySide6 also work, as pyface uses whichever it finds.

On a bare Linux machine you may also need Qt's system libraries — see the CI workflow for the exact package list.

Installation

With pip:

git clone https://github.com/bgriffen/Python3GUITemplate.git
cd Python3GUITemplate
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

Or with conda:

conda env create -f environment.yml
conda activate Python3GUITemplate

Usage

python main.py

Drag the gradient and y-intercept sliders to redraw the line, then change the Marker, Size and Colour controls to restyle it. Button 1 rolls a random number, which disables the Boolean Option group once it lands above 0.5 — an example of enabled_when.

To check your environment in isolation before debugging the full app:

python standalonematplotlib.py

That opens a single figure with a rectangle selector attached; drag a box over the curve and the selection coordinates are printed to stdout.

Project structure

File Purpose
main.py ApplicationMain — the window, the shared figure, and the marker controls. Start here.
firstcalc.py FirstCalc — the example tab. Copy this to add your own.
CommonMPL.py MPLFigureEditor, the TraitsUI editor that embeds a matplotlib figure in Qt.
standalonematplotlib.py The editor on its own, for smoke-testing an environment.
tests/ Headless regression tests.

Adding a tab

Write a HasTraits class with a view, taking the main window as its first argument — say in a new secondcalc.py:

class SecondCalc(HasTraits):
    amplitude = Range(0.0, 5.0, 1.0)
    view = View(Item("amplitude"))

    def __init__(self, main, **kwargs):
        super().__init__(**kwargs)
        self.main = main

    def _amplitude_changed(self):
        x = np.linspace(0, 10, 200)
        self.main.plot_xy(x, self.amplitude * np.sin(x))

Then import it in main.py and register it on ApplicationMain:

secondcalc = Instance(SecondCalc)

def _secondcalc_default(self):
    return SecondCalc(self)

right_panel = Tabbed(
    Item("firstcalc", style="custom", label="First Tab", show_label=False),
    Item("secondcalc", style="custom", label="Second Tab", show_label=False),
)

Route drawing through ApplicationMain.plot_xy() rather than touching the figure directly. It reuses the axes and keeps the marker controls pointing at the current line; clearing the figure yourself throws away the axis labels and silently breaks them.

Testing

pip install pytest
pytest

The suite runs without a display — conftest.py selects Qt's offscreen platform plugin — so it works over ssh, in containers and in CI.

ruff check .    # lint, matching the CI job

Notes on Traits

Traits calls a method named _<trait>_changed whenever that trait's value changes, and _<trait>_fired when a Button is pressed. That naming is the whole wiring mechanism — there is no explicit connect step. Two consequences worth knowing:

  • A Range whose default sits outside its own bounds will be clamped by the editor as the UI is built, firing a change handler before the window is ever shown.
  • The methods are bound when the class is created, so patching one afterwards has no effect.

There is comprehensive documentation for Traits and a set of TraitsUI tutorials.

Acknowledgments

License

MIT

About

A minimal, working template for scientific desktop apps built with Traits, TraitsUI, Qt5 and matplotlib.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages