You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在.NET MAUI中基于自定义类创建动态菜单的实现问询

在.NET MAUI中创建动态多级菜单(XAML/编程实现)

需求说明

ViewModel中已定义MyMenuItem类及对应的可观察集合,希望基于该集合生成支持子项的菜单栏(支持垂直/水平布局):

public class MyMenuItem
{
    public string MyMenuItemName { get; set; }
    public MyMenuItem[] ChildMenuItems { get; set; }
    public ICommand MyMenuItemCommand { get; set; }
}

已尝试编程实现但未成功,尝试的代码如下:

public void GenerateMenuBar()
{
    var viewModel = (HomePageViewModel)BindingContext;
    AddMenuItemsToMenuBar(viewModel.MenuItems);
}

private void AddMenuItemsToMenuBar(IEnumerable<MyMenuItem> menuItems, IElement parent = null)
{
    foreach (var menuItem in menuItems)
    {
        IElement currentMenuItem;

        if (parent is MenuFlyoutSubItem)
        {
            var parentElement = parent as MenuFlyoutSubItem;
            currentMenuItem = new MenuFlyoutItem {Text = menuItem.MyMenuItemName, Parent = parentElement};
            parentElement.Add((MenuFlyoutItem)currentMenuItem);
        }
        else if (parent is MenuBarItem && menuItem.ChildMenuItems.Length > 0)
        {
            var parentElement = parent as MenuBarItem;
            currentMenuItem = new MenuFlyoutSubItem {Text = menuItem.MyMenuItemName, Parent = parentElement};
            parentElement.Add((MenuFlyoutSubItem)currentMenuItem);
        }
        else if (parent is MenuBarItem && menuItem.ChildMenuItems.Length == 0)
        {
            var parentElement = parent as MenuBarItem;
            currentMenuItem = new MenuFlyoutItem { Text = menuItem.MyMenuItemName, Parent = parentElement };
            parentElement.Add((MenuFlyoutItem)currentMenuItem);
        }
        else
        {
            currentMenuItem = new MenuBarItem {Text = menuItem.MyMenuItemName};
            this.MenuBarItems.Add((MenuBarItem)currentMenuItem);
        }

        if (menuItem.ChildMenuItems != null)
            AddMenuItemsToMenuBar(menuItem.ChildMenuItems, currentMenuItem);
    }
}

一、编程方式修复

你的代码存在空数组判断遗漏、未绑定命令、手动设置Parent属性等问题,调整后可正常生成菜单:

public void GenerateMenuBar()
{
    var viewModel = (HomePageViewModel)BindingContext;
    MenuBarItems.Clear(); // 清空原有菜单避免重复添加
    AddMenuItemsToMenuBar(viewModel.MenuItems);
}

private void AddMenuItemsToMenuBar(IEnumerable<MyMenuItem> menuItems, IElement parent = null)
{
    foreach (var menuItem in menuItems)
    {
        if (parent is MenuFlyoutSubItem flyoutParent)
        {
            var flyoutItem = new MenuFlyoutItem
            {
                Text = menuItem.MyMenuItemName,
                Command = menuItem.MyMenuItemCommand
            };
            flyoutParent.Add(flyoutItem);

            // 递归添加子项
            if (menuItem.ChildMenuItems?.Length > 0)
            {
                var subFlyout = new MenuFlyoutSubItem { Text = menuItem.MyMenuItemName };
                flyoutParent.Add(subFlyout);
                AddMenuItemsToMenuBar(menuItem.ChildMenuItems, subFlyout);
            }
        }
        else if (parent is MenuBarItem barParent)
        {
            if (menuItem.ChildMenuItems?.Length > 0)
            {
                var subFlyout = new MenuFlyoutSubItem { Text = menuItem.MyMenuItemName };
                barParent.Add(subFlyout);
                AddMenuItemsToMenuBar(menuItem.ChildMenuItems, subFlyout);
            }
            else
            {
                var barItem = new MenuFlyoutItem
                {
                    Text = menuItem.MyMenuItemName,
                    Command = menuItem.MyMenuItemCommand
                };
                barParent.Add(barItem);
            }
        }
        else
        {
            // 顶级菜单处理
            var barItem = new MenuBarItem { Text = menuItem.MyMenuItemName };
            MenuBarItems.Add(barItem);

            if (menuItem.ChildMenuItems?.Length > 0)
            {
                AddMenuItemsToMenuBar(menuItem.ChildMenuItems, barItem);
            }
            else
            {
                var flyoutItem = new MenuFlyoutItem
                {
                    Text = menuItem.MyMenuItemName,
                    Command = menuItem.MyMenuItemCommand
                };
                barItem.Add(flyoutItem);
            }
        }
    }
}

