MVVMCross add multiple dynamic properties to viewmodel - c#

I have an requirement to render the screen (screen type : form) dynamically based on the service response (rather than defining the UI manually). I was able to successfully bind predefined properties to dynamically created textboxes & textviews. Following is the sample code that used to bind predefined property to dynamic textbox
Type myType = typeof(DynamicViewModel);
PropertyInfo myPropInfo = myType.GetProperty(nameof(dynamicProperty)); //dynamicProperty -static property in VM
var set = this.CreateBindingSet<DynamicActivity, DynamicViewModel>();
set.Bind(editText).To(myPropInfo.Name); //editText - dynamically created textbox
set.Apply();
But the code needs to be further improved by dynamically creating the no of properties - matching with the no of UI elements dynamically created.
The project is created using Xamarin.Android with MVVMCross's latest version. Please share the way to generate dynamic string(or object type) properties in viewmodels that can be binded with dynamically generated view elements(textboxes & textviews).
Thanks

There are a couple of ways to do this.
One is using Reflection as you are doing there, but you could have performance issues.
The other way is to arrange a bit the data and model you are getting from the server to be something like you can then use some Factories to build your View/VM:
So it could be:
public enum DataType
{
String,
Number,
Boolean,
List,
// and any other types that you need
}
public class OptionItemModel
{
public int Id { get; set; }
public string Name { get; set; }
}
public class FieldModel
{
public DataType Type { get; set; }
public string DefaultValue { get; set; } // this is a serialized value
public List<OptionItemModel> Options { get; set; } // this only applies to DataType -> List
}
public class StructureModel
{
public List<FieldModel> Fields { get; set; }
}
So then you can have an ObservableCollection on your VM and your items can be created by a factory iterating for each one of the fields of the structure and so you can have custom Item View Models depending on the DataType of the field.
Then you can have a List on your View that uses a Template selector where you can create the Cell/Row depending on the DataType or the ItemViewModel and that would be it.
Then you can have a similar model structure to fill the values and upload them to the server.
Hope it's clear enough

Related

What is best approach for data inheritation in elasticsearch?

I have an parent class and two child like these:
public class Parent {
public Guid Id { get; set; }
public string Name { get; set; }
}
public class FirstChild {
public string IdentityCode { get; set; }
}
public class OtherChild {
public string RegistrationCode { get; set; }
}
There is a question: Is it a good approach to store these two inherited classes in the same Index inside ElasticSearch?
I see there is a _type property that is added to my docs after they are stored in DB but it has always "doc" value.
I test this code to fill it but it seems it is not working this way.
await ElasticClient.IndexAsync<FirstChild>(child, m => m.Index(IndexName));
And Also, I found this question on SO for retrieving my entries from DB but it is outdated and the API is changed and no more accessible.
I want to know if it is a good approach to store sibling data in the same index how can I do this properly.
As of ES 6.0, it is not possible anymore to store multiple types inside the same index, i.e. the _type field you're referring to will always be either doc or _doc. In ES 8.0, the _type field will be removed altogether.
However, if it makes sense for your use case, you can still decide to store several types inside a single index using a custom type field that is present in your document.
You should strive to only store in the same index data that share the same (or very similar) mapping, which doesn't seem to be the case for Parent, FirstChild and SecondChild, but if you add a public string type property to your classes you can still do it.

