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

Обновить

April 2019

Просмотры

37 раз

1

это может быть очень простой вопрос, но я растрескивание голову на это, так как месяцы. Я хочу, чтобы создать простой пользовательский интерфейс для создания объектов класса я передаю в конструктор пользовательского интерфейса. Пусть говорят, у меня есть 2 классов:

    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;
    }

(Статические классы будут содержать все объекты, созданные из этого класса)

что я хотел бы сделать это состоит в создании кода, который принимает класс в качестве переменной (может быть общий класс?) и на основе, что создает все метки и текстовые поля на основе полей, доступных в классе. например

    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);
        }
    }

Является ли это вообще возможно? Я надеюсь, что я не должен был расплывчатым с кодом макете, и вы можете понять, что я возглавляю для. Любые советы будут высоко оценены. Большое спасибо :)

2 ответы

0

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:

enter image description here

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));
0

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:

enter image description here

And after the config is loaded it looks like:

enter image description here

Obviously it is rough, but it should provide all the basics you need to do something similar.