尝试扩展List<T>类中的LastIndexOf方法

今天在学习Linq的基础知识的时候遇到这么一个问题:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 
  6 namespace DemoLinq
  7 {
  8     class Program
  9     {
 10         public static LinqData dataForTest = new LinqData { Name = "lsb", Score = "85", Age = 15 };
 11         static void Main(string[] args)
 12         {
 13             List<LinqData> linqDatas = new List<LinqData>();
 14             linqDatas.Add(new LinqData() { Name = "zsgg", Score = "98", Age = 12 }); //这里使用了对象初始化器
 15             linqDatas.Add(new LinqData() { Name = "lsb", Score = "85", Age = 15 });
 16             linqDatas.Add(new LinqData() { Name = "ww", Score = "87", Age = 15 });
 17             linqDatas.Add(new LinqData() { Name = "zd", Score = "85", Age = 18 });
 18             int lastIndex=linqDatas.LastIndexOf(dataForTest);
 19             Console.WriteLine(lastIndex);
 20         }
 21 
 22     }
 23 
 24     public class LinqData
 25     {
 26         public string Name { get; set; }
 27         public string Score { get; set; }
 28         public int Age { get; set; }
 29 
 30     }
 31 
 32 
 33 }
 34 
original code

在18行用List<T>类内置的LastIndexOf方法来求取对象的下标时发现其下标一直为-1(即未在linqDatas集合中找到dataForTest对象),可是该集合中明明包含dataForTest对象的,这是为什么呢?

经过群里好友的提醒我明白了,原来是LastIndexOf方法内置的比较逻辑无法判别出两个LinqData对象是相等的,故而无法找到dataForTest对象,所以返回-1。那么该怎么办呢?我的想法是对LastIndexOf方法进行扩展。故而有了以下的代码:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 
  6 namespace DemoLinq
  7 {
  8     class Program
  9     {
 10         public static LinqData dataForTest = new LinqData { Name = "lsb", Score = "85", Age = 15 };
 11         static void Main(string[] args)
 12         {
 13 
 14             List<LinqData> linqDatas = new List<LinqData>();
 15             linqDatas.Add(new LinqData() { Name = "zsgg", Score = "98", Age = 12 }); //这里使用了对象初始化器
 16             linqDatas.Add(new LinqData() { Name = "lsb", Score = "85", Age = 15 });
 17             linqDatas.Add(new LinqData() { Name = "ww", Score = "87", Age = 15 });
 18             linqDatas.Add(new LinqData() { Name = "zd", Score = "85", Age = 18 });
 19 	        int lastIndex=linqDatas.LastIndexOf(dataForTest); 
 20             Console.WriteLine(lastIndex);
 21         }
 22 
 23 
 24     }
 25 
 26     public class LinqData:IEquatable<LinqData>  //自定义类需实现IEquatable<T>接口
 27     {
 28         public string Name { get; set; }
 29         public string Score { get; set; }
 30         public int Age { get; set; }
 31 
 32         public bool Equals(LinqData other)
 33         { 
 34            return Name.Equals(other.Name) && Score.Equals(other.Score) && Age.Equals(other.Age);
 35         }
 36     }
 37 
 38 
 39 
 40     public static class Extend
 41     {
 42        
 43         public static int MyLastIndexOf<TSource>(this IEnumerable<TSource> source, TSource item)  where TSource:IEquatable<TSource>
 44         {
 45             List<TSource> data = source.ToList();
 46             int index = -1;
 47             foreach (TSource t in data)
 48             {
 49                 if (t.Equals(item))
 50                 {
 51                     index = data.Count - 1 - data.IndexOf(t);
 52                 }
 53             }
 54             return index;
 55         }
 56 
 57       
 58     }
 59 }
 60 
new code

主要思路如下:

  • 自定义类实现IEquatable<T>接口(即实现Equals方法来定义判等规则)
  • 编写扩展方法MyLastIndexOf(注意扩展方法的参数,在这里为this IEnumerable<TSource> source与TSource item)
  • 对扩展方法添加泛型约束(where TSource:IEquatable<TSource>)

                   

转载于:https://www.cnblogs.com/kuangyan/p/4637819.html

