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

来源:互联网 发布:淘宝上兼职是真的吗 编辑:程序博客网 时间:2024/05/23 18:14

转载请注明出处:http://blog.csdn.net/fuemocheng


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}

转载请注明出处:http://blog.csdn.net/fuemocheng