menuChildren property

List<Widget> menuChildren
final

A list of children containing the menu items that are the contents of the menu surrounded by this MenuAnchor.

Menus using MenuItemButton can have a SingleActivator or CharacterActivator assigned to them as their MenuItemButton.shortcut, which will display an appropriate shortcut hint. Even though the shortcut labels are displayed in the menu, shortcuts are not automatically handled. They must be available in whatever context they are appropriate, and handled via another mechanism.

If shortcuts should be generally enabled, but are not easily defined in a context surrounding the menu bar, consider registering them with a ShortcutRegistry (one is already included in the WidgetsApp, and thus also MaterialApp and CupertinoApp), as shown in the example below. To be sure that selecting a menu item and triggering the shortcut do the same thing, it is recommended that they call the same callback.

This example shows a MenuBar that contains a single top level menu, containing three items: "About", a checkbox menu item for showing a message, and "Quit". The items are identified with an enum value, and the shortcuts are registered globally with the ShortcutRegistry.

{@macro material_ui.dartpad_guide}

import 'package:flutter/services.dart';
import 'package:material_ui/material_ui.dart';

/// Flutter code sample for [MenuBar].

void main() => runApp(const MenuBarApp());

/// A class for consolidating the definition of menu entries.
///
/// This sort of class is not required, but illustrates one way that defining
/// menus could be done.
class MenuEntry {
  const MenuEntry({
    required this.label,
    this.shortcut,
    this.onPressed,
    this.menuChildren,
  }) : assert(
         menuChildren == null || onPressed == null,
         'onPressed is ignored if menuChildren are provided',
       );
  final String label;

  final MenuSerializableShortcut? shortcut;
  final VoidCallback? onPressed;
  final List<MenuEntry>? menuChildren;

  static List<Widget> build(
    List<MenuEntry> selections, [
    Duration hoverOpenDelay = .zero,
  ]) {
    Widget buildSelection(MenuEntry selection) {
      if (selection.menuChildren != null) {
        return SubmenuButton(
          menuChildren: MenuEntry.build(
            selection.menuChildren!,
            const Duration(milliseconds: 150),
          ),
          child: Text(selection.label),
        );
      }
      return MenuItemButton(
        shortcut: selection.shortcut,
        onPressed: selection.onPressed,
        child: Text(selection.label),
      );
    }

    return selections.map<Widget>(buildSelection).toList();
  }

  static Map<MenuSerializableShortcut, Intent> shortcuts(
    List<MenuEntry> selections,
  ) {
    final Map<MenuSerializableShortcut, Intent> result =
        <MenuSerializableShortcut, Intent>{};
    for (final MenuEntry selection in selections) {
      if (selection.menuChildren != null) {
        result.addAll(MenuEntry.shortcuts(selection.menuChildren!));
      } else {
        if (selection.shortcut != null && selection.onPressed != null) {
          result[selection.shortcut!] = VoidCallbackIntent(
            selection.onPressed!,
          );
        }
      }
    }
    return result;
  }
}

class MyMenuBar extends StatefulWidget {
  const MyMenuBar({super.key, required this.message});

  final String message;

  @override
  State<MyMenuBar> createState() => _MyMenuBarState();
}

class _MyMenuBarState extends State<MyMenuBar> {
  ShortcutRegistryEntry? _shortcutsEntry;
  String? _lastSelection;

  Color get backgroundColor => _backgroundColor;
  Color _backgroundColor = Colors.red;
  set backgroundColor(Color value) {
    if (_backgroundColor != value) {
      setState(() {
        _backgroundColor = value;
      });
    }
  }

  bool get showingMessage => _showMessage;
  bool _showMessage = false;
  set showingMessage(bool value) {
    if (_showMessage != value) {
      setState(() {
        _showMessage = value;
      });
    }
  }

  @override
  void dispose() {
    _shortcutsEntry?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Row(
          mainAxisSize: .min,
          children: <Widget>[
            Expanded(child: MenuBar(children: MenuEntry.build(_getMenus()))),
          ],
        ),
        Expanded(
          child: Container(
            alignment: .center,
            color: backgroundColor,
            child: Column(
              mainAxisAlignment: .center,
              children: <Widget>[
                Padding(
                  padding: const .all(12.0),
                  child: Text(
                    showingMessage ? widget.message : '',
                    style: Theme.of(context).textTheme.headlineSmall,
                  ),
                ),
                Text(
                  _lastSelection != null
                      ? 'Last Selected: $_lastSelection'
                      : '',
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }

  List<MenuEntry> _getMenus() {
    final List<MenuEntry> result = <MenuEntry>[
      MenuEntry(
        label: 'Menu Demo',
        menuChildren: <MenuEntry>[
          MenuEntry(
            label: 'About',
            onPressed: () {
              showAboutDialog(
                context: context,
                applicationName: 'MenuBar Sample',
                applicationVersion: '1.0.0',
              );
              setState(() {
                _lastSelection = 'About';
              });
            },
          ),
          MenuEntry(
            label: showingMessage ? 'Hide Message' : 'Show Message',
            onPressed: () {
              setState(() {
                _lastSelection = showingMessage
                    ? 'Hide Message'
                    : 'Show Message';
                showingMessage = !showingMessage;
              });
            },
            shortcut: const SingleActivator(
              LogicalKeyboardKey.keyS,
              control: true,
            ),
          ),
          // Hides the message, but is only enabled if the message isn't
          // already hidden.
          MenuEntry(
            label: 'Reset Message',
            onPressed: showingMessage
                ? () {
                    setState(() {
                      _lastSelection = 'Reset Message';
                      showingMessage = false;
                    });
                  }
                : null,
            shortcut: const SingleActivator(LogicalKeyboardKey.escape),
          ),
          MenuEntry(
            label: 'Background Color',
            menuChildren: <MenuEntry>[
              MenuEntry(
                label: 'Red Background',
                onPressed: () {
                  setState(() {
                    _lastSelection = 'Red Background';
                    backgroundColor = Colors.red;
                  });
                },
                shortcut: const SingleActivator(
                  LogicalKeyboardKey.keyR,
                  control: true,
                ),
              ),
              MenuEntry(
                label: 'Green Background',
                onPressed: () {
                  setState(() {
                    _lastSelection = 'Green Background';
                    backgroundColor = Colors.green;
                  });
                },
                shortcut: const SingleActivator(
                  LogicalKeyboardKey.keyG,
                  control: true,
                ),
              ),
              MenuEntry(
                label: 'Blue Background',
                onPressed: () {
                  setState(() {
                    _lastSelection = 'Blue Background';
                    backgroundColor = Colors.blue;
                  });
                },
                shortcut: const SingleActivator(
                  LogicalKeyboardKey.keyB,
                  control: true,
                ),
              ),
            ],
          ),
        ],
      ),
    ];
    // (Re-)register the shortcuts with the ShortcutRegistry so that they are
    // available to the entire application, and update them if they've changed.
    _shortcutsEntry?.dispose();
    _shortcutsEntry = ShortcutRegistry.of(
      context,
    ).addAll(MenuEntry.shortcuts(result));
    return result;
  }
}

class MenuBarApp extends StatelessWidget {
  const MenuBarApp({super.key});

  static const String kMessage = '"Talk less. Smile more." - A. Burr';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: SafeArea(child: MyMenuBar(message: kMessage)),
      ),
    );
  }
}

Implementation

final List<Widget> menuChildren;