How to display custom field type on data grid view? [C# / WinForms]

I have employee class, which has field of type ISalary (Interface). In data grid view, I want to display that salary, but what I get is empty field. Is it any possible way to display that "custom type field" in data grid view? invoking toString method would help, but I can't understand how to do that.
Here is how I am binding data:
employeeBindingSource.DataSource = employeesList;
All fields in that list, ofc is not null. And here is some of my class, which list I want to display:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public int DepartmentId { get; set; }
// how to display this?
public ISalary Salary { get; set; }
...
}
I did some research and could not find any example on it. Or maybe was not trying hard enough.:/
Since you are using a interface as type of property, you can not override ToString method for that ISalary type without knowing the concrete type. So if you know a Salary class which implemented that interface and used instead of that interface at run-time, you can override ToString of that type.
But, in general it's not a good idea to rely on ToString of that concrete type, this way your program will be tightly relied on the concrete type and will loose its goal in using the interface.
Instead, you can use either of these options:
Use CellFormatting event to provide display value.
Use a DataGridViewComboBoxColumn which contains a List<Salary> but, set its display style property to nothing to not show dropdown button.
Use a readonly property in Employee class which return a known property of ISalary, like return this.Salary.SomeProperty;
And still there are more options. To see some other options and examples, take a look at this post: How to bind a column from second level list on bindsource in winforms datagridview or this one: Show Properties of a Navigation Property in DataGridView (Second Level Properties).
This should work for you:
public ISalary Salary
{
get{ return this.Salary.ToString(); };
set;
}
To use ToString(), you only append it at the end of the object you want to be returned as a string.
See the official MSDN on the ToString() function:
Object.ToString Method
interface ISalary
{
public decimal Amount { get; set; }
public string GetSalaryString();
}
the code:
List<Employee> lstEmployee = GetEmployeeList();
dataGridView1.Rows.Clear();
if (lstEmployee.Count == 0)
return;
foreach (var employee in lstEmployee)
{
DataGridViewRow dgvr = dataGridView1.Rows[dataGridView1.Rows.Add()];
dgvr.Cells[colnId.Index].Value = employee.Id;
dgvr.Cells[colnName.Index].Value = employee.Name;
dgvr.Cells[colnGender.Index].Value = employee.Gender;
dgvr.Cells[colnDepartmentId.Index].Value = employee.DepartmentId;
dgvr.Cells[colnSalary.Index].Value = employee.Salary.GetSalaryString();
}

Dynamic Binding at runtime

Today I was working on a WPF UserControl to display the current value of a few variables. I was wondering if there would be a way to dynamically add a property at runtime,this is my fix model but i want to add public string grapes{get;set} property at runtime and a value to that property.
is it possible to do? if yes then how?
public class Food
{
public string Apple { get; set; }
public string Orange { get; set; }
}
Perhaps the easiest way to achieve this is using a dictionary instead of properties... You can easily add new fruit types at runtime. .net also has support for dynamic types. Have a look at expandoObject https://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject(v=vs.110).aspx

Glass mapper populate list from Link path

I'm trying to populate a list on my current model with subitems from a different folder.
On my current model I want to define a field of type General Link in which I will select an item from Sitecore who has different subitems.
The "public virtual IEnumerable List" should be populated with the above subitems.
I have read different posts related to how you can make the following type of queries:
[SitecoreQuery("./*[##templatename='Testimonial']", IsRelative = true)]
public virtual IEnumerable Children { get; set; }
but this does not apply in my case because most probably I will have this template in different areas which I do not want to be included here.
Does anyone know if this is possible with Glass or should I just use a custom query to populate the list with an item subitems?
If you use a droplink or droptree field, rather than a General Link, you could do what you want by creating a generic Folder model.
namespace MySite.Models
{
[SitecoreType(AutoMap = true)]
public class Folder<T> : GlassBase
{
[SitecoreChildren]
public virtual IEnumerable<T> Children { get; set; }
}
}
And then use it from another model like so:
[SitecoreField("My Link Field")]
public virtual Folder<ChildModel> MyLinkField { get; set; }

WPF: Best way to create bindings to unknown types in MVVM

