Automatically generating a UI based on the Class that has been passed - c#

this might be a very basic question but I am cracking my head at this since months.
I want to create a simple UI for creating objects of the class I pass into the UI constructor.
Let say I have 2 classes:
class Test1
{
public static List<Test1> objectList;
public string code;
public string name;
}
class Test2
{
public static List<Test2> objectList;
public string code;
public string name;
public int value;
}
(the static classes would contain all the objects created out of that class)
what I would like to do is to is to create a Code which takes a class as a variable (maybe a generic class?) and based on that creates all the labels and textboxes based on the fields available in the class.
e.g.
public RegisterUI<T> ()
{
Grid grd = new Grid();
DataGrid dg = new DataGrid();
Button saveBtn = new Button();
//Binding the static list of objects to the DataGrid
dg.ItemSource = T.objectList;
//Adding the UI elemnts to the grid
grd.children.add(dg);
grd.children.add(saveBtn);
//Creating for each field in the Class T a lable based on its name and a combobox + binding
foreach(field in T)
{
Lable lbl = new Lable(field.getName);
ComboBox cbx = new ComboBox();
grd.children.add(lbl);
grd.children.add(cbx);
}
}
Is this even possible? I hope I was not to vague with the mockup code, and you can understand what I am heading for.
Any advice would be highly appreciated. Thanks a lot :)

Yes, it's possible. I've done this exact thing for the purpose of automatically creating settings dialogs (I got tired of making a custom form anytime one of my programs had settings that needed modified by the user).
How?
You're going to need to look into "reflection" which provides you a way to interrogate the structure of objects dynamically.
I don't use generics for this, but rather interrogate the Type from within the class.
If I pass in Test1 into my class I get this:
I wish I could supply source code, but alas, it belongs to my employer. I can, however, supply a short snippet to get you started:
Type type = typeof(Test1);
//Get public fields
List<FieldInfo> fieldInfo = type.GetFields().ToList();
//Get private fields. Ensure they are not a backing field.
IEnumerable<FieldInfo> privateFieldInfo = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(f => !f.Name.Contains("k__BackingField"));
//Get public properties
List<PropertyInfo> properties = type.GetProperties().ToList();
//Get private properties
properties.AddRange(type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance));

Hmm, looks like something an old demo might help solve:
Note: I'm using JSONs and NewtonSoft's JSON library for my implementation to read a JSON and build the object / UI from that:
private void LoadConfig()
{
JsonSerializerSettings jss = new JsonSerializerSettings()
{
DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate
};
var cfg = ConfigIO.OpenDefault();
ConfigItem ci = JsonConvert.DeserializeObject<ConfigItem>(cfg.Object);
IEnumerable<MemberInfo> atts = ConfigInterOps.GetAttributes(typeof(ConfigItem));
FillForm(ci, atts);
}
private void FillForm(Object referenceObject, IEnumerable<MemberInfo> atts)
{
int location = 5;
foreach (var att in atts)
{
var cfg = new ConfigurationBox(att.Name, referenceObject.GetType()
.GetProperty(att.Name).GetValue(referenceObject, null));
cfg.Name = $"cfg_ {att.Name}";
cfg.Top = 3 * location;
location += 10;
Controls["flowLayoutPanel1"].Controls.Add(cfg);
}
}
A couple classes I made and use that are referenced above:
public static class ConfigInterOps
{
public static IEnumerable<MemberInfo> GetAttributes(Type type)
{
return type.GetMembers()
.Where(x => x.MemberType == MemberTypes.Property ||
x.MemberType == MemberTypes.Field);
}
}
public static class ConfigIO
{
public static void Save(Config cfg)
{
UseDefaultLocation(cfg);
if (!File.Exists(cfg.FileLocation))
{
File.Create(cfg.FileLocation);
}
File.WriteAllText(cfg.FileLocation, JsonConvert.SerializeObject(cfg));
}
private static void UseDefaultLocation(Config cfg)
{
cfg.FileLocation = cfg.FileLocation ?? Path.Combine($"{AppContext.BaseDirectory}", "conf.jobj");
}
public static Config OpenDefault()
{
var cfg = new Config();
UseDefaultLocation(cfg);
return Open(cfg);
}
public static Config Open(Config config)
{
var text = File.ReadAllText(config.FileLocation);
Config openedCfg = JsonConvert.DeserializeObject<Config>(text);
return openedCfg;
}
}
the reference to ConfigurationBox is a custom control:
And after the config is loaded it looks like:
Obviously it is rough, but it should provide all the basics you need to do something similar.

