C#利用反射调用基类私有方法及Unity实现自定义InputField
发表于2018-11-07
Unity官方源码:https://bitbucket.org/Unity-Technologies/
需求:新建一个类 MyInputField 继承自 UnityEngine.UI 的 InputField ,即输入框,要求新类中实现整块字符的删除。比如输入一个表情(字符串代码是 #F20),要求删除时能整块删除。
首先找到 InputField 的源码(Unity5.6)。
分析源码:
当按键事件发生, OnUpdateSelected() 方法检测到,通过 KeyPressed() 来判断事件,并执行相应逻辑。当按下Backspace键时,执行 Backspace() 方法,然后根据情况Delete()。其中 Backspace() 和 Delete() 都是父类私有方法,不可由子类直接调用。
看下面源码:
/// <summary> /// Handle the specified event. /// </summary> private Event m_ProcessingEvent = new Event(); public virtual void OnUpdateSelected(BaseEventData eventData) { if (!isFocused) return; bool consumedEvent = false; while (Event.PopEvent(m_ProcessingEvent)) { if (m_ProcessingEvent.rawType == EventType.KeyDown) { consumedEvent = true; var shouldContinue = KeyPressed(m_ProcessingEvent); if (shouldContinue == EditState.Finish) { DeactivateInputField(); break; } } switch (m_ProcessingEvent.type) { case EventType.ValidateCommand: case EventType.ExecuteCommand: switch (m_ProcessingEvent.commandName) { case "SelectAll": SelectAll(); consumedEvent = true; break; } break; } } if (consumedEvent) UpdateLabel(); eventData.Use(); } protected EditState KeyPressed(Event evt) { ... switch (evt.keyCode) { case KeyCode.Backspace: { Backspace(); return EditState.Continue; } case KeyCode.Delete: { ForwardSpace(); return EditState.Continue; } ... } ... } private void Backspace() { if (m_ReadOnly) return; if (hasSelection) { Delete(); SendOnValueChangedAndUpdateLabel(); } else { if (caretPositionInternal > 0) { m_Text = text.Remove(caretPositionInternal - 1, 1); caretSelectPositionInternal = caretPositionInternal = caretPositionInternal - 1; SendOnValueChangedAndUpdateLabel(); } } } private void Delete() { if (m_ReadOnly) return; if (caretPositionInternal == caretSelectPositionInternal) return; if (caretPositionInternal < caretSelectPositionInternal) { m_Text = text.Substring(0, caretPositionInternal) + text.Substring(caretSelectPositionInternal, text.Length - caretSelectPositionInternal); caretSelectPositionInternal = caretPositionInternal; } else { m_Text = text.Substring(0, caretSelectPositionInternal) + text.Substring(caretPositionInternal, text.Length - caretPositionInternal); caretPositionInternal = caretSelectPositionInternal; } }
如果要实现整块删除,则必然在 KeyPressed() 之前执行我们自定义删除函数。因为我们没法重写 KeyPressed(),但是Unity提供了 virtual void OnUpdateSelected() 方法,这就是我们可以重写的方法。于是重写如下:
private Event m_ProcessingEvent = new Event(); public override void OnUpdateSelected(BaseEventData eventData) { if (!isFocused) return; bool consumedEvent = false; while (Event.PopEvent(m_ProcessingEvent)) { if (m_ProcessingEvent.rawType == EventType.KeyDown) { consumedEvent = true; /// <summary> /// 如果是Backspace键,执行我们自定义删除方法 /// </summary> if (m_ProcessingEvent.keyCode == KeyCode.Backspace) { DelNodeFace(); break; } var shouldContinue = KeyPressed(m_ProcessingEvent); if (shouldContinue == EditState.Finish) { DeactivateInputField(); break; } } switch (m_ProcessingEvent.type) { case EventType.ValidateCommand: case EventType.ExecuteCommand: switch (m_ProcessingEvent.commandName) { case "SelectAll": SelectAll(); consumedEvent = true; break; } break; } } if (consumedEvent) UpdateLabel(); eventData.Use(); }
DelNodeFace() 就是我们自定义删除 整块内容 的函数。主要实现原理就是记录这一 整块输入内容 在输入框中开始和结束的位置,删除时和光标位置进行比较,一旦开始删除 整块内容 的最后位置,就将 整块内容 删除。
实现如下:
public bool DelNodeFace() //自定义删除函数 { int currentPos = this.caretPosition; for (int i=m_inputNodeList.Count-1; i>=0; i--) { TInputNode tempNode = m_inputNodeList[i]; //这里其实存在Bug,如果从showStr中间删除,也会删除同样长度的输入信息,应该限制只能从末尾删除(暂时不解决这个问题) if (currentPos > tempNode.m_iCharBegin && currentPos <= tempNode.m_iCharEnd) { string showStr = "#f" + tempNode.m_myInfo.GetValueInt("faceId").ToString(); for (int j = 0; j < showStr.Length; j++) { //删除 字符串中 tempNode.m_iCharBegin 到 tempNode.m_iCharEnd DeleteOne(); } m_inputNodeList.RemoveAt(i); return true; } } //否则只删除一个字符信息 DeleteOne(); return false; }
但是,仍然要实现其中的删除每一个字符的方法 DeleteOne() 。
这时就需要调用父类中的 Backspace() 方法了。如何调用父类中的私有方法呢?利用反射。
C#反射怎么用,可以百度。 这里给有两个链接:
1、子类用反射可以访问父类中的私有成员变量及方法
2、反射(C#编程指南)
这里实现如下:
public void DeleteOne() { // 指明当前对象 object obj = (InputField)this; // 获取对象的类型 Type type = obj.GetType(); // 对象的父类类型 type = type.BaseType; //字段绑定标志 BindingFlags flag = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; //获取对象的私有方法print MethodInfo mf = type.GetMethod("Backspace", flag); // 实现对象中的方法 mf.Invoke(obj, null); }
于是可以实现 整块删除。
当然,插入字符时,整块字符 的起始位置也要发生变化,所以也要重写插入字符的方法。
InputField 源码中插入字符实现
/// <summary> /// Append the specified text to the end of the current. /// </summary> protected virtual void Append(string input) { if (m_ReadOnly) return; if (!InPlaceEditing()) return; for (int i = 0, imax = input.Length; i < imax; ++i) { char c = input[i]; if (c >= ' ' || c == '\t' || c == '\r' || c == 10 || c == '\n') { Append(c); } } } protected virtual void Append(char input) { if (m_ReadOnly) return; if (!InPlaceEditing()) return; // If we have an input validator, validate the input first int insertionPoint = Math.Min(selectionFocusPosition, selectionAnchorPosition); if (onValidateInput != null) input = onValidateInput(text, insertionPoint, input); else if (characterValidation != CharacterValidation.None) input = Validate(text, insertionPoint, input); // If the input is invalid, skip it if (input == 0) return; // Append the character and update the label Insert(input); } // Insert the character and update the label. private void Insert(char c) { if (m_ReadOnly) return; string replaceString = c.ToString(); Delete(); // Can't go past the character limit if (characterLimit > 0 && text.Length >= characterLimit) return; m_Text = text.Insert(m_CaretPosition, replaceString); caretSelectPositionInternal = caretPositionInternal += replaceString.Length; SendOnValueChanged(); }
我们进行重写,实现插入字符串时,块内容整体后移:
protected override void Append(string input) { if (readOnly) return; if (!InPlaceEditing()) return; for (int i = 0, imax = input.Length; i < imax; ++i) { char c = input[i]; if (c >= ' ' || c == '\t' || c == '\r' || c == 10 || c == '\n') { Append(c); } } } protected override void Append(char input) { if (readOnly) return; if (!InPlaceEditing()) return; // If we have an input validator, validate the input first int insertionPoint = Math.Min(selectionFocusPosition, selectionAnchorPosition); if (onValidateInput != null) input = onValidateInput(text, insertionPoint, input); else if (characterValidation != CharacterValidation.None) input = Validate(text, insertionPoint, input); // If the input is invalid, skip it if (input == 0) return; #region 将列表里存储表情开始结束位置后移 for (int i = m_inputNodeList.Count - 1; i >= 0; i--) { TInputNode tempNode = m_inputNodeList[i]; if (tempNode.m_iCharBegin >= m_CaretPosition) { tempNode.m_iCharBegin++; tempNode.m_iCharEnd++; } } #endregion // Append the character and update the label InsertOne(input); } //利用反射调用父类私有方法Insert(),并传参; public void InsertOne(char c) { // 指明当前对象 object obj = (InputField)this; // 获取对象的类型 Type type = obj.GetType(); // 对象的父类类型 type = type.BaseType; //字段绑定标志 BindingFlags flag = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; object[] parameters = new object[1]; parameters[0] = c; //获取对象的私有方法print MethodInfo mf = type.GetMethod("Insert", flag); // 实现对象中的方法 mf.Invoke(obj, parameters); }
最终实现要求,实现了自定义MyInputFiled类。
这里给出MyInputField类源码,完全继承InputFiled的功能,而且实现自定义删除块内容的功能。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using System.Reflection; using System; using UnityEngine.EventSystems; public class MyInputField : InputField { private int m_nCurrentPos = 0; // Use this for initialization void Start () { } // Update is called once per frame void Update () { } public enum eInputNode { eInputNode_none = 0, eInputNode_face = 1, eInputNode_item = 2, eInputNode_itemByType = 3, eInputNode_pet = 4, } //输入节点,可以是表情、物品、宠物 public class TInputNode { public TInputNode() { } public eInputNode m_eInputNode = eInputNode.eInputNode_none; public int m_nID; public int m_iCharBegin = 0; public int m_iCharEnd = 0; } public List<TInputNode> m_inputNodeList = new List<TInputNode>(); public void AddNodeFace(int faceId) { //只能最多输入两个表情 if (m_inputNodeList.Count >= 2) return; TInputNode newNode = new TInputNode(); newNode.m_eInputNode = eInputNode.eInputNode_face; newNode.m_nID = faceId; string showStr = "#f" + faceId.ToString(); newNode.m_iCharBegin = this.caretPosition; this.Append(showStr); this.UpdateLabel(); newNode.m_iCharEnd = this.caretPosition; m_inputNodeList.Add(newNode); } public bool DelNodeFace() //自定义删除函数 { int currentPos = this.caretPosition; for (int i=m_inputNodeList.Count-1; i>=0; i--) { TInputNode tempNode = m_inputNodeList[i]; //这里其实存在Bug,如果从showStr中间删除,也会删除同样长度的输入信息,应该只删除m_iCharBegin 到 m_iCharEnd 之间的字符(暂时不解决这个问题) if (currentPos > tempNode.m_iCharBegin && currentPos <= tempNode.m_iCharEnd) { string showStr = "#f" + tempNode.m_myInfo.GetValueInt("faceId").ToString(); for (int j = 0; j < showStr.Length; j++) { //删除 字符串中 tempNode.m_iCharBegin 到 tempNode.m_iCharEnd DeleteOne(); } m_inputNodeList.RemoveAt(i); return true; } } //否则只删除一个字符信息 DeleteOne(); return false; } public string GetSendStr() { return ""; } #region 重写父类方法 private Event m_ProcessingEvent = new Event(); public override void OnUpdateSelected(BaseEventData eventData) { if (!isFocused) return; bool consumedEvent = false; while (Event.PopEvent(m_ProcessingEvent)) { if (m_ProcessingEvent.rawType == EventType.KeyDown) { consumedEvent = true; /// <summary> /// 如果是Backspace键,执行我们自定义删除方法 /// </summary> if (m_ProcessingEvent.keyCode == KeyCode.Backspace) { DelNodeFace(); break; } var shouldContinue = KeyPressed(m_ProcessingEvent); if (shouldContinue == EditState.Finish) { DeactivateInputField(); break; } } switch (m_ProcessingEvent.type) { case EventType.ValidateCommand: case EventType.ExecuteCommand: switch (m_ProcessingEvent.commandName) { case "SelectAll": SelectAll(); consumedEvent = true; break; } break; } } if (consumedEvent) UpdateLabel(); eventData.Use(); } protected override void Append(string input) { if (readOnly) return; if (!InPlaceEditing()) return; for (int i = 0, imax = input.Length; i < imax; ++i) { char c = input[i]; if (c >= ' ' || c == '\t' || c == '\r' || c == 10 || c == '\n') { Append(c); } } } protected override void Append(char input) { if (readOnly) return; if (!InPlaceEditing()) return; // If we have an input validator, validate the input first int insertionPoint = Math.Min(selectionFocusPosition, selectionAnchorPosition); if (onValidateInput != null) input = onValidateInput(text, insertionPoint, input); else if (characterValidation != CharacterValidation.None) input = Validate(text, insertionPoint, input); // If the input is invalid, skip it if (input == 0) return; #region 将列表里存储表情开始结束位置后移 for (int i = m_inputNodeList.Count - 1; i >= 0; i--) { TInputNode tempNode = m_inputNodeList[i]; if (tempNode.m_iCharBegin >= m_CaretPosition) { tempNode.m_iCharBegin++; tempNode.m_iCharEnd++; } } #endregion // Append the character and update the label InsertOne(input); } private bool InPlaceEditing() { return !TouchScreenKeyboard.isSupported; } #endregion #region 利用反射调用基类私有方法 public void DeleteOne() { // 指明当前对象 object obj = (InputField)this; // 获取对象的类型 Type type = obj.GetType(); // 对象的父类类型 type = type.BaseType; //字段绑定标志 BindingFlags flag = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; //获取对象的私有方法print MethodInfo mf = type.GetMethod("Backspace", flag); // 实现对象中的方法 mf.Invoke(obj, null); } public void InsertOne(char c) { // 指明当前对象 object obj = (InputField)this; // 获取对象的类型 Type type = obj.GetType(); // 对象的父类类型 type = type.BaseType; //字段绑定标志 BindingFlags flag = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; object[] parameters = new object[1]; parameters[0] = c; //获取对象的私有方法print MethodInfo mf = type.GetMethod("Insert", flag); // 实现对象中的方法 mf.Invoke(obj, parameters); } #endregion }
来自:https://blog.csdn.net/fuemocheng/article/details/76595738