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; }
}
Related
Can someone explain to me why the first property below (Name) updates fine from the UI, but the second one (End) does not? Both properties display correctly, so it IS bound. It just won't update the child property.
Period.Period (not my choice in the naming) is defined as a datetimeoffset.
<custom:FieldControl TargetObject="{Binding Path=Period}" TargetProperty="Name" IsReadOnly="False" />
<custom:FieldControl TargetObject="{Binding Path=Period.Period}" TargetProperty="End" IsReadOnly="False" />
I'm very new to XAML, so, if I haven't included enough detail, let me know and I'll edit the question.
You probably need to implement a INotifyPropertyChanged.
Here is an example with your two properties:
public class PeriodSample : INotifyPropertyChanged
{
private string name;
private DateTimeOffset period;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
public DateTimeOffset Period
{
get
{
return period;
}
set
{
period = value;
OnPropertyChanged(nameof(Period));
}
}
public PeriodSample()
{
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
This concept allow to notify the Control when the binded properties changed.
If that doesn't solve the problem, could you provide a more complete example please?
I have this object
[DataContract]
public class FilterList<T> : List<T>
{
[DataMember]
public int Total { get; set; }
}
In my controller:
public ActionResult<FilterList<MyPOCO>> GetFilteredResult(string filter)
{
var l = new FilterList<MyPOCO>();
l.Total = 123456;
// Continue to add many MyPOCO objects into the list
return l;
}
I can get back the MyPOCO list at the client side, but the l.Total is NOT serialize. May I know what I had done wrongly?
Here is a workaround , you could try to use [JsonObject] attribute . But the items will not be serialized, because a JSON container can have properties, or items -- but not both. If you want both, you will need to add a synthetic list property to hold the items.
[JsonObject] will also cause base class properties such as Capacity to be serialized, which you likely do not want. To suppress base class properties, use MemberSerialization.OptIn. Thus your final class should look something like:
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class FilterList<T> : List<T>
{
[JsonProperty]
public int Total { get; set; }
[JsonProperty]
List<T> Items
{
get
{
return this.ToList();
}
set
{
if (value != null)
this.AddRange(value);
}
}
}
Result:
My user model has an int property called active (its an int because I didn't create the model nor the database, its getting used as a boolean..). I'm creating the action /Users/Edit/ that will be editting the model, so I've got a form with a checkbox for the active property. When the submit button is pressed the model binder will put all the form data together into one single object and pass it as a parameter to /Users/Edit.
But when I try to save the changes to the database, I check ModelState.isValid and it returns false because the model binder won't change the boolean into an integer.
How can I solve this problem?
1) Edit Db to make it a bit
2) Change the property on your view model to a bool
3) Use DisplayFor in your Razor View
It will now automatically generate a checkbox and check it accordingly.
Let's assume worst case that you cannot change your Db to a bool. Then you would use a View Model to perform the data transformation for you.
Say your user model is such:
public class StaticModel {
public string description { get; set; }
public int IsActive { get; set; }
}
Then you create a view model (facade style):
public class StaticModelFacade {
StaticModel _value;
public StaticModelFacade(StaticModel value) {
_value = value;
}
public string description {
get {return _value.description;}
set {_value.description = value;}
}
public bool IsActive {
get { return _value.IsActive = 1; }
set { _value.IsActive = value ? 1 : 0 }
}
}
Your view now binds to a bool and all is wired up Ok.
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>
I am having a bit of trouble understanding how I will design a class.
I want to be able to get n amount of System fields out onto a report alongside custom fields.
I want a simple method on an interface called:
ICollection<Field> GetFieldDefinitions();
Internally this should get all the fields that I need to show on the report.
A second method will return field and their values too:
ICollection<Field> GetFieldDefinitionsWithValues(T src);
T is the source of where the information for each field will be populated from, e.g. if I pass in Company, the field definition if it contains CompanyName, I will do a lookup on the Company table and retrieve the info and add it to the field.
public Class SystemFieldCompany
{
IDictionary<string,Field> list;
private readonly ValidationEngine _val;
public SystemFieldCompany(ValidationEngine val)
{
_val = val;
list = new Dictionary<string,Field>();
}
public ICollection<Field> GetFields()
{
list.add("id",new Field{name = "id", value = "5"});
list.add("nameofcompany",new Field{name = "nameofcompany", value = "super guys"});
return list.Values;
}
//pass in model object with values on it, set up fields, then pass back all fields
ICollection<Field> GetFieldsWithValues(T object);
}
Should this class above be a concrete class?
e.g. var fields = new FieldClass().GetFields();
or should I use composition? How can I do this via an interface?
Abstract Class is what your after
public abstract class FieldBase
{
ICollection<Field> _data=new List<Field>();
abstract void DoValidationOrSomething();
ICollection<Field> virtual GetFields() //perform validation internally - return back the object
{
DoValidationOrSomething();
return _data;
}
T virtual UpdateFields(ICollection<Field> fields); //pass in model object with values on it, set
{
_data.Clear();
_data.AddRange(fields);
}
up fields, then pass back all fields
ICollection<Field> virtual GetFieldsWithValues(T object)
{
return _data.Where(f=>f.Name=T);
}
}
then in your concrete
public class SomeTable:FieldBase
{
public void DoValidationOrSomething()
{
//per class validation here
}
}