Steven Marcus
Steven Marcus

Reputation: 391

Trying to set picker within listview MVVM Xamarin

Trying to set the initial value to a picker through the use of SelectedItem. I can do this without any issue if the picker isn't within a listview. However, once I try to accomplish this in a listview no dice.

I never can get the picker to display the initially downloaded value. If I use the same binding for an entry it displays the expected string.

Thoughts??


This can be reproduced in this simplistic standalone project. Please help. Thanks.

https://github.com/smarcus3/DebuggingProject


XAML

<ListView x:Name="listView" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" ItemsSource="{Binding downloadedRecipeIngredients}">  <!--SelectedItem="{Binding SelectedItem}"-->
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Orientation="Horizontal">
                            <!-- Element Label -->
                            <Entry VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" Text="{Binding IngredientName}"/>
                            <!--<Picker x:Name="pickerIngredient" HorizontalOptions = "StartAndExpand" ItemsSource="{Binding listIngredients}" BindingContext="{Binding Source={x:Reference Page}, Path=BindingContext}" SelectedItem="{Binding IngredientName}" WidthRequest="100"/>-->
                            <Picker x:Name="pickerIngredientancestor" HorizontalOptions = "StartAndExpand" WidthRequest="100" ItemsSource="{Binding listIngredients, Source={RelativeSource AncestorType={x:Type viewModel:testPageViewModel}}}" SelectedItem="{Binding IngredientName}"/>
                            <Entry Text="{Binding Quantity}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />
                            <Entry Text="{Binding UnitName}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />
                            <Entry Text="{Binding Comments}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />
                            <!-- Assessment Menu Icon -->
                            <Label Text="Clickable Label" VerticalOptions="CenterAndExpand" HorizontalOptions="EndAndExpand">
                                <Label.GestureRecognizers>
                                    <TapGestureRecognizer Command="{Binding Path=BindingContext.btnPress, Source={x:Reference Page}}" CommandParameter="{Binding .}" />
                                </Label.GestureRecognizers>
                            </Label>
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

VIEW MODEL

public class testPageViewModel : BaseViewModel
{
   
    clRecipeIngredient[] _downloadedRecipeIngredients;
    public clRecipeIngredient[] downloadedRecipeIngredients
    { 
        get { 
            return _downloadedRecipeIngredients; 
        } 
        set 
        {
            //if (downloadedRecipeIngredients != value)
            //{
                _downloadedRecipeIngredients = value;
                OnPropertyChanged("downloadedRecipeIngredients");
            //}
        } 
    }

    //Lists for Pickers
    ObservableCollection<string> _listIngredients = new ObservableCollection<string>();
    public ObservableCollection<string> listIngredients { get { return _listIngredients; } }


    private clRecipeDataBase recipeDataBase;

    public testPageViewModel()
    {
        recipeDataBase = new clRecipeDataBase();

        btnPress = new Command<clRecipeIngredient>(madeIt);

        getData();
    }

    async void getData()
    {
        //PICKER INGREDIENT DATA
        clIngredient[] tmp = await recipeDataBase.getIngredientData();
        for (int i = 0; i < tmp.Length; i++)
        {
            _listIngredients.Add(tmp[i].IngredientName);
        }

        _downloadedRecipeIngredients = await recipeDataBase.getRecipeIngredientsDataByRecipeID(310); //HARDCODED TO CRISPY PIZZA RECIPE

       
        OnPropertyChanged("downloadedRecipeIngredients");

    }

    public ICommand btnPress { get; private set; }
    void madeIt(clRecipeIngredient x)
    {
        Console.WriteLine(x.IngredientName + " -- " + x.Comments);

        //_downloadedRecipeIngredients.Remove(x);

    }



}

enter image description here

clRecipeIngredients

public class clRecipeIngredient
{
    public int RecipeIngredientsID { get; set; }
    public int RecipeIDLookedUP { get; set; }
    public int IngredientIDLookedUp { get; set; }
    public double Quantity { get; set; }
    public int UnitIDLookedUp { get; set; }
    public bool HiddenFlag { get; set; }
    public string UnitName { get; set; }
    public string IngredientName { get; set; }
    public string Comments { get; set; }

Upvotes: 1

Views: 1652

Answers (2)

Steven Marcus
Steven Marcus

Reputation: 391

Its still unclear why you cannot directly set the selectedItem with a string as I can do in pickers not inside of a ListView.

However, setting the picker to use the SelectedIndex property works great. This is the KEY. For ListView's containing pickers I will not set their value based on INDEX instead of SelectedItem.

Final Code Snippets

XAML

<Picker HorizontalOptions = "StartAndExpand" WidthRequest="100" ItemsSource="{Binding Path=BindingContext.listIngredients, Source={x:Reference Page}}" SelectedIndex="{Binding IngredientIDLookedUp}" />

View Model

clRecipeIngredient[] arryRecipeIngredients = await recipeDataBase.getRecipeIngredientsDataByRecipeID(310); //HARDCODED TO CRISPY PIZZA RECIPE

