Picker inside collectionview looses value in SelectedIndexChanged - xamarin.forms

I have picker inside Xamarin Forms collectionview, which populated from viewModel. In case of changing index, i.e SelectedIndexChanged event, all pickers loose value and only the last one will be updated to the one which I seleced.
Picker in collectionview,which has 4 rows.
<local:CustomPicker x:Name="HoursPicker"
Title="select"
FontSize="14"
Grid.Column="3"
ItemsSource="{Binding Hours}"
SelectedIndex="{Binding AmountHour, Mode=TwoWay}"
SelectedIndexChanged="HoursPicker_SelectedIndexChanged"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand">
</local:CustomPicker>
and its my SelectedChangeIndex event
private void HoursPicker_SelectedIndexChanged(object sender, EventArgs e)
{
Picker picker = (sender) as Picker;
int selectedIndex = picker.SelectedIndex;
int selectedIndexValue = picker.SelectedIndex + 1;
ObservableCollection<OrderCardModel> list = new ObservableCollection<OrderCardModel>();
OrderViewModel vm = new OrderViewModel();
foreach (var item in MyCollectionView.ItemsSource)
{
OrderCardModel model = item as OrderCardModel;
int changedPrice = 0;
if (selectedIndex != -1)
{
changedPrice = selectedIndexValue * model.Price;
}
else
{
changedPrice = model.Price;
}
model.Price = changedPrice;
list.Add(model);
picker.SelectedIndex = selectedIndex;
}
MyCollectionView.ItemsSource = list;
}

Finally I figured out where is the problem. The model, i.e. OrderCardViewModel should implement INotifyPropertyChanged.
The page (view) binded to the ViewModel, but the collectionview itemsource binded to ObservableCollection of model, even the property comes from view model, but it did not work properly, thatswhy pickers cant respond indexchanging. To do that, model must implement the interface and OnPropertyChanged will update values.

Related

Xamarin Forms Picker Value Binding

