0

I have cart items in a Grid inside a DataTemplate for showing CollectionView items. In the cart I have Add/Remove buttons with Commands that route to the CartViewModel and pass a CommandParameter of which CartViewItem they belong to.

Now I need to do the same with a picker for the pricing type drop down I can't figure out how to route the picker PropertyChanged to the model and pass along which item had its picker changed.

What's the best way to get the information I need on which cart item the PropertyChanged event is referring to? This code just sends the command to the CartPage Content Page with no info on which cart item it belongs to.

            <Picker Grid.Row="3" 
                    SelectedIndexChanged="Picker_SelectedIndexChanged" 
                    ItemsSource="{Binding CostTypeList}"
                    SelectedItem="{Binding CostType}"
                    />
            <Button Text="ADD" 
                        Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:CartViewModel}},Path=AddCommand}" 
                        CommandParameter="{Binding .}" 
                        Grid.Row="4" BackgroundColor="Blue"></Button>
            <Button Text="REMOVE" 
                        Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:CartViewModel}},Path=RemoveCommand}" 
                        CommandParameter="{Binding .}"  Grid.Row="4" Grid.Column="1" BackgroundColor="DarkRed"></Button>

And I can't get a syntax similar to the button syntax to work.

            <Picker Grid.Row="3" 
                    SelectedIndexChanged="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:CartViewModel}},Path=Picker_SelectedIndexChanged}"
                    ItemsSource="{Binding CostTypeList}"
                    SelectedItem="{Binding CostType}"
                    />

Gives this error before building

XFC0000 Cannot resolve type "clr-namespace:SVCA_Android.ViewModels:CartViewItem". sign-in-maui (net8.0-android34.0)

And this error after building

Error (active) XFC0009 No property, BindableProperty, or event found for "SelectedIndexChanged", or mismatching type between value and property. sign-in-maui (net8.0-android34.0)

public partial class CartViewModel : ObservableObject
{

    [ObservableProperty]
    ObservableCollection<CartViewItem> items;
    [ObservableProperty]
    public string costType;
    [ObservableProperty]
    public List<string> costTypeList;

    public partial class CartViewItem : ObservableObject
    {
        [ObservableProperty]
        public string costType;
        [ObservableProperty]
        public List<string> costTypeList;
    }

    public void Picker_SelectedIndexChanged(object sender, EventArgs e)
    {

    }
    [RelayCommand]
    async Task Add(CartViewItem i)
    {
        System.Diagnostics.Debug.WriteLine(i.ItemName + " " + i.ItemQty);
    }
    [RelayCommand]
    async Task Remove(CartViewItem i)
    {
        System.Diagnostics.Debug.WriteLine(i.ItemName + " " + i.ItemQty);
    }
}
3
  • Why is your picker in a collectionview? Commented Sep 11, 2024 at 15:52
  • Because each cart item has a picker to choose what pricing type. [normal, after-hours, warranty]. Commented Sep 11, 2024 at 21:02
  • Of course you could get the corresponding cart item in viewmodel when picker changes the item, you could use EventToCommandBehavior and this implements the MVVM Pattern. See my answer below. Commented Sep 14, 2024 at 11:47

2 Answers 2

1

Using Picker_SelectedIndexChanged was the wrong approach for the situation of getting picker changed information to a CollectionViews item.

The correct approach using MVVM community toolkit is to use the OberservableProperty watchers that are generated by the toolkit. Note you need to use the uppercase of the ObservableProperty like this

    public partial class CartViewItem : ObservableObject
    {
        [ObservableProperty]
        public string costType;
        [ObservableProperty]
        public List<string> costTypeList;

        partial void OnCostTypeChanged(string value)
        {
            System.Diagnostics.Debug.WriteLine(value);
        }

With xaml

            <Picker Grid.Row="3" 
                    ItemsSource="{Binding CostTypeList}"
                    SelectedItem="{Binding CostType}"
                    />

Also be aware that at least on Android that watcher is going to fire once on the init of each cart item.

Refer to Passing parameter from ViewModel to ViewModel in .NET MAUI using CommunityToolkit.MVVM

Sign up to request clarification or add additional context in comments.

Comments

0

SelectedIndexChanged is Picker's Event which can only be handled in code-behind file. If you want to handle this Event in the CartViewModel in MVVM pattern, consider using EventToCommandBehavior from CommunityToolkit.Maui Nuget.

Here is my solution. To use communitytoolkit, please first install official CommunityToolkit.Maui Nuget. For more info, please refer to Getting Started with the .NET Multi-platform App UI (.NET MAUI) Community Toolkit.

In XAML, when we change the selection of Picker, we would like to invoke SelectedIndexChangeCommand command in ViewModel. Here we use x:Reference Expression because RelativeBinding not work in EventToCommandBehavior. Then we pass the current cart item using {Binding .} through the CommandParameter

<Picker Grid.Row="3" 
    SelectedIndexChanged="Picker_SelectedIndexChanged"     
    ItemsSource="{Binding CostTypeList}"
    SelectedItem="{Binding CostType}">
    <Picker.Behaviors>
        <toolkit:EventToCommandBehavior
            EventName="SelectedIndexChanged"
            Command="{Binding Source={x:Reference cartPage},Path=BindingContext.SelectedIndexChangeCommand}" 
            CommandParameter="{Binding .}"/>
    </Picker.Behaviors>
</Picker>

Please note that cartPage is the name of the ContentPage, so remember to set x:Name for the ContentPage,

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    .....
    
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    x:Name="cartPage">

In CartViewModel, let's define the SelectedIndexChangeCommand,

[RelayCommand]
async Task SelectedIndexChange(CartViewItem cartViewItem)
{
    if(cartViewItem != null)
    {
        var item = cartViewItem as CartViewItem;

        Console.WriteLine(item.ItemName);
    }
   
}

Edited with a null check to prevent the exception on the initial loading.

Now, we could get the current cart item through the parameter cartViewItem in CartViewModel.

6 Comments

Verified working in my code with 1 addition. On the initial loading of the page, both this method and property watcher method fired once for each picker However, the initial firing so SelectedIndexChange passed in a NULL for the cartViewItem. On later actions by changing selection, it was non-null and worked fine after I added a null check guard to the code. Can you edit the answer and show null protection with a comment?
@Joelxxx Thanks for response. I've edited the SelectedIndexChangeCommand with a null check.
Another thing is that you put costType and costTypeList in both CartViewModel and CartViewItem. They should be placed only in CartViewItem.
When I don't have them listed on both, I get a binding error on property CostTypeList no t found on ViewModels.CartViewModel. I'll ask this in a separate question because I'll need a larger context regarding my xaml.
Isn't the Picker control inside the CollectionView DataTemplate? Then the CostTypeList should be a Model's property, not a ViewModel's property.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.