mauimvvm-toolkit

Binding a relay command from a class to a CustomCommand mismatching


I am making a ribbon like menu.

For achieving, I created three basic components.

The class define each item

public class MenuComponentItems {

    public string ImageSource { get; set; }

    public string CommandParameter { get; set; }

    public string Section { get; set; }

    public IRelayCommand Command { get; set; }

}

then we will define the UI

<Border
    Margin="5,5,0,0"
    HeightRequest="120"
    VerticalOptions="StartAndExpand">
    <Grid
        RowSpacing="5"
        VerticalOptions="Center">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <HorizontalStackLayout
            x:Name="MenuItemsStack"
            BackgroundColor="Bisque" />

        <Label
            Grid.Row="1"
            HorizontalTextAlignment="Center"
            Text="{Binding SectionName, Source={x:Reference MenuComponent}}" />
    </Grid>
</Border>

and finally, the code behind of the control

public partial class MenuTemplate : ContentView {

public ObservableCollection<MenuComponentItems> MenuItems { get; set; } = [];

public MenuTemplate() {
        InitializeComponent();
        MenuItems.CollectionChanged += (sender, e) => UpdateMenu();
}

public static readonly BindableProperty SectionNameProperty = BindableProperty.Create(
    nameof(SectionName),
    typeof(string),
    typeof(MenuTemplate));

public string SectionName {
    get => (string)GetValue(SectionNameProperty);
    set => SetValue(SectionNameProperty, value);
}

private void UpdateMenu() {
    MenuItemsStack.Children.Clear();

    foreach (var menuItem in MenuItems) {
        var imageButton = new ImageButton {
            Source = menuItem.ImageSource,
            WidthRequest = 60,
            HeightRequest = 60,
            Margin = new Thickness(10, 0, 10, 0),
            Command = menuItem.Command,
            CommandParameter = menuItem.CommandParameter
        };

        MenuItemsStack.Children.Add(imageButton);
    }
}

usage

<Grid RowDefinitions="200, *">
    <HorizontalStackLayout>
        <controls:MenuTemplate SectionName="ferwqfkrew">
            <controls:MenuTemplate.MenuItems>
                <models:MenuComponentItems
                    ImageSource="dotnet_bot.png"
                    Section="Section 1" />
                <models:MenuComponentItems
                    ImageSource="mario.png"
                    Section="Section 1" />
            </controls:MenuTemplate.MenuItems>
        </controls:MenuTemplate>
    </HorizontalStackLayout>
</Grid>

output

output

Problem

when I create a relay command in my view model

[RelayCommand]
public void MenuItemSelected(object param) {
    
    Debug.WriteLine("dcd");
}

and bind it I get

property, BindableProperty, or event found for "Command", or mismatching.

What I do not, is the fact that in my class I have a RelayCommand and my vm is a RelayCommand as well.

At first, I thought that is because a ImageButton command is an ICommand and not a RelayCommand, but I tested an ImageButton with the same command and it triggered

Thing that I tried

  1. Using ICommand instead of the RelayCommand of the Microsoft mvvm toolkit
  2. Created a RelayCommand property in the control itself

Update

I made my class inherit form ContentView

public class MenuComponentItems : ContentView {

    public string ImageSource { get; set; }

    public string CommandParameter { get; set; }


    public static readonly BindableProperty CommandProperty = BindableProperty.Create(
        nameof(Command), typeof(RelayCommand), typeof(MenuComponentItems));

    public RelayCommand Command {
        get => (RelayCommand)GetValue(CommandProperty);
        set => SetValue(CommandProperty, value);
    }
}

but it doesn't trigger the command.

Usage

public class MenuComponentItems : ContentView {

public string ImageSource { get; set; }

public string CommandParameter { get; set; }


public static readonly BindableProperty CommandProperty = BindableProperty.Create(
    nameof(Command), typeof(RelayCommand), typeof(MenuTemplate));

public RelayCommand Command {
    get => (RelayCommand)GetValue(CommandProperty);
    set => SetValue(CommandProperty, value);
}
<controls:MenuTemplate SectionName="ferwqfkrew">
    <controls:MenuTemplate.MenuItems>
        <models:MenuComponentItems Command="{Binding MenuItemSelectedCommand}"
            ImageSource="dotnet_bot.png" />
public partial class MainViewModel : ObservableObject {
     
    [RelayCommand]
    public void MenuItemSelected() {
    
        Debug.WriteLine("dcd");
    }
}

I inspected the MenuItemsStack.Children.Add(imageButton); and for some reason the command is null.

I tested it with a button, and it works, so the VM is fine, the problem is the custom control that doesn't get the command


Solution

  • Okay, after trying several things, I actually found a workaround for your situation : it seems no Command was called because when generating your ImageButtons, the BindingContext of MenuComponentItems was null.

    The workaround :

    public void UpdateMenu() {
        MenuItemsStack.Children.Clear();
    
        foreach (var menuItem in MenuItems) {
            menuItem.BindingContext = this.BindingContext; // Somehow the MenuTemplate's BindingContext is correct, only item's one isn't set, we fix this here.
            var imageButton = new ImageButton {
                Source = menuItem.ImageSource,
                WidthRequest = 60,
                HeightRequest = 60,
                Command = menuItem.Command,
                CommandParameter = menuItem.CommandParameter
            };
    
            MenuItemsStack.Children.Add(imageButton);
        }
    }
    
    public MainPage(MainViewModel mainViewModel) {
        BindingContext = mainViewModel;
        InitializeComponent();
        YourMainTemplate.UpdateMenu();
    }
    

    Not gonna lie, I'm still not sure what the issue is and why this approach works, but at least now it calls your Command !