Using Qt Designer .ui Files with fbs for PyQt5 Packaging

Load your Qt Designer interfaces directly in fbs-based applications without converting to Python
Heads up! You've already completed this tutorial.

If you're building a PyQt5 desktop application, there's a good chance you're using Qt Designer to lay out your interface visually. And if you want to package that application into a standalone executable, fbs (fman build system) is a popular choice that simplifies the whole process.

The question is: how do you connect these two tools? Specifically, can you load .ui files directly in your fbs application — without converting them to Python code first using pyuic5?

Yes, you can. In this tutorial, we'll walk through exactly how to do it.

The usual workflow (and why it's annoying)

When you design a window in Qt Designer, the result is a .ui file — an XML description of your layout. To use it in Python, many developers run pyuic5 to convert it into a .py file:

sh
pyuic5 mainwindow.ui -o mainwindow_ui.py

This works, but it adds friction. Every time you tweak the layout in Qt Designer — adjusting a label, adding a button, rearranging a group box — you need to re-run that conversion command before your changes appear. If you forget, you'll be scratching your head wondering why nothing changed.

A smoother approach is to load the .ui file directly at runtime using uic.loadUiType(). This way, you edit in Qt Designer, save, and immediately run your application to see the result. No extra conversion step needed.

Loading .ui files with uic.loadUiType()

The PyQt5.uic module provides a function called loadUiType() that reads a .ui file and returns two things: a generated form class and the Qt base class it's built on. You can then use these with multiple inheritance to create your main window class.

Here's a minimal standalone example (no fbs yet):

python
import sys

from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow

# Load the .ui file. This returns a tuple: (form_class, base_class)
Ui_MainWindow, QtBaseClass = uic.loadUiType("mainwindow.ui")


class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        # Now you can access any widget you named in Qt Designer.
        # For example, if you have a QPushButton called "myButton":
        # self.myButton.clicked.connect(self.on_button_clicked)

    def on_button_clicked(self):
        print("Button clicked!")


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

After calling self.setupUi(self), every widget you named in Qt Designer becomes an attribute on self. If you named a QLineEdit widget searchInput in Designer, you can access it as self.searchInput in your Python code. No need for findChild() calls or any special loop — setupUi() handles all of that for you.

Setting up an fbs project

If you haven't used fbs before, start by installing it and creating a new project:

sh
pip install fbs PyQt5
fbs startproject

This creates a project structure like:

python
src/
└── main/
    ├── icons/
    │   └── base/
    │       └── ...
    ├── python/
    │   └── main.py
    └── resources/
        └── base/
            └── ...

The main.py file inside src/main/python/ is your application entry point. The src/main/resources/base/ directory is where fbs looks for resource files that get bundled with your app.

Where to put the .ui file

For fbs to include your .ui file when packaging, place it in the resources directory:

python
src/
└── main/
    ├── python/
    │   └── main.py
    └── resources/
        └── base/
            └── mainwindow.ui

The ApplicationContext class from fbs provides a get_resource() method that resolves the correct path to files in this directory — both during development (fbs run) and after packaging (fbs freeze). This is how you make sure the .ui file is always found, regardless of where or how the app is launched.

Putting it all together: .ui files with fbs

Here's a complete, working example that loads a .ui file inside an fbs application:

python
import sys

from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow
from fbs_runtime.application_context.PyQt5 import (
    ApplicationContext,
    cached_property,
)


class AppContext(ApplicationContext):
    def run(self):
        self.main_window.show()
        return self.app.exec_()

    @cached_property
    def main_window(self):
        return MainWindow(self)


class MainWindow(QMainWindow):
    def __init__(self, ctx):
        super().__init__()
        self.ctx = ctx

        # Use ctx.get_resource() to find the .ui file in the
        # resources folder. This works both during development
        # and after freezing with fbs.
        ui_path = self.ctx.get_resource("mainwindow.ui")
        Ui_MainWindow, QtBaseClass = uic.loadUiType(ui_path)

        # We need to apply the UI to self. Create a temporary
        # mixin and call setupUi.
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # Connect signals and slots, set initial values, etc.
        # Access widgets via self.ui.<widget_name>
        # For example:
        # self.ui.myButton.clicked.connect(self.on_button_clicked)

    def on_button_clicked(self):
        print("Button was clicked!")


if __name__ == "__main__":
    appctxt = AppContext()
    exit_code = appctxt.run()
    sys.exit(exit_code)

There's a subtle difference from the standalone example earlier. Because uic.loadUiType() is called inside __init__ (after resolving the resource path), we can't use the returned classes in the class statement's inheritance list. Instead, we create an instance of the UI class (self.ui = Ui_MainWindow()) and call self.ui.setupUi(self). Widgets are then accessible through self.ui — for example, self.ui.myButton.

If you prefer accessing widgets directly on self (without the self.ui prefix), you can restructure things slightly by moving the loadUiType call to module level and using multiple inheritance:

python
import sys
import os

from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow
from fbs_runtime.application_context.PyQt5 import (
    ApplicationContext,
    cached_property,
)


class AppContext(ApplicationContext):
    def run(self):
        self.main_window.show()
        return self.app.exec_()

    @cached_property
    def main_window(self):
        return MainWindow(self)

    @cached_property
    def ui_base_classes(self):
        """Load and cache the UI classes from the .ui file."""
        ui_path = self.get_resource("mainwindow.ui")
        return uic.loadUiType(ui_path)


class MainWindow(QMainWindow):
    def __init__(self, ctx):
        super().__init__()
        self.ctx = ctx

        # Get the generated UI class and call setupUi
        Ui_MainWindow, _ = ctx.ui_base_classes
        self.ui_class = Ui_MainWindow()
        self.ui_class.setupUi(self)

        # Widgets are now accessible directly on self:
        # self.myButton.clicked.connect(self.on_button_clicked)


if __name__ == "__main__":
    appctxt = AppContext()
    exit_code = appctxt.run()
    sys.exit(exit_code)

When setupUi(self) is called with self (the QMainWindow) as the argument, it sets up all the widgets as attributes on that window. So self.myButton, self.searchInput, and any other named widget from your .ui file are all available directly.

Creating a simple .ui file to test with

If you don't already have a .ui file, open Qt Designer and create a new Main Window. Add a few widgets — perhaps a QLabel, a QLineEdit, and a QPushButton. Give them descriptive object names in the property editor (for example, greetingLabel, nameInput, and greetButton).

Save the file as mainwindow.ui in your src/main/resources/base/ folder.

A complete working example

Here's everything together, including signal connections, using a simple greeting app as an example. This assumes you have a mainwindow.ui file with a QLabel named greetingLabel, a QLineEdit named nameInput, and a QPushButton named greetButton.

python
import sys

from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow
from fbs_runtime.application_context.PyQt5 import (
    ApplicationContext,
    cached_property,
)


class AppContext(ApplicationContext):
    def run(self):
        version = self.build_settings["version"]
        self.main_window.setWindowTitle(
            "Greeting App v" + version
        )
        self.main_window.show()
        return self.app.exec_()

    @cached_property
    def main_window(self):
        return MainWindow(self)


class MainWindow(QMainWindow):
    def __init__(self, ctx):
        super().__init__()
        self.ctx = ctx

        # Load the .ui file from fbs resources
        ui_path = self.ctx.get_resource("mainwindow.ui")
        Ui_MainWindow, _ = uic.loadUiType(ui_path)

        # Apply the UI to this window
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # Set initial state
        self.ui.greetingLabel.setText("Enter your name below:")

        # Connect signals
        self.ui.greetButton.clicked.connect(self.greet)

    def greet(self):
        name = self.ui.nameInput.text().strip()
        if name:
            self.ui.greetingLabel.setText(f"Hello, {name}!")
        else:
            self.ui.greetingLabel.setText("Please enter your name first.")


if __name__ == "__main__":
    appctxt = AppContext()
    exit_code = appctxt.run()
    sys.exit(exit_code)

Run this with:

sh
fbs run

You should see your Designer-created interface appear, fully functional. Edit the .ui file in Qt Designer, save, and run fbs run again — your changes will appear immediately, no conversion step needed.

Packaging your app

When you're ready to create a distributable executable:

sh
fbs freeze

Because the .ui file lives in src/main/resources/base/, fbs automatically includes it in the packaged application. The get_resource() call resolves to the correct bundled path at runtime, so everything works the same way in the packaged version as it does during development.

You can also build an installer:

sh
fbs installer

This workflow — designing visually in Qt Designer and loading the .ui file directly at runtime — gives you the best of both worlds: rapid visual prototyping with the full power of Python for application logic. Combined with fbs for packaging, you have a complete pipeline from design to distributable 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

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

More info Get the book

Martin Fitzpatrick

Using Qt Designer .ui Files with fbs for PyQt5 Packaging 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.