Related

Making AutoMapper reuse previously mapped instances

I would like AutoMapper to map same object instances Source to the same instances of Target.
I'm sure that AutoMapper can be configured to do this, but how? I need to convert an object graph A to B, keeping the references between the mapped objects.
For example, this fails:
[Fact]
public void Same_instances_are_mapped_to_same_instances()
{
var configuration = new MapperConfiguration(config => config.CreateMap<Source, Target>());
var mapper = configuration.CreateMapper();
var source = new Source();
var list = mapper.Map<IEnumerable<Target>>(new[] { source, source, source });
list.Distinct().Should().HaveCount(1);
}
public class Source
{
}
public class Target
{
}
It would like to make this test pass.
Also, please notice that the mapper should "remember" instances mapped during the current Map call.
That's the nearest you can get:
public static class Program
{
public static void Main()
{
var config = new MapperConfiguration(conf => conf.CreateMap<Source, Target>().PreserveReferences());
var mapper = config.CreateMapper();
var source = new Source();
var list = new[] { source, source, source };
var firstRun = mapper.Map<IEnumerable<Target>>(list);
var secondRun = mapper.Map<IEnumerable<Target>>(list);
// Returns two items
var diffs = firstRun.Concat(secondRun).Distinct();
foreach (var item in diffs)
{
Console.WriteLine(item.Id);
}
}
}
public class Source
{
public Guid Id { get; } = Guid.NewGuid();
}
public class Target
{
public string Id { get; set; }
}
This means, that you only get the same item back in case of each call to mapper.Map(), but if you call the mapper multiple times, you'll get back new items for each call (which makes sense, otherwise the mapper had to hold references to all given and created instances over it's whole lifetime, which could lead to some serious memory problems).

"Reflection": Method with "params" does not initialize my custom Objects

