C#利用反射调用基类私有方法及Unity实现自定义InputField

发表于2018-11-07
评论0 3.5k浏览
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

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引