I am trying to pass the selected item from the list to the detail view, but myitem is null in the DetailViewmodel even though it is not in the MyViewModel.
MyViewModel.cs
public virtual ICommand ItemSelected
{
get
{
return new MvxCommand<MyViewModel>(item =>{SelectedItem = item;});
}
}
public MyViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
// myItem is NOT null here!!!
ShowViewModel<MyDetailViewModel>(new { date = Date, myItem = _selectedItem });
RaisePropertyChanged(() => SelectedItem);
}
}
MyDetailViewModel.cs
public class MyDetailViewModel: MvxViewModel
{
private MyViewModel _myItem;
public void Init(DateTime date, MyViewModel myItem = null)
{
// myItem is NULL here!!!
_myItem = myItem;
}
}
You can use a parameter object, because you can only pass one parameter. I usually crate a nested class Parameter for this.
public class MyDetailViewModel: MvxViewModel
{
private MyViewModel _myItem;
public class Parameter
{
public DateTime Date {get; set; }
public string Name {get; set;}
}
public void Init(Parameter param)
{
Name = param.Name;
}
}
and show the viewmodel like:
ShowViewModel<MyDetailViewModel>(new MyDetailViewModel.Parameter { Date = Date, Name = _selectedItem.Name });
But be aware!
The paramters cannot be complex due certain platform issues. You might have to pass only the Id of your Item within the Parameter object and then load MyItem in your Init function. Or you pass only a string and use serialization: https://stackoverflow.com/a/19059938/1489968
myItem is null because if you pass typed parameter to Init it should be the only parameter you pass. According to MvvmCross ViewModel Creation documentation:
Init() can come in several flavors:.
individual simply-Typed parameters
a single Typed parameter object with simply-Typed properties
as InitFromBundle() with an IMvxBundle parameter - this last flavor is always supported via the IMvxViewModel interface.
Related
I am searching for a solution where i can ask a model if a property has changed. But i want to prevent to write own setter methods for all models and all their properties.
I want to use this to automatically generate a update queries based models and there changed properties. But if my model has a boolean property Test which is by default false, then i can't differentiate if the value is from the request payload or if it is the default value.
I already saw the INotifyPropertyChanged Implementation but there i have to write a setter for all properties too.
public class Main
{
public static void main()
{
var person = new Person();
Console.WriteLine(person.HasChanged("Firstname")); // false
Console.WriteLine(person.HasChanged("Lastname")); // false
Console.WriteLine(person.HasChanged("LikesChocolate")); // false
person.Firstname = "HisFirstname";
person.LikesChocolate = true;
Console.WriteLine(person.HasChanged("Firstname")); // true
Console.WriteLine(person.HasChanged("Lastname")); // false
Console.WriteLine(person.HasChanged("LikesChocolate")); // true
}
}
public class Person : BaseModel
{
public string Firstname { get; set; }
public string Lastname { get; set; }
public bool LikesChocolate { get; set; }
}
public class BaseModel
{
public bool HasChanged(string propertyName)
{
// ...
}
}
I'd probably reuse the idea from WPF with their INotifyPropertyChanged pattern and simplify it a bit for the current needs. However, it resolves the question only partially, as you still need to write setters. But at least, you don't need to manage each property on its own.
So, the solution will be something like this:
void Main()
{
var person = new Person();
Console.WriteLine(person.HasChanged(nameof(Person.FirstName))); // false
Console.WriteLine(person.HasChanged(nameof(Person.LastName))); // false
Console.WriteLine(person.HasChanged(nameof(Person.LikesChocolate))); // false
person.FirstName = "HisFirstname";
person.LikesChocolate = true;
Console.WriteLine(person.HasChanged(nameof(Person.FirstName))); // true
Console.WriteLine(person.HasChanged(nameof(Person.LastName))); // false
Console.WriteLine(person.HasChanged(nameof(Person.LikesChocolate))); // true
}
public class Person : ChangeTrackable
{
private string _firstName;
private string _lastName;
private bool _likesChocolate;
public string FirstName
{
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
public string LastName
{
get { return _lastName; }
set { SetProperty(ref _lastName, value); }
}
public bool LikesChocolate
{
get { return _likesChocolate; }
set { SetProperty(ref _likesChocolate, value); }
}
}
public class ChangeTrackable
{
private ConcurrentDictionary<string, bool> _changes =
new ConcurrentDictionary<string, bool>();
public bool HasChanged(string propertyName)
{
return _changes.TryGetValue(propertyName, out var isChanged)
? isChanged : false;
}
public void ResetChanges()
{
_changes.Clear();
}
protected void SetProperty<T>(
ref T storage, T value, [CallerMemberName] string propertyName = "")
{
if (!Equals(storage, value))
{
_changes[propertyName] = true;
}
}
}
The ChangeTrackable tracks if property was changed and does it without any reflection that guarantees high performance. Note, that with this implementation you need to call ResetChanges if you initialize property with some actual values after constructing the object. Drawback is that you need to write each property with its backing field and call SetProperty. On the other side, you decide what to track, that could be handy in the future in your application. Also we don't need to write property as strings (thanks to compile-time CallerMemberName and nameof) that simplifies refactorings.
INotifyPropertyChanged is the established practice for this type of requirement. Part of keeping your code maintainable is by keeping it predictable and by adopting best practices and patterns.
An alternative, which I wouldn't recommend, would be to use reflection to iterate over all of your properties and dynamically add a property changed event handler. This handler could then set a boolean flag which can be returned by your HasChanges method. Please refer to this for a staring point: AddEventHandler using reflection
I would recommend avoiding unnecessary complexity though and stick with PropertyChanged notifications in your setters.
As followup for my comment a proof of concept (online):
using System.Reflection;
public class HasChangedBase
{
private class PropertyState
{
public PropertyInfo Property {get;set;}
public Object Value {get;set;}
}
private Dictionary<string, PropertyState> propertyStore;
public void SaveState()
{
propertyStore = this
.GetType()
.GetProperties()
.ToDictionary(p=>p.Name, p=>new PropertyState{Property = p, Value = p.GetValue(this)});
}
public bool HasChanged(string propertyName)
{
return propertyStore != null
&& propertyStore.ContainsKey(propertyName)
&& propertyStore[propertyName].Value != propertyStore[propertyName].Property.GetValue(this);
}
}
public class POCO : HasChangedBase
{
public string Prop1 {get;set;}
public string Prop2 {get;set;}
}
var poco = new POCO();
poco.Prop1 = "a";
poco.Prop2 = "B";
poco.SaveState();
poco.Prop2 = "b";
poco.HasChanged("Prop1");
poco.HasChanged("Prop2");
Be aware, that reflection may reduce the performance of your application when used extensively.
I create (if not exists) a new ViewModel Instance via the IMessenger (MVVM Light Toolkit) and pass a custom Object trough the constructor, which is a Property of my MainViewModel.
In this ViewModel I set it to a Property too and using it e.g. for a Command Execute Method. But when I trigger the Command via Binding, the custom Object Property looses its values.
I already debugged and saw, that the object get's passed correctly with its values, but after initializing from the constructor its empty (Not null, just empty Properties).
My custom Object
public class CustomObject : ObservableObject
{
public int Id { get; set; }
public string Property1 { get; set; }
public string Property2 { get; set; }
// etc...
}
I create the ViewModel like this
public CustomObject CustomObj
{
get { return _customObj; }
set { Set(ref _customObj, value); }
}
_customViewModel = new CustomViewModel(CustomObj, _dataService);
The ViewModel
public CustomViewModel(CustomObject obj, IDataService dataService)
{
_dataService = dataService;
// Here it sets correctly the Object
CustomObj = obj;
}
public CustomObject CustomObj
{
get { return _customObj; }
set { Set(ref _customObj, value); }
}
// Even before the Command is triggered, the Object is already empty
public ICommand SomeCommand => new RelayCommand<string>(async s =>
{
var someThing = await _dataService.GetSomeData(CustomObj.Id);
// Stuff...
}
They are registered in the SimpleIoC Container as well, if that matters.
What could result that?
I'm trying to make a chemistry equation balancer thingy. For that I made a class Element:
class Element
{
public elemEnum ElemType {get; set;}
public double Amount {get; set;} // How many atoms of this type in the formula
}
*elemEnum is an Enum of all the chemical elements.
I wanted to make the set for ElemType parse a string to the enumeration, but since set can only take in values of the same type as value I decided to add a method:
public void SetElemType(string type)
{
this.ElemType = (elemEnum)Enum.Parse(typeof(elemEnum), type);
}
Is there an option to have the ElemType property be only set-able by the SetElemType method without having to make it private and adding a GetElemType method?
Since the most obvious solution from the comments has not been written as an answer:
Use a private setter.
class Element
{
public ElemEnum ElemType {get; private set;}
public double Amount {get; set;}
public void SetElemType(string type)
{
this.ElemType = (ElemEnum)Enum.Parse(typeof(ElemEnum), type);
}
}
This way, ElemType can only be set from within your own class.
without having to make it private
Well, you could create a workaround solution by adding a bool field to your class and modifying the property a little bit.
class Element
{
private bool _elemCanBeSet = false;
private elemNum _elemType;
public elemEnum ElemType
{
get { return _elemType; }
set { if (_elemCanBeSet) _elemType = value; }
}
public double Amount {get; set;} // How many atoms of this type in the formula
public void SetElemType(string type)
{
_elemCanBeSet = true;
this.ElemType = (elemEnum)Enum.Parse(typeof(elemEnum), type);
_elemCanBeSet = false;
}
}
This solution may confuse the developer using your class, because setting the property will have no effect. It is much better to use a private setter for your task, as stated by others. I just wanted to show an alternative approach.
As already was pointed, you can use private setter, alternatively you can use readonly property with public getter that works with field and method to modify this field :
class Element
{
private elemEnum _elemType;
public elemEnum ElemType { get { return _elemType; } }
public void SetElemType(string type)
{
this._elemType = (elemEnum)Enum.Parse(typeof(elemEnum), type);
}
public double Amount {get; } // How many atoms of this type in the formula
}
While it is practically the same as property with private setter it uses a slightly different approach...
Well if you really want to allow only one(!) method to change value you can use reflection and add class that envelopes your enum :
class MyElemSetter
{
private readonly elemEnum elem;
public MyElemSetter(elemEnum e, Action helperAction)
{
MethodInfo callingMethodInfo = helperAction.Method;
if (helperAction.Method.Name.Contains("<SetElemType>")) elem = e;
}
public static implicit operator elemEnum(MyElemSetter e)
{
return e.elem;
}
}
class Element
{
private MyElemSetter _elemType;
public elemEnum ElemType { get { return _elemType; } }
public void SetElemType(string type)
{
this._elemType = new MyElemSetter((elemEnum)Enum.Parse(typeof(elemEnum), type), () => { });
}
public double Amount { get; set; } // How many atoms of this type in the formula
}
let's say I've got this code (in Winforms):
public class SomeClass
{
public string Name { get; set; }
}
public partial class SomeControl : UserControl
{
private SomeClass inClass;
public string MyName { get; set; }
public SomeControl(SomeClass someClass)
{
InitializeComponent();
this.inClass = someClass;
SetupBinding();
}
private void SetupBinding()
{
this.DataBindings.Clear();
this.DataBindings.Add("MyName", this.inClass, "Name");
}
}
If I'll change the the value of SomeClass.Name outside the user control, the property MyName never changes. What am I doing wrong?
Thank you
SomeClass must impelement INotifyPropertyChanged or have event named NameChanged if you want bidirectional data binding. You can implement it yourself, but I highly recommend Fody.PropertyChanged project.
PS: I wrote some extension method to create binding more easier and refactoring friendly way. In your case it would look like this:
this.BindTo(inClass, c => c.MyName , m => m.Name);
The method itself:
public static class BindingExtensions
{
public static Binding BindTo<TControl, TControlProperty, TModel, TModelProperty>(this TControl control, TModel model, Expression<Func<TControl, TControlProperty>> controlProperty, Expression<Func<TModel, TModelProperty>> modelProperty, string format = null)
where TControl : Control
{
var controlPropertyName = ((MemberExpression)controlProperty.Body).Member.Name;
var modelPropertyName = ((MemberExpression)modelProperty.Body).Member.Name;
var formattingEnabled = !string.IsNullOrWhiteSpace(format);
var binding = control.DataBindings.Add(controlPropertyName, model, modelPropertyName, formattingEnabled, DataSourceUpdateMode.OnPropertyChanged);
if (formattingEnabled)
{
binding.FormatString = format;
}
return binding;
}
}
I have ViewModel in my Silverlight project
public class MainViewModel : BaseNotify, IMainViewModel
{
[Dependency("AccountID")]
public Guid AccountID { get; set; }
...
public MainViewModel()
{
if (AccountID != Guid.Empty)
{
PreSelectedAccount = new Account() { ID = AccountID };
SingleAccountMode = true;
}
}
....
}
I'm using Unity this way:
public static class IoC
{
static IoC()
{
Current = new UnityContainer();
Current.RegisterType<IMainViewModel, MainViewModel>();
}
public static UnityContainer Current { get; set; }
}
public partial class App : Application
{
[Dependency]
public IMainViewModel ViewModel { get; set; }
private void Application_Startup(object sender, StartupEventArgs e)
{
Guid accountId = "1234-5678-1234-5678-1234";
IoC.Current.BuildUp(this);
}
}
After calling BuildUp method,I have instance of MainViewModel in the App.ViewModel, but how I can set up Unity to inject also some value for MainViewModel.AccountId property value during BuildUp?
You need to resolve/buildup with an override:
IoC.Current.BuildUp(this, new PropertyOverride("AccountID", accountId));
Your code looks like you are using the ServiceLocator anti-pattern. A pattern like constructor injection is most often preferable.
Another question: Why do you hook up your MainViewModel with your App class? Usually you would use it as the DataContext for your MainView.
And it is not common to inject primitive values like Guids using a DI container.
As #nemesv mention you might need to change your BuildUp method call, but doing so, I don't think you can achieve what you need.
The problem is first instance of the MainViewModel is created and then AccountId is passed into it, so your logic in the constructor is never true, e.g.
if(AccountID != Guid.Empty)
is never true. Alternativly you can add this logic to Setter of the AccountID property, something inline with this:
public Guid AccountId {
get { return _accountId; }
set {
_accountId = value;
OnAccountIdChanged();
}
}
protected virtual void OnAccountIdChanged() {
if(AccountId != Guid.Empty) {
//do your thing here
}
}