package com.example.kucun2.entity.data; import android.util.Log; import androidx.annotation.NonNull; import com.google.gson.Gson; import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import okhttp3.Call; import okhttp3.Callback; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; /** * 支持数据变更自动同步的泛型集合 * @param <T> 继承自 SynchronizableEntity 的实体型 */ public class SynchronizedList<T extends SynchronizableEntity> implements List<T> { private final List<T> list = new ArrayList<>(); private final OkHttpClient client = new OkHttpClient(); private static final Gson gson = new Gson(); private static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); private final Class<T> entityClass; // 型标记 // 添加批量操作模式标志 private boolean batchMode = false; // 属性变更监听器 private final List<PropertyChangeListener<T>> listeners = new ArrayList<>(); public SynchronizedList(Class<T> entityClass) { Type superClass = getClass().getGenericSuperclass(); ParameterizedType type = (ParameterizedType) superClass; Type[] typeArgs = type.getActualTypeArguments(); this.entityClass = (Class<T>) typeArgs[0]; } // 安全的型转换方法 private T safeCast(Object o) { if (o != null && entityClass.isInstance(o)) { return entityClass.cast(o); } return null; } public interface PropertyChangeListener<T> { void onPropertyChange(T entity, String propertyName, Object oldValue, Object newValue); } public void addPropertyChangeListener(PropertyChangeListener<T> listener) { listeners.add(listener); } public void removePropertyChangeListener(PropertyChangeListener<T> listener) { listeners.remove(listener); } /** * 同步实体到后端 * @param entity 要同步的实体 */ private void syncEntity(T entity) { if (batchMode) return; String endpoint = entity.getEndpoint("add"); // 从实体获取API端点 String json = gson.toJson(entity); RequestBody body = RequestBody.create(json, JSON); Request request = new Request.Builder() .url(endpoint) .put(body) // 使用PUT更新实体 .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { Log.e("SynchronizedList", "同步失败: " + entity.getClass().getSimpleName(), e); } @Override public void onResponse(@NonNull Call call, @NonNull Response response) { if (!response.isSuccessful()) { Log.e("SynchronizedList", "同步失败: " + response.code() + " - " + response.message()); } response.close(); } }); } // 添加用于批量操作的方法 public void beginBatch() { this.batchMode = true; } public void endBatch() { this.batchMode = false; // 批量结束后触发一次全量同步 syncAllEntities(); } private void syncAllEntities() { for (T entity : list) { syncEntity(entity); } } /** * 创建代理对象,用于监听属性变更 */ private T createProxy(T original) { if (!entityClass.isInstance(original)) { throw new IllegalArgumentException("Invalid entity type"); } // 这里使用动态代理模式监听setter方法调用 return (T) java.lang.reflect.Proxy.newProxyInstance( original.getClass().getClassLoader(), original.getClass().getInterfaces(), (proxy, method, args) -> { // 只拦截setter方法 if (method.getName().startsWith("set") && args.length == 1) { String propertyName = method.getName().substring(3); propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1); // 获取旧值 Object oldValue = original.getClass() .getMethod("get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1)) .invoke(original); // 调用原始方法 Object result = method.invoke(original, args); // 触发监听器 for (PropertyChangeListener<T> listener : listeners) { listener.onPropertyChange(original, propertyName, oldValue, args[0]); } // 自动同步到后端 syncEntity(original); return result; } return method.invoke(original, args); } ); } // 以下是List接口的实现 @Override public int size() { return list.size(); } @Override public boolean isEmpty() { return list.isEmpty(); } @Override public boolean contains(Object o) { return list.contains(o); } @NonNull @Override public Iterator<T> iterator() { return list.iterator(); } @NonNull @Override public Object[] toArray() { return list.toArray(); } @NonNull @Override public <T1> T1[] toArray(@NonNull T1[] a) { return list.toArray(a); } @Override public boolean add(T t) { T proxy = createProxy(t); boolean result = list.add(proxy); if (!batchMode) { syncEntity(proxy); } return result; // 添加时创建代理 } @Override public boolean remove(Object o) { T entity = safeCast(o); if (entity != null) { syncEntity((T) entity); // 删除前同步 } return list.remove(o); } @Override public boolean containsAll(@NonNull Collection<?> c) { return list.containsAll(c); } @Override public boolean addAll(@NonNull Collection<? extends T> c) { boolean modified = false; for (T t : c) { modified |= add(t); } return modified; } @Override public boolean addAll(int index, @NonNull Collection<? extends T> c) { for (T t : c) { add(index++, t); } return true; } @Override public boolean removeAll(@NonNull Collection<?> c) { for (Object o : c) { T entity = safeCast(o); if (entity != null) { syncEntity(entity); } } return list.removeAll(c); } @Override public boolean retainAll(@NonNull Collection<?> c) { List<T> toRemove = new ArrayList<>(); for (T t : list) { if (!c.contains(t)) { toRemove.add(t); } } // 使用安全转换 for (T entity : toRemove) { syncEntity(entity); } return list.retainAll(c); } @Override public void clear() { for (T t : list) { syncEntity(t); // 清空前同步 } list.clear(); } @Override public T get(int index) { return list.get(index); } @Override public T set(int index, T element) { T old = list.set(index, createProxy(element)); if (old != null) { syncEntity(old); // 替换旧元素前同步 } return old; } @Override public void add(int index, T element) { list.add(index, createProxy(element)); } @Override public T remove(int index) { T removed = list.remove(index); if (removed != null) { syncEntity(removed); // 删除前同步 } return removed; } @Override public int indexOf(Object o) { return list.indexOf(o); } @Override public int lastIndexOf(Object o) { return list.lastIndexOf(o); } @NonNull @Override public ListIterator<T> listIterator() { return list.listIterator(); } @NonNull @Override public ListIterator<T> listIterator(int index) { return list.listIterator(index); } @NonNull @Override public List<T> subList(int fromIndex, int toIndex) { return list.subList(fromIndex, toIndex); } }package com.example.kucun2.entity.data; import android.content.Context; import android.util.Log; import com.example.kucun2.entity.*; import com.example.kucun2.function.MyAppFnction; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import java.io.IOException; import java.lang.reflect.Type; import java.util.*; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class Data { // 数据集合声明(保持原有属性名不变) public static SynchronizedList<Bancai> bancais = new SynchronizedList<>(Bancai.class); public static SynchronizedList<Caizhi> caizhis = new SynchronizedList<>(Caizhi.class); public static SynchronizedList<Mupi> mupis = new SynchronizedList<>(Mupi.class); public static SynchronizedList<Chanpin> chanpins = new SynchronizedList<>(Chanpin.class); public static SynchronizedList<Chanpin_Zujian> chanpinZujians = new SynchronizedList<>(Chanpin_Zujian.class); public static SynchronizedList<Dingdan> dingdans = new SynchronizedList<>(Dingdan.class); public static SynchronizedList<Dingdan_Chanpin> dingdanChanpins = new SynchronizedList<>(Dingdan_Chanpin.class); public static SynchronizedList<Dingdan_Bancai> dingdanBancais = new SynchronizedList<>(Dingdan_Bancai.class); public static SynchronizedList<Kucun> kucuns = new SynchronizedList<>(Kucun.class); public static SynchronizedList<Zujian> zujians = new SynchronizedList<>(Zujian.class); public static SynchronizedList<User> users = new SynchronizedList<>(User.class); public static SynchronizedList<Jinhuo> jinhuoList = new SynchronizedList<>(Jinhuo.class); private static final Gson gson = new Gson(); private static final OkHttpClient client = new OkHttpClient(); private static final String TAG = "DataLoader"; // 加载所有数据的方法 public static void loadAllData(Context context, LoadDataCallback callback) { // 开始批量模式 for (SynchronizedList<?> list : getAllSyncLists()) { list.beginBatch(); } String url = MyAppFnction.getStringResource("String", "url") + MyAppFnction.getStringResource("String", "url_all"); Request request = new Request.Builder() .url(url) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e(TAG, "Failed to load data", e); if (callback != null) callback.onFailure(); } @Override public void onResponse(Call call, Response response) throws IOException { parseAndAssignData(response.body().string(), context); // 结束批量模式(会触发一次全量同步) for (SynchronizedList<?> list : getAllSyncLists()) { list.endBatch(); } if (callback != null) callback.onSuccess(); } }); } // 获取所有同步列表的方法 private static List<SynchronizedList<?>> getAllSyncLists() { return Arrays.asList(bancais, caizhis, mupis, chanpins, chanpinZujians, dingdans, dingdanChanpins, dingdanBancais, kucuns, zujians, users, jinhuoList); } // 解析并赋值数据 private static void parseAndAssignData(String jsonData, Context context, LoadDataCallback callback) { // 创建包含所有数据型的TypeToken Type informationType = new TypeToken<Information<AllDataResponse>>(){}.getType(); Information<AllDataResponse> information = gson.fromJson(jsonData, informationType); if (information == null || information.getData() == null || information.getStatus() != 200) { Log.e(TAG, "Invalid response data"); if (callback != null) callback.onFailure(); return; } AllDataResponse allData = information.getData(); // 赋值到对应的列表(使用安全方法保持已有引用) updateList(bancais, allData.bancais); updateList(caizhis, allData.caizhis); updateList(mupis, allData.mupis); updateList(chanpins, allData.chanpins); updateList(chanpinZujians, allData.chanpin_zujians); updateList(dingdans, allData.dingdans); updateList(dingdanChanpins, allData.dingdan_chanpins); updateList(dingdanBancais, allData.dingdan_bancais); updateList(kucuns, allData.kucuns); updateList(zujians, allData.zujians); updateList(users, allData.users); updateList(jinhuoList, allData.jinhuos); resolveReferences(); if (callback != null) callback.onSuccess(); } // 更新列表内容但保持对象引用不变 private static <T> void updateList(List<T> existingList, List<T> newList) { if (newList == null) return; existingList.clear(); for (T item : newList) { existingList.add(item); // 会被包装成代理对象 } } // 解析对象间的引用关系(使用ID关联) private static void resolveReferences() { // 创建ID到对象的映射 Map<Integer, Bancai> bancaiMap = createIdMap(bancais); Map<Integer, Caizhi> caizhiMap = createIdMap(caizhis); Map<Integer, Mupi> mupiMap = createIdMap(mupis); Map<Integer, Chanpin> chanpinMap = createIdMap(chanpins); Map<Integer, Zujian> zujianMap = createIdMap(zujians); Map<Integer, Dingdan> dingdanMap = createIdMap(dingdans); Map<Integer, Kucun> kucunMap = createIdMap(kucuns); // 解析Bancai引用 for (Bancai banca : bancais) { Bancai bancai = bancais.getOriginal(banca); if (bancai.getCaizhi() != null) { bancai.setCaizhi(caizhiMap.get(bancai.getCaizhi().getId())); } if (bancai.getMupi1() != null) { bancai.setMupi1(mupiMap.get(bancai.getMupi1().getId())); } if (bancai.getMupi2() != null) { bancai.setMupi2(mupiMap.get(bancai.getMupi2().getId())); } } // 解析Kucun引用 for (Kucun kucu : kucuns) { Kucun kucun = kucuns.getOriginal(kucu); if (kucun.getBancai() != null) { kucun.setBancai(bancaiMap.get(kucun.getBancai().getId())); } } // 解析订单相关的嵌套对象 for (Dingdan_Chanpin dca : dingdanChanpins) { Dingdan_Chanpin dc = dingdanChanpins.getOriginal(dca); if (dc.getDingdan() != null) { dc.setDingdan(dingdanMap.get(dc.getDingdan().getId())); } if (dc.getChanpin() != null) { dc.setChanpin(chanpinMap.get(dc.getChanpin().getId())); } } // 解析产品相关的嵌套对象 for (Chanpin_Zujian czz : chanpinZujians) { Chanpin_Zujian cz = chanpinZujians.getOriginal(czz); if (cz.getChanpin() != null) { cz.setChanpin(chanpinMap.get(cz.getChanpin().getId())); } if (cz.getZujian() != null) { cz.setZujian(zujianMap.get(cz.getZujian().getId())); } if (cz.getBancai() != null) { cz.setBancai(bancaiMap.get(cz.getBancai().getId())); } } // 解析库存关联 for (Kucun ks : kucuns) { Kucun k = kucuns.getOriginal(ks); if (k.getBancai() != null) { k.setBancai(bancaiMap.get(k.getBancai().getId())); } } } // 添加方法来获取原始对象 @SuppressWarnings("unchecked") public T getOriginal(T proxy) { if (proxy instanceof java.lang.reflect.Proxy) { java.lang.reflect.InvocationHandler handler = java.lang.reflect.Proxy.getInvocationHandler(proxy); if (handler instanceof EntityProxyHandler) { return ((EntityProxyHandler<T>) handler).getOriginal(); } } return proxy; } // 创建ID到对象的映射 private static <T extends EntityClassGrassrootsid> Map<Integer, T> createIdMap(List<T> list) { Map<Integer, T> map = new HashMap<>(); for (T item : list) { map.put(item.getId(), item); } return map; } // 回调接口 public interface LoadDataCallback { void onSuccess(); void onFailure(); } // 内部用于解析JSON响应 public static class AllDataResponse { public List<Bancai> bancais; public List<Caizhi> caizhis; public List<Mupi> mupis; public List<Chanpin> chanpins; public List<Chanpin_Zujian> chanpin_zujians; public List<Dingdan> dingdans; public List<Dingdan_Chanpin> dingdan_chanpins; public List<Dingdan_Bancai> dingdan_bancais; public List<Kucun> kucuns; public List<Zujian> zujians; public List<User> users; public List<Jinhuo> jinhuos; } }
06-10
//---------------------------------------------- // NGUI: Next-Gen UI kit // Copyright © 2011-2015 Tasharen Entertainment //---------------------------------------------- using UnityEngine; using System; using System.Collections.Generic; using System.IO; using System.Reflection; /// <summary> /// Helper class containing generic functions used throughout the UI library. /// </summary> static public class NGUITools { static AudioListener mListener; static bool mLoaded = false; static float mGlobalVolume = 1f; /// <summary> /// Globally accessible volume affecting all sounds played via NGUITools.PlaySound(). /// </summary> static public float soundVolume { get { if (!mLoaded) { mLoaded = true; mGlobalVolume = PlayerPrefs.GetFloat("Sound", 1f); } return mGlobalVolume; } set { if (mGlobalVolume != value) { mLoaded = true; mGlobalVolume = value; PlayerPrefs.SetFloat("Sound", value); } } } /// <summary> /// Helper function -- whether the disk access is allowed. /// </summary> static public bool fileAccess { get { return Application.platform != RuntimePlatform.WindowsWebPlayer && Application.platform != RuntimePlatform.OSXWebPlayer; } } /// <summary> /// Play the specified audio clip. /// </summary> static public AudioSource PlaySound (AudioClip clip) { return PlaySound(clip, 1f, 1f); } /// <summary> /// Play the specified audio clip with the specified volume. /// </summary> static public AudioSource PlaySound (AudioClip clip, float volume) { return PlaySound(clip, volume, 1f); } static float mLastTimestamp = 0f; static AudioClip mLastClip; /// <summary> /// Play the specified audio clip with the specified volume and pitch. /// </summary> static public AudioSource PlaySound (AudioClip clip, float volume, float pitch) { float time = Time.time; if (mLastClip == clip && mLastTimestamp + 0.1f > time) return null; mLastClip = clip; mLastTimestamp = time; volume *= soundVolume; if (clip != null && volume > 0.01f) { if (mListener == null || !NGUITools.GetActive(mListener)) { AudioListener[] listeners = GameObject.FindObjectsOfType(typeof(AudioListener)) as AudioListener[]; if (listeners != null) { for (int i = 0; i < listeners.Length; ++i) { if (NGUITools.GetActive(listeners[i])) { mListener = listeners[i]; break; } } } if (mListener == null) { Camera cam = Camera.main; if (cam == null) cam = GameObject.FindObjectOfType(typeof(Camera)) as Camera; if (cam != null) mListener = cam.gameObject.AddComponent<AudioListener>(); } } if (mListener != null && mListener.enabled && NGUITools.GetActive(mListener.gameObject)) { #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 AudioSource source = mListener.audio; #else AudioSource source = mListener.GetComponent<AudioSource>(); #endif if (source == null) source = mListener.gameObject.AddComponent<AudioSource>(); #if !UNITY_FLASH source.priority = 50; source.pitch = pitch; #endif source.PlayOneShot(clip, volume); return source; } } return null; } /// <summary> /// New WWW call can fail if the crossdomain policy doesn't check out. Exceptions suck. It's much more elegant to check for null instead. /// </summary> // static public WWW OpenURL (string url) // { //#if UNITY_FLASH // Debug.LogError("WWW is not yet implemented in Flash"); // return null; //#else // WWW www = null; // try { www = new WWW(url); } // catch (System.Exception ex) { Debug.LogError(ex.Message); } // return www; //#endif // } // /// <summary> // /// New WWW call can fail if the crossdomain policy doesn't check out. Exceptions suck. It's much more elegant to check for null instead. // /// </summary> // static public WWW OpenURL (string url, WWWForm form) // { // if (form == null) return OpenURL(url); //#if UNITY_FLASH // Debug.LogError("WWW is not yet implemented in Flash"); // return null; //#else // WWW www = null; // try { www = new WWW(url, form); } // catch (System.Exception ex) { Debug.LogError(ex != null ? ex.Message : "<null>"); } // return www; //#endif // } /// <summary> /// Same as Random.Range, but the returned value is between min and max, inclusive. /// Unity's Random.Range is less than max instead, unless min == max. /// This means Range(0,1) produces 0 instead of 0 or 1. That's unacceptable. /// </summary> static public int RandomRange (int min, int max) { if (min == max) return min; return UnityEngine.Random.Range(min, max + 1); } /// <summary> /// Returns the hierarchy of the object in a human-readable format. /// </summary> static public string GetHierarchy (GameObject obj) { if (obj == null) return ""; string path = obj.name; while (obj.transform.parent != null) { obj = obj.transform.parent.gameObject; path = obj.name + "\\" + path; } return path; } /// <summary> /// Find all active objects of specified type. /// </summary> static public T[] FindActive<T> () where T : Component { return GameObject.FindObjectsOfType(typeof(T)) as T[]; } /// <summary> /// Find the camera responsible for drawing the objects on the specified layer. /// </summary> static public Camera FindCameraForLayer (int layer) { int layerMask = 1 << layer; Camera cam; for (int i = 0; i < UICamera.list.size; ++i) { cam = UICamera.list.buffer[i].cachedCamera; if (cam && (cam.cullingMask & layerMask) != 0) return cam; } cam = Camera.main; if (cam && (cam.cullingMask & layerMask) != 0) return cam; #if UNITY_4_3 || UNITY_FLASH Camera[] cameras = NGUITools.FindActive<Camera>(); for (int i = 0, imax = cameras.Length; i < imax; ++i) #else Camera[] cameras = new Camera[Camera.allCamerasCount]; int camerasFound = Camera.GetAllCameras(cameras); for (int i = 0; i < camerasFound; ++i) #endif { cam = cameras[i]; if (cam && cam.enabled && (cam.cullingMask & layerMask) != 0) return cam; } return null; } /// <summary> /// Add a collider to the game object containing one or more widgets. /// </summary> static public void AddWidgetCollider (GameObject go) { AddWidgetCollider(go, false); } /// <summary> /// Add a collider to the game object containing one or more widgets. /// </summary> static public void AddWidgetCollider (GameObject go, bool considerInactive) { if (go != null) { // 3D collider Collider col = go.GetComponent<Collider>(); BoxCollider box = col as BoxCollider; if (box != null) { UpdateWidgetCollider(box, considerInactive); return; } // Is there already another collider present? If so, do nothing. if (col != null) return; // 2D collider BoxCollider2D box2 = go.GetComponent<BoxCollider2D>(); if (box2 != null) { UpdateWidgetCollider(box2, considerInactive); return; } UICamera ui = UICamera.FindCameraForLayer(go.layer); if (ui != null && (ui.eventType == UICamera.EventType.World_2D || ui.eventType == UICamera.EventType.UI_2D)) { box2 = go.AddComponent<BoxCollider2D>(); box2.isTrigger = true; #if UNITY_EDITOR UnityEditor.Undo.RegisterCreatedObjectUndo(box2, "Add Collider"); #endif UIWidget widget = go.GetComponent<UIWidget>(); if (widget != null) widget.autoResizeBoxCollider = true; UpdateWidgetCollider(box2, considerInactive); return; } else { box = go.AddComponent<BoxCollider>(); #if UNITY_EDITOR UnityEditor.Undo.RegisterCreatedObjectUndo(box, "Add Collider"); #endif box.isTrigger = true; UIWidget widget = go.GetComponent<UIWidget>(); if (widget != null) widget.autoResizeBoxCollider = true; UpdateWidgetCollider(box, considerInactive); } } return; } /// <summary> /// Adjust the widget's collider based on the depth of the widgets, as well as the widget's dimensions. /// </summary> static public void UpdateWidgetCollider (GameObject go) { UpdateWidgetCollider(go, false); } /// <summary> /// Adjust the widget's collider based on the depth of the widgets, as well as the widget's dimensions. /// </summary> static public void UpdateWidgetCollider (GameObject go, bool considerInactive) { if (go != null) { BoxCollider bc = go.GetComponent<BoxCollider>(); if (bc != null) { UpdateWidgetCollider(bc, considerInactive); return; } BoxCollider2D box2 = go.GetComponent<BoxCollider2D>(); if (box2 != null) UpdateWidgetCollider(box2, considerInactive); } } /// <summary> /// Adjust the widget's collider based on the depth of the widgets, as well as the widget's dimensions. /// </summary> static public void UpdateWidgetCollider (BoxCollider box, bool considerInactive) { if (box != null) { GameObject go = box.gameObject; UIWidget w = go.GetComponent<UIWidget>(); if (w != null) { Vector4 dr = w.drawRegion; if (dr.x != 0f || dr.y != 0f || dr.z != 1f || dr.w != 1f) { Vector4 region = w.drawingDimensions; box.center = new Vector3((region.x + region.z) * 0.5f, (region.y + region.w) * 0.5f); box.size = new Vector3(region.z - region.x, region.w - region.y); } else { Vector3[] corners = w.localCorners; box.center = Vector3.Lerp(corners[0], corners[2], 0.5f); box.size = corners[2] - corners[0]; } } else { Bounds b = NGUIMath.CalculateRelativeWidgetBounds(go.transform, considerInactive); box.center = b.center; box.size = new Vector3(b.size.x, b.size.y, 0f); } #if UNITY_EDITOR NGUITools.SetDirty(box); #endif } } /// <summary> /// Adjust the widget's collider based on the depth of the widgets, as well as the widget's dimensions. /// </summary> static public void UpdateWidgetCollider (BoxCollider2D box, bool considerInactive) { if (box != null) { GameObject go = box.gameObject; UIWidget w = go.GetComponent<UIWidget>(); if (w != null) { Vector3[] corners = w.localCorners; #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 box.center = Vector3.Lerp(corners[0], corners[2], 0.5f); #else box.offset = Vector3.Lerp(corners[0], corners[2], 0.5f); #endif box.size = corners[2] - corners[0]; } else { Bounds b = NGUIMath.CalculateRelativeWidgetBounds(go.transform, considerInactive); #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 box.center = b.center; #else box.offset = b.center; #endif box.size = new Vector2(b.size.x, b.size.y); } #if UNITY_EDITOR NGUITools.SetDirty(box); #endif } } /// <summary> /// Helper function that returns the string name of the type. /// </summary> static public string GetTypeName<T> () { string s = typeof(T).ToString(); if (s.StartsWith("UI")) s = s.Substring(2); else if (s.StartsWith("UnityEngine.")) s = s.Substring(12); return s; } /// <summary> /// Helper function that returns the string name of the type. /// </summary> static public string GetTypeName (UnityEngine.Object obj) { if (obj == null) return "Null"; string s = obj.GetType().ToString(); if (s.StartsWith("UI")) s = s.Substring(2); else if (s.StartsWith("UnityEngine.")) s = s.Substring(12); return s; } /// <summary> /// Convenience method that works without warnings in both Unity 3 and 4. /// </summary> static public void RegisterUndo (UnityEngine.Object obj, string name) { #if UNITY_EDITOR UnityEditor.Undo.RecordObject(obj, name); NGUITools.SetDirty(obj); #endif } /// <summary> /// Convenience function that marks the specified object as dirty in the Unity Editor. /// </summary> static public void SetDirty (UnityEngine.Object obj) { #if UNITY_EDITOR if (obj) { //if (obj is Component) Debug.Log(NGUITools.GetHierarchy((obj as Component).gameObject), obj); //else if (obj is GameObject) Debug.Log(NGUITools.GetHierarchy(obj as GameObject), obj); //else Debug.Log("Hmm... " + obj.GetType(), obj); UnityEditor.EditorUtility.SetDirty(obj); } #endif } /// <summary> /// Add a new child game object. /// </summary> static public GameObject AddChild (GameObject parent) { return AddChild(parent, true); } /// <summary> /// Add a new child game object. /// </summary> static public GameObject AddChild (GameObject parent, bool undo) { GameObject go = new GameObject(); #if UNITY_EDITOR if (undo) UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Create Object"); #endif if (parent != null) { Transform t = go.transform; t.parent = parent.transform; t.localPosition = Vector3.zero; t.localRotation = Quaternion.identity; t.localScale = Vector3.one; go.layer = parent.layer; } return go; } /// <summary> /// Instantiate an object and add it to the specified parent. /// </summary> static public GameObject AddChild (GameObject parent, GameObject prefab) { GameObject go = GameObject.Instantiate(prefab) as GameObject; #if UNITY_EDITOR UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Create Object"); #endif if (go != null && parent != null) { Transform t = go.transform; t.parent = parent.transform; t.localPosition = Vector3.zero; t.localRotation = Quaternion.identity; t.localScale = Vector3.one; go.layer = parent.layer; } return go; } /// <summary> /// Calculate the game object's depth based on the widgets within, and also taking panel depth into consideration. /// </summary> static public int CalculateRaycastDepth (GameObject go) { UIWidget w = go.GetComponent<UIWidget>(); if (w != null) return w.raycastDepth; UIWidget[] widgets = go.GetComponentsInChildren<UIWidget>(); if (widgets.Length == 0) return 0; int depth = int.MaxValue; for (int i = 0, imax = widgets.Length; i < imax; ++i) { if (widgets[i].enabled) depth = Mathf.Min(depth, widgets[i].raycastDepth); } return depth; } /// <summary> /// Gathers all widgets and calculates the depth for the next widget. /// </summary> static public int CalculateNextDepth (GameObject go) { int depth = -1; UIWidget[] widgets = go.GetComponentsInChildren<UIWidget>(); for (int i = 0, imax = widgets.Length; i < imax; ++i) depth = Mathf.Max(depth, widgets[i].depth); return depth + 1; } /// <summary> /// Gathers all widgets and calculates the depth for the next widget. /// </summary> static public int CalculateNextDepth (GameObject go, bool ignoreChildrenWithColliders) { if (ignoreChildrenWithColliders) { int depth = -1; UIWidget[] widgets = go.GetComponentsInChildren<UIWidget>(); for (int i = 0, imax = widgets.Length; i < imax; ++i) { UIWidget w = widgets[i]; #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 if (w.cachedGameObject != go && (w.collider != null || w.GetComponent<Collider2D>() != null)) continue; #else if (w.cachedGameObject != go && (w.GetComponent<Collider>() != null || w.GetComponent<Collider2D>() != null)) continue; #endif depth = Mathf.Max(depth, w.depth); } return depth + 1; } return CalculateNextDepth(go); } /// <summary> /// Adjust the widgets' depth by the specified value. /// Returns '0' if nothing was adjusted, '1' if panels were adjusted, and '2' if widgets were adjusted. /// </summary> static public int AdjustDepth (GameObject go, int adjustment) { if (go != null) { UIPanel panel = go.GetComponent<UIPanel>(); if (panel != null) { UIPanel[] panels = go.GetComponentsInChildren<UIPanel>(true); for (int i = 0; i < panels.Length; ++i) { UIPanel p = panels[i]; #if UNITY_EDITOR RegisterUndo(p, "Depth Change"); #endif p.depth = p.depth + adjustment; } return 1; } else { panel = FindInParents<UIPanel>(go); if (panel == null) return 0; UIWidget[] widgets = go.GetComponentsInChildren<UIWidget>(true); for (int i = 0, imax = widgets.Length; i < imax; ++i) { UIWidget w = widgets[i]; if (w.panel != panel) continue; #if UNITY_EDITOR RegisterUndo(w, "Depth Change"); #endif w.depth = w.depth + adjustment; } return 2; } } return 0; } /// <summary> /// Bring all of the widgets on the specified object forward. /// </summary> static public void BringForward (GameObject go) { int val = AdjustDepth(go, 1000); if (val == 1) NormalizePanelDepths(); else if (val == 2) NormalizeWidgetDepths(); } /// <summary> /// Push all of the widgets on the specified object back, making them appear behind everything else. /// </summary> static public void PushBack (GameObject go) { int val = AdjustDepth(go, -1000); if (val == 1) NormalizePanelDepths(); else if (val == 2) NormalizeWidgetDepths(); } /// <summary> /// Normalize the depths of all the widgets and panels in the scene, making them start from 0 and remain in order. /// </summary> static public void NormalizeDepths () { NormalizeWidgetDepths(); NormalizePanelDepths(); } /// <summary> /// Normalize the depths of all the widgets in the scene, making them start from 0 and remain in order. /// </summary> static public void NormalizeWidgetDepths () { NormalizeWidgetDepths(FindActive<UIWidget>()); } /// <summary> /// Normalize the depths of all the widgets in the scene, making them start from 0 and remain in order. /// </summary> static public void NormalizeWidgetDepths (GameObject go) { NormalizeWidgetDepths(go.GetComponentsInChildren<UIWidget>()); } /// <summary> /// Normalize the depths of all the widgets in the scene, making them start from 0 and remain in order. /// </summary> static public void NormalizeWidgetDepths (UIWidget[] list) { int size = list.Length; if (size > 0) { Array.Sort(list, UIWidget.FullCompareFunc); int start = 0; int current = list[0].depth; for (int i = 0; i < size; ++i) { UIWidget w = list[i]; if (w.depth == current) { w.depth = start; } else { current = w.depth; w.depth = ++start; } } } } /// <summary> /// Normalize the depths of all the panels in the scene, making them start from 0 and remain in order. /// </summary> static public void NormalizePanelDepths () { UIPanel[] list = FindActive<UIPanel>(); int size = list.Length; if (size > 0) { Array.Sort(list, UIPanel.CompareFunc); int start = 0; int current = list[0].depth; for (int i = 0; i < size; ++i) { UIPanel p = list[i]; if (p.depth == current) { p.depth = start; } else { current = p.depth; p.depth = ++start; } } } } /// <summary> /// Create a new UI. /// </summary> static public UIPanel CreateUI (bool advanced3D) { return CreateUI(null, advanced3D, -1); } /// <summary> /// Create a new UI. /// </summary> static public UIPanel CreateUI (bool advanced3D, int layer) { return CreateUI(null, advanced3D, layer); } /// <summary> /// Create a new UI. /// </summary> static public UIPanel CreateUI (Transform trans, bool advanced3D, int layer) { // Find the existing UI Root UIRoot root = (trans != null) ? NGUITools.FindInParents<UIRoot>(trans.gameObject) : null; if (root == null && UIRoot.list.Count > 0) { foreach (UIRoot r in UIRoot.list) { if (r.gameObject.layer == layer) { root = r; break; } } } // Try to find an existing panel if (root == null) { for (int i = 0, imax = UIPanel.list.Count; i < imax; ++i) { UIPanel p = UIPanel.list[i]; GameObject go = p.gameObject; if (go.hideFlags == HideFlags.None && go.layer == layer) { trans.parent = p.transform; trans.localScale = Vector3.one; return p; } } } // If we are working with a different UI type, we need to treat it as a brand-new one instead if (root != null) { UICamera cam = root.GetComponentInChildren<UICamera>(); #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 if (cam != null && cam.camera.isOrthoGraphic == advanced3D) #else if (cam != null && cam.GetComponent<Camera>().orthographic == advanced3D) #endif { trans = null; root = null; } } // If no root found, create one if (root == null) { GameObject go = NGUITools.AddChild(null, false); root = go.AddComponent<UIRoot>(); // Automatically find the layers if none were specified if (layer == -1) layer = LayerMask.NameToLayer("UI"); if (layer == -1) layer = LayerMask.NameToLayer("2D UI"); go.layer = layer; if (advanced3D) { go.name = "UI Root (3D)"; root.scalingStyle = UIRoot.Scaling.Constrained; } else { go.name = "UI Root"; root.scalingStyle = UIRoot.Scaling.Flexible; } } // Find the first panel UIPanel panel = root.GetComponentInChildren<UIPanel>(); if (panel == null) { // Find other active cameras in the scene Camera[] cameras = NGUITools.FindActive<Camera>(); float depth = -1f; bool colorCleared = false; int mask = (1 << root.gameObject.layer); for (int i = 0; i < cameras.Length; ++i) { Camera c = cameras[i]; // If the color is being cleared, we won't need to if (c.clearFlags == CameraClearFlags.Color || c.clearFlags == CameraClearFlags.Skybox) colorCleared = true; // Choose the maximum depth depth = Mathf.Max(depth, c.depth); // Make sure this camera can't see the UI c.cullingMask = (c.cullingMask & (~mask)); } // Create a camera that will draw the UI Camera cam = NGUITools.AddChild<Camera>(root.gameObject, false); cam.gameObject.AddComponent<UICamera>(); cam.clearFlags = colorCleared ? CameraClearFlags.Depth : CameraClearFlags.Color; cam.backgroundColor = Color.grey; cam.cullingMask = mask; cam.depth = depth + 1f; if (advanced3D) { cam.nearClipPlane = 0.1f; cam.farClipPlane = 4f; cam.transform.localPosition = new Vector3(0f, 0f, -700f); } else { cam.orthographic = true; cam.orthographicSize = 1; cam.nearClipPlane = -10; cam.farClipPlane = 10; } // Make sure there is an audio listener present AudioListener[] listeners = NGUITools.FindActive<AudioListener>(); if (listeners == null || listeners.Length == 0) cam.gameObject.AddComponent<AudioListener>(); // Add a panel to the root panel = root.gameObject.AddComponent<UIPanel>(); #if UNITY_EDITOR UnityEditor.Selection.activeGameObject = panel.gameObject; #endif } if (trans != null) { // Find the root object while (trans.parent != null) trans = trans.parent; if (NGUITools.IsChild(trans, panel.transform)) { // Odd hierarchy -- can't reparent panel = trans.gameObject.AddComponent<UIPanel>(); } else { // Reparent this root object to be a child of the panel trans.parent = panel.transform; trans.localScale = Vector3.one; trans.localPosition = Vector3.zero; SetChildLayer(panel.cachedTransform, panel.cachedGameObject.layer); } } return panel; } /// <summary> /// Helper function that recursively sets all children with widgets' game objects layers to the specified value. /// </summary> static public void SetChildLayer (Transform t, int layer) { for (int i = 0; i < t.childCount; ++i) { Transform child = t.GetChild(i); child.gameObject.layer = layer; SetChildLayer(child, layer); } } /// <summary> /// Add a child object to the specified parent and attaches the specified script to it. /// </summary> static public T AddChild<T> (GameObject parent) where T : Component { GameObject go = AddChild(parent); go.name = GetTypeName<T>(); return go.AddComponent<T>(); } /// <summary> /// Add a child object to the specified parent and attaches the specified script to it. /// </summary> static public T AddChild<T> (GameObject parent, bool undo) where T : Component { GameObject go = AddChild(parent, undo); go.name = GetTypeName<T>(); return go.AddComponent<T>(); } /// <summary> /// Add a new widget of specified type. /// </summary> static public T AddWidget<T> (GameObject go) where T : UIWidget { int depth = CalculateNextDepth(go); // Create the widget and place it above other widgets T widget = AddChild<T>(go); widget.width = 100; widget.height = 100; widget.depth = depth; return widget; } /// <summary> /// Add a new widget of specified type. /// </summary> static public T AddWidget<T> (GameObject go, int depth) where T : UIWidget { // Create the widget and place it above other widgets T widget = AddChild<T>(go); widget.width = 100; widget.height = 100; widget.depth = depth; return widget; } /// <summary> /// Add a sprite appropriate for the specified atlas sprite. /// It will be sliced if the sprite has an inner rect, and a regular sprite otherwise. /// </summary> static public UISprite AddSprite (GameObject go, UIAtlas atlas, string spriteName) { UISpriteData sp = (atlas != null) ? atlas.GetSprite(spriteName) : null; UISprite sprite = AddWidget<UISprite>(go); sprite.type = (sp == null || !sp.hasBorder) ? UISprite.Type.Simple : UISprite.Type.Sliced; sprite.atlas = atlas; sprite.spriteName = spriteName; return sprite; } /// <summary> /// Get the rootmost object of the specified game object. /// </summary> static public GameObject GetRoot (GameObject go) { Transform t = go.transform; for (; ; ) { Transform parent = t.parent; if (parent == null) break; t = parent; } return t.gameObject; } /// <summary> /// Finds the specified component on the game object or one of its parents. /// </summary> static public T FindInParents<T> (GameObject go) where T : Component { if (go == null) return null; // Commented out because apparently it causes Unity 4.5.3 to lag horribly: // http://www.tasharen.com/forum/index.php?topic=10882.0 //#if UNITY_4_3 #if UNITY_FLASH object comp = go.GetComponent<T>(); #else T comp = go.GetComponent<T>(); #endif if (comp == null) { Transform t = go.transform.parent; while (t != null && comp == null) { comp = t.gameObject.GetComponent<T>(); t = t.parent; } } #if UNITY_FLASH return (T)comp; #else return comp; #endif //#else // return go.GetComponentInParent<T>(); //#endif } /// <summary> /// Finds the specified component on the game object or one of its parents. /// </summary> static public T FindInParents<T> (Transform trans) where T : Component { if (trans == null) return null; #if UNITY_4_3 #if UNITY_FLASH object comp = trans.GetComponent<T>(); #else T comp = trans.GetComponent<T>(); #endif if (comp == null) { Transform t = trans.transform.parent; while (t != null && comp == null) { comp = t.gameObject.GetComponent<T>(); t = t.parent; } } #if UNITY_FLASH return (T)comp; #else return comp; #endif #else return trans.GetComponentInParent<T>(); #endif } /// <summary> /// Destroy the specified object, immediately if in edit mode. /// </summary> static public void Destroy (UnityEngine.Object obj) { if (obj != null) { if (obj is Transform) obj = (obj as Transform).gameObject; if (Application.isPlaying) { if (obj is GameObject) { GameObject go = obj as GameObject; go.transform.parent = null; } UnityEngine.Object.Destroy(obj); } else UnityEngine.Object.DestroyImmediate(obj); } } /// <summary> /// Destroy the specified object immediately, unless not in the editor, in which case the regular Destroy is used instead. /// </summary> static public void DestroyImmediate (UnityEngine.Object obj) { if (obj != null) { if (Application.isEditor) UnityEngine.Object.DestroyImmediate(obj); else UnityEngine.Object.Destroy(obj); } } /// <summary> /// Call the specified function on all objects in the scene. /// </summary> static public void Broadcast (string funcName) { GameObject[] gos = GameObject.FindObjectsOfType(typeof(GameObject)) as GameObject[]; for (int i = 0, imax = gos.Length; i < imax; ++i) gos[i].SendMessage(funcName, SendMessageOptions.DontRequireReceiver); } /// <summary> /// Call the specified function on all objects in the scene. /// </summary> static public void Broadcast (string funcName, object param) { GameObject[] gos = GameObject.FindObjectsOfType(typeof(GameObject)) as GameObject[]; for (int i = 0, imax = gos.Length; i < imax; ++i) gos[i].SendMessage(funcName, param, SendMessageOptions.DontRequireReceiver); } /// <summary> /// Determines whether the 'parent' contains a 'child' in its hierarchy. /// </summary> static public bool IsChild (Transform parent, Transform child) { if (parent == null || child == null) return false; while (child != null) { if (child == parent) return true; child = child.parent; } return false; } /// <summary> /// Activate the specified object and all of its children. /// </summary> static void Activate (Transform t) { Activate(t, false); } /// <summary> /// Activate the specified object and all of its children. /// </summary> static void Activate (Transform t, bool compatibilityMode) { SetActiveSelf(t.gameObject, true); if (compatibilityMode) { // If there is even a single enabled child, then we're using a Unity 4.0-based nested active state scheme. for (int i = 0, imax = t.childCount; i < imax; ++i) { Transform child = t.GetChild(i); if (child.gameObject.activeSelf) return; } // If this point is reached, then all the children are disabled, so we must be using a Unity 3.5-based active state scheme. for (int i = 0, imax = t.childCount; i < imax; ++i) { Transform child = t.GetChild(i); Activate(child, true); } } } /// <summary> /// Deactivate the specified object and all of its children. /// </summary> static void Deactivate (Transform t) { SetActiveSelf(t.gameObject, false); } /// <summary> /// SetActiveRecursively enables children before parents. This is a problem when a widget gets re-enabled /// and it tries to find a panel on its parent. /// </summary> static public void SetActive (GameObject go, bool state) { SetActive(go, state, true); } /// <summary> /// SetActiveRecursively enables children before parents. This is a problem when a widget gets re-enabled /// and it tries to find a panel on its parent. /// </summary> static public void SetActive (GameObject go, bool state, bool compatibilityMode) { if (go) { if (state) { Activate(go.transform, compatibilityMode); #if UNITY_EDITOR if (Application.isPlaying) #endif CallCreatePanel(go.transform); } else Deactivate(go.transform); } } /// <summary> /// Ensure that all widgets have had their panels created, forcing the update right away rather than on the following frame. /// </summary> [System.Diagnostics.DebuggerHidden] [System.Diagnostics.DebuggerStepThrough] static void CallCreatePanel (Transform t) { UIWidget w = t.GetComponent<UIWidget>(); if (w != null) w.CreatePanel(); for (int i = 0, imax = t.childCount; i < imax; ++i) CallCreatePanel(t.GetChild(i)); } /// <summary> /// Activate or deactivate children of the specified game object without changing the active state of the object itself. /// </summary> static public void SetActiveChildren (GameObject go, bool state) { Transform t = go.transform; if (state) { for (int i = 0, imax = t.childCount; i < imax; ++i) { Transform child = t.GetChild(i); Activate(child); } } else { for (int i = 0, imax = t.childCount; i < imax; ++i) { Transform child = t.GetChild(i); Deactivate(child); } } } /// <summary> /// Helper function that returns whether the specified MonoBehaviour is active. /// </summary> [System.Obsolete("Use NGUITools.GetActive instead")] static public bool IsActive (Behaviour mb) { return mb != null && mb.enabled && mb.gameObject.activeInHierarchy; } /// <summary> /// Helper function that returns whether the specified MonoBehaviour is active. /// </summary> [System.Diagnostics.DebuggerHidden] [System.Diagnostics.DebuggerStepThrough] static public bool GetActive (Behaviour mb) { return mb && mb.enabled && mb.gameObject.activeInHierarchy; } /// <summary> /// Unity4 has changed GameObject.active to GameObject.activeself. /// </summary> [System.Diagnostics.DebuggerHidden] [System.Diagnostics.DebuggerStepThrough] static public bool GetActive (GameObject go) { return go && go.activeInHierarchy; } /// <summary> /// Unity4 has changed GameObject.active to GameObject.SetActive. /// </summary> [System.Diagnostics.DebuggerHidden] [System.Diagnostics.DebuggerStepThrough] static public void SetActiveSelf (GameObject go, bool state) { go.SetActive(state); } /// <summary> /// Recursively set the game object's layer. /// </summary> static public void SetLayer (GameObject go, int layer) { go.layer = layer; Transform t = go.transform; for (int i = 0, imax = t.childCount; i < imax; ++i) { Transform child = t.GetChild(i); SetLayer(child.gameObject, layer); } } /// <summary> /// Helper function used to make the vector use integer numbers. /// </summary> static public Vector3 Round (Vector3 v) { v.x = Mathf.Round(v.x); v.y = Mathf.Round(v.y); v.z = Mathf.Round(v.z); return v; } /// <summary> /// Make the specified selection pixel-perfect. /// </summary> static public void MakePixelPerfect (Transform t) { UIWidget w = t.GetComponent<UIWidget>(); if (w != null) w.MakePixelPerfect(); if (t.GetComponent<UIAnchor>() == null && t.GetComponent<UIRoot>() == null) { #if UNITY_EDITOR RegisterUndo(t, "Make Pixel-Perfect"); #endif t.localPosition = Round(t.localPosition); t.localScale = Round(t.localScale); } // Recurse into children for (int i = 0, imax = t.childCount; i < imax; ++i) MakePixelPerfect(t.GetChild(i)); } /// <summary> /// Save the specified binary data into the specified file. /// </summary> static public bool Save (string fileName, byte[] bytes) { #if UNITY_WEBPLAYER || UNITY_FLASH || UNITY_METRO || UNITY_WP8 || UNITY_WP_8_1 return false; #else if (!NGUITools.fileAccess) return false; string path = ""; if (bytes == null) { if (File.Exists(path)) File.Delete(path); return true; } FileStream file = null; try { file = File.Create(path); } catch (System.Exception ex) { Debug.LogError(ex.Message); return false; } file.Write(bytes, 0, bytes.Length); file.Close(); return true; #endif } /// <summary> /// Load all binary data from the specified file. /// </summary> static public byte[] Load (string fileName) { #if UNITY_WEBPLAYER || UNITY_FLASH || UNITY_METRO || UNITY_WP8 || UNITY_WP_8_1 return null; #else if (!NGUITools.fileAccess) return null; string path = ""; if (File.Exists(path)) { return File.ReadAllBytes(path); } return null; #endif } /// <summary> /// Pre-multiply shaders result in a black outline if this operation is done in the shader. It's better to do it outside. /// </summary> static public Color ApplyPMA (Color c) { if (c.a != 1f) { c.r *= c.a; c.g *= c.a; c.b *= c.a; } return c; } /// <summary> /// Inform all widgets underneath the specified object that the parent has changed. /// </summary> static public void MarkParentAsChanged (GameObject go) { UIRect[] rects = go.GetComponentsInChildren<UIRect>(); for (int i = 0, imax = rects.Length; i < imax; ++i) rects[i].ParentHasChanged(); } /// <summary> /// Access to the clipboard via undocumented APIs. /// </summary> static public string clipboard { get { TextEditor te = new TextEditor(); te.Paste(); return te.content.text; } set { TextEditor te = new TextEditor(); te.content = new GUIContent(value); te.OnFocus(); te.Copy(); } } [System.Obsolete("Use NGUIText.EncodeColor instead")] static public string EncodeColor (Color c) { return NGUIText.EncodeColor24(c); } [System.Obsolete("Use NGUIText.ParseColor instead")] static public Color ParseColor (string text, int offset) { return NGUIText.ParseColor24(text, offset); } [System.Obsolete("Use NGUIText.StripSymbols instead")] static public string StripSymbols (string text) { return NGUIText.StripSymbols(text); } /// <summary> /// Extension for the game object that checks to see if the component already exists before adding a new one. /// If the component is already present it will be returned instead. /// </summary> static public T AddMissingComponent<T> (this GameObject go) where T : Component { #if UNITY_FLASH object comp = go.GetComponent<T>(); #else T comp = go.GetComponent<T>(); #endif if (comp == null) { #if UNITY_EDITOR if (!Application.isPlaying) RegisterUndo(go, "Add " + typeof(T)); #endif comp = go.AddComponent<T>(); } #if UNITY_FLASH return (T)comp; #else return comp; #endif } // Temporary variable to avoid GC allocation static Vector3[] mSides = new Vector3[4]; /// <summary> /// Get sides relative to the specified camera. The order is left, top, right, bottom. /// </summary> static public Vector3[] GetSides (this Camera cam) { return cam.GetSides(Mathf.Lerp(cam.nearClipPlane, cam.farClipPlane, 0.5f), null); } /// <summary> /// Get sides relative to the specified camera. The order is left, top, right, bottom. /// </summary> static public Vector3[] GetSides (this Camera cam, float depth) { return cam.GetSides(depth, null); } /// <summary> /// Get sides relative to the specified camera. The order is left, top, right, bottom. /// </summary> static public Vector3[] GetSides (this Camera cam, Transform relativeTo) { return cam.GetSides(Mathf.Lerp(cam.nearClipPlane, cam.farClipPlane, 0.5f), relativeTo); } /// <summary> /// Get sides relative to the specified camera. The order is left, top, right, bottom. /// </summary> static public Vector3[] GetSides (this Camera cam, float depth, Transform relativeTo) { #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 if (cam.isOrthoGraphic) #else if (cam.orthographic) #endif { float os = cam.orthographicSize; float x0 = -os; float x1 = os; float y0 = -os; float y1 = os; Rect rect = cam.rect; Vector2 size = screenSize; float aspect = size.x / size.y; aspect *= rect.width / rect.height; x0 *= aspect; x1 *= aspect; // We want to ignore the scale, as scale doesn't affect the camera's view region in Unity Transform t = cam.transform; Quaternion rot = t.rotation; Vector3 pos = t.position; int w = Mathf.RoundToInt(size.x); int h = Mathf.RoundToInt(size.y); if ((w & 1) == 1) pos.x -= 1f / size.x; if ((h & 1) == 1) pos.y += 1f / size.y; mSides[0] = rot * (new Vector3(x0, 0f, depth)) + pos; mSides[1] = rot * (new Vector3(0f, y1, depth)) + pos; mSides[2] = rot * (new Vector3(x1, 0f, depth)) + pos; mSides[3] = rot * (new Vector3(0f, y0, depth)) + pos; } else { mSides[0] = cam.ViewportToWorldPoint(new Vector3(0f, 0.5f, depth)); mSides[1] = cam.ViewportToWorldPoint(new Vector3(0.5f, 1f, depth)); mSides[2] = cam.ViewportToWorldPoint(new Vector3(1f, 0.5f, depth)); mSides[3] = cam.ViewportToWorldPoint(new Vector3(0.5f, 0f, depth)); } if (relativeTo != null) { for (int i = 0; i < 4; ++i) mSides[i] = relativeTo.InverseTransformPoint(mSides[i]); } return mSides; } /// <summary> /// Get the camera's world-space corners. The order is bottom-left, top-left, top-right, bottom-right. /// </summary> static public Vector3[] GetWorldCorners (this Camera cam) { float depth = Mathf.Lerp(cam.nearClipPlane, cam.farClipPlane, 0.5f); return cam.GetWorldCorners(depth, null); } /// <summary> /// Get the camera's world-space corners. The order is bottom-left, top-left, top-right, bottom-right. /// </summary> static public Vector3[] GetWorldCorners (this Camera cam, float depth) { return cam.GetWorldCorners(depth, null); } /// <summary> /// Get the camera's world-space corners. The order is bottom-left, top-left, top-right, bottom-right. /// </summary> static public Vector3[] GetWorldCorners (this Camera cam, Transform relativeTo) { return cam.GetWorldCorners(Mathf.Lerp(cam.nearClipPlane, cam.farClipPlane, 0.5f), relativeTo); } /// <summary> /// Get the camera's world-space corners. The order is bottom-left, top-left, top-right, bottom-right. /// </summary> static public Vector3[] GetWorldCorners (this Camera cam, float depth, Transform relativeTo) { #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 if (cam.isOrthoGraphic) #else if (cam.orthographic) #endif { float os = cam.orthographicSize; float x0 = -os; float x1 = os; float y0 = -os; float y1 = os; Rect rect = cam.rect; Vector2 size = screenSize; float aspect = size.x / size.y; aspect *= rect.width / rect.height; x0 *= aspect; x1 *= aspect; // We want to ignore the scale, as scale doesn't affect the camera's view region in Unity Transform t = cam.transform; Quaternion rot = t.rotation; Vector3 pos = t.position; mSides[0] = rot * (new Vector3(x0, y0, depth)) + pos; mSides[1] = rot * (new Vector3(x0, y1, depth)) + pos; mSides[2] = rot * (new Vector3(x1, y1, depth)) + pos; mSides[3] = rot * (new Vector3(x1, y0, depth)) + pos; } else { mSides[0] = cam.ViewportToWorldPoint(new Vector3(0f, 0f, depth)); mSides[1] = cam.ViewportToWorldPoint(new Vector3(0f, 1f, depth)); mSides[2] = cam.ViewportToWorldPoint(new Vector3(1f, 1f, depth)); mSides[3] = cam.ViewportToWorldPoint(new Vector3(1f, 0f, depth)); } if (relativeTo != null) { for (int i = 0; i < 4; ++i) mSides[i] = relativeTo.InverseTransformPoint(mSides[i]); } return mSides; } /// <summary> /// Convenience function that converts Class + Function combo into Class.Function representation. /// </summary> static public string GetFuncName (object obj, string method) { if (obj == null) return "<null>"; string type = obj.GetType().ToString(); int period = type.LastIndexOf('/'); if (period > 0) type = type.Substring(period + 1); return string.IsNullOrEmpty(method) ? type : type + "/" + method; } #if UNITY_EDITOR || !UNITY_FLASH /// <summary> /// Execute the specified function on the target game object. /// </summary> static public void Execute<T> (GameObject go, string funcName) where T : Component { T[] comps = go.GetComponents<T>(); foreach (T comp in comps) { #if !UNITY_EDITOR && (UNITY_WEBPLAYER || UNITY_FLASH || UNITY_METRO || UNITY_WP8 || UNITY_WP_8_1) comp.SendMessage(funcName, SendMessageOptions.DontRequireReceiver); #else MethodInfo method = comp.GetType().GetMethod(funcName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) method.Invoke(comp, null); #endif } } /// <summary> /// Execute the specified function on the target game object and all of its children. /// </summary> static public void ExecuteAll<T> (GameObject root, string funcName) where T : Component { Execute<T>(root, funcName); Transform t = root.transform; for (int i = 0, imax = t.childCount; i < imax; ++i) ExecuteAll<T>(t.GetChild(i).gameObject, funcName); } /// <summary> /// Immediately start, update, and create all the draw calls from newly instantiated UI. /// This is useful if you plan on doing something like immediately taking a screenshot then destroying the UI. /// </summary> static public void ImmediatelyCreateDrawCalls (GameObject root) { ExecuteAll<UIWidget>(root, "Start"); ExecuteAll<UIPanel>(root, "Start"); ExecuteAll<UIWidget>(root, "Update"); ExecuteAll<UIPanel>(root, "Update"); ExecuteAll<UIPanel>(root, "LateUpdate"); } #endif #if UNITY_EDITOR static int mSizeFrame = -1; static System.Reflection.MethodInfo s_GetSizeOfMainGameView; static Vector2 mGameSize = Vector2.one; /// <summary> /// Size of the game view cannot be retrieved from Screen.width and Screen.height when the game view is hidden. /// </summary> static public Vector2 screenSize { get { int frame = Time.frameCount; if (mSizeFrame != frame || !Application.isPlaying) { mSizeFrame = frame; if (s_GetSizeOfMainGameView == null) { System.Type type = System.Type.GetType("UnityEditor.GameView,UnityEditor"); s_GetSizeOfMainGameView = type.GetMethod("GetSizeOfMainGameView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); } mGameSize = (Vector2)s_GetSizeOfMainGameView.Invoke(null, null); } return mGameSize; } } #else /// <summary> /// Size of the game view cannot be retrieved from Screen.width and Screen.height when the game view is hidden. /// </summary> static public Vector2 screenSize { get { return new Vector2(Screen.width, Screen.height); } } #endif } 这个代码是Unity5.3.3f1版本的,现在升级到Unity 2017.4.0f1 (64-bit)版本 以下是报错信息Assets/NGUI/Scripts/Internal/NGUITools.cs(1438,37): error CS1519: Unexpected symbol `<' in class, struct, or interface member declaration ,把修改后的完整代码给我
最新发布
07-08
java package com.sinosoft.system.util; import com.spire.doc.DocumentProperties; import io.swagger.annotations.ApiModelProperty; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.http.converter.HttpMessageNotWritableException; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; import javax.servlet.http.HttpServletResponse; import java.awt.image.BufferedImage; import java.io.*; import java.lang.reflect.Field; import java.net.URLEncoder; import java.util.*; public class ExcelExporterUtil<T> { @ApiModelProperty(value = "数据") private final List<T> obj; @ApiModelProperty(value = "数据") private final Class<T> clazz; @ApiModelProperty(value = "查询字段") private final List<String> selectFields; @ApiModelProperty(value = "文件名称") private final String fileName; @ApiModelProperty(value = "表格页名称") private final String sheetName; @ApiModelProperty(value = "Excel 工作簿") private Workbook workbook; @ApiModelProperty(value = "表格页") private Sheet sheet; @ApiModelProperty(value = "表头样式") private CellStyle headerStyle; @ApiModelProperty(value = "数据样式") private CellStyle dataStyle; @ApiModelProperty(value = "图片样式(居中对齐)") private CellStyle imageStyle; // 初始化构造方法 public ExcelExporterUtil(List<T> obj, Class<T> clazz, List<String> selectFields, String fileName) { this(obj, clazz, selectFields, fileName, "Sheet1"); } public ExcelExporterUtil(List<T> obj, Class<T> clazz, List<String> selectFields, String fileName, String sheetName) { this.obj = obj; this.clazz = clazz; // 处理 selectFields 为 null 的情况 this.selectFields = selectFields != null ? selectFields : new ArrayList<>(); // 处理 fileName 为 null 的情况,随机生成文件名 this.fileName = fileName != null ? fileName : "export_" + System.currentTimeMillis(); this.sheetName = sheetName; // 使用 SXSSFWorkbook 处理大量数据 this.workbook = new SXSSFWorkbook(100); this.sheet = workbook.createSheet(sheetName); initStyles(); } // 初始化样式 private void initStyles() { // 表头样式 headerStyle = workbook.createCellStyle(); Font headerFont = workbook.createFont(); headerFont.setBold(true); headerFont.setFontHeightInPoints((short) 12); headerStyle.setFont(headerFont); headerStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex()); headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); headerStyle.setAlignment(HorizontalAlignment.CENTER); headerStyle.setBorderBottom(BorderStyle.THIN); headerStyle.setBorderLeft(BorderStyle.THIN); headerStyle.setBorderRight(BorderStyle.THIN); headerStyle.setBorderTop(BorderStyle.THIN); // 数据样式 dataStyle = workbook.createCellStyle(); dataStyle.setAlignment(HorizontalAlignment.LEFT); dataStyle.setBorderBottom(BorderStyle.THIN); dataStyle.setBorderLeft(BorderStyle.THIN); dataStyle.setBorderRight(BorderStyle.THIN); dataStyle.setBorderTop(BorderStyle.THIN); // 图片样式(居中对齐) imageStyle = workbook.createCellStyle(); imageStyle.setAlignment(HorizontalAlignment.CENTER); imageStyle.setVerticalAlignment(VerticalAlignment.CENTER); imageStyle.setBorderBottom(BorderStyle.THIN); imageStyle.setBorderLeft(BorderStyle.THIN); imageStyle.setBorderRight(BorderStyle.THIN); imageStyle.setBorderTop(BorderStyle.THIN); } public void export(HttpServletResponse response) throws IOException { // 移除文件名中的扩展名(如果有) String fileNameWithoutExt = removeExtension(this.fileName); // 清理文件名,移除前后的非法字符(包括下划线) String cleanedFileName = cleanFileName(fileNameWithoutExt); // 添加正确的.xlsx扩展名 String fileNameWithExt = cleanedFileName + ".xlsx"; // 设置完整的响应头 response.reset(); response.setCharacterEncoding("UTF-8"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(fileNameWithExt, "UTF-8") + "\""); response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); response.setHeader("Pragma", "no-cache"); response.setDateHeader("Expires", 0); try (OutputStream outputStream = response.getOutputStream()) { // 生成Excel内容 createHeader(); fillData(); autoSizeColumns(); // 写入前强制刷新区 sheet.getWorkbook().getCreationHelper().createFormulaEvaluator().evaluateAll(); // 设置安全属性 workbook.setForceFormulaRecalculation(false); // 防止外部链接 // 写入Excel数据 workbook.write(outputStream); outputStream.flush(); } catch (Exception e) { // 记录错误日志 System.err.println("导出Excel失败: " + e.getMessage()); e.printStackTrace(); // 如果发生错误,尝试重置响应 try { response.reset(); response.setContentType("text/plain; charset=UTF-8"); response.getWriter().write("导出失败: " + e.getMessage()); } catch (IOException ex) { ex.printStackTrace(); } } finally { if (workbook instanceof SXSSFWorkbook) { ((SXSSFWorkbook) workbook).dispose(); } try { workbook.close(); } catch (IOException e) { e.printStackTrace(); } } } // 清理文件名,移除前后的非法字符(包括下划线、空格等) private String cleanFileName(String originalFileName) { if (originalFileName == null) { return "export_" + System.currentTimeMillis(); } // 移除前后的非法字符(正则表达式匹配所有非法字符,包括下划线) String cleaned = originalFileName.trim(); cleaned = cleaned.replaceAll("^[^a-zA-Z0-9\\u4e00-\\u9fa5]+", ""); // 移除开头的非法字符 cleaned = cleaned.replaceAll("[^a-zA-Z0-9\\u4e00-\\u9fa5]+$", ""); // 移除结尾的非法字符 // 替换中间的多个连续下划线为单个下划线 cleaned = cleaned.replaceAll("_+", "_"); // 确保文件名不以下划线开头或结尾 cleaned = cleaned.replaceAll("^_+|_+$", ""); // 如果清理后文件名为空,生成一个默认文件名 if (cleaned.isEmpty()) { return "export_" + System.currentTimeMillis(); } return cleaned; } // 移除文件名中的扩展名 private String removeExtension(String fileName) { if (fileName == null) { return null; } int dotIndex = fileName.lastIndexOf('.'); if (dotIndex > 0) { return fileName.substring(0, dotIndex); } return fileName; } // 获取需要导出的字段 private List<Field> getExportFields() { List<Field> fields = new ArrayList<>(); // 获取及其父的所有字段 Class<?> currentClass = clazz; while (currentClass != null) { fields.addAll(Arrays.asList(currentClass.getDeclaredFields())); currentClass = currentClass.getSuperclass(); } // 过滤不需要导出的字段(可根据需要添加更多过滤条件) fields.removeIf(field -> // 过滤静态字段 java.lang.reflect.Modifier.isStatic(field.getModifiers()) || // 过滤 transient 字段 java.lang.reflect.Modifier.isTransient(field.getModifiers()) ); return fields; } private List<T> getData() { if (obj == null || obj.isEmpty()) { throw new HttpMessageNotWritableException("列表为空"); } return obj; } // 创建表头 private void createHeader() { Row headerRow = sheet.createRow(0); List<Field> fields = getExportFields(); for (int i = 0; i < selectFields.size(); i++) { for (Field field : fields) { // 获取字段名 String columnName = field.getName(); if (Objects.equals(columnName.trim(), selectFields.get(i).trim())) { // 如果有 ApiModelProperty 注解,使用注解中的 value 作为列名 if (field.isAnnotationPresent(io.swagger.annotations.ApiModelProperty.class)) { columnName = field.getAnnotation(io.swagger.annotations.ApiModelProperty.class).value(); } Cell cell = headerRow.createCell(i); cell.setCellValue(columnName); cell.setCellStyle(headerStyle); } } } } // 填充数据 private void fillData() { List<Field> fields = getExportFields(); List<T> data = getData(); for (int i = 0; i < data.size(); i++) { Row dataRow = sheet.createRow(i + 1); T item = data.get(i); for (int k = 0; k < selectFields.size(); k++) { for (Field field : fields) { if (Objects.equals(field.getName().trim(), selectFields.get(k).trim())) { field.setAccessible(true); Cell cell = dataRow.createCell(k); try { Object value = field.get(item); if (value != null) { if (value instanceof byte[]) { // 调用修复的图片处理方法 handleImageCell(cell, (byte[]) value, i + 1, k); } else if (value instanceof String) { cell.setCellValue((String) value); cell.setCellStyle(dataStyle); } else if (value instanceof Integer) { cell.setCellValue((Integer) value); cell.setCellStyle(dataStyle); } else if (value instanceof Long) { cell.setCellValue((Long) value); cell.setCellStyle(dataStyle); } else if (value instanceof Double) { cell.setCellValue((Double) value); cell.setCellStyle(dataStyle); } else if (value instanceof Boolean) { cell.setCellValue((Boolean) value); cell.setCellStyle(dataStyle); } else if (value instanceof Date) { // 日期格式处理 cell.setCellValue((Date) value); CellStyle dateStyle = workbook.createCellStyle(); dateStyle.cloneStyleFrom(dataStyle); CreationHelper createHelper = workbook.getCreationHelper(); dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd")); cell.setCellStyle(dateStyle); } else { cell.setCellValue(value.toString()); cell.setCellStyle(dataStyle); } } } catch (Exception e) { // 添加日志记录 System.err.println("数据填充失败: " + e.getMessage()); e.printStackTrace(); } } } } } } // 修复的图片处理方法 private void handleImageCell(Cell cell, byte[] imageBytes, int rowIndex, int colIndex) { try { // 1. 明确图片格式检测逻辑 int imageType = Workbook.PICTURE_TYPE_PNG; // 默认为PNG try (ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes)) { ImageInputStream iis = ImageIO.createImageInputStream(bis); Iterator<ImageReader> readers = ImageIO.getImageReaders(iis); if (readers.hasNext()) { ImageReader reader = readers.next(); reader.setInput(iis); String formatName = reader.getFormatName().toLowerCase(); if (formatName.equals("jpeg") || formatName.equals("jpg")) { imageType = Workbook.PICTURE_TYPE_JPEG; } else if (formatName.equals("png")) { imageType = Workbook.PICTURE_TYPE_PNG; } } } // 2. 安全属性设置 CreationHelper helper = workbook.getCreationHelper(); ClientAnchor anchor = helper.createClientAnchor(); anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE); // 关键安全设置 // 2. 共享DrawingPatriarch对象(关键修复) Drawing<?> drawing = sheet.getDrawingPatriarch(); if (drawing == null) { drawing = sheet.createDrawingPatriarch(); } // 3. 添加图片 int pictureIdx = workbook.addPicture(imageBytes, imageType); // 4. 设置图片位置和大小 anchor.setCol1(colIndex); anchor.setRow1(rowIndex); anchor.setCol2(colIndex + 1); anchor.setRow2(rowIndex + 1); // 5. 创建图片并设置单元格样式 Picture pict = drawing.createPicture(anchor, pictureIdx); cell.setCellStyle(imageStyle); // 6. 调整行高适应图片 Row row = sheet.getRow(rowIndex); short rowHeight = row.getHeight(); // 图片高度大约等于20个POI单位/像素 if (rowHeight < 20 * 20) { row.setHeight((short) (20 * 20)); } } catch (Exception e) { // 添加日志记录 System.err.println("图片处理失败: " + e.getMessage()); e.printStackTrace(); cell.setCellValue("[Image Error]"); } } // 自适应列宽 private void autoSizeColumns() { for (int i = 0; i < selectFields.size(); i++) { sheet.autoSizeColumn(i); // 适当增加列宽,避免内容显示不全 sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 1000); } } } 导出文件名assets.xlsx ,实际导出后名称前后都带下划线,并且不能打开
06-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值