I have a custom class from which I create over 200 objects for a Gui.
In my main class with my business logic I want to apply the instantiation of all my objects, attach them to an event handler and set their Name. Instead of doing all this by hand for every object I thought to create a method that takes as parameters a "params" list of my objects. The problem is that this method seems to work out a "copy" of my objects instead the reference of those. What I have so far is:
My object base class:
public class MyObject
{
... // this works
}
My business class now (what works):
public class Logic
{
public MyObject Object001, Object002,... Object200; // note that they are not instantiated yet
public Logic()
{
Object001 = new MyObject();
Object001.Name = nameof(Object001);
Object001.PropertyChanged += (s, e) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
Object002 = new MyObject();
Object002.Name = nameof(Object002);
Object002.PropertyChanged += (s, e) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
...
Object200 = new MyObject();
Object200.Name = nameof(Object200 );
Object200.PropertyChanged += (s, e) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
}
My desired business class (what has not worked):
public class Logic
{
public MyObject Object001, Object002,... Object200; // note that they are not instantiated yet
public Logic()
{
InstantiateAllObjects(Object001, Object002, ..., Object200); // here my 200 objects!
}
private void InstantiateAllObjects(params MyObject[] list)
{
for (int i=0; i<list.Length; i++)
{
// if(list[i]==Object001) Console.WriteLine("Object001== null?: " + (Object001 == null)); --> this executes for EVERY object, instead for only Object001!!
MyObject obj = list[i];
obj = new MyObject();
obj.Name = nameof(obj); // why "nameof(list[i])" didn't work?
obj.PropertyChanged += (s, e) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
// if (list[i] == Object001) Console.WriteLine("Object001== null?: " + (Object001== null)); --> this executes again for EVERY object AND (Object001== null) is ALWAYS true!!
}
}
}
Can anybody explain me why my method seems not to create my objects?
Thanks in advance for your time and help!
EDIT
In my opinion, the problem seems to be i have to pass a reference to these declared objects in the 'params' list of my method... is it possible? how? I tried to use the modifiers "out" and "ref" near the "params" in the method, but with "params" seems not to be possible...
EDIT2
Following some suggestions I created in my class: logic an object list:
MyObject[] list = new MyObject[] { Object001, Object002, ..., Object200 };
InstantiateAllObjects(ref list);
and modified my method as per private void InstantiateAllObjects(ref MyObject[] list) iterating inside over list[i], but unfortunately with the same wrong result...
and also tried
List<MyObject> list = new List<MyObject>() { Object001, Object002, ..., Object200 };
InstantiateAllObjects(ref list);
and modified my method as per private void InstantiateAllObjects(ref List<MyObject> list) iterating inside over list[i], but unfortunately also with the same wrong result...
Forget about all those Object001, Object002 etc fields. Just use a new List<MyObject>() and add your objects into it inside the for-loop in your InstantiateAllObjects.
public class Logic
{
private readonly List<MyObject> allMyObjects; // note that they still are not instantiated yet
public Logic()
{
cont int amount = 200;
allMyObjects = new List<MyObject>(amount); // reserve space, but all are still null
InstantiateAllObjects(allMyObjects, amount);
}
private void InstantiateAllObjects(List<MyObject> list, int amount)
{
for (int i=0; i<amount; i++)
{
MyObject obj = new MyObject();
obj.Name = "Object" + (i+1).ToString("000");
obj.PropertyChanged += (s, e) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
list.Add(obj); // place the newly created object in the list
}
}
}
Your original test if(list[i]==Object001) fires every time, because both list[i] (for every i) and Object001 are null.
Also note that you can have multiple references to one instance of your MyObject (this already happens when you pass the reference as parameter to some method). The fact that one of those is called "Object001" is not important and in fact unknown to the instance. That is why nameof(list[i]) cannot return "Object001".
Another try, based on Hans Kesting'a answer and anna's statement
2) the name of every object will be (very) different one to another,
it is here just that i used the indexes 001...200 to concept the idea,
To avoid any trouble with accessing the fields and to regard what you wrote in the comments, no getter/setter is used and everything is public. Not the best approach but well, compared to handling 200 separate objects...
public class Logic
{
public List<MyObject> MyObjectList;
public List<string> MyObjectNames;
public Logic()
{
var anotherClass = new AnotherClass();
MyObjectNames = new List<string>() {"Object01", "Object02", "Object03"}; // either add your names here...
MyObjectNames.Add("Object04"); // or add additional names this way
//MyObjectNames.AddRange(anotherNameList); // or add another list or use Linq or whatever
MyObjectList = anotherClass.InstantiateAllObjects(MyObjectNames);
}
}
public class AnotherClass
{
public List<MyObject> InstantiateAllObjects(List<string> nameList)
{
var objectList = new List<MyObject>(nameList.Count);
foreach (var name in nameList)
{
objectList.Add(new MyObject(){Name = name});
}
return objectList;
}
}
Does this meet your requirements?
If you prefer a Dictionary, it's similar:
public class Logic
{
public Dictionary<string, MyObject> MyObjectDict;
public List<string> MyObjectNames;
public Logic()
{
var anotherClass = new AnotherClass();
MyObjectNames = new List<string>() { "Object01", "Object02", "Object03" }; // either add your names here...
MyObjectNames.Add("Object04"); // or add additional names this way
//MyObjectNames.AddRange(anotherNameList); // or add another list or use Linq or whatever
MyObjectDict = anotherClass.InstantiateAllObjects(MyObjectNames);
// objects in dict can be accessed directly by their names:
var object01 = MyObjectDict["Object01"];
}
}
// You can access in derived classes or any other classes
public class DerivedLogic : Logic
{
public void SomeFunc()
{
var object01 = MyObjectDict["Object01"];
}
public void SomeOtherFunc(string objectName)
{
var object01 = MyObjectDict[objectName];
}
}
public class AnotherClass
{
public Dictionary<string, MyObject> InstantiateAllObjects(List<string> nameList)
{
var objectList = new Dictionary<string, MyObject>(nameList.Count);
foreach (var name in nameList)
{
// check if object with name does not already exist.
if(!objectList.ContainsKey(name)
{
// For your property changed assignment, you can separate the object creation and DIctionary/List assignment
var obj = new MyObject() { Name = name };
obj.PropertyChanged += (s, e) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
objectList.Add(name, obj);
}
// else .... doe something
}
return objectList;
}
}
All of those arguments are being passed by value. You're passing the references, but the InstantiateAllObjects method can't change the values of any of the fields.
Simplifying this a bit, it's a little like having code like this:
string x = "Original value";
// This copies the value of x into the array
string[] array = new string[] { x };
// This changes the array element, but doesn't affect the x variable at all
array[0] = "Different value";
// Still prints "Original value", because x hasn't changed
Console.WriteLine(x);
As noted in comments, using a list is likely to be a much better approach than lots of different fields here.
Use a class (can be the same as the one with the logic) to hold your MyObject Fields, pass it into a function that uses reflection to populate the fields.
// Class with Fields
public class MyObjectCollection
{
public MyObject Object001;
public MyObject ObjTwo;
}
public class Logic
{
// Init logic
public void Initialise(object controls)
{
Type targType = typeof(MyObject);
var t = controls.GetType();
// iterate all fields of type MyObject
foreach(var fi in t.GetFields().Where(f=>f.FieldType == targType))
{
// initialise as required.
var o = new MyObject();
o.Name = fi.Name;
fi.SetValue(controls, o);
}
}
}
If the field name is not enough for the object name you could use Attributes on the fields to direct initialisation.
This should do the job. It is a reflection-based approach and what it does is:
find all public instance fields
use only those whose name contains Object
create a new instance of MyObject and store that in the field
public void InstantiateAllObjects()
{
foreach (FieldInfo field in this.GetType()
.GetFields(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.Name.Contains("Object")))
{
MyObject obj= new MyObject();
obj.PropertyChanged += (s, e) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
field.SetValue(this, obj);
}
}
If you add above method to your Logic class, you can then call it without having to explicitly pass Object001, Object002 etc. to it.
public class Logic
{
public MyObject Object001, Object002, ... Object200;
public Logic()
{
this.InstantiateAllObjects();
}
}
EDIT: Differently named fields
If the fields do not share a common prefix (that is, if they are not all named Object + ...), there are other ways to get the fields:
.Where(x => x.FieldType == typeof(MyObject))
yields only the fields whose type is MyObject.
You could also create an attribute
[AttributeUsage(AttributeTargets.Field)]
public sealed class FieldIWantToSetAttribute : Attribute { }
and apply that to all fields you want to set, e.g.
public class Logic
{
[FieldIWantToSet] public MyObject Object001; // will be set
[FieldIWantToSet] public MyObject Foo; // will be set
public MyObject Bar; // will not be set
}
Then, change the Where to
.Where(x => x.GetCustomAttribute<FieldIWantToSet>() != null)
Please however note that 1. you should definitely use caching. Reflection without caching is expensive, and 2. please overthink your design - why exactly do you feel the need to expose 200 fields for every other class to see?

How do I access List<int> value during reflection of a class?

I am fairly new to Reflection, but have been able to retrieve all fields of my passed class. Now I am trying to retrieve the values of each field, but I'm having an issue with List<T>.
I have a simple class for testing:
public class MyTestClass
{
public string Name;
public int Age;
public bool Alive;
public List<int> Counters;
public List<string> People;
public List<Tool> Tools;
public string[] Stuff;
public Tool[] NeededTools;
public MyTestClass(string name, int age, bool alive = true)
{
Name = name;
Age = age;
Alive = alive;
Counters = new List<int>();
Counters.Add(7);
People = new List<string>();
People.Add("Seven");
Tools = new List<Tool>();
Stuff = new string[2];
NeededTools = new Tool[3];
}
}
Here is the code I am using:
private void AttachControl(object source, FieldInfo fi, Control control)
{
switch (fi.FieldType.Name)
{
case "Boolean":
(control.Controls[fi.Name] as ComboBox).SelectedIndex = (fi.GetValue(source).ToString().ToUpper() == "TRUE") ? 1 : 0;
break;
case "List`1":
Control listControl = control.Controls[fi.Name];
var listType = fi.FieldType.GetGenericArguments();
var listFields = listType[0].GetFields(
BindingFlags.Public |
BindingFlags.Instance
);
if (listFields.Length > 0)
{
AttachToControls(listFields, listControl.Controls.Cast<Control>().ToArray());
}
else
{
// *** Here is the issue ***
var values = fi.GetValue(source);
listControl.Controls[fi.Name].Text = values[0].ToString();
}
break;
default:
control.Controls[fi.Name].Text = fi.GetValue(source).ToString();
break;
}
}
When I get to Counters I can retrieve the value var values = fi.GetValue(source); and during debug I can see the List with the value 7 in it, but it states
cannot apply indexing with [] to an expression of type object on the line:
listControl.Controls[fi.Name].Text = values[0].ToString();
I assume I need to cast it, but it will not always be an int type. Do I need to write a section for every type or is there an easier way to accomplish what I need?
FYI - I am writing a Class Library that will allow me to pass any class in and auto create a form to edit all fields.
I'd suggest something along the lines of:
var bob = values as IEnumerable;
listControl.Controls[fi.Name].Text = bob?.Cast<object>()?.FirstOrDefault()?.ToString();
Since the thing you want is a string (not a specific type) then the above code will work fine (assuming values is some form of an enumerable, like a list or an array).
Note, in particular, that IEnumerable interface is this one, not the more commonly used IEnumerable<T>. This allows you to use it without a specific type.

c# data binding with a structure value

I need some help regarding a driver I`m building. I have a structure of some data in my static class. The data of this structure object has to be manipulated from outside my driver class. Into my class I have to prepare some textBoxes, which can be assigned and used from outside the class. Each structure value becomes one textBox. Now my problem is, I have to connect this dynamic changable structure values with the corresponding textBox. I have to use dataBinding, cause there will be a huge amount of data I have to use.
Pls check out the following code snippet for understanding:
public static class driver
{
#region " data preparation "
//structure definition
public struct _data
{
public string moduleName;
public string dynamicNumber1;
//...
}
//instance object of struct
private _data moduleData = new _data();
//get;set property
public _data pModuleData
{
get
{
return moduleData;
}
set
{
moduleData = value;
}
}
#endregion
//build data binding(s) for each single "moduleData.structureItem"
//???????????????????? moduleData_itemBinding_ModuleName
//???????????????????? moduleData_itemBinding_dynamicNumber1
//...
#region " form elements preparation for external assignments "
//instance of forms objects, data can be assigned and used outside of this public static class
public static System.Windows.Forms.TextBox textBox_ModuleName = new System.Windows.Forms.TextBox();
public static System.Windows.Forms.TextBox textBox_dynamicNumber1 = new System.Windows.Forms.TextBox();
#endregion
#region " class initialisation "
static driver()
{
// class initialisation part
textBox_ModuleName.DataBindings = moduleData_itemBinding_ModuleName; //assign databindings from above ???????????
textBox_ModuleName.DataBindings = moduleData_itemBinding_dynamicNumber1; //adding databindings from above ???????????
}
#endregion
}
Thanks for help!
KISS.
I think it is possible to make as follows. Define your class:
public class Driver
{
public Driver(TextBox moduleName, TextBox dynamicNumber)
{
textBox_ModuleName = moduleName;
textBox_DynamicNumber = dynamicNumber;
textBox_ModuleName.DataBindings.Add("Text", this, "ModuleName");
textBox_DynamicNumber.DataBindings.Add("Text", this, "DynamicNumber");
}
public string ModuleName { get; set; }
public string DynamicNumber { get; set; }
private TextBox textBox_ModuleName;
private TextBox textBox_DynamicNumber;
}
Then create textboxes on the form:
var textBox1 = new TextBox { Parent = this };
var textBox2 = new TextBox { Parent = this, Top = 30 };
Create instance of your class and pass to it these textboxes:
var driver = new Driver(textBox1, textBox2);
driver.ModuleName = "foo";
driver.DynamicNumber = "bar";
// data will be appear in the textboxes
It works.

Use lambda/linq on structs

I have my constants in a class that looks like this:
public partial class CODE
{
public struct Status
{
public const long Registered = 5;
public const long Active = 6;
}
}
I would like to use lambda on the constants (this won't work):
var foo = CODE.Status.Where(x=> x > 5);
I have made a method that generates a dictionary from my structs which is somewhat close to what I need. The problem is that I don't get any intellisense on the sections. I have to pass them as strings (due to the dynamic datatype).
Is there any way to get my sample above working with my current constant class?
private Dictionary<string, Dictionary<string, dynamic>> GenerateConstants(
List<Type> classTypes)
{
var ret = new Dictionary<string, Dictionary<string, dynamic>>();
foreach (Type t in classTypes)
{
var fields = new Dictionary<string, dynamic>();
var structs = t.GetNestedTypes().Where(x => x.IsValueType &&
!x.IsPrimitive && !x.IsEnum);
foreach (var nestedStruct in structs)
{
var innerFields = new Dictionary<string, dynamic>();
foreach (var field in nestedStruct.GetFields())
{
innerFields.Add(field.Name, field.GetValue(null));
}
fields.Add(nestedStruct.Name, innerFields);
}
foreach (var field in t.GetFields())
{
if (field.FieldType == typeof(long))
{
fields.Add(field.Name, field.GetValue(null));
}
}
ret.Add(t.Name, fields);
}
return ret;
}
var constants = GenerateConstants(new List<Type> { typeof(CODE) });
var foo = constants.FirstOrDefault(x => x.Key == "CODE");
CODE.Status would never work, as is it presented in question, cause Status is a type inside your class and, not it's member.
You can think about something like:
public partial class CODE
{
public struct Status
{
public static readonly long Registered = 5; //STATIC
public static readonly long Active = 6; //STATIC
}
}
In this way you can access those fields, but it's not clear to me if this completely answers your question.
If it's not, please clarify.
If you want a list of items that you where you can adress the keys with intellisense I would make something along the following lines:
public partial class CODE
{
public const long Registered = 5;
public const long Active = 6;
public List<long> StatusList;
public Status()
{
StatusList = new Dictionairy<int, long>();
StatusList.Add(Registered);
StatusList.Add(Active);
}
}
Since it's a List you should be able to use Linq and you can use the public const values to check your 'item type' (e.g. if you do a foreach on your list you could use them in if/else statements or a switch inside your loop).
Actual implementation could be fancier (Dictionary with Enumerable as key and long as value), but that would depend on what you want to do with it.

Categories