        clRecipeIngredient ing = new clRecipeIngredient();

        _downloadedRecipeIngredients.Clear();
        for (int i = 0;i < arryRecipeIngredients.Length;i++)
        {
              
            for (int j=0; j<_listIngredients.Count;j++)
            {
        //FIND THE SELECTED INDEX BASED ON PICKER’S LIST and STORE IN THE CUSTOM CLASS
                if(arryRecipeIngredients[i].IngredientName == _listIngredients[j])
                {
                    arryRecipeIngredients[i].IngredientIDLookedUp = j;
                }
            }

            _downloadedRecipeIngredients.Add(arryRecipeIngredients[i]);

        }

Upvotes: 0

Lucas Zhang
Lucas Zhang

Reputation: 18861

I checked your sample and you could modify it like following .

in Xaml

<Picker HorizontalOptions = "StartAndExpand" WidthRequest="100" ItemsSource="{Binding Path=BindingContext.listIngredients, Source={x:Reference Page}}" SelectedItem="{Binding IngredientName, Mode=TwoWay}" />

in ViewModel

ObservableCollection had implemented the interface INotifyPropertyChanged in default . So you could simplify the code in your ViewModel .

Note : You could not set the value of SelectItem as a string directly even if they are equal . You need to set it like following

 ing.IngredientName = listIngredients[0];

So the ViewModel could like

public class testPageViewModel : BaseViewModel
{

   
    public ObservableCollection<clRecipeIngredient> downloadedRecipeIngredients
    {
        get;set;
    }

 
  
    public ObservableCollection<string> listIngredients { get; set; }

   

    //private clRecipeDataBase recipeDataBase;

    public testPageViewModel()
    {
        //recipeDataBase = new clRecipeDataBase();

        btnPress = new Command<clRecipeIngredient>(madeIt);

        downloadedRecipeIngredients = new ObservableCollection<clRecipeIngredient>();
        listIngredients = new ObservableCollection<string>();
        getData();
    }

    async void getData()
    {
        //PICKER INGREDIENT DATA
        //clIngredient[] arrayIngredients = await recipeDataBase.getIngredientData();

        //clIngredient[] arrayIngredients = new clIngredient[5];

        //arrayIngredients[0].IngredientName = "Apple";
        //arrayIngredients[1].IngredientName = "Salt";
        //arrayIngredients[2].IngredientName = "Buuter";
        //arrayIngredients[3].IngredientName = "Flour";
        //arrayIngredients[4].IngredientName = "Egg";

        listIngredients.Add("Apple");
        listIngredients.Add("Salt");
        listIngredients.Add("Butter");
        listIngredients.Add("Flour");
        listIngredients.Add("Egg");

        //for (int i = 0; i < arrayIngredients.Length; i++)
        //{
        //    _listIngredients.Add(arrayIngredients[i].IngredientName);
        //}

        //clRecipeIngredient[] arryRecipeIngredients = await recipeDataBase.getRecipeIngredientsDataByRecipeID(310); //HARDCODED TO CRISPY PIZZA RECIPE

        clRecipeIngredient ing = new clRecipeIngredient();
      
        ing.IngredientName = listIngredients[0];
        ing.Quantity = 1;
        ing.UnitName = "Cups";
        ing.Comments = "Comments0";
       
        clRecipeIngredient ing2 = new clRecipeIngredient();

        ing2.IngredientName = listIngredients[1];
        ing2.Quantity = 2;
        ing2.UnitName = "Whole";
        ing2.Comments = "Comments1";

        downloadedRecipeIngredients.Add(ing);
        downloadedRecipeIngredients.Add(ing2);


       


    }

    public ICommand btnPress { get; private set; }
    void madeIt(clRecipeIngredient x)
    {
        Console.WriteLine(x.IngredientName + " -- " + x.Comments);

        //_downloadedRecipeIngredients.Remove(x);

    }



}

And don't forget to implement INotifyPropertyChanged in your model as the value of IngredientName will been changed .

public class clRecipeIngredient : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(
    [CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this,new PropertyChangedEventArgs(propertyName));
    }

   //...
    string ingredientName;

    public string IngredientName
    {
        get
        {
            return ingredientName;
        }
        set
        {
            if (ingredientName != value)
            {
                ingredientName = value;
                OnPropertyChanged("IngredientName");
            }
        }
    }

   //...
}

enter image description here

Upvotes: 2

Related Questions