二、XAML绑定方式(推荐,符合MVVM)

通过DataTemplate和递归绑定实现,无需手动生成UI:

1. 页面XAML代码

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="YourNamespace.HomePage"
             BindingContext="{StaticResource HomePageViewModel}">

    <ContentPage.MenuBarItems>
        <CollectionView ItemsSource="{Binding MenuItems}">
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <MenuBarItem Text="{Binding MyMenuItemName}">
                        <MenuBarItem.ItemsSource>
                            <CollectionView ItemsSource="{Binding ChildMenuItems}">
                                <CollectionView.ItemTemplate>
                                    <DataTemplate>
                                        <ContentView>
                                            <ContentView.Content>
                                                <!-- 有子项时显示折叠菜单 -->
                                                <MenuFlyoutSubItem Text="{Binding MyMenuItemName}"
                                                                  ItemsSource="{Binding ChildMenuItems}"
                                                                  IsVisible="{Binding ChildMenuItems, Converter={StaticResource HasItemsConverter}}">
                                                    <MenuFlyoutSubItem.ItemTemplate>
                                                        <DataTemplate>
                                                            <MenuFlyoutItem Text="{Binding MyMenuItemName}"
                                                                            Command="{Binding MyMenuItemCommand}" />
                                                        </DataTemplate>
                                                    </MenuFlyoutSubItem.ItemTemplate>
                                                </MenuFlyoutSubItem>
                                                <!-- 无子项时显示可点击项 -->
                                                <MenuFlyoutItem Text="{Binding MyMenuItemName}"
                                                                Command="{Binding MyMenuItemCommand}"
                                                                IsVisible="{Binding ChildMenuItems, Converter={StaticResource NoItemsConverter}}" />
                                            </ContentView.Content>
                                        </ContentView>
                                    </DataTemplate>
                                </CollectionView.ItemTemplate>
                            </CollectionView>
                        </MenuBarItem.ItemsSource>
                    </MenuBarItem>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </ContentPage.MenuBarItems>

    <!-- 页面其他内容 -->
</ContentPage>

2. 转换器实现

// 判断是否有子项的转换器
public class HasItemsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value is MyMenuItem[] items && items.Length > 0;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

// 判断是否无子项的转换器
public class NoItemsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value is not MyMenuItem[] items || items.Length == 0;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

3. 注册转换器(App.xaml)

<Application.Resources>
    <ResourceDictionary>
        <local:HasItemsConverter x:Key="HasItemsConverter" />
        <local:NoItemsConverter x:Key="NoItemsConverter" />
        <local:HomePageViewModel x:Key="HomePageViewModel" />
    </ResourceDictionary>
</Application.Resources>

三、垂直布局菜单实现

如果需要垂直菜单(而非顶部菜单栏),可以用Expander配合CollectionView实现:

<CollectionView ItemsSource="{Binding MenuItems}"
                ItemsLayout="VerticalList">
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <Expander Header="{Binding MyMenuItemName}"
                      TappedCommand="{Binding MyMenuItemCommand}">
                <Expander.Content>
                    <CollectionView ItemsSource="{Binding ChildMenuItems}"
                                    Margin="20,0,0,0">
                        <CollectionView.ItemTemplate>
                            <DataTemplate>
                                <Label Text="{Binding MyMenuItemName}"
                                       Margin="10,5">
                                    <Label.GestureRecognizers>
                                        <TapGestureRecognizer Command="{Binding MyMenuItemCommand}" />
                                    </Label.GestureRecognizers>
                                </Label>
                            </DataTemplate>
                        </CollectionView.ItemTemplate>
                    </CollectionView>
                </Expander.Content>
            </Expander>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

内容的提问来源于stack exchange,提问作者Pomodoro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 03:15:42