This question already has an answer here:
How to get the index of a clicked CollectionView element?
(1 answer)
Closed 2 years ago.
I need to get the index of the element I have clicked from my CollectionView and use it in the ViewModel. How can I get it? I am new to Xamarin Forms
There are a few options for this; I'll name a few:
An 'set-once' index property in the item ViewModel. When creating the list in the viewmodel, also set the index for each item ViewModel. (e.g. with a for-loop and an iterator). You can then bind to that index property. Downside is that changes in the list are not reflected on the index.
for (int i = 0; i < Parent.Childs.Count; i++)
{
string child = (string)Parent.Childs[i];
Childs.Add(new ChildViewModel(i, child));
}
with
public class ChildViewModel
{
public int Index { get; set; }
...
}
A 'live' index property. Pass the containing list into the ViewModel and add a property with a getter that returns 'Parent.IndexOf(this)'. But you need some way of raising PropertyChanged for the Index when the collection changes. Using an ObservableCollection and subscribing to CollectionItemsChanged would be one way to achieve that.
foreach (var item in Parent.Childs)
{
Childs.Add(new ChildViewModel(Parent.Childs, child));
}
with
public class ChildViewModel
{
public int Index { get; } => Parent.IndexOf(this);
...
public ChildViewModel(IList<Child> parent)
{
if (parent is INotifyCollectionChanged p)
p.CollectionChanged += ParentCollectionChanged;
}
private void ParentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged(nameof(Index));
}
}
Binding the item and using a ValueConverter taking the list as the converter parameter. Again using IndexOf(...)
Binding the collection and using a ValueConverter taking the item as the converter parameter. Again using IndexOf(...)
One example of the valueconverter taking the item is given in this answer: https://stackoverflow.com/a/662232/11201993 this does not require you to add fields to the viewmodel.
Related
I'm new to Xamarin world, and its data binding rules. I have ViewModel with observable CurrentOrder property, and 3 properties which values depend on it. All 4 properties are used in View with bindings, so that each change in CurrentOrder should propagate changes to other 3 properties and that impact how view displays its controls and data. I'm confused with how I'm supposed to propagate the signal of CurrentOrder change to other 3 dependent properties. I came up with code that actually works, but to me it looks a bit awkward, and sets dependency inside independent property CurrentOrder, while I would prefer it to be other way around: dependent properties should better know what property they depend on.
Please note that SetProperty and OnPropertyChanged methods are in a base view model, and generated by standard VS Xamarin.Forms project pattern.
private int _currentOrder = 1;
public int CurrentOrder
{
get => _currentOrder;
set => SetProperty(ref _currentOrder, value, onChanged: () =>
{
OnPropertyChanged(nameof(CurrentItem));
OnPropertyChanged(nameof(IsTheLastItem));
OnPropertyChanged(nameof(IsTheFirstItem));
});
}
public string CurrentItem => Items[CurrentOrder - 1];
public bool IsTheLastItem => CurrentOrder == Items.Count;
public bool IsTheFirstItem => CurrentOrder == 1;
Any recommendations over best practices here are very appreciated
I would use Fody propertychanged for dependent properties like this.
Just add the nuget and dont forget to add FodyWeavers.xml, your class will be like below
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int CurrentOrder {get; set;}
public string CurrentItem => Items[CurrentOrder - 1];
public bool IsTheLastItem => CurrentOrder == Items.Count;
public bool IsTheFirstItem => CurrentOrder == 1;
}
This question already has answers here:
C# object to array
(6 answers)
Closed 3 years ago.
I want to obtain the values of properties of a complex model (IList(Object) in object)). I find the the main properties of the parent object and also the type of the childobject which I need. But I cannot extract its values.
I think the issue is due to the obect parameter in the GetValue method. It needs to be the "TheMovieDatabaseModelDetails" object. I've tried a lot of various options here, but get the error: "object does not match target type".
Model:
public class TheMovieDatabaseModel
{
public int page { get; set; }
public int total_results { get; set; }
public int total_pages { get; set; }
public IList<TheMovieDatabaseModelDetails> results { get; set; }
}
Code:
private async Task GetMovieDetailsForTheMovieDatabase<T>(T movieModel)
{
PropertyInfo[] propertyInfo = movieModel.GetType().GetProperties();
foreach (PropertyInfo property in propertyInfo)
{
if (property.Name.Equals("results"))
{
var movieDetails = property.GetType().GetProperties();
foreach (var detail in movieDetails)
{
detail.GetValue(movieDetails, null); // here I need to fill in the right "object".
}
}
// etc..
}
}
research (amongst others):
Get Values From Complex Class Using Reflection
I've found the answer in:
C# object to array
I needed to created an IEnumerable first, as the parent model creates an IList of the ChildModel (movies, which have the moviedetails):
if (property.Name.Equals("results"))
{
object movieObject = property.GetValue(movieModel);
IEnumerable movieObjectList = movieObject as IEnumerable;
if (movieObjectList != null)
{
foreach (object movie in movieObjectList)
{
PropertyInfo[] movieDetails = movie.GetType().GetProperties();
foreach (PropertyInfo detail in movieDetails)
{
detail.GetValue(movie, null);
}
}
}
}
In order to explain my problem let's say I have in my ViewModel an ObservableCollection which has several elements of my type Item defined in my Models. These items as in any other shop have a name and a price and are all listed in one of my views.
My question is if there is any way to create variables to alter, for example, the price of each item dynamically. What I would like to do is: for each element in my listView, have one or more entries to allow me to customize his characteristics. Let's say that the user enters the value 2 in one of this entries, the price of the item corresponding to that line of the listView should be 2x higher but the others must remain the same.
To make it a practical example, take into consideration the following image. Let's say the first line is the name of the product and the second line is the price, I would like to have, in each row, at least one entry to allow me to customize the value of the price. Is it possible?
Article List
You can create a new property to bind to Label.Text, and update it when the property bound to the Entry.Text property changes.
Yes,
you can add the Entry to your ViewCell, and bind the Entry to the same property you bind to the Label. When you change the value in the Entry, the value in the Label should change.
Possibly the simplest way to achieve this is to wrap each Item in an ItemViewModel.
The ItemViewModel has a public Property Multiplier where - in the Setter - the price of the containing Item will be updated.
For the price change to be reflected in the view - it must implement INotifyPropertyChanged and of course raise the event when the 'Price' is set.
Alternatively: copy the Price from the Item to the ItemViewModel in the constructor to an additional Price property on the ItemViewModel
With the sample code you'd need a IValueConverter to convert the Entry Text from string to double (or use an apropriate control that allows binding to double)
public class ItemsViewModel
{
public ObservableCollection<ItemViewModel> Items { get; set; }
public ItemsViewModel()
{
this.Items = new ObservableCollection<ItemViewModel>(getItemsFromSomewhere().Select(item => new ItemViewModel(item)));
}
}
public class ItemViewModel : INotifyPropertyChanged
{
private readonly Item item;
private double price;
private double multiplicator;
public ItemViewModel(Item item)
{
this.item = item;
this.price = item.Price;
}
public double Multiplicator {
get { return this.multiplicator; }
set {
this.multiplicator = value;
this.Price = this.item.Price * value;
}
}
public double Price {
get { return this.price; }
set {
this.price = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Item
{
public double Price { get; set; }
}
In a "Blank App" (Visual C#, Windows Store), I create a new "Grouped Items Page", then declare a MyItemViewModel class deriving from DependencyObject with a Dependency Property for a String Title.
This is the page's LoadState method:
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
this.DefaultViewModel["Groups"] = this.items;
this.items.Add(new MyItemViewModel { Title = "My Title" });
for (int i = 0; i < 5; i++)
{
await Task.Delay(1000);
this.items.First().Title += ".";
}
}
The expectation is that dots appear after the item's title every second. The actual output is simply "My Title" and nothing else happens.
By adding the following unreferenced dependency property the dots will then appear:
public MyItemViewModel blah
{
get { return (MyItemViewModel)GetValue(blahProperty); }
set { SetValue(blahProperty, value); }
}
public static readonly DependencyProperty blahProperty =
DependencyProperty.Register("blah", typeof(MyItemViewModel), typeof(GroupedItemsPage1), new PropertyMetadata(0));
Why does the GridView only refresh the Title property of the item view model when there is an unused dependency property with the same type?
Do view model classes always have to be explicitly declared as a dependency property somewhere in an app at least once?
DependencyObject is usually inherited by UIElement (e.g., Grid, TextBlock, etc.), and their properties are DependencyProperty which allows, for example, Binding.
A ViewModel should implement INotifyPropertyChanged instead of inherit from DependencyObject. If you look at sample templates like GridApp, you will see that BindableBase implements INotifyPropertyChanged.
While I was developing a startscreen for my app using the GridView control, I run into a problem. I have a GridView on my main screen which has a CollectionViewSource set as ItemSource.
For this CollectionViewSource the source is set to an ObservableCollection list. Each GroupViewModel has a ObservableCollection in it. In code the important parts looks like the following:
public class StartPageViewModel : ViewModelBase
{
public ObservableCollection<GroupViewModel> Groups { get; set; }
public CollectionViewSource GroupsCvs { get; set; }
public StartPageViewModel()
{
// fill Groups with some mock data
GroupsCvs.Source = Groups;
GroupsCvs.IsSourceGrouped = true;
}
public void MoveItems(GroupViewModel grp)
{
// add a dummy item
grp.AddRecipe(new ItemViewModel(new Item()) { Id = "123" });
RaisePropertyChanged("GroupsCvs");
RaisePropertyChanged("Groups");
}
}
public class GroupViewModel : ViewModelBase, IEnumerable<ItemViewModel>
{
public ObservableCollection<ItemViewModel> Items { get; set; }
}
View:
public sealed partial class MainPage : LayoutAwarePage
{
private ViewModelLocator locator = new ViewModelLocator();
public MainPage()
{
this.InitializeComponent();
this.DataContext = locator.Main; // returns StartPageViewModel
}
}
XAML part for MainPage, GridView
<GridView ItemsSource="{Binding GroupsCvs.View}" ...
</GridView>
How is it possible to get the UI refreshed when I add an Item to a Group's collection? In my StartPageViewModel I'm adding dummy item to the GroupViewModel and I raise propertychanged, but the Grid remains the same.
I've also tried to fire property changed event in the GroupViewModel class, when the Items collection changes without any luck.
Edit: As I wrote in comments it's possible to refresh with reassigning the source property however this gets the GridView rendered again which is not nice. I'm looking to options which would result in a nicer user experience.
I suppose CollectionViewSource doesn't react to PropertyChanged event. Try reassigning Source to GroupCvs after you modify it. It's not elegant but it should work:
GroupsCvs.Source = Groups;
As a last resort you could create a new instance of ObservableCollection<GroupViewModel> before reassigning it:
Groups = new ObservableCollection<GroupViewModel>(Groups)
GroupsCvs.Source = Groups;
<GridView ItemsSource="{Binding GroupsCvs.View, **BindingMode=TwoWay**}" ...
</GridView>