Flet is a rich User Interface framework to quickly build interactive web, desktop and mobile apps in Python without prior knowledge of web technologies.
—
This document covers Flet's comprehensive event system and user interaction capabilities, including gesture recognition, drag and drop, keyboard/mouse events, and real-time communication.
import flet as ftAll Flet controls support event handling through callback functions. Events are triggered by user interactions and system changes.
Common Event Patterns:
def handle_click(e):
# e.control - reference to the control that triggered the event
# e.data - event-specific data
print(f"Button clicked: {e.control.text}")
button = ft.ElevatedButton(
"Click me!",
on_click=handle_click
)class ControlEvent:
"""Base control event class."""
control: Control # Control that triggered the event
name: str # Event name
data: str # Event data (varies by event type)
page: Page # Page reference
target: str # Target control IDclass Event:
"""Base event class for system events."""
name: str # Event name
data: str # Event data
page: Page # Page referenceclass TapEvent(ControlEvent):
"""Tap gesture event."""
local_x: float # X coordinate relative to control
local_y: float # Y coordinate relative to control
global_x: float # X coordinate relative to screen
global_y: float # Y coordinate relative to screenExample:
def on_tap(e):
print(f"Tapped at: ({e.local_x}, {e.local_y})")
ft.Container(
content=ft.Text("Tap me"),
width=200, height=100,
bgcolor=ft.colors.BLUE_100,
on_click=on_tap
)class HoverEvent(ControlEvent):
"""Hover event."""
local_x: float # X coordinate relative to control
local_y: float # Y coordinate relative to control
global_x: float # X coordinate relative to screen
global_y: float # Y coordinate relative to screenExample:
def on_hover(e):
e.control.bgcolor = ft.colors.GREY_200 if e.data == "true" else ft.colors.WHITE
e.control.update()
ft.Container(
content=ft.Text("Hover over me"),
padding=20,
bgcolor=ft.colors.WHITE,
on_hover=on_hover
)class GestureDetector(Control):
"""Gesture detection wrapper control."""
def __init__(
self,
content: Control = None,
on_tap: callable = None,
on_tap_down: callable = None,
on_tap_up: callable = None,
on_secondary_tap: callable = None,
on_secondary_tap_down: callable = None,
on_secondary_tap_up: callable = None,
on_long_press_start: callable = None,
on_long_press_end: callable = None,
on_double_tap: callable = None,
on_double_tap_down: callable = None,
on_force_press_start: callable = None,
on_force_press_end: callable = None,
on_force_press_peak: callable = None,
on_pan_start: callable = None,
on_pan_update: callable = None,
on_pan_end: callable = None,
on_scale_start: callable = None,
on_scale_update: callable = None,
on_scale_end: callable = None,
on_hover: callable = None,
on_enter: callable = None,
on_exit: callable = None,
drag_interval: int = None,
hover_interval: int = None,
**kwargs
)Parameters:
content (Control, optional): Child control to wrapon_tap (callable, optional): Single tap eventon_double_tap (callable, optional): Double tap eventon_long_press_start (callable, optional): Long press start eventon_pan_start (callable, optional): Pan gesture start eventon_pan_update (callable, optional): Pan gesture update eventon_scale_start (callable, optional): Scale gesture start eventon_scale_update (callable, optional): Scale gesture update eventExample:
def on_pan_update(e):
e.control.top = max(0, e.control.top + e.delta_y)
e.control.left = max(0, e.control.left + e.delta_x)
e.control.update()
draggable_box = ft.GestureDetector(
content=ft.Container(
content=ft.Text("Drag me"),
width=100, height=100,
bgcolor=ft.colors.BLUE,
border_radius=10
),
on_pan_update=on_pan_update
)class DragStartEvent(ControlEvent):
"""Drag operation start event."""
local_x: float # X coordinate relative to control
local_y: float # Y coordinate relative to control
global_x: float # X coordinate relative to screen
global_y: float # Y coordinate relative to screen
timestamp: int # Event timestampclass DragUpdateEvent(ControlEvent):
"""Drag operation update event."""
delta_x: float # X movement delta
delta_y: float # Y movement delta
local_x: float # Current X coordinate relative to control
local_y: float # Current Y coordinate relative to control
global_x: float # Current X coordinate relative to screen
global_y: float # Current Y coordinate relative to screen
primary_delta: float # Primary axis movement delta
timestamp: int # Event timestampclass DragEndEvent(ControlEvent):
"""Drag operation end event."""
primary_velocity: float # Primary axis velocity
velocity: float # Overall velocity
local_x: float # Final X coordinate relative to control
local_y: float # Final Y coordinate relative to control
global_x: float # Final X coordinate relative to screen
global_y: float # Final Y coordinate relative to screenclass Draggable(Control):
"""Draggable control wrapper."""
def __init__(
self,
group: str = None,
content: Control = None,
content_when_dragging: Control = None,
content_feedback: Control = None,
**kwargs
)Parameters:
group (str, optional): Drag group identifiercontent (Control, optional): Control to make draggablecontent_when_dragging (Control, optional): Content shown while draggingcontent_feedback (Control, optional): Drag feedback contentclass DragTarget(Control):
"""Drop target for draggable controls."""
def __init__(
self,
group: str = None,
content: Control = None,
on_will_accept: callable = None,
on_accept: callable = None,
on_leave: callable = None,
**kwargs
)Example:
def on_accept_drag(e):
# Handle dropped item
src_control = page.get_control(e.src_id)
print(f"Dropped: {src_control.content.value}")
# Draggable item
draggable_item = ft.Draggable(
group="items",
content=ft.Container(
content=ft.Text("Drag me"),
width=100, height=50,
bgcolor=ft.colors.BLUE_200,
border_radius=5
)
)
# Drop target
drop_target = ft.DragTarget(
group="items",
content=ft.Container(
content=ft.Text("Drop here"),
width=200, height=100,
bgcolor=ft.colors.GREEN_100,
border_radius=5,
alignment=ft.alignment.center
),
on_accept=on_accept_drag
)class Dismissible(Control):
"""Swipe-to-dismiss wrapper."""
def __init__(
self,
content: Control = None,
background: Control = None,
secondary_background: Control = None,
dismiss_direction: DismissDirection = None,
dismiss_thresholds: dict = None,
movement_duration: int = None,
resize_duration: int = None,
cross_axis_end_offset: float = None,
on_dismiss: callable = None,
on_update: callable = None,
**kwargs
)Parameters:
content (Control, optional): Content to make dismissiblebackground (Control, optional): Background shown when swipingdismiss_direction (DismissDirection, optional): Allowed dismiss directionson_dismiss (callable, optional): Dismiss event handleron_update (callable, optional): Dismiss progress update handlerclass ScrollEvent(ControlEvent):
"""Scroll event."""
pixels: float # Current scroll offset
min_scroll_extent: float # Minimum scroll extent
max_scroll_extent: float # Maximum scroll extent
viewport_dimension: float # Viewport size
scroll_delta: float # Scroll delta
direction: str # Scroll directionExample:
def on_scroll(e):
print(f"Scroll position: {e.pixels}")
# Show/hide scroll indicator based on position
if e.pixels > 100:
scroll_indicator.visible = True
else:
scroll_indicator.visible = False
page.update()
scrollable_content = ft.ListView(
controls=[ft.ListTile(title=ft.Text(f"Item {i}")) for i in range(100)],
on_scroll=on_scroll,
auto_scroll=True
)class KeyboardEvent(Event):
"""Keyboard input event."""
key: str # Key identifier
shift: bool # Shift key pressed
ctrl: bool # Ctrl key pressed
alt: bool # Alt key pressed
meta: bool # Meta key pressedExample:
def on_keyboard(e):
if e.key == "Enter":
handle_submit()
elif e.key == "Escape":
handle_cancel()
elif e.ctrl and e.key == "s":
handle_save()
page.on_keyboard_event = on_keyboarddef page_event_handlers(page: ft.Page):
"""Example page event handlers."""
def on_route_change(e):
"""Route navigation event."""
print(f"Route changed to: {e.route}")
def on_view_pop(e):
"""View pop event."""
print(f"View popped: {e.view}")
def on_resize(e):
"""Window resize event."""
print(f"Window resized: {page.window_width}x{page.window_height}")
def on_close(e):
"""Page close event."""
print("Page closing")
e.page.window_destroy()
page.on_route_change = on_route_change
page.on_view_pop = on_view_pop
page.on_resize = on_resize
page.on_window_event = on_closeclass RouteChangeEvent(Event):
"""Route change navigation event."""
route: str # New route
old_route: str # Previous routeclass ViewPopEvent(Event):
"""View pop navigation event."""
view: View # View being poppedclass WindowEvent(Event):
"""Window state change event."""
event_type: WindowEventType # Event type (close, focus, etc.)class ReorderableListView(Control):
"""List with reorderable items."""
def __init__(
self,
controls: List[Control] = None,
on_reorder: callable = None,
padding: PaddingValue = None,
**kwargs
)class OnReorderEvent(ControlEvent):
"""List reorder event."""
old_index: int # Original item index
new_index: int # New item indexExample:
def handle_reorder(e):
# Reorder items in data model
item = items.pop(e.old_index)
items.insert(e.new_index, item)
print(f"Moved item from {e.old_index} to {e.new_index}")
items = ["Item 1", "Item 2", "Item 3", "Item 4"]
ft.ReorderableListView(
controls=[
ft.ListTile(title=ft.Text(item)) for item in items
],
on_reorder=handle_reorder
)class InteractiveViewer(Control):
"""Zoomable and pannable content viewer."""
def __init__(
self,
content: Control = None,
clip_behavior: ClipBehavior = None,
pan_enabled: bool = True,
scale_enabled: bool = True,
double_tap_to_zoom_enabled: bool = True,
min_scale: float = None,
max_scale: float = None,
interaction_end_friction_coefficient: float = None,
on_interaction_start: callable = None,
on_interaction_update: callable = None,
on_interaction_end: callable = None,
**kwargs
)Parameters:
content (Control, optional): Content to make interactivepan_enabled (bool, optional): Enable panningscale_enabled (bool, optional): Enable scaling/zoomingmin_scale (float, optional): Minimum zoom scalemax_scale (float, optional): Maximum zoom scaleclass PubSubClient(Control):
"""Publish-subscribe client for real-time communication."""
def __init__(
self,
on_message: callable = None,
**kwargs
)
def send_all(self, message: str) -> None:
"""Send message to all subscribers."""
def send_all_on_topic(self, topic: str, message: str) -> None:
"""Send message to topic subscribers."""
def send_others(self, message: str) -> None:
"""Send message to other subscribers."""
def send_others_on_topic(self, topic: str, message: str) -> None:
"""Send message to other topic subscribers."""Example:
def on_message_received(e):
messages.controls.append(
ft.Text(f"{e.topic}: {e.message}")
)
page.update()
pubsub = ft.PubSubClient(
on_message=on_message_received
)
def send_message(e):
message = message_field.value
if message:
pubsub.send_all_on_topic("chat", message)
message_field.value = ""
page.update()
message_field = ft.TextField(
hint_text="Enter message",
on_submit=send_message
)
messages = ft.Column(scroll=ft.ScrollMode.AUTO)class EventManager:
"""Centralized event management."""
def __init__(self, page):
self.page = page
self.handlers = {}
def register_handler(self, event_type, handler):
if event_type not in self.handlers:
self.handlers[event_type] = []
self.handlers[event_type].append(handler)
def emit_event(self, event_type, data):
if event_type in self.handlers:
for handler in self.handlers[event_type]:
handler(data)
def handle_control_event(self, e):
# Delegate to registered handlers
self.emit_event(f"{e.control.data}_click", e)
# Usage
event_manager = EventManager(page)
event_manager.register_handler("menu_item_click", handle_menu_click)
event_manager.register_handler("button_click", handle_button_click)def create_multi_gesture_control():
"""Control with multiple gesture handlers."""
def on_tap(e):
print("Single tap")
def on_double_tap(e):
print("Double tap")
def on_long_press(e):
print("Long press")
def on_pan_update(e):
# Handle panning
pass
def on_scale_update(e):
# Handle pinch-to-zoom
pass
return ft.GestureDetector(
content=ft.Container(
content=ft.Text("Multi-gesture area"),
width=300, height=200,
bgcolor=ft.colors.BLUE_100,
alignment=ft.alignment.center
),
on_tap=on_tap,
on_double_tap=on_double_tap,
on_long_press_start=on_long_press,
on_pan_update=on_pan_update,
on_scale_update=on_scale_update
)import time
class ThrottledEventHandler:
"""Throttle event handling to prevent spam."""
def __init__(self, delay_ms=100):
self.delay_ms = delay_ms
self.last_call = 0
def throttle(self, func):
def wrapper(*args, **kwargs):
now = time.time() * 1000
if now - self.last_call >= self.delay_ms:
self.last_call = now
return func(*args, **kwargs)
return wrapper
# Usage
throttler = ThrottledEventHandler(delay_ms=200)
@throttler.throttle
def on_scroll_throttled(e):
# Handle scroll event with throttling
print(f"Throttled scroll: {e.pixels}")
scrollable_list.on_scroll = on_scroll_throttledclass AppState:
"""Application state management with events."""
def __init__(self):
self.data = {}
self.listeners = {}
def set_state(self, key, value):
old_value = self.data.get(key)
self.data[key] = value
# Notify listeners
if key in self.listeners:
for listener in self.listeners[key]:
listener(value, old_value)
def get_state(self, key):
return self.data.get(key)
def listen(self, key, callback):
if key not in self.listeners:
self.listeners[key] = []
self.listeners[key].append(callback)
# Usage
app_state = AppState()
def on_counter_change(new_value, old_value):
counter_text.value = f"Count: {new_value}"
page.update()
app_state.listen("counter", on_counter_change)
def increment_counter(e):
current = app_state.get_state("counter") or 0
app_state.set_state("counter", current + 1)
ft.ElevatedButton("Increment", on_click=increment_counter)# Use event delegation for many similar controls
def create_efficient_list():
def handle_item_click(e):
# Single handler for all items
item_index = int(e.control.data)
print(f"Clicked item {item_index}")
items = []
for i in range(1000):
item = ft.ListTile(
title=ft.Text(f"Item {i}"),
data=str(i), # Store index in data
on_click=handle_item_click
)
items.append(item)
return ft.ListView(controls=items)
# Debounce rapid events
def debounce(delay_ms):
def decorator(func):
timer = None
def wrapper(*args, **kwargs):
nonlocal timer
if timer:
timer.cancel()
timer = threading.Timer(delay_ms / 1000, func, args, kwargs)
timer.start()
return wrapper
return decorator
@debounce(300)
def on_search_input(e):
# Debounced search
search_term = e.control.value
perform_search(search_term)This covers Flet's comprehensive event system and interaction capabilities, enabling you to create highly interactive and responsive applications with sophisticated user interactions.
Install with Tessl CLI
npx tessl i tessl/pypi-flet