Search code examples
c#xamlwinui-3winuicommunity-toolkit-mvvm

WinUI 3 DropDownButton data binding


How to bind DropDownButton's items to a ViewModel?

<DropDownButton Width="100" Grid.Column="2" ToolTipService.ToolTip="Language">
    <TextBlock FontSize="14" Text="Select language"/>
    <DropDownButton.Flyout>
        <MenuFlyout Placement="BottomEdgeAlignedLeft">
            <MenuFlyoutItem Text="English" Tag="enEn" Click="DropdownOptionLanguage_Select" />
        </MenuFlyout>
    </DropDownButton.Flyout>
</DropDownButton>

If we follow the logic here MenuFlyout or DropDownButton.Flyout is the container for items. But there is no ItemSource property or something what I can bind to an enumerable.


Solution

  • In this case you can add items in code-behind:

    MainPage.xaml

    <DropDownButton Content="Select language">
        <DropDownButton.Flyout>
            <MenuFlyout x:Name="SelectLanguageMenuLayout" />
        </DropDownButton.Flyout>
    </DropDownButton>
    
    public enum Languages
    {
        English,
        Spanish,
        French,
        German,
        Italian,
    }
    
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
    
            foreach (Languages language in Enum.GetValues<Languages>())
            {
                this.SelectLanguageMenuLayout.Items.Add(
                    new MenuFlyoutItem
                    {
                        Text = language.ToString(),
                        Command = ViewModel.ChangeLanguageCommand,
                        CommandParameter = language,
                    });
            }
        }
    
        public MainPageViewModel ViewModel { get; } = new();
    }
    
    public partial class MainPageViewModel : ObservableObject
    {
        [RelayCommand]
        private void ChangeLanguage(Languages language)
        {
        }
    }
    

    I'm using the CommunityToolkit.Mvvm NuGet package for the MVVM design.

    UPDATE

    This should be a closer approach to what you mentioned in the comments:

    MenuFlyoutEx.cs

    public class MenuFlyoutEx : MenuFlyout
    {
        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register(
                nameof(ItemsSource),
                typeof(IEnumerable),
                typeof(MenuFlyoutEx),
                new PropertyMetadata(default, OnItemsSourcePropertyChanged));
    
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register(
                nameof(Command),
                typeof(ICommand),
                typeof(MenuFlyoutEx),
                new PropertyMetadata(default));
    
        public IEnumerable ItemsSource
        {
            get => (IEnumerable)GetValue(ItemsSourceProperty);
            set => SetValue(ItemsSourceProperty, value);
        }
    
        public ICommand Command
        {
            get => (ICommand)GetValue(CommandProperty);
            set => SetValue(CommandProperty, value);
        }
    
        private static void OnItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is not MenuFlyoutEx menuFlyoutEx ||
                e.NewValue is not IEnumerable itemsSource)
            {
                return;
            }
    
            menuFlyoutEx.Items.Clear();
    
            foreach (object item in itemsSource)
            {
                MenuFlyoutItem menuFlyoutItem = new()
                {
                    Text = item.ToString(),
                    Command = menuFlyoutEx.Command,
                    CommandParameter = item,
                };
    
                menuFlyoutEx.Items.Add(menuFlyoutItem);
            }
        }
    }
    

    MainPageViewModel.cs

    // This class needs to be "partial" for the CommunityToolkit.Mvvm.
    public partial class MainPageViewModel : ObservableObject
    {
        // The CommunityToolkit.Mvvm will generate a "ChangeLanguageCommand" for you.
        [RelayCommand]
        private void ChangeLanguage(Languages language)
        {
            // Do your logic here...
        }
    
        // The CommunityToolkit.Mvvm will generate a "LanguageOptions" property for you.
        [ObservableProperty]
        private Languages[] _languageOptions = Enum.GetValues<Languages>();
    }
    

    MainPage.xaml.cs

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }
    
        public MainPageViewModel ViewModel { get; } = new();
    }
    

    and use it like this:

    <DropDownButton Content="Select language">
        <DropDownButton.Flyout>
            <local:MenuFlyoutEx
                Command="{x:Bind ViewModel.ChangeLanguageCommand}"
                ItemsSource="{x:Bind ViewModel.LanguageOptions}" />
        </DropDownButton.Flyout>
    </DropDownButton>