一、前言
在制作UI的时候,经常会碰到对不同的结构体或者类进行编辑,然后要根结构体或类的字段或属性制作对应的输入框。比如int型的输入框内就不能输入字符,就要限定输入框的类型;每个输入框还要对应字段或属性的名字。另外,不同的结构体或类的字段或属性的名字和类型都可能不一样。如果每一个结构体或类都要单独的制作一个编辑栏,每一个输入框都对应指定的字段或属性,如字段名称、类型等,无疑将是一个巨大的工作量。而且维护成本也很高,比如后续对某个结构体或者类添加或删除其中的某个字段,相应的也要手动修改编辑栏。本文就是提供一种可以实现动态创建结构体或类编辑栏输入框,根据结构体或类的字段名称、类型等进行创建可编辑的输入框。
二、实现
1、反射
主要用的编程技巧是C#的反射功能,利用反射可以做下面的事情(C#本质论5.0第四版 472页):
1>:访问程序集中类型的元数据。其中包括像完整类型名和成员名这样的构造,以及对一个构造进行修饰的任何特性。
2>:使用元数据,在运行时动态调用一个类型的成员,而不是执行编译时绑定。
反射时指对程序集中的元数据进行检查的过程。在以前,当代码编译成一种机器语言时,关于代码的所有元数据(比如类型和方法名)都会被丢弃。相反,当C#编译成CIL时,它会维持关于代码的大部分元数据。除此之外,可以利用反射枚举程序集中的所有类型,找出满足特定条件的那些。
2、反射提取结构体中元数据信息
说了这么多理论的东西,我们就可以着手编写代码了,对结构体进行反射提取信息的代码如下:
public void OnCreateAttr_OnClick(object data)
{
//先使用反射将结构体数据内的所有字段列举出来
Type tempPPType = data.GetType();
FieldInfo[] tempFI = tempPPType.GetFields();//字段
//PropertyInfo[] tempPI = tempPPType.GetProperties();//属性
//然后根据这些字段创建一个ui编辑条
for (int i = 0; i < tempFI.Length; i++)
{
UI2D_SubObjEditorAttr tempEPPA = Instantiate(prefabEditorAttr);
tempEPPA.transform.SetParent(ui2DCanvas.transform);
tempEPPA.transform.localPosition = new Vector3(0, -subObjIntervalHeight * i, 0);
tempEPPA.transform.localScale = Vector3.one;
tempEPPA.Init(tempFI.FieldType, tempFI.Name, tempFI.GetValue(data).ToString());
//获取字段的类型、名字和值
//MethodInfo tempMI = tempPI.GetSetMethod();//设置器为空的即是私有的
//if (tempMI != null)
//{
// Debug.Log(tempMI.Name + tempMI.IsPublic);
//}
// tempEPPA.Init(tempPI.PropertyType, tempPI.Name, tempPI.GetValue(curEditorRP, null).ToString());
//添加该编辑条的编辑结束事件方法
tempEPPA.OnEndEditor_Event += OnEndEditor_Event;
listSubObj_EditorAttr.Add(tempEPPA);
}
}
述代码中用到了一个自定义的类,UI2D_SubObjEditorAttr,代码:这段代码主要是对一个输入框和Text进行封装的类,Text显示变量的名称,如图所示:创建的单个编辑条的预设,只有一个Text和输入框
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UI2D_SubObjEditorAttr : MonoBehaviour {
public delegate bool Editor_Event(string name, string value);
public event Editor_Event OnEndEditor_Event;
public string M_CurATTRName
{
get
{
return textName.text;
}
}
[SerializeField]
private Text textName;
private InputField inputContent;
private string lastValue;
private string curValue;
private bool isInitSucced = false;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update()
{
if (!isInitSucced) return;
}
/// <summary>
/// 挂在面板上的方法
/// </summary>
public void UIOnEndEditor_Event()
{
if (null != OnEndEditor_Event)
{
curValue = inputContent.text;
if (!OnEndEditor_Event(textName.text, curValue))
{
inputContent.text = lastValue;
}
else
{
inputContent.text = curValue;
lastValue = curValue;
}
}
}
public void Init(Type type,string name,string vaule)
{
textName.text = name;
inputContent = GetComponentInChildren<InputField>();
inputContent.text = vaule;
curValue = vaule;
lastValue = curValue;
isInitSucced = true;
}
public void SetValue(string value)
{
curValue = value;
lastValue = curValue;
inputContent.text = curValue;
}
}
输入框可以显示结构体的值,并且在后期的时候对该字段的值进行修改。这里我定义了两个结构体:
[Serializable]
public struct PD_RoutePoint
{
public int RouteID;
public int Number;
public double Lon;
public float Height;
public double Lat;
public float Pitch_X;
public float Yaw_Y;
public float Roll_Z;
public float Speed;
public int OffsetTime;
}
[Serializable]
public struct PD_Route
{
public int RouteID;
public int FigherID;
public long BaseTime;
public string Describe;
}
3、使用方法
将定义的这两个结构体变量分别传给该方法,就可以实现根据结构体的字段自动创建该自动的编辑条,如图所示:
void Update () {
if(Input.GetKeyDown(KeyCode.C))
{
// OnCreateAttr_OnClick(curEditorRoute);
OnCreateAttr_OnClick(curEditorRP);
}
}
不同的结构体变量可以动态的创建不同的编辑条,并且将默认的值赋值给每个对应的编辑条。
三、总结
1、完成了初步的将不同的结构体动态的创建编辑条的功能
2、尚未根据不同字段的类型进行编辑条细化
3、编辑条的值改变没有关联到指定的结构体变量中,也即无法动态修改结构体变量的值
未完待续。。。。