.net Maui: How to bind SelectedItem in a CollectionView to a command / command parameter in view model in code, not xaml

76 Views Asked by At

I have a CollectionView in code, and in fact am using the CommunityToolkit.Maui.Markup extensions to fluently describe the CV.

However, I am unable to find an elegant formulation for binding the SelectedItem to a command parameter in the view model.

The CollectionView has properties:

SelectionChangedCommand
SelectionChagedCommandParameter

My thought was that I could just do something like:

Content = new CollectionView {
    ...
    SelectionChangedCommand = viewModel.SelectionChangedCommand
    ...
}.Bind(CollectionView.SelectionChangedCommandParameterProperty, nameof(CollectionView.SelectedItem));

But this doesn't work - the app crashing immediately on selection with a NullReferenceException in some Xamarin internal code. In fact I can't find any documentation on how to do this using bindings in code (with or without community markup). The only thing i could get working was using events, so something like:

var collectionView = new CollectionView {
...
}

collectionView.SelectionChanged +=
    (sender, e) =>
    {
        if (e.CurrentSelection.FirstOrDefault() is Caption caption)
        {
            viewModel.ItemTappedCommand.Execute(caption);
        }
    };

Content = collectionView;

Can anyone render assistance?

2

There are 2 best solutions below

1
Liqun Shen-MSFT On BEST ANSWER

You want to bind the SelectionChangedCommandParameter property to the SelectedItem of the CollectionView. That means the Binding Source for the SelectionChangedCommandParameter property is the CollectionView itself, instead of the items Collection in viewModel.

So, you could use Relative Binding to achieve this. Try below code in C# Markup,

new CollectionView {
            ...
            SelectionChangedCommand = ViewModel.SelectionChangedCommand,
            ...
}.Bind(CollectionView.SelectionChangedCommandParameterProperty, path:nameof(CollectionView.SelectedItem),source:RelativeBindingSource.Self)

Please let me know if you have any questions!

0
Albert D. Kallal On

You really don't have to use some parameter here at all.

Say this collection view.

In that collection view (CV) I dropped in a button, and of course you have the CV selected changed event. However, using changed event, or the simple button click event?

Note below how in both cases, I don't use nor care nor worry about any parameters at all here.

markup:

<CollectionView x:Name="MyColview" VerticalOptions="FillAndExpand" 
                MaximumHeightRequest="700"
                SelectionChanged="MyColview_SelectionChanged" 
                SelectionMode="Single"
                >
    <CollectionView.Header>
        <Grid ColumnDefinitions="100,100,100,150,200,100">

            <Label Text="First" Grid.Column="0" FontAttributes="Italic,Bold" />
            <Label Text="Last" Grid.Column="1" FontAttributes="Italic,Bold" />
            <Label Text="City" Grid.Column="2" FontAttributes="Italic,Bold" />
            <Label Text="HotelName" Grid.Column="3" FontAttributes="Italic,Bold" />
            <Label Text="Description" Grid.Column="4" FontAttributes="Italic,Bold" />
            <Label Text="View" Grid.Column="5" FontAttributes="Italic,Bold" />
        </Grid>
    </CollectionView.Header>
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <Grid  ColumnDefinitions="100,100,100,150,200,100" Padding="0,5,0,5">
                <Label Grid.Column="0" Text="{Binding FirstName}" />
                <Label Grid.Column="1" Text="{Binding LastName}" />
                <Label Grid.Column="2" Text="{Binding City}" />
                <Label Grid.Column="3" Text="{Binding HotelName}" />
                <Label Grid.Column="4" Text="{Binding Description}" HorizontalOptions="FillAndExpand" />

                <Button x:Name="cmdView" Grid.Column="5"
                    Text="View"
                    Clicked="cmdView_Clicked"
                    />


                <BoxView  Grid.ColumnSpan="5" Margin="0,-4,0,0"
                        Color="SkyBlue"
                        MinimumHeightRequest="1" MaximumHeightRequest="1" HeightRequest="1" 
                        VerticalOptions="Start"
                        />
            </Grid>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

And code behind:

public TestHotels2()
{
    InitializeComponent();
    LoadGrid();
}

void LoadGrid()
{
    string strSQL =
        "SELECT * FROM tblHotel ORDER BY HotelName";
    List<tblHotel> hList = dbLocal.con.Query<tblHotel>(strSQL);
    MyColview.ItemsSource = hList;
}


async private void MyColview_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    tblHotel MySelHotel = (tblHotel)MyColview.SelectedItem;
    await DisplayAlert("Hotel selection", MySelHotel.HotelName,"ok");
}

async private void cmdView_Clicked(object sender, EventArgs e)
{
    Button btn = (Button)sender;
    tblHotel MySelHotel = (tblHotel)btn.BindingContext;
    await DisplayAlert("Hotel selected", MySelHotel.HotelName, "ok");
}

Note how both the index changed event, and the simple button click both are able to pick up the current row selection with ease (just use BindingContext for the button click), and no need to pass parameters is required.

The end result is this:

enter image description here