I am looking for a way to display data in a DataGrid from types that are unknown at compile-time.
I have the following base class
public abstract class Entity
{
// Some implementation of methods ...
}
In run-time, I load a plug-in DLL and use reflection to get a list of all the types derived from Entity. For example:
public class A : Entity
{
public LocalAddress Address{ get; set; }
}
public class B : Entity
{
public Vendor Vendor { get; set; }
public string Name { get; set; }
}
Then I retreive a list of their instances from DB
public IEnumerable<Entity> Entities { get; set; } // A list of instances of type A for example
Entities is the DataGrid's ItemsSource, But what's the best way I can bind the properties to the DataGrid?
Since the properties can be complex, I also need to be able to bind to a specific path, for example Address.HomeNum ...
Clarifications
I only need to show a one grid of a type's instances at a time. The complete scenario is this:
I get a list of types that derive from Entity from the plug-in DLL through reflection
I show their names in a List. (in this example that list will contain A and B
When the user clicks on a specific item, let's say A, I get a list of A instances from DB - so far so good.
I want to display that list of A's instances in a DataGrid.
When the user selects another item from the list (meaning another type, lets say B), I get a list of B's instances from DB and need to display those in the grid and so on ...
The plug-in DLL is a class library with no xamls (also my users are the ones making this plug-ins and I don't want them to have to write DataTemplates for their entities.
I also can't make predifned DataTemplates as I don't know the types I'll need to display until run-time. Each type can have different types and amount of properties. All I know in complie-time is that they all derived from Entity.
The grid should also be editable.
A DataGrid seems inappropriate in this case. If your list was bound to two separate entities, it would break badly.
A better option would potentially be to use some other ItemsControl and set up a DataTemplate for each type of Entity. This would allow you to build custom editors per entity, and have a "list" of them to edit.
If you know the entities will always be of a single type, I'd instead build the collection of that specific type, and bind to it.
Since you don't know the property names of the Entities beforehand, I think your best option is to keep your DataGrid in Xaml but move the defintion and the Bindings of its DataGridColumns to the code behind.
AddColumnsForProperty(PropertyInfo property, string parentPath = "")
{
var title = property.Name;
var path = parentPath + (parentPath=="" ? "" : ".") + property.Name;
if(property.PropertyType == typeof(string))
{
var column = new DataGridTextColumn();
column.Header = title;
column.Binding = new Binding(path);
dataGrid.Columns.Add(column);
}
else if(property.PropertyType == typeof(bool))
{
//use DataGridCheckBoxColumn and so on
}
else
{
//...
}
var properties = property.GetProperties();
foreach(var item in properties)
{
AddColumnsForProperty(item, path);
}
}
Now if you execute these you'll have your dataGrid columns filled. and by adding all instances of the desired type in an observable collection and bind it to ItemsSource of the DataGrid it should work. selectedItem should be an instance of one the classes derived from Entity. The listbox contains new A() and new B() (or any existing instances of A and B) so selectedItem can be used in the following statement.
var propertyList = selectedItem.GetType().GetProperties();
foreach (var property in propertyList)
AddColumnsForProperty(PropertyInfo property);
how to write DataGridColumnTemplate in code
Edit:
Member can't be used in this scenario because INotifyPropertyChanged should get involved, so I replaced members with properties.
I would use attributes to specify what exactly is bindable (including composite object):
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public abstract class EntityAttribute : Attribute
{
internal abstract IEnumerable<EntityColumn> GetColumns(object instance, PropertyInfo property);
}
This attribute supports plain properties as well as composite structures. You should simply inherit and implement the method.
EntityColumn represents single value. Simplified version can be implemented like this:
public class EntityColumn
{
private readonly Action<object> _setMethod;
private readonly Func<object> _getMethod;
public string Caption { get; private set; }
public object Value
{
get { return _getMethod(); }
set { _setMethod(value);}
}
internal EntityColumn(string caption, Action<object> setMethod, Func<object> getMethod)
{
_getMethod = getMethod;
_setMethod = setMethod;
Caption = caption;
}
}
Later you can create single DataTemplate for EntityColumn and use it for all properties for all possible entities. Entity Object will contain additional method to return all EntityColumn relevant to it:
public IList<EntityColumn> GetColumns()
{
var objectType = GetType();
var properties = objectType.GetProperties();
return properties.SelectMany(
p => p.GetCustomAttributes<EntityAttribute>().SelectMany(a => a.GetColumns(this, p))).ToList();
}
For collection of Entities you can introduce EntityCollection which will absorb column information and provide structure similar to DataSet.
This implementation gives you flexibility of dynamic structure and keeps almost everything strongly typed. You can even extend attributes and EntityColumn to support validation.
As of displaying object, you'd rather use ItemsControl or even self written control inherited from ItemsControl to take advantage of knowing about Entity and EntityCollection classes.

Categories