Skip to content

Conversation

@FeodorFitsner
Copy link
Contributor

@FeodorFitsner FeodorFitsner commented Nov 25, 2025

This is how we can define custom control and their default property values:

from dataclasses import dataclass, field
from typing import Any

import flet as ft


def main(page: ft.Page):
    @ft.control
    class MyButton(ft.Button):
        expand: int = field(default_factory=lambda: 1)
        style: ft.ButtonStyle = field(
            default_factory=lambda: ft.ButtonStyle(
                shape=ft.RoundedRectangleBorder(radius=10)
            )
        )
        bgcolor: ft.Colors = ft.Colors.BLUE_ACCENT
        icon: Any = ft.Icons.HEADPHONES

    @dataclass
    class MyButton2(ft.Button):
        expand: Any = 1
        bgcolor: ft.Colors = ft.Colors.GREEN_ACCENT
        style: ft.ButtonStyle = field(
            default_factory=lambda: ft.ButtonStyle(
                shape=ft.RoundedRectangleBorder(radius=20)
            )
        )
        icon: ft.IconDataOrControl = ft.Icons.HEADPHONES

    @ft.control
    class MyButton3(ft.Button):
        def init(self):
            self.expand = 1
            self.bgcolor = ft.Colors.RED_ACCENT
            self.style = ft.ButtonStyle(shape=ft.RoundedRectangleBorder(radius=30))
            self.icon = ft.Icons.HEADPHONES

    page.add(
        ft.Row([MyButton(content="1")]),
        ft.Row([MyButton2(content="2")]),
        ft.Row([MyButton3(content="3")]),
    )


ft.run(main)

Rules:

  1. You should define either @dataclass or @ft.control decorator on the inherited class - both methods works the same.
  2. A field, to be a class field, must have a type annotation, so expand: int = 1 will override inherited property, but expand = 1 won't. Not sure which type to use? Just Any will work, for example expand: Any = 1.
  3. Use literal default value for simple data types, such as int, bool, str and field(default_factory=lambda: <new_value>) for immutable types such as class, list, dict.
  4. You can set property values in init() method, but you won't be able to override if write MyButton3(expand=False).

Summary by Sourcery

Support correct encoding of dataclass-based controls with overridden default values and document custom control default configuration through examples.

New Features:

  • Add support for emitting overridden default values when encoding dataclass-based controls that inherit from other controls.
  • Provide an example app demonstrating different ways to define custom controls and their default property values using dataclasses and control decorators.

Bug Fixes:

  • Fix control encoding so that defaults defined on base dataclass controls are respected while still emitting overridden defaults from subclasses.

Tests:

  • Add a regression test ensuring that encoded output includes subclass-overridden defaults for dataclass control fields.

Added logic to retrieve default values from base dataclasses when encoding controls, ensuring overridden defaults in subclasses are correctly emitted. Includes new tests and example usage with custom button controls.
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've reviewed this pull request using the Sourcery rules engine

@cloudflare-workers-and-pages
Copy link

Deploying flet-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 464cafc
Status: ✅  Deploy successful!
Preview URL: https://30a8a486.flet-docs.pages.dev
Branch Preview URL: https://override-control-props-fix.flet-docs.pages.dev

View logs

Copy link
Contributor

@ndonkoHenri ndonkoHenri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice feature!

Can you please update docs too: https://docs.flet.dev/cookbook/custom-controls/ ?

Comment on lines 25 to 64
def configure_encode_object_for_msgpack(control_cls):
def encode_object_for_msgpack(obj):
if is_dataclass(obj):
r = {}
prev_lists = {}
prev_dicts = {}
prev_classes = {}
for field in fields(obj):
if "skip" in field.metadata: # or hasattr(obj, f"_prev_{field.name}"):
continue
v = getattr(obj, field.name)
if isinstance(v, list):
v = v[:]
if len(v) > 0:
r[field.name] = v
prev_lists[field.name] = v
elif isinstance(v, dict):
v = v.copy()
if len(v) > 0:
r[field.name] = v
prev_dicts[field.name] = v
elif field.name.startswith("on_") and field.metadata.get("event", True):
v = v is not None
if v:
r[field.name] = v
elif is_dataclass(v):
r[field.name] = v
prev_classes[field.name] = v
elif v is not None and (
v != field.default or not isinstance(obj, control_cls)
):
r[field.name] = v
else:
default_value = field.default
if isinstance(obj, control_cls):
root_field = _get_root_dataclass_field(
obj.__class__, field.name
)
if root_field is not None:
default_value = root_field.default
if v is not None and (
v != default_value or not isinstance(obj, control_cls)
):
r[field.name] = v
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit;

Me going through this function: 😵‍💫
Little comments here and there will be awesome.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😈

@FeodorFitsner FeodorFitsner merged commit 862ffbf into main Nov 26, 2025
41 of 53 checks passed
@FeodorFitsner FeodorFitsner deleted the override-control-props-fix branch November 26, 2025 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants