I have a Xamarin.Forms application, and it uses FreshMvvm. I have two picker controls for selecting countries and states/provinces. The picker of countries is populated initially, but the list of states/provinces should be populated on the fly based on the selected country. I cannot find how it can be done using command and not code-behind event handling.
Here are my controls in MyPage.xaml:
<Picker Title="Choose Country..."
ItemsSource="{Binding Countries}"
ItemDisplayBinding="{Binding Value}"
SelectedItem="{Binding SelectedCountry}"
Margin="0, 0, 0, 5" />
<Picker Title="Choose State..."
ItemsSource="{Binding States}"
ItemDisplayBinding="{Binding Value}"
SelectedItem="{Binding SelectedState}"
Margin="0, 0, 0, 5" />
What should I put in MyPageModel.cs?
using Freshmvvm you can make use of the WhenAny method and listen to changes on the SelectedCountry property. When this happens you will get filter the collection of the states by country using the SelectedCountry and update your States collection with the result.
That should look like this:
[PropertyChanged.AddINotifyPropertyChangedInterface]
public class MyViewModel : FreshBasePageModel
{
public ObservableCollection<Country> Countries { get; set; }
public ObservableCollection<State> States { get; set; }
// This would be the collection where you have all the States
private List<State> _allStatesCollection = new List<State>();
public Country SelectedCountry { get; set; }
public MyViewModel()
{
// Listening for changes on the `SelectedCountry`
this.WhenAny(OnCountryChanged, o => o.SelectedCountry);
}
//Method called when a new value is set in the `SelectedCountry` property
private void OnCountryChanged(string property)
{
//Filter the collection of states and set the results
var states = _allStatesCollection.Where(a => a.CountryCode == SelectedCountry.Code).ToList();
States = new ObservableCollection<State>(states);
}
}
Note: The code above expects you to be using the Fody INotifyPropertyChanged Nuget package. In case you are not using it, you can either install it or implement your properties PropertyChanged manually. That won't change the rest of the code.
Hope this helps.-
Related
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.
I got my grid binded to viewmodel's SelectedOrder.OrderItems. I know which Order is selected but how to know once user clicks on grid's row (so specific Item) which Item was selected in view model?
Property:
public Order SelectedOrder
{
get => _selectedOrder;
set => SetValue(ref _selectedOrder, value);
}
Order contains list of OrderItems which are shown on grid:
public class Order : BaseModel
{
public ObservableCollection<Order.Item> OrderItems { get; set; }
}
Grid:
<dataGrid:DataGrid ItemsSource="{Binding SelectedOrder.OrderItems}" SelectionEnabled="True" SelectedItem="{Binding SelectedOrder.OrderItems}">
<dataGrid:DataGrid.Columns>
<dataGrid:DataGridColumn Title="ItemId" PropertyName="ItemId" />
<dataGrid:DataGridColumn Title="ItemName" PropertyName="ItemId" />
</dataGrid:DataGrid.Columns>
</dataGrid:DataGrid>
You want to bind SelectedItem to a property on your view model not to the collection of possible items. If you look in the Output window you should hopefully see some form of Binding errors.
Basically change this:
SelectedItem="{Binding SelectedOrder.OrderItems}"
to something like:
SelectedItem="{Binding SelectedOrderItem}"
You will then also need to add the property to your view model.
e.g.
private Order.Item _selectedOrderItem;
public Order.Item SelectedOrderItem
{
get => _selectedOrderItem;
set => SetValue(ref _selectedOrderItem, value);
}
I'm creating an educational app for learning terminology... using UWP via Xamarin.Forms for the data entry. This view is for creating lists of terms and saving them to my database. In this case, we're making a list for bones of the body. Here's what it looks like:
Pretty simple UI. Every time I click "Add New Item(s)" it creates a "<*New Item*>" Entry at the top of my CollectionView. I overwrite "<*New Item*>" with the word I want, and then click the "Save" button.
The "Add New Item(s)" button works like a champ, but the "Save" button is sick. After clicking "Save" the first time, it sends the list to my json database just fine. When I inspect the database I see "<*New Item*>" along with the rest of the list, like so:
"name": "Bones of the Body, Common",
"choiceFormat": "6x1",
"items": [
"<*New Item*>",
"Ankle Bone",
"Arm Bone",
"Breastbone",
"Cheek Bone",
"Collarbone",
"Forearm Bone (Large)",
"Forearm Bone (Small)",
"Hammer",
"Hard Palate (back)",
"Heel Bone",
"Hip Bone",
"Knee Cap",
"Leg Bone (Large)",
"Leg Bone (Small)",
"Lower Jaw",
"Rib",
"Rib Cage",
"Shoulder Blade",
"Spine",
"Stirrup",
"Tailbone",
"Thigh Bone",
"Tongue Bone",
"Upper Jaw"
],
"spellable": true
Now in my UI, I click into the top Entry for "<*New Item*>" and rename it to "Anvil" like so...
Then when I click save, the database list stays the same... it still says "<*New Item*>" and not "Anvil".
So I dropped a breakpoint in the save function and the debugger's telling me that here's where the issue is... the ObservableCollection that's the source for my CollectionView (named OCItems) doesn't say "Anvil", it's stuck on "<*New Item*>". (The location of the breakpoint is noted in my ViewModel code below.)
My other controls (Choice Format and Spellable) bind, update, and save to the database just fine. What's going on with the Entry controls in my CollectionView? Why don't they update when I replace the text in them? Note that both {Binding}s are TwoWay.
Here's the relevant code:
XAML
...
<Button Text="Add New Item(s)" Grid.Row="2" Grid.Column="0" Command="{Binding AddItemSlot}" /><Button Text="Save" Grid.Row="2" Grid.Column="2" Command="{Binding SaveAnswerSet}" />
<CollectionView x:Name="MyCollectionView" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" ItemsSource="{Binding OCItems, Mode=TwoWay}" >
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<Entry Text="{Binding Mode=TwoWay}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
...
ViewModel
...
public ObservableCollection<string> OCItems { get; set; }
public ICommand AddItemSlot { get; set; }
public ICommand SaveAnswerSet { get; set; }
...
...
// (in constuctor)
AddItemSlot = new Command(() =>
{
OCItems.Insert(0, "<*New Item*>");
});
SaveAnswerSet = new Command(() =>
{
App.Database.Save(OCItems); // BREAKPOINT
});
...
Thanks for your consideration.
Kind regards,
David
Entry controls in a CollectionView aren't updating the source they're bound to
The problem is string does not implement INotifyCollectionChanged interface, so it will not notify the item change after modified. For this scenario, we suggest make a class to wrap string type and implement INotifyCollectionChanged interface. And use ObservableCollection to replace ObservableCollection.
public class StringWrap : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string PropertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
private string _content;
public string Content
{
get
{
return _content;
}
set
{
_content = value;
OnPropertyChanged();
}
}
}
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
}
}
}
I have a ListView which is bound to an ObservableCollection.
Is there a way to update a single cell whenever a property of a SomeModel item changed, without reloading the ListView by changing the ObservableCollection?
(Question is copied from https://forums.xamarin.com/discussion/40084/update-item-properties-in-a-listviews-observablecollection, as is my answer there.)
As I can see you are trying to use MVVM as a pattern for your Xamarin.Forms app. You are already using the ObservableCollection for displaying a list of the data. When a new item is added or removed from collection UI will be refreshed accordingly and that is because the ObserverbleCollection is implementing INotifyCollectionChanged.
What you want to achieve with this question is next behaviour, when you want to change the particular value for the item in the collection and update the UI the best and simplest way to achieve that is to implement INotifyPropertyChanged for a model of the item from your collection.
Bellow, I have a simple demo example on how to achieve that, your answer is working as I can see but I am sure this example would be nicer for you to use it.
I have simple Button with command and ListView which holds my collection data.
Here is my page, SimpleMvvmExamplePage.xaml:
<StackLayout>
<Button Text="Set status"
Command="{Binding SetStatusCommand}"
Margin="6"/>
<ListView ItemsSource="{Binding Cars}"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical"
Margin="8">
<Label Text="{Binding Name}"
FontAttributes="Bold" />
<StackLayout Orientation="Horizontal">
<Label Text="Seen?"
VerticalOptions="Center"/>
<CheckBox IsChecked="{Binding Seen}"
Margin="8,0,0,0"
VerticalOptions="Center"
IsEnabled="False" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
The basic idea from this demo is to change the value of the property Seen and set value for the CheckBox when the user clicks on that Button above the ListView.
This is my Car.cs class.
public class Car : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
private bool seen;
public bool Seen
{
get { return seen; }
set
{
seen = value;
OnPropertyChanged();
}
}
// Make base class for this logic, something like BindableBase
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In the full demo example which is on my Github, I am using my BindableBase class where I handle raising the INotifyPropertyChanged when some property value is changed with this SetProperty method in the setter of the props.
You can find the implementation here: https://github.com/almirvuk/Theatrum/tree/master/Theatrum.Mobile/Theatrum.Mobile
The last thing to show is my ViewModel for this page, and inside of the ViewModel, I will change the value of Seen property to True for items in the collection when the user clicks on the Button above the ListView. Here is my SimpleMvvmExamplePageViewModel.cs
public class SimpleMvvmExamplePageViewModel
{
public ObservableCollection<Car> Cars { get; set; }
public ICommand SetStatusCommand { get; private set; }
public SimpleMvvmExamplePageViewModel()
{
// Set simple dummy data for our ObservableCollection of Cars
Cars = new ObservableCollection<Car>()
{
new Car()
{
Name = "Audi R8",
Seen = false
},
new Car()
{
Name = "BMW M5",
Seen = false
},
new Car()
{
Name = "Ferrari 430 Scuderia",
Seen = false
},
new Car()
{
Name = "Lamborghini Veneno",
Seen = false
},
new Car()
{
Name = "Mercedes-AMG GT R",
Seen = false
}
};
SetStatusCommand = new Command(SetStatus);
}
private void SetStatus()
{
Car selectedCar = Cars.Where(c => c.Seen == false)
.FirstOrDefault();
if (selectedCar != null)
{
// Change the value and update UI automatically
selectedCar.Seen = true;
}
}
}
This code will help us to achieve this kind of behaviour: When the user clicks on the Button we will change value of the property of the item from collection and UI will be refreshed, checkbox value will be checked.
The final result of this demo could be seen on this gif bellow.
P.S. I could combine this with ItemTapped event from ListView but I wanted to make this very simple so this example is like this.
Hope this was helpful for you, wishing you lots of luck with coding!
Any UI associated with a model item will be refreshed, if replace the item with itself, in the Observable Collection.
Details:
In ViewModel, given property:
public ObservableCollection<Item> Items { get; set; } = new ObservableCollection<Item>();
Where Item is your model class.
After adding some items (not shown), suppose you want to cause item "item" to refresh itself:
public void RefreshMe(Item item)
{
// Replace the item with itself.
Items[Items.IndexOf(item)] = item;
}
NOTE: The above code assumes "item" is known to be in "Items". If this is not known, test that IndexOf returns >= 0 before performing the replacement.
In my case, I had a DataTemplateSelector on the collection, and the item was changed in such a way that a different template was required. (Specifically, clicking on the item toggled it between collapsed view and expanded/detailed view, by the TemplateSelector reading an IsExpanded property from the model item.)
NOTE: tested with a CollectionView, but AFAIK will also work with the older ListView class.
Tested on iOS and Android.
Technical Note:
This replacement of an item presumably triggers a Replace NotifyCollectionChangedEvent, with newItems and oldItems both containing only item.