Skip to content

Commit 01d28c6

Browse files
v1: Add Dropdown.text property, add on_select event (#5593)
* Remove before_update method from Dropdown Deleted the before_update method in the Dropdown class, which previously set expand_loose based on expand. This change may address redundant or obsolete logic related to display issues. * Add reactive dropdown example using Flet Introduces a new example demonstrating a reactive dropdown control in Flet. The example uses a dataclass to manage state and updates the selected color dynamically. * Refactor Dropdown event and property names Renamed Dropdown event handlers from on_change to on_select and on_text_change to on_change for clarity and consistency. Updated related property names from 'value' to 'text' where appropriate, and improved documentation for several Dropdown properties. Added a new example demonstrating both select and change events. * Copilot fix: border_radius docs Co-authored-by: Copilot <[email protected]> * Revert "Copilot fix: border_radius docs" This reverts commit 536e8b3. * Clarify border_radius docstring in Dropdown Expanded the docstring for the border_radius property to specify accepted value types and default behavior when set to None. * Do not implicitly create `storage/data` and `temp` dir on flet run * Add CPU-bound async examples to Python cookbook Introduces three new example scripts demonstrating CPU-bound task handling in Flet apps: cpu_bound_callback.py (thread-safe UI updates via callback), cpu_bound_queue.py (progress updates via asyncio.Queue), and cpu_bound_png_stream.py (streaming PNG frames from a producer thread to an async consumer). These examples illustrate best practices for integrating blocking operations with asyncio and Flet UI. --------- Co-authored-by: Copilot <[email protected]>
1 parent 7b38442 commit 01d28c6

File tree

11 files changed

+423
-93
lines changed

11 files changed

+423
-93
lines changed

packages/flet/lib/src/controls/dropdown.dart

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ class _DropdownControlState extends State<DropdownControl> {
2222
super.initState();
2323
_focusNode = FocusNode();
2424

25-
_controller =
26-
TextEditingController(text: widget.control.getString("value"));
25+
_controller = TextEditingController(text: widget.control.getString("text"));
2726
_focusNode.addListener(_onFocusChange);
2827
widget.control.addInvokeMethodListener(_invokeMethod);
2928

@@ -32,9 +31,9 @@ class _DropdownControlState extends State<DropdownControl> {
3231

3332
void _onTextChange() {
3433
debugPrint("Typed text: ${_controller.text}");
35-
if (_controller.text != widget.control.getString("value")) {
36-
widget.control.updateProperties({"value": _controller.text});
37-
widget.control.triggerEvent("text_change", _controller.text);
34+
if (_controller.text != widget.control.getString("text")) {
35+
widget.control.updateProperties({"text": _controller.text});
36+
widget.control.triggerEvent("change", _controller.text);
3837
}
3938
}
4039

@@ -224,7 +223,7 @@ class _DropdownControlState extends State<DropdownControl> {
224223
? null
225224
: (String? value) {
226225
widget.control.updateProperties({"value": value});
227-
widget.control.triggerEvent("change", value);
226+
widget.control.triggerEvent("select", value);
228227
},
229228
dropdownMenuEntries: items,
230229
);

sdk/python/examples/apps/controls-gallery/components/properties_table.py

Lines changed: 59 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ def did_mount(self):
1111
self.update_source_code(self.control)
1212

1313
def update_source_code(self, control):
14-
text = ""
1514
# for property in self.properties:
1615
# if type(getattr(self.control, property["name"])).__name__ == "str":
1716
# property_value = f"""'{getattr(self.control, property["name"])}'"""
@@ -45,7 +44,7 @@ def __init__(self, properties, control, top_control=None):
4544
self.divider_thickness = 3
4645
self.width = 500
4746
self.auto_scroll = True
48-
if top_control == None:
47+
if top_control is None:
4948
self.top_control = control
5049
else:
5150
self.top_control = top_control
@@ -59,7 +58,7 @@ def switch_changed(e):
5958
setattr(self.control, property["name"], None)
6059
self.top_control.update()
6160

62-
if getattr(self.control, property["name"]) == None:
61+
if getattr(self.control, property["name"]) is None:
6362
switch_value = False
6463
else:
6564
switch_value = True
@@ -89,9 +88,9 @@ def get_properties_list(self):
8988

9089
for property in self.properties:
9190

92-
def add_list_item(e):
91+
def add_list_item(e, property=property):
9392
items_list = getattr(self.control, property["name"])
94-
if items_list == None:
93+
if items_list is None:
9594
items_list = []
9695
dataclass_type = property["dataclass"]
9796
# adding new item to a list
@@ -106,10 +105,10 @@ def add_list_item(e):
106105
if "list" in property["value_type"]:
107106
list_items = []
108107
n = 0
109-
if value != None:
110-
for item in value:
108+
if value is not None:
109+
for _ in value:
111110

112-
def delete_item(e):
111+
def delete_item(e, property=property):
113112
items_list = getattr(self.control, property["name"])
114113
# removing item from the list
115114
items_list.remove(items_list[e.control.data])
@@ -173,7 +172,7 @@ def delete_item(e):
173172
)
174173
)
175174
elif property["value_type"] == "dataclass":
176-
if value == None:
175+
if value is None:
177176
dataclass_type = property["dataclass"]
178177
value = dataclass_type()
179178
print(value)
@@ -216,62 +215,58 @@ def value_changed(self, e):
216215
def get_value_control(self, property):
217216
value = getattr(self.control, property["name"])
218217

219-
match property["value_type"]:
220-
case "str":
221-
return ft.TextField(
222-
border_color=ft.Colors.SECONDARY,
223-
content_padding=3,
224-
value=value,
225-
data=property["name"],
226-
on_change=self.value_changed,
227-
)
228-
case "number":
229-
return ft.Row(
230-
alignment=ft.MainAxisAlignment.CENTER,
231-
controls=[
232-
ft.Text(property["min"]),
233-
ft.Slider(
234-
min=property["min"],
235-
max=property["max"],
236-
label="{value}%",
237-
value=value,
238-
data=property["name"],
239-
on_change=self.value_changed,
240-
),
241-
ft.Text(property["max"]),
242-
],
243-
)
244-
case "bool":
245-
return ft.Checkbox(
246-
value=value,
247-
data=property["name"],
248-
on_change=self.value_changed,
249-
)
250-
case "enum":
251-
options = []
218+
if property["value_type"] == "str":
219+
return ft.TextField(
220+
border_color=ft.Colors.SECONDARY,
221+
content_padding=3,
222+
value=value,
223+
data=property["name"],
224+
on_change=self.value_changed,
225+
)
226+
elif property["value_type"] == "number":
227+
return ft.Row(
228+
alignment=ft.MainAxisAlignment.CENTER,
229+
controls=[
230+
ft.Text(property["min"]),
231+
ft.Slider(
232+
min=property["min"],
233+
max=property["max"],
234+
label="{value}%",
235+
value=value,
236+
data=property["name"],
237+
on_change=self.value_changed,
238+
),
239+
ft.Text(property["max"]),
240+
],
241+
)
242+
elif property["value_type"] == "bool":
243+
return ft.Checkbox(
244+
value=value,
245+
data=property["name"],
246+
on_change=self.value_changed,
247+
)
248+
elif property["value_type"] == "enum":
249+
options = []
252250

253-
options_list = property["values"]
254-
for item in options_list:
255-
options.append(ft.dropdown.Option(item.value))
251+
options_list = property["values"]
252+
for item in options_list:
253+
options.append(ft.dropdown.Option(item.value))
256254

257-
return ft.Dropdown(
258-
options=options,
259-
value=value,
260-
data=property["name"],
261-
on_change=self.value_changed,
262-
)
255+
return ft.Dropdown(
256+
options=options,
257+
value=value,
258+
data=property["name"],
259+
on_select=self.value_changed,
260+
)
261+
elif property["value_type"] == "dataclass":
262+
if value is None:
263+
print("This dataclass value is None")
263264

264-
case "dataclass":
265-
if value == None:
266-
print("This dataclass value is None")
265+
properties_list = PropertiesList(
266+
properties=property["properties"], control=value
267+
)
267268

268-
properties_list = PropertiesList(
269-
properties=property["properties"], control=value
270-
)
271-
272-
return properties_list
273-
# ft.Container(bgcolor=ft.Colors.YELLOW, width=30, height=30)
274-
275-
# If an exact match is not confirmed, this last case will be used if provided
276-
case _:
277-
return ft.Text("Something's wrong with the type")
269+
return properties_list
270+
# ft.Container(bgcolor=ft.Colors.YELLOW, width=30, height=30)
271+
else:
272+
return ft.Text("Something's wrong with the type")

sdk/python/examples/apps/controls-gallery/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ authors = [
77
]
88
license = "MIT"
99
readme = "README.md"
10-
requires-python = ">=3.9"
10+
requires-python = ">=3.10"
1111
dependencies = [
1212
"flet",
1313
"flet-contrib==2024.12.3"

sdk/python/examples/controls/dropdown/color_selection_with_filtering.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def get_options():
2020
for color in colors
2121
]
2222

23-
def handle_dropdown_change(e: ft.Event[ft.Dropdown]):
23+
def handle_dropdown_select(e: ft.Event[ft.Dropdown]):
2424
e.control.color = e.control.value
2525
page.update()
2626

@@ -29,7 +29,7 @@ def handle_dropdown_change(e: ft.Event[ft.Dropdown]):
2929
editable=True,
3030
label="Color",
3131
options=get_options(),
32-
on_change=handle_dropdown_change,
32+
on_select=handle_dropdown_select,
3333
)
3434
)
3535

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from dataclasses import dataclass
2+
from typing import cast
3+
4+
import flet as ft
5+
6+
7+
@dataclass
8+
class Form:
9+
color: str = "red"
10+
11+
def change_color(self, new_color: str):
12+
print("New color:", new_color)
13+
self.color = new_color
14+
15+
16+
def main(page: ft.Page):
17+
form = Form()
18+
page.theme_mode = ft.ThemeMode.LIGHT
19+
20+
page.add(
21+
ft.SelectionArea(
22+
ft.StateView(
23+
form,
24+
lambda state: ft.Column(
25+
cast(
26+
list[ft.Control],
27+
[
28+
ft.Text(f"Selected color: {form.color}"),
29+
ft.Column(
30+
[
31+
ft.Dropdown(
32+
editable=True,
33+
label="Color",
34+
value=form.color,
35+
on_select=lambda e: form.change_color(
36+
cast(str, e.control.value)
37+
),
38+
options=[
39+
ft.DropdownOption(key="red", text="Red"),
40+
ft.DropdownOption(
41+
key="green", text="Green"
42+
),
43+
ft.DropdownOption(key="blue", text="Blue"),
44+
],
45+
),
46+
]
47+
),
48+
],
49+
)
50+
),
51+
)
52+
)
53+
)
54+
55+
56+
ft.run(main)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import flet as ft
2+
3+
4+
def main(page: ft.Page):
5+
colors = [
6+
ft.Colors.RED,
7+
ft.Colors.BLUE,
8+
ft.Colors.YELLOW,
9+
ft.Colors.PURPLE,
10+
ft.Colors.LIME,
11+
]
12+
13+
def get_options():
14+
options = []
15+
for color in colors:
16+
options.append(
17+
ft.DropdownOption(
18+
key=color.value,
19+
content=ft.Text(
20+
value=color.value,
21+
color=color,
22+
),
23+
leading_icon=ft.Icon(ft.Icons.PALETTE, color=color),
24+
)
25+
)
26+
return options
27+
28+
def dropdown_select(e):
29+
e.control.color = e.control.value
30+
display_value.value = f"VALUE changed to {e.control.value}"
31+
32+
def dropdown_change(e):
33+
display_text.value = f"TEXT changed to {e.control.text}"
34+
35+
page.scroll = ft.ScrollMode.AUTO
36+
page.add(
37+
display_value := ft.Text(),
38+
display_text := ft.Text(),
39+
ft.Dropdown(
40+
editable=True,
41+
label="Color",
42+
width=float("inf"),
43+
options=get_options(),
44+
on_select=dropdown_select,
45+
on_change=dropdown_change,
46+
),
47+
)
48+
49+
50+
ft.run(main)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import asyncio
2+
import time
3+
4+
import flet as ft
5+
6+
7+
class CpuProgressApp:
8+
def __init__(self, page: ft.Page):
9+
self.page = page
10+
self.loop = asyncio.get_running_loop()
11+
12+
self.page.title = "Simple thread + asyncio task"
13+
self.pb = ft.ProgressBar(value=0.0, width=420)
14+
self.btn = ft.Button("Start", on_click=self.start_clicked)
15+
self.page.add(ft.Column([self.btn, self.pb], tight=True, spacing=10))
16+
17+
# ---------- Thread-safe UI updates ----------
18+
async def set_progress(self, p: float):
19+
self.pb.value = p
20+
self.page.update()
21+
22+
def report_callback(self, p: float):
23+
# Called from worker thread -> hop to main asyncio loop
24+
asyncio.run_coroutine_threadsafe(self.set_progress(p), self.loop)
25+
26+
# ---------- Handlers ----------
27+
async def start_clicked(self, _=None):
28+
self.btn.disabled = True
29+
await self.set_progress(0.0)
30+
31+
# Run blocking job in a thread and wait for it to finish
32+
await asyncio.to_thread(self.long_job, 100, self.report_callback)
33+
34+
self.btn.disabled = False
35+
await self.set_progress(1.0)
36+
37+
# ---------- Blocking job ----------
38+
@staticmethod
39+
def long_job(steps: int, report_callback):
40+
for i in range(steps):
41+
_ = sum(range(200_000)) # small CPU burn; bump if needed
42+
time.sleep(0.1)
43+
report_callback((i + 1) / steps) # 0..1
44+
45+
46+
if __name__ == "__main__":
47+
ft.run(CpuProgressApp)

0 commit comments

Comments
 (0)