For a presentation involving six components of a Person object's PersonName, I added an extension and a 'mini view model' (PersonNamePropertyTextBox) to cut down on duplicated code and facilitate data binding.
So in the constructor of the parent view model, I create these mini view models like:
public PimDetailVm(Person person, ..)
{
LastName = new PersonNamePropertyTextBox(
() => Model.GetPersonName().LastName, v => this.UpdatePersonNameProperty(pn => pn.LastName, v))
{
Label = PeopleResources.LastName_Label
};
FirstName = new PersonNamePropertyTextBox(
() => Model.GetPersonName().FirstName, v => this.UpdatePersonNameProperty(pn => pn.FirstName, v))
{
Label = PeopleResources.FirstName_Label
};
... etc.
}
public PersonNamePropertyTextBox LastName { get; private set; }
public PersonNamePropertyTextBox FirstName { get; private set; }
What I would really like now is to be able to do is just pass in the current property, ie "LastName" and the label value, and let the mini view model set the appropriate Getter/Setter delegates, something like:
LastName = new PersonNamePropertyTextBox(vm=>LastName, PeopleResources.LastName_Label);
I am struggling as to how to do this though. Any ideas?
Extension (handle updating the PersonName in the Model)
public static void UpdatePersonNameProperty(this PimDetailVm vm, Expression<Func<PersonName, object>> propertyExpression, string value)
{
var pn = vm.Model.GetPersonName();
var pnProps = pn.GetType().GetProperties();
var subj = ExprHelper.GetPropertyName(propertyExpression);
var subjProp = pnProps.Single(pi => pi.Name.Equals(subj));
var currentVal = subjProp.GetValue(pn, null);
// split if there is nothing to update
if(currentVal==null && value==null) return;
if (currentVal != null && currentVal.Equals(value)) return;
// update the property
var capitalized = value == null ? null : value.Capitalize();
subjProp.SetValue(pn, capitalized, null);
// update the model
vm.Model.SetName(pn);
// broadcast the update
vm.NotifyOfPropertyChange(subj, value);
}
Mini View Model for some property of a PersonName
public class PersonNamePropertyTextBox : TextBoxActionData
{
public PersonNamePropertyTextBox(Func<string> getterFunc, Action<string> setterAction) {
if (getterFunc == null) throw new ArgumentNullException("getterFunc");
if (setterAction == null) throw new ArgumentNullException("setterAction");
GetterFunc = getterFunc;
SetterAction = setterAction;
}
}
Try implementing a binder class to manage the binding. In this case I have used PropertyBinding.
public class PropertyBinding
{
public static PropertyBinding To(ViewModel vm, Name name, string label)
{
return new PropertyBinding { ViewModel = vm, Getter = new Func<string>(delegate() { return name.Value; }), Setter = new Action<string>(delegate(string value) { name.Value = value; }), Label = label };
}
public string Label { get; set; }
public ViewModel ViewModel { get; set; }
public Func<string> Getter { get; set; }
public Action<string> Setter { get; set; }
public string Value
{
get { return this.Get(); }
set { this.Set(value); }
}
internal string Get()
{
// Implement UpdatePersonNamePropert here.
// Maybe convert culture before returning.
return this.Getter();
}
internal void Set(string value)
{
// Maybe convert culture before storing.
this.Setter(value);
}
}
It would be called like:
LastName = new PersonNamePropertyTextBox(PropertyBinding.To(Model, Model.GetPersonName().LastName, PeopleResources.LastName_Label));
Please note that Model.GetPersonName().LastName must return a pointer type not a value type otherwise the LastName can not be updated when the Setter is called. For example:
public sealed class Name
{
public string Value { get; set; }
}
In this example PersonName is implemented as below however your implmentation may be different.
public class PersonName
{
public Name LastName { get; set; }
public Name FirstName { get; set; }
}
Without all your class information and the strong types associated with some of the variables you used it was hard to validate but I think this should get you out of trouble.
Hope this helps.
Related
I have following two classes
public class Family
{
public string ChildName { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public Family Child { get; set; }
}
I have an instance of Employee class as follows.
Employee employee = new Employee();
employee.Name = "Ram";
employee.Id = 77;
employee.Child = new Family() { ChildName = "Lava" };
I have a method which gets the property value based on the property name as follows:
public static object GetPropertyValue(object src, string propName)
{
string[] nameParts = propName.Split('.');
if (nameParts.Length == 1)
{
return src.GetType().GetRuntimeProperty(propName).GetValue(src, null);
}
foreach (String part in nameParts)
{
if (src == null) { return null; }
Type type = src.GetType();
PropertyInfo info = type.GetRuntimeProperty(part);
if (info == null)
{ return null; }
src = info.GetValue(src, null);
}
return src;
}
In the above method,when I try to get property value of nested class like
GetPropertyValue(employee, "employee.Child.ChildName")
or
GetPropertyValue(GetPropertyValue(employee, "Family"), "ChildName"
doesn't return any value because type.GetRuntimeProperty(part) is always null.
Is there any way to fix this problem?
You problem lies in this line:
foreach (String part in nameParts)
Because you are iterating over each part of nameParts, you are also iterating over "employee", which of course is not a valid property.
Try either this:
foreach (String part in nameParts.Skip(1))
Or calling the method like this:
GetPropertyValue(employee, "Child.ChildName")
(Notice no "employee.", because you already pass in an employee)
The problem in this case is that when you split the string employee.Child.ChildName, the "employee" is the first part. However, employee is not a property of the source i.e. Employee Class.
Try this:
public class Program
{
public static void Main()
{
Employee employee = new Employee();
employee.Name = "Ram";
employee.Id = 77;
employee.Child = new Family() { ChildName = "Lava" };
GetPropertyValue(employee, "employee.Child.ChildName");
}
public class Family
{
public string ChildName { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public Family Child { get; set; }
}
public static object GetPropertyValue(object src, string propName)
{
string[] nameParts = propName.Split('.');
if (nameParts.Length == 1)
{
return src.GetType().GetRuntimeProperty(propName).GetValue(src, null);
}
nameParts = nameParts.Skip(1).ToArray();
foreach (String part in nameParts)
{
if (src == null) { return null; }
Type type = src.GetType();
PropertyInfo info = type.GetRuntimeProperty(part);
if (info == null)
{ return null; }
src = info.GetValue(src, null);
}
return src;
}
Here, i have skipped the first part of the string i.e. "employee". However, you can solve the problem by passing Child.ChildName
This question is around 2 years old, but I found a another working solution for you question, which is easy to understand. If you initialize the object in calling calss constructor you can use dot(.) notation to assign or read property. Example -
public class Family{
public string ChildName { get; set; }
}
public class Employee{
public int Id { get; set; }
public string Name { get; set; }
public Family Child { get; set; }
public Employee(){
Child = new Family();
}
}
Employee emp = new Employee();
emp.Family.ChildName = "Nested calss attribute value";
public class UFMLine
{
public UFMTemplate elementTag;
public int posX1;
public int posY1;
public int posX2;
public int posY2;
public string property;
public string hierStr = string.Empty;
public List<UFMLine> ufmLines = new List<UFMLine>(); // the tricky nested class field
}
UFMLine ufmobj = new UFMLine();
This ufmobj is populated perfectly at the window load.
In my button click of wpf window xaml code behind...
string nthItem = "ufmobj.ufmLines[0].ufmLines[1].ufmLines[1].ufmLines[1].ufmLines[2].ufmLines[2].elementTag";
// Tried reflection method, but giving null exception
var result = typeof(UFMLine).GetField(nthItem).GetValue(ufmobj);
So when opening watch window and fetch the value for nthItem name it gives appropriate value.
How to get it in the code behind or am i not properly using the reflection?
Thanks.
The "name" of the variable (ufmobj) could be a problem since it could be lost in a release build, but the rest can be achieved with reflection if you don't mind implementing your own parser.
Split path (Syntax of your choice)
Resolve one path segment after another
Determine if path is Field/Property/Indexer/Method/etc.
Repeat until done
Here is a small snippet to get you going (far from complete, but works with your example):
Resolve a single Field or Property
private object GetFieldOrProperty(object obj, string name)
{
Type objType = obj.GetType();
if (objType.GetField(name) != null)
return objType.GetField(name).GetValue(obj);
if (objType.GetProperty(name) != null)
return objType.GetProperty(name).GetValue(obj, null);
return null;
}
Resolve the entire path:
private object Resolve(object parent, string path)
{
string[] paths = path.Split('.');
foreach (string p in paths)
{
if (p.EndsWith("]"))
{
int start = p.IndexOf("[");
string property = p.Substring(0, start);
string index = p.Substring(start + 1, p.Length - start - 2);
parent = GetFieldOrProperty(parent, property);
if (parent == null)
return null;
foreach (PropertyInfo info in parent.GetType().GetProperties())
{
if (info.GetIndexParameters().Length < 1) continue;
parent = info.GetValue(parent, new object[] {int.Parse(index)});
break;
}
}
else
{
parent = GetFieldOrProperty(parent, p);
if (parent == null)
return null;
}
}
return parent;
}
Test case:
UFMLine ufmobj = new UFMLine();
ufmobj.ufmLines.Add(new UFMLine());
ufmobj.ufmLines[0].ufmLines.Add(new UFMLine());
ufmobj.ufmLines[0].ufmLines[0].ufmLines.Add(new UFMLine{property = "Success"});
Debug.WriteLine(Resolve("ufmLines[0].ufmLines[0].ufmLines[0].property.Length", ufmobj));
7 (length of "Success")
You may change the fields of your UFMLine class to public properties:
public class UFMLine
{
public UFMTemplate elementTag { get; set; }
public int posX1 { get; set; }
public int posY1 { get; set; }
public int posX2 { get; set; }
public int posY2 { get; set; }
public string property { get; set; }
public string hierStr { get; set; } = string.Empty;
public List<UFMLine> ufmLines { get; set; } = new List<UFMLine>();
}
Now you could use the part of your nthItem string after "ufmobj." as the Path of a Binding, for which you would use ufmobj as Source:
var binding = new Binding
{
Source = ufmobj,
Path = new PropertyPath("ufmLines[0].ufmLines[1].ufmLines[1].ufmLines[1].ufmLines[2].ufmLines[2].elementTag")
};
To make use of this Binding, you would also need a target dependency property, which you might declare in a helper class like this:
public class BindingHelper : DependencyObject
{
public static readonly DependencyProperty ResultProperty =
DependencyProperty.Register("Result", typeof(object), typeof(MainWindow));
public object Result
{
get { return GetValue(ResultProperty); }
set { SetValue(ResultProperty, value); }
}
}
Finally you would assign the Binding to the Target property and then retrieve the resulting value like this:
var bindingHelper = new BindingHelper();
BindingOperations.SetBinding(bindingHelper, BindingHelper.ResultProperty, binding);
var result = bindingHelper.Result;
please tell me best way to implement many duplicate INotifyPropertyChanged.
I have a MainClass that has 10 children, every child has six field and every field must fired property change when own value changed.
this my code but not work:
public class BaseModel
{
public string S1 { get; set; }
public string S2 { get; set; }
public string S3 { get; set; }
public string S4 { get; set; }
public string S5 { get; set; }
public string S6 { get; set; }
}
and I use a class named ViewModelBase to implement INotifyPropertyChanged.
in second step use a class to implement duplicate INotifyPropertyChanged:
public class ImplementBaseModel : ViewModelBase
{
private readonly BaseModel _baseModel;
public ImplementBaseModel()
{
_baseModel = new BaseModel();
}
public string S1
{
get { return _baseModel.S1; }
set
{
if (_baseModel.S1 == value)
return;
_baseModel.S1 = value;
base.OnPropertyChanged("S1");
}
}
public string S2
{
get { return _baseModel.S2; }
set
{
if (_baseModel.S2 == value)
return;
_baseModel.S1 = value;
base.OnPropertyChanged("S2");
}
}
// other code...
}
then a model has 10 of this class:
public class MidClass
{
public ImplementBaseModel ImplementBaseModel1 { get; set; }
public ImplementBaseModel ImplementBaseModel2 { get; set; }
// other field
public ImplementBaseModel ImplementBaseModel10 { get; set; }
public MidClass()
{
ImplementBaseModel1 = new ImplementBaseModel();
ImplementBaseModel2 = new ImplementBaseModel();
// ....
ImplementBaseModel10 = new ImplementBaseModel();
}
}
OK finish code! now please tell me why some property not fired when value change? is a best way to implement this code?
In your setters, you never actually set the value. Use:
public string S1
{
get { return _baseModel.S1; }
set
{
if (_baseModel.S1 == value)
return;
baseModel.S1 = value;
OnPropertyChanged("S1");
}
}
Note that I removed the base from OnPropertyChanged. It isn't normal to invoke the PropertyChanged event in this way.
All NotifyPropertyChanged does is cause every binding to perform a "get" on their bound property. If the backing field is never updated, they will just get the same data.
as a shortcut, you could also create a local method like
bool UpdateAndRaiseIfNecessary( ref string baseValue, string newValue, [CallerMemberName] string propertyName = null)
{
if (baseValue != newValue)
{
baseValue = newValue;
OnPropertyChanged( propertyName );
return true;
}
return false;
}
and then all of the setters would be like this:
set
{
this.UpdateAndRaiseIfNecessary( ref _baseModel.S1, value );
}
I'm developing a simple Crud Application (a windows 8.1 store application) using Caliburn Micro 2.0.0-alpha2
I'm in trouble with navigation between viewmodels, passing object.
I read many times the solution proposed by
Anders Gustafsson (How to pass parameter to navigated view model with WinRT Caliburn.Micro?)
and i tried to adapt it to my scope.
But the object is alwais null.
I need to pass a single object selected from a listView to my crudPage.
The crudPage is composed by an userControl that shown the FormView.
So i want to initialize this Form, with the values of the passed object.
I think that the problem is that the "Parameter" is initialized only after the ViewModel is created, but i don't know how to fix that problem.
There is my code, according with the idea of Anders Gustafsson
TransporterListViewModel (a list of Transporters from Database)
public class TransporterListViewModel : ViewModelBase
{
public string Title { get; set; }
public TransporterListViewModel(INavigationService navigationService)
: base(navigationService)
{
LoadData();
}
public async void LoadData() {
_transporters = await TransporterService.GetAll();
}
private BindableCollection<Transporter> _transporters;
public BindableCollection<Transporter> Transporters
{
get
{
return this._transporters;
}
set
{
this._transporters = value;
NotifyOfPropertyChange(() => this.Transporters);
}
}
private Transporter _selectedItem;
public Transporter SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
NotifyOfPropertyChange(() => this.SelectedItem);
navigationService.Navigated += NavigationServiceOnNavigated;
navigationService.NavigateToViewModel<TransporterCrudPageViewModel>(_selectedItem;);
navigationService.Navigated -= NavigationServiceOnNavigated;
}
}
private static void NavigationServiceOnNavigated(object sender, NavigationEventArgs args)
{
FrameworkElement view;
TransporterCrudPageViewModel transporterCrudPageViewModel;
if ((view = args.Content as FrameworkElement) == null ||
(transporterCrudPageViewModel = view.DataContext as TransporterCrudPageViewModel) == null) return;
transporterCrudPageViewModel.InitializeTransporterForm(args.Parameter as Transporter);
}
TransporterCrudViewModel (the page that cointains the UserControl to initialize)
public class TransporterCrudPageViewModel : ViewModelBase
{
public string Title { get; set; }
public Transporter Parameter { get; set; }
public TransporterFormViewModel TransporterFormVM { get; set; }
public async void InitializeTransporterForm(Transporter enumerable)
{
TransporterFormVM = new TransporterFormViewModel(navigationService, enumerable);
await SetUpForm(enumerable);
}
public async Task SetUpForm(Transporter t){
TransporterFormVM.trName = t.trName;
TransporterFormVM.trUrl = t.trUrl;
}
public TransporterCrudPageViewModel(INavigationService navigationService)
: base(navigationService)
{
Title = "TransporterCrud Page";
//this.navigationService = navigationService;
this.InitializeTransporterForm(Parameter);
}
TransporterFormViewModel (the userContol to initialize)
public class TransporterFormViewModel :ViewModelBase
{
public string Title { get; set; }
public Transporter Transporter { get; set; }
public TransporterFormViewModel(INavigationService navigationService,Transporter trans)
: base(navigationService)
{
Transporter = trans;
}
private string _trName;
public string trName
{
get
{
return _trName;
}
set
{
_trName = value;
NotifyOfPropertyChange(() => trName);
}
}
public string trCode { get; set; }
public string trUrl { get; set; }
public int trId { get; set; }
In the constructor TransporterCrudViewModel class you have:
this.InitializeTransporterForm(Parameter);
where Parameter is a property of type Transporter not initialized and you will call the method InitializeTransporterForm with a null parameter. Then you'll call SetUpForm method with a null value of the parameter Transporter t. I think you should initialize in some way this property.
Then, supposing you're continuing in your TransporterListViewModel class with this:
transporterCrudPageViewModel.InitializeTransporterForm(args.Parameter as Transporter);
in the method InitializeTransporterForm, you don't set the passed parameter as value of the property Parameter with something like this:
public async void InitializeTransporterForm(Transporter enumerable)
{
TransporterFormVM = new TransporterFormViewModel(navigationService, enumerable);
this.Parameter = enumerable; //setting the Parameter property..
await SetUpForm(enumerable);
}
Beside these notes, you should put a breakpoint with your IDE in the line
transporterCrudPageViewModel.InitializeTransporterForm(args.Parameter as Transporter);
Make sure that the property Parameter of the NavigationEventArgs object is not null.
,I have one class in which I have three properties now what I want to do, if in the object if any one of null or empty then I want to remove it from the object below is my code.
public class TestClass
{
public string Name { get; set; }
public int ID { get; set; }
public DateTime? DateTime { get; set; }
public string Address { get; set; }
}
TestClass t=new TestClass();
t.Address="address";
t.ID=132;
t.Name=string.Empty;
t.DateTime=null;
Now here I want the object of TestClass but in that Name and DateTime property should not be their in the object,
is it possible?
Please help me
There's no such concept as removing a property from an individual object. The type decided which properties are present - not individual objects.
In particular, it will always be valid to have a method like this:
public void ShowDateTime(TestClass t)
{
Console.WriteLine(t.DateTme);
}
That code has no way of knowing whether you've wanted to "remove" the DateTime property from the object that t refers to. If the value is null, it will just get that value - that's fine. But you can't remove the property itself.
If you're listing the properties of an object somewhere, you should do the filtering there, instead.
EDIT: Okay, no you've given us some context:
ok I am using Schemaless database so null and empty value also store space in database that's the reason
So in the code you're using which populates that database, just don't set any fields which corresponds to properties with a null value. That's purely a database population concern - not a matter for the object itself.
(I'd also argue that you should consider how much space you'll really save by doing this. Do you really care that much?)
I was bored and got this in LINQPad
void Main()
{
TestClass t=new TestClass();
t.Address="address";
t.ID=132;
t.Name=string.Empty;
t.DateTime=null;
t.Dump();
var ret = t.FixMeUp();
((object)ret).Dump();
}
public static class ReClasser
{
public static dynamic FixMeUp<T>(this T fixMe)
{
var t = fixMe.GetType();
var returnClass = new ExpandoObject() as IDictionary<string, object>;
foreach(var pr in t.GetProperties())
{
var val = pr.GetValue(fixMe);
if(val is string && string.IsNullOrWhiteSpace(val.ToString()))
{
}
else if(val == null)
{
}
else
{
returnClass.Add(pr.Name, val);
}
}
return returnClass;
}
}
public class TestClass
{
public string Name { get; set; }
public int ID { get; set; }
public DateTime? DateTime { get; set; }
public string Address { get; set; }
}
Hereby a 'slightly' more clear and shorter version of the accepted answer.
/// <returns>A dynamic object with only the filled properties of an object</returns>
public static object ConvertToObjectWithoutPropertiesWithNullValues<T>(this T objectToTransform)
{
var type = objectToTransform.GetType();
var returnClass = new ExpandoObject() as IDictionary<string, object>;
foreach (var propertyInfo in type.GetProperties())
{
var value = propertyInfo.GetValue(objectToTransform);
var valueIsNotAString = !(value is string && !string.IsNullOrWhiteSpace(value.ToString()));
if (valueIsNotAString && value != null)
{
returnClass.Add(propertyInfo.Name, value);
}
}
return returnClass;
}
You could take advantage of the dynamic type:
class Program
{
static void Main(string[] args)
{
List<dynamic> list = new List<dynamic>();
dynamic
t1 = new ExpandoObject(),
t2 = new ExpandoObject();
t1.Address = "address1";
t1.ID = 132;
t2.Address = "address2";
t2.ID = 133;
t2.Name = "someName";
t2.DateTime = DateTime.Now;
list.AddRange(new[] { t1, t2 });
// later in your code
list.Select((obj, index) =>
new { index, obj }).ToList().ForEach(item =>
{
Console.WriteLine("Object #{0}", item.index);
((IDictionary<string, object>)item.obj).ToList()
.ForEach(i =>
{
Console.WriteLine("Property: {0} Value: {1}",
i.Key, i.Value);
});
Console.WriteLine();
});
// or maybe generate JSON
var s = JsonSerializer.Create();
var sb=new StringBuilder();
var w=new StringWriter(sb);
var items = list.Select(item =>
{
sb.Clear();
s.Serialize(w, item);
return sb.ToString();
});
items.ToList().ForEach(json =>
{
Console.WriteLine(json);
});
}
}
May be interfaces will be handy:
public interface IAdressAndId
{
int ID { get; set; }
string Address { get; set; }
}
public interface INameAndDate
{
string Name { get; set; }
DateTime? DateTime { get; set; }
}
public class TestClass : IAdressAndId, INameAndDate
{
public string Name { get; set; }
public int ID { get; set; }
public DateTime? DateTime { get; set; }
public string Address { get; set; }
}
Creating object:
IAdressAndId t = new TestClass()
{
Address = "address",
ID = 132,
Name = string.Empty,
DateTime = null
};
Also u can put your interfaces in separate namespace and make your class declaration as internal. After that create some public factories which will create the instances of your classes.