When binding to a picker, you can use ItemDisplayBinding to bind the displayed value, but I do not see a way to map each item to a selection value. Because of this, I'm having to write some very convoluted code to keep my pickers in sync with data source changes.
Original Model
// NOTE: this implements INPC, just abbreviated for clarity
public class DataModel
{
public ICollection<DataItem> Items;
pubilc DataItem SelectedItem;
}
Original Picker:
<Picker Title="Select Item..."
ItemsSource="{Binding Items}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding Path=SelectedItem}"></Picker>
New Model
// NOTE: this implements INPC, just abbreviated for clarity
public class DataModel
{
public ICollection<DataItems> Items;
public ICollection<string> ItemNames;
public DataItem SelectedItem;
public string SelectedItemName;
public DataModel()
{
this.PropertyChanged += (s, e) =>
{
// I feel like I shouldn't have to do this...
if(StringComparer.Ordinal.Equals(e.PropertyName, nameof(Items)))
{
if(!String.IsNullOrWhitespace(this.SelectedItemName))
{
this.SelectedItem = this.Items.FirstOrDefault(x => StringComparer.Ordinal.Equals(x.Name, this.SelectedItemName));
if (this.SelectedItem == null) { this.SelectedItemName = null; }
}
}
}
}
New Picker:
<Picker Title="Select Item..."
ItemsSource="{Binding ItemNames}"
SelectedItem="{Binding Path=SelectedItemName}"></Picker>
I would like to be able to do something like this:
<Picker Title="Select Item..."
ItemsSource="{Binding Items}"
ItemDisplayBinding="{Binding Name}"
ItemValueBinding="{Binding Name}"
SelectedItem="{Binding Path=SelectedItemName}"></Picker>
I do not need a reference to the item, I need a property off of it. In this way, when the Items collection changes, it automatically reselects the correct item if it's still present. I find that I'm adding a second collection everywhere with just the properties I want to choose and doing all this mapping. Every other platform I've worked on, this is pretty straight forward, so I feel like I have to be missing something with Xamarin.Forms.
I think you don't need to do this.The SelectedItem property data binds to the SelectedItem(in your original model) property of the connected view model, which is of type DataItem. Therefore, when the user selects an item in the Picker, the SelectedItem property will be set to the selected DataItem object automatically.
You could test it in its SelectedIndexChanged event like:
<Picker Title="Select Item..."
ItemsSource="{Binding Items}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding Path=SelectedItem}"
SelectedIndexChanged="Picker_SelectedIndexChanged">
</Picker>
private void Picker_SelectedIndexChanged(object sender, EventArgs e)
{
Picker picker = sender as Picker;
DataItem dt = picker.SelectedItem as DataItem ;
Console.WriteLine(dt.Name); // you will see when you select a item,the SelectedItem will be changed automatically
}
And i suggest you use ObservableCollection<Item> Items, so it will automatically update your Items when it changes.

How can i Refresh a object?

I have a property representing an actor
private Actor _actor;
public Actor Actor
{
get => _actor;
set
{
if (_actor != value) {
_actor = value;
OnPropertyChanged("Actor");
}
}
}
and a list, with a checkmark that depends on the state of Actor. When I click over the label the state of Actor shall change the checkmark
private async void OnSelectionAsync(object item)
{
Actor = item;
but I cannot see the changes in my ListView, why?
Edit 1:
in my list, i am binding the actor Text="{Binding Actor.id} to send my converter and to change the item check
<Label IsVisible="False" x:Name="dTd" Text="{Binding Actor.id}" />
<ListView ItemsSource="{Binding Actors}">
...
<Image Source="" IsVisible="{Binding id , Converter={StaticResource MyList}, ConverterParameter={x:Reference dTd}}"/>
As far as I can tell, you are using the text of your label to determine whether an actor is selected or not. Anyway, you are not binding to that text in any way, but you are using the Label itself as a binding parameter. I do not know for sure, but it seems to me as if "binding" the value this way does not work as intended by you, because change notifications are not subscribed to this way. Anyway, when you refresh that list, the bindings are refreshed and the converter is used with the new value. Since ConverterParameter is not a bindable property I doubt that there is any chance to use it this way.
What I'd do is to either extend your Actor class or create a new ActorViewModel class with the property
public bool IsSelected
{
get => isSelected;
set
{
if (value == isSelected)
{
return;
}
isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
From your ListView you can now bind to that property
<Image Source="..." IsVisible="{Binding IsSelected}"/>
All you have to do now is setting the property accordingly
private async void OnSelectionAsync(object item)
{
Actor = item as Actor;
foreach(var actor in Actors)
{
Actor.IsSelected = actor.id == Actor.id;
}
}
this way, the change should reflect in the ListView.

How to have Select as default option in Xamarin forms Picker Control

I have a picker control which is optional field and the user can set the selected item of picker to be empty if he wants.
Is it possible to have Select as the additional option in itemsource of xamarin forms picker control.
Code for Custom picker:
<controls:CustomPicker
Grid.Row="1"
Grid.Column="1"
Margin="0,5,0,0"
Image="Downarrow"
ItemDisplayBinding="{Binding abb}"
ItemsSource="{Binding StateList}"
Placeholder="Select"
SelectedIndex="{Binding StateSelectedIndex}"
Style="{StaticResource Key=PickerHeight}" />
StateSelectedIndex = -1;
I tried setting selected index = -1. That works only when nothing is selected in the picker control, but once if an item is selected from the picker, then option "Select" can not be chosen (disappears).
I tried referring below url Default value for Picker but this did not help.
Any help is appreciated.
Solution 1:
You should set the binding mode of SelectedIndex
SelectedIndex="{Binding StateSelectedIndex,Mode=TwoWay}"
Solution 2:
You could binding the value of SelectedItem in ViewModel .
public class YourViewModel: INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<string> MyItems { get; set; }
private string selectItem;
public string SelectItem
{
get
{
return selectItem;
}
set
{
if(value!=null)
{
selectItem = value;
NotifyPropertyChanged("SelectItem");
int SelectIndex = MyItems.IndexOf(value);
// this will been invoked when user change the select , do some thing you want
}
}
}

Can't get parent binding context from data template created in code

I'm creating a data template in code in a view model and binding it to a ListView like so
<ListView
x:Name="MainList
ItemTemplate="{Binding JobListTemplate}"
I have a button in the template that binds to a command in the view model.
Previously the data template was hard coded in the page XAML and the button looked like this
<Button
Text="Click me"
Command="{Binding Path=BindingContext.ClickMeCommand, Source={x:Reference MainList}}"
CommandParameter="{Binding .}" />
All worked well.
Now the button is created in code like this
var button = new Button();
button.Text = "Click me;
button.SetBinding(Button.CommandProperty, new Binding("BindingContext.ClickMeCommand",
BindingMode.Default, null, null, null,
new ReferenceExtension { Name = "MainList" }));
and I can't seem to get the parent (list view) binding context for the command properly bind to the vm.
I have read some posts that suggest it can be an issue if the template is not in the same file as the page.
Any ideas?
This was quite complex and took parts from various SO answers and Xamarin forum posts so I thought I'd post the answer.
First I created a ViewCell class where I assembled the view cell in code. Note the _favouriteButton variable.
public class JobListViewCell : ViewCell
{
private Button _favouriteButton;
public JobListViewCell()
{
BuildViewCell();
}
I created the button and bound the command parameter property - this will pass the list view item
_favouriteButton = new Button();
_favouriteButton.SetDynamicResource(VisualElement.StyleProperty, "IconButtonSolid");
_favouriteButton.Text = FontAwesomeIcons.Heart;
_favouriteButton.SetBinding(Button.CommandParameterProperty, new Binding("."));
I created a bindable property called ParentBindingContext - this will allow me to update the binding context on the buttons command
//
// ParentBindingContext
//
public static readonly BindableProperty ParentBindingContextProperty =
BindableProperty.Create(nameof(ParentBindingContext), typeof(object),
typeof(JobListViewCell), "", BindingMode.OneWay, null,
OnParentBindingContextPropertyChanged);
private static void OnParentBindingContextPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
if (bindable is JobListViewCell control)
{
control._favouriteButton?.SetBinding(Button.CommandProperty,
new Binding("ToggleIsFavouriteCommand",
BindingMode.Default, null, null, null,
newvalue));
}
}
public object ParentBindingContext
{
get => (object)GetValue(ParentBindingContextProperty);
set => SetValue(ParentBindingContextProperty, value);
}
Finally, in my page XAML I added the view cell and bound ParentBindingContext to the list view.
<ListView
Grid.Row="1"
ItemsSource="{Binding Jobs}"
SelectedItem="{Binding SelectedJob, Mode=TwoWay}"
x:Name="MainList">
<ListView.ItemTemplate>
<DataTemplate>
<viewCells:JobListViewCell
ParentBindingContext="{Binding BindingContext, Source={x:Reference MainList}}">
</viewCells:JobListViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here is the command in the view model. The list view item containing the button that was pressed is passed in.
private Command _toggleIsFavouriteCommand;
public Command ToggleIsFavouriteCommand => _toggleIsFavouriteCommand ??= new Command<JobMaster>(ToggleIsFavourite);
private void ToggleIsFavourite(JobMaster jobMaster)
{
jobMaster.IsFavourite = !jobMaster.IsFavourite;
}
Hope this helps someone.
Your setting the source reference is wrong. Refer to this link Data Binding.
var button = new Button
{
Text = "Click me",
};
button.SetBinding(Button.CommandProperty, new Binding("BindingContext.ClickMeCommand",source: listView));

get the selected itemssource binding value on selectedindexchanged on picker xamarin forms

I am using Picker in xamarin forms. I am binding the below class to Picker:
public string FieldCode{get; set; }
public string FieldValue{get; set; }
The Picker is like below:
<Picker Grid.Row="1" Grid.Column="0" x:Name="pkrMvmtCat" Style="{StaticResource WOFormPicker}" Title="Select" ItemsSource="{Binding FieldCode}" ItemDisplayBinding="{Binding FieldValue}" SelectedIndexChanged="pkrMvmtCat_SelectedIndexChanged"></Picker>
I want to get the FieldCode value, when i change the picker index.
Please me to resolve this issue.
You have to listen to the event, and then cast the selected item from the list. Here is an example:
void OnPickerSelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
if (selectedIndex != -1)
{
var field = (string)picker.ItemsSource[selectedIndex];
}
}

Resources