using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Thrift; using Thrift.Protocol; using Thrift.Transport; using Thrift.Transport.Client; using UnityEditor; using UnityEngine; namespace BytesViewerTool { /// /// Bytes文件查看器 - 将Thrift bytes数据以表格形式展示 /// public class BytesViewerWindow : EditorWindow { private TextAsset selectedBytesFile; private string currentFilePath = ""; private object parsedData; private Vector2 scrollPosition; private bool dataLoaded = false; private string errorMessage = ""; // 表格显示相关 private List columnNames = new List(); private List rowDataList = new List(); private List filteredRowDataList = new List(); private Vector2 tableScrollPosition; // 配置选择 private string[] availableConfigs = new string[0]; private string[] filteredConfigs = new string[0]; private int selectedConfigIndex = 0; private string currentConfigName = ""; private string configSearchText = ""; private Vector2 configListScrollPosition; private bool isConfigListExpanded = true; // 分页相关 private int currentPage = 0; private int pageSize = 50; private int[] pageSizeOptions = new int[] { 20, 50, 100, 200, 500 }; private string pageInputText = "1"; // 筛选相关 private string filterText = ""; private string lastFilterText = ""; private int filterColumnIndex = 0; private string[] filterColumnOptions = new string[0]; // 全局搜索 private string globalSearchText = ""; private List globalSearchResults = new List(); private Vector2 globalSearchScrollPosition; // 单元格详情查看 private int selectedRowIndex = -1; private string selectedColumnName = ""; private string selectedCellFullData = ""; private Vector2 detailScrollPosition; private class GlobalSearchResult { public string ConfigName; public int RowIndex; public string ColumnName; public object RowData; public string MatchedValue; } [MenuItem("配置表pipeline/Bytes查看器")] public static void ShowWindow() { BytesViewerWindow window = GetWindow("Bytes查看器"); window.minSize = new Vector2(1000, 600); window.Show(); } private void OnEnable() { // 窗口启动时自动查找并加载AllConfigs.bytes AutoLoadAllConfigs(); } private void OnGUI() { EditorGUILayout.Space(10); EditorGUILayout.LabelField("Bytes文件查看器", EditorStyles.boldLabel, GUILayout.Height(24)); EditorGUILayout.Space(5); // 文件选择区域 EditorGUILayout.BeginVertical("box"); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("选择Bytes文件:", GUILayout.Width(100)); TextAsset newFile = EditorGUILayout.ObjectField(selectedBytesFile, typeof(TextAsset), false) as TextAsset; if (newFile != selectedBytesFile) { selectedBytesFile = newFile; dataLoaded = false; errorMessage = ""; availableConfigs = new string[0]; selectedConfigIndex = 0; } if (GUILayout.Button("加载并解析", GUILayout.Width(100))) { LoadAndParseBytes(); } EditorGUILayout.EndHorizontal(); if (!string.IsNullOrEmpty(currentFilePath)) { EditorGUILayout.LabelField("当前文件:", currentFilePath, EditorStyles.miniLabel); } EditorGUILayout.EndVertical(); EditorGUILayout.Space(5); // 全局搜索功能 if (availableConfigs.Length > 0) { EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("🔍 全局搜索 (所有配置表)", EditorStyles.boldLabel); EditorGUILayout.Space(3); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("关键词:", GUILayout.Width(60)); string newGlobalSearch = EditorGUILayout.TextField(globalSearchText, GUILayout.MinWidth(300)); if (newGlobalSearch != globalSearchText) { globalSearchText = newGlobalSearch; } if (GUILayout.Button("搜索", GUILayout.Width(80))) { PerformGlobalSearch(); } if (GUILayout.Button("清空", GUILayout.Width(60))) { globalSearchText = ""; globalSearchResults.Clear(); } EditorGUILayout.EndHorizontal(); // 显示搜索结果 if (globalSearchResults.Count > 0) { EditorGUILayout.Space(3); EditorGUILayout.LabelField($"找到 {globalSearchResults.Count} 条结果:", EditorStyles.boldLabel); globalSearchScrollPosition = EditorGUILayout.BeginScrollView( globalSearchScrollPosition, GUILayout.Height(Mathf.Min(globalSearchResults.Count * 22 + 10, 150))); for (int i = 0; i < globalSearchResults.Count; i++) { var result = globalSearchResults[i]; string displayText = $"{result.ConfigName} - 行{result.RowIndex} - {result.ColumnName}: {result.MatchedValue}"; if (GUILayout.Button(displayText, EditorStyles.label)) { // 点击结果跳转到对应配置 JumpToSearchResult(result); } } EditorGUILayout.EndScrollView(); } else if (!string.IsNullOrEmpty(globalSearchText) && globalSearchResults.Count == 0) { EditorGUILayout.HelpBox("未找到匹配的结果", MessageType.Info); } EditorGUILayout.EndVertical(); EditorGUILayout.Space(5); } // 配置类型选择 if (availableConfigs.Length > 0) { EditorGUILayout.BeginVertical("box"); EditorGUILayout.BeginHorizontal(); // 折叠按钮 isConfigListExpanded = EditorGUILayout.Foldout(isConfigListExpanded, "配置类型", true, EditorStyles.foldoutHeader); GUILayout.FlexibleSpace(); EditorGUILayout.LabelField("配置类型:", GUILayout.Width(70)); // 搜索框 string newSearchText = EditorGUILayout.TextField(configSearchText, EditorStyles.toolbarSearchField, GUILayout.Width(200)); if (newSearchText != configSearchText) { configSearchText = newSearchText; FilterConfigList(); } if (GUILayout.Button("X", GUILayout.Width(25))) { configSearchText = ""; FilterConfigList(); } // 显示当前选中的配置 if (!string.IsNullOrEmpty(currentConfigName)) { GUILayout.FlexibleSpace(); EditorGUILayout.LabelField($"当前: {currentConfigName}", EditorStyles.boldLabel, GUILayout.Width(250)); } EditorGUILayout.LabelField($"{rowDataList.Count} 条", EditorStyles.miniLabel, GUILayout.Width(60)); EditorGUILayout.EndHorizontal(); // 配置列表(可折叠) if (isConfigListExpanded && filteredConfigs.Length > 0) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.Space(10); configListScrollPosition = EditorGUILayout.BeginScrollView( configListScrollPosition, GUILayout.Height(Mathf.Min(filteredConfigs.Length * 22 + 10, 120))); for (int i = 0; i < filteredConfigs.Length; i++) { string configName = filteredConfigs[i]; int actualIndex = Array.IndexOf(availableConfigs, configName); bool isSelected = actualIndex == selectedConfigIndex; GUIStyle buttonStyle = isSelected ? new GUIStyle(EditorStyles.toolbarButton) { fontStyle = FontStyle.Bold } : EditorStyles.toolbarButton; if (GUILayout.Button(configName, buttonStyle, GUILayout.Height(20))) { if (actualIndex != selectedConfigIndex) { selectedConfigIndex = actualIndex; PrepareTableDataForSelectedConfig(); Repaint(); } } } EditorGUILayout.EndScrollView(); EditorGUILayout.EndHorizontal(); } else if (!string.IsNullOrEmpty(configSearchText)) { EditorGUILayout.HelpBox("没有匹配的配置类型", MessageType.Info); } EditorGUILayout.EndVertical(); EditorGUILayout.Space(5); } // 筛选和分页控制 if (rowDataList.Count > 0) { EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("📋 表格筛选 (仅过滤显示)", EditorStyles.boldLabel); EditorGUILayout.Space(3); // 筛选区域 EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("选择字段:", GUILayout.Width(70)); if (filterColumnOptions.Length > 0) { filterColumnIndex = EditorGUILayout.Popup(filterColumnIndex, filterColumnOptions, GUILayout.Width(150)); } EditorGUILayout.LabelField("条件:", GUILayout.Width(40)); string newFilterText = EditorGUILayout.TextField(filterText, GUILayout.MinWidth(200)); if (newFilterText != filterText) { filterText = newFilterText; } if (GUILayout.Button("筛选", GUILayout.Width(60))) { ApplyFilter(); } if (GUILayout.Button("重置", GUILayout.Width(60))) { filterText = ""; ApplyFilter(); } EditorGUILayout.EndHorizontal(); // 分页区域 EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("每页:", GUILayout.Width(50)); int selectedPageSizeIndex = Array.IndexOf(pageSizeOptions, pageSize); if (selectedPageSizeIndex < 0) selectedPageSizeIndex = 1; int newPageSizeIndex = EditorGUILayout.Popup(selectedPageSizeIndex, pageSizeOptions.Select(p => p.ToString()).ToArray(), GUILayout.Width(80)); if (newPageSizeIndex != selectedPageSizeIndex) { pageSize = pageSizeOptions[newPageSizeIndex]; currentPage = 0; } int totalPages = Mathf.CeilToInt((float)filteredRowDataList.Count / pageSize); EditorGUILayout.LabelField($"显示 {filteredRowDataList.Count} 条 (共 {rowDataList.Count} 条)", EditorStyles.miniLabel, GUILayout.Width(150)); GUILayout.FlexibleSpace(); GUI.enabled = currentPage > 0; if (GUILayout.Button("<<", GUILayout.Width(40))) { currentPage = 0; pageInputText = "1"; } if (GUILayout.Button("<", GUILayout.Width(40))) { currentPage--; pageInputText = (currentPage + 1).ToString(); } GUI.enabled = true; // 页码输入框 EditorGUILayout.LabelField("第", GUILayout.Width(20)); string newPageInput = EditorGUILayout.TextField(pageInputText, GUILayout.Width(50)); if (newPageInput != pageInputText) { pageInputText = newPageInput; // 尝试解析输入 if (int.TryParse(pageInputText, out int inputPage)) { inputPage = Mathf.Clamp(inputPage, 1, Mathf.Max(1, totalPages)); currentPage = inputPage - 1; } } if (GUILayout.Button("跳转", GUILayout.Width(50))) { if (int.TryParse(pageInputText, out int inputPage)) { inputPage = Mathf.Clamp(inputPage, 1, Mathf.Max(1, totalPages)); currentPage = inputPage - 1; pageInputText = (currentPage + 1).ToString(); } } EditorGUILayout.LabelField($"/ {Mathf.Max(1, totalPages)} 页", GUILayout.Width(60)); GUI.enabled = currentPage < totalPages - 1; if (GUILayout.Button(">", GUILayout.Width(40))) { currentPage++; pageInputText = (currentPage + 1).ToString(); } if (GUILayout.Button(">>", GUILayout.Width(40))) { currentPage = Mathf.Max(0, totalPages - 1); pageInputText = (currentPage + 1).ToString(); } GUI.enabled = true; EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); EditorGUILayout.Space(5); } // 错误信息 if (!string.IsNullOrEmpty(errorMessage)) { EditorGUILayout.HelpBox(errorMessage, MessageType.Error); } // 数据表格 if (dataLoaded && parsedData != null && filteredRowDataList.Count > 0) { DrawDataTable(); // 单元格详细信息显示 if (selectedRowIndex >= 0 && !string.IsNullOrEmpty(selectedColumnName)) { DrawCellDetailPanel(); } } else if (dataLoaded && rowDataList.Count > 0 && filteredRowDataList.Count == 0) { EditorGUILayout.HelpBox("没有匹配筛选条件的数据", MessageType.Info); } else if (dataLoaded && rowDataList.Count == 0 && availableConfigs.Length == 0) { EditorGUILayout.HelpBox("未找到可用的配置数据", MessageType.Warning); } else if (!dataLoaded && selectedBytesFile == null) { EditorGUILayout.HelpBox("请选择一个.bytes文件进行查看\n\n推荐路径: Assets/Resources/ConfigData/AllConfigs.bytes", MessageType.Info); } } private void LoadAndParseBytes() { if (selectedBytesFile == null) { errorMessage = "请先选择一个bytes文件!"; return; } try { currentFilePath = AssetDatabase.GetAssetPath(selectedBytesFile); byte[] bytes = selectedBytesFile.bytes; if (bytes == null || bytes.Length == 0) { errorMessage = "文件为空或无法读取!"; return; } // 解析Thrift数据 parsedData = DeserializeThriftData(bytes); if (parsedData != null) { PrepareTableData(); dataLoaded = true; errorMessage = ""; } else { errorMessage = "无法解析文件数据,请确认这是一个有效的Thrift配置文件。"; } } catch (Exception ex) { errorMessage = $"解析失败: {ex.Message}\n{ex.StackTrace}"; dataLoaded = false; } } private void AutoLoadAllConfigs() { // 如果已经加载了文件,不再自动加载 if (selectedBytesFile != null) return; // 在整个Assets目录下查找AllConfigs.bytes string[] guids = AssetDatabase.FindAssets("AllConfigs t:TextAsset"); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); // 检查是否为.bytes文件 if (path.EndsWith(".bytes", StringComparison.OrdinalIgnoreCase)) { TextAsset asset = AssetDatabase.LoadAssetAtPath(path); if (asset != null) { selectedBytesFile = asset; Debug.Log($"[Bytes查看器] 自动找到并加载: {path}"); LoadAndParseBytes(); return; } } } // 如果没找到AllConfigs.bytes,尝试查找任何Config相关的bytes文件 if (selectedBytesFile == null) { guids = AssetDatabase.FindAssets("Config t:TextAsset"); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); if (path.EndsWith(".bytes", StringComparison.OrdinalIgnoreCase) && path.Contains("Config")) { TextAsset asset = AssetDatabase.LoadAssetAtPath(path); if (asset != null) { selectedBytesFile = asset; Debug.Log($"[Bytes查看器] 自动找到并加载: {path}"); LoadAndParseBytes(); return; } } } } } private object DeserializeThriftData(byte[] bytes) { try { // 创建Thrift transport和protocol using (var transport = new TMemoryBufferTransport(bytes, new TConfiguration())) using (var protocol = new TCompactProtocol(transport)) { // 尝试读取为AllConfigs var allConfigs = new Byway.Thrift.Data.AllConfigs(); allConfigs.ReadAsync(protocol, System.Threading.CancellationToken.None).Wait(); return allConfigs; } } catch (Exception ex) { Debug.LogError($"Thrift反序列化失败: {ex.Message}"); return null; } } private void PrepareTableData() { columnNames.Clear(); rowDataList.Clear(); List configNames = new List(); if (parsedData == null) return; Type dataType = parsedData.GetType(); // 获取所有public属性 PropertyInfo[] properties = dataType.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { // 跳过__isset等内部属性 if (prop.Name.StartsWith("__")) continue; object propValue = prop.GetValue(parsedData); if (propValue == null) continue; // 检查配置对象内部是否有字典 Type propType = propValue.GetType(); PropertyInfo[] innerProps = propType.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var innerProp in innerProps) { if (innerProp.Name.StartsWith("__")) continue; object innerValue = innerProp.GetValue(propValue); if (innerValue != null && innerValue is IDictionary dict && dict.Count > 0) { // 找到了包含数据的字典,记录这个配置 configNames.Add(prop.Name); break; // 找到一个字典就够了 } } } availableConfigs = configNames.ToArray(); if (availableConfigs.Length > 0) { selectedConfigIndex = 0; FilterConfigList(); PrepareTableDataForSelectedConfig(); } } private void FilterConfigList() { if (string.IsNullOrEmpty(configSearchText)) { filteredConfigs = availableConfigs; } else { string searchLower = configSearchText.ToLower(); filteredConfigs = availableConfigs .Where(c => c.ToLower().Contains(searchLower)) .ToArray(); } } private void PerformGlobalSearch() { globalSearchResults.Clear(); if (string.IsNullOrEmpty(globalSearchText) || parsedData == null) return; string searchLower = globalSearchText.ToLower(); Type dataType = parsedData.GetType(); PropertyInfo[] properties = dataType.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { if (prop.Name.StartsWith("__")) continue; object propValue = prop.GetValue(parsedData); if (propValue == null) continue; // 检查配置对象内部的字典 Type propType = propValue.GetType(); PropertyInfo[] innerProps = propType.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var innerProp in innerProps) { if (innerProp.Name.StartsWith("__")) continue; object innerValue = innerProp.GetValue(propValue); if (innerValue != null && innerValue is IDictionary dict && dict.Count > 0) { // 在这个配置的数据中搜索 SearchInConfig(prop.Name, dict, searchLower); break; } } } } private void SearchInConfig(string configName, IDictionary dataDict, string searchLower) { int rowIndex = 0; foreach (DictionaryEntry entry in dataDict) { object rowData = entry.Value; if (rowData == null) { rowIndex++; continue; } Type rowType = rowData.GetType(); PropertyInfo[] props = rowType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => !p.Name.StartsWith("__")) .ToArray(); foreach (var prop in props) { object value = prop.GetValue(rowData); string valueStr = FormatValue(value).ToLower(); if (valueStr.Contains(searchLower)) { globalSearchResults.Add(new GlobalSearchResult { ConfigName = configName, RowIndex = rowIndex, ColumnName = prop.Name, RowData = rowData, MatchedValue = FormatValue(value) }); // 每个配置最多显示100个结果 if (globalSearchResults.Count >= 100) return; } } rowIndex++; } } private void JumpToSearchResult(GlobalSearchResult result) { // 切换到对应的配置 int configIndex = Array.IndexOf(availableConfigs, result.ConfigName); if (configIndex >= 0 && configIndex != selectedConfigIndex) { selectedConfigIndex = configIndex; PrepareTableDataForSelectedConfig(); } // 找到对应的行在filteredRowDataList中的位置 int rowIndexInFiltered = filteredRowDataList.IndexOf(result.RowData); if (rowIndexInFiltered >= 0) { // 计算应该跳转到哪一页 currentPage = rowIndexInFiltered / pageSize; pageInputText = (currentPage + 1).ToString(); // 高亮选中这一行的单元格 selectedRowIndex = rowIndexInFiltered; selectedColumnName = result.ColumnName; selectedCellFullData = GetFullValueText(result.RowData.GetType().GetProperty(result.ColumnName).GetValue(result.RowData)); } Repaint(); } private void PrepareTableDataForSelectedConfig() { columnNames.Clear(); rowDataList.Clear(); if (parsedData == null || availableConfigs.Length == 0) return; string configName = availableConfigs[selectedConfigIndex]; currentConfigName = configName; Type dataType = parsedData.GetType(); PropertyInfo configProp = dataType.GetProperty(configName); if (configProp == null) return; object configValue = configProp.GetValue(parsedData); if (configValue == null) return; // 在配置对象中查找字典属性 Type configType = configValue.GetType(); PropertyInfo[] innerProps = configType.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var innerProp in innerProps) { if (innerProp.Name.StartsWith("__")) continue; object innerValue = innerProp.GetValue(configValue); if (innerValue != null && innerValue is IDictionary dict && dict.Count > 0) { PrepareTableForConfig(configName, dict); return; } } } private void PrepareTableForConfig(string configName, IDictionary dataDict) { if (dataDict == null || dataDict.Count == 0) return; // 获取第一个元素来确定列结构 object firstValue = null; foreach (DictionaryEntry entry in dataDict) { firstValue = entry.Value; break; } if (firstValue == null) return; Type itemType = firstValue.GetType(); PropertyInfo[] itemProps = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => !p.Name.StartsWith("__")) .OrderBy(p => p.Name == "Id" || p.Name == "ID" ? 0 : 1) // ID放在最前面 .ToArray(); // 构建列名 foreach (var prop in itemProps) { columnNames.Add(prop.Name); } // 构建行数据 foreach (DictionaryEntry entry in dataDict) { rowDataList.Add(entry.Value); } // 初始化筛选列选项 filterColumnOptions = new string[columnNames.Count]; for (int i = 0; i < columnNames.Count; i++) { filterColumnOptions[i] = columnNames[i]; } // 重置筛选和分页 filterText = ""; lastFilterText = ""; currentPage = 0; filterColumnIndex = 0; ApplyFilter(); } private void ApplyFilter() { filteredRowDataList.Clear(); if (string.IsNullOrEmpty(filterText) || filterColumnIndex < 0 || filterColumnIndex >= columnNames.Count) { filteredRowDataList.AddRange(rowDataList); lastFilterText = filterText; currentPage = 0; return; } string filterColumn = columnNames[filterColumnIndex]; string filterLower = filterText.ToLower(); foreach (var rowData in rowDataList) { Type rowType = rowData.GetType(); PropertyInfo prop = rowType.GetProperty(filterColumn); if (prop != null) { object value = prop.GetValue(rowData); string valueStr = FormatValue(value).ToLower(); if (valueStr.Contains(filterLower)) { filteredRowDataList.Add(rowData); } } } lastFilterText = filterText; currentPage = 0; } private void DrawDataTable() { EditorGUILayout.BeginVertical("box"); tableScrollPosition = EditorGUILayout.BeginScrollView(tableScrollPosition); // 绘制表头 EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); foreach (string columnName in columnNames) { EditorGUILayout.LabelField(columnName, EditorStyles.toolbarButton, GUILayout.MinWidth(80), GUILayout.MaxWidth(250)); } EditorGUILayout.EndHorizontal(); // 计算当前页的数据范围 int startIndex = currentPage * pageSize; int endIndex = Mathf.Min(startIndex + pageSize, filteredRowDataList.Count); // 绘制当前页的数据行 for (int i = startIndex; i < endIndex; i++) { Color backgroundColor = (i - startIndex) % 2 == 0 ? new Color(0.85f, 0.85f, 0.85f, 0.3f) : Color.clear; EditorGUILayout.BeginHorizontal(); Rect rowRect = EditorGUILayout.BeginVertical(); if (backgroundColor != Color.clear) { EditorGUI.DrawRect(rowRect, backgroundColor); } DrawRowData(filteredRowDataList[i], i); EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); EditorGUILayout.EndVertical(); } private void DrawRowData(object rowData, int rowIndex) { if (rowData == null) return; EditorGUILayout.BeginHorizontal(); Type rowType = rowData.GetType(); PropertyInfo[] props = rowType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => !p.Name.StartsWith("__")) .OrderBy(p => p.Name == "Id" || p.Name == "ID" ? 0 : 1) .ToArray(); foreach (var prop in props) { object value = prop.GetValue(rowData); string displayValue = FormatValue(value); GUIStyle style = new GUIStyle(EditorStyles.label); style.wordWrap = false; style.clipping = TextClipping.Clip; // 高亮选中的单元格 if (selectedRowIndex == rowIndex && selectedColumnName == prop.Name) { style.normal.textColor = new Color(0.2f, 0.5f, 1f); style.fontStyle = FontStyle.Bold; } Rect cellRect = GUILayoutUtility.GetRect(new GUIContent(displayValue), style, GUILayout.MinWidth(80), GUILayout.MaxWidth(250)); // 检测点击 if (Event.current.type == EventType.MouseDown && cellRect.Contains(Event.current.mousePosition)) { selectedRowIndex = rowIndex; selectedColumnName = prop.Name; selectedCellFullData = GetFullValueText(value); Event.current.Use(); Repaint(); } GUI.Label(cellRect, displayValue, style); } EditorGUILayout.EndHorizontal(); } private void DrawCellDetailPanel() { EditorGUILayout.Space(5); EditorGUILayout.BeginVertical("box"); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("单元格详情", EditorStyles.boldLabel); if (GUILayout.Button("关闭", GUILayout.Width(60))) { selectedRowIndex = -1; selectedColumnName = ""; selectedCellFullData = ""; } if (GUILayout.Button("复制", GUILayout.Width(60))) { EditorGUIUtility.systemCopyBuffer = selectedCellFullData; Debug.Log($"已复制到剪贴板: {selectedCellFullData}"); } EditorGUILayout.EndHorizontal(); EditorGUILayout.LabelField($"列: {selectedColumnName}", EditorStyles.miniLabel); EditorGUILayout.LabelField($"行: {selectedRowIndex}", EditorStyles.miniLabel); EditorGUILayout.Space(3); // 显示完整内容 detailScrollPosition = EditorGUILayout.BeginScrollView(detailScrollPosition, GUILayout.Height(100)); EditorGUILayout.SelectableLabel(selectedCellFullData, EditorStyles.textArea, GUILayout.ExpandHeight(true)); EditorGUILayout.EndScrollView(); EditorGUILayout.EndVertical(); } private string GetFullValueText(object value) { if (value == null) return "null"; // 处理列表 if (value is IList list) { var items = new System.Text.StringBuilder(); items.AppendLine($"List ({list.Count} items):"); int index = 0; foreach (var item in list) { items.AppendLine($" [{index}]: {FormatValue(item)}"); index++; if (index > 100) // 限制显示数量 { items.AppendLine($" ... and {list.Count - 100} more items"); break; } } return items.ToString(); } // 处理字典 if (value is IDictionary dict) { var items = new System.Text.StringBuilder(); items.AppendLine($"Dictionary ({dict.Count} pairs):"); int index = 0; foreach (DictionaryEntry entry in dict) { items.AppendLine($" [{FormatValue(entry.Key)}]: {FormatValue(entry.Value)}"); index++; if (index > 100) { items.AppendLine($" ... and {dict.Count - 100} more pairs"); break; } } return items.ToString(); } // 处理基本类型 Type valueType = value.GetType(); if (valueType.IsPrimitive || valueType == typeof(string) || valueType == typeof(decimal)) { return value.ToString(); } // 复杂对象 - 显示所有属性 var props = valueType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => !p.Name.StartsWith("__")); var sb = new System.Text.StringBuilder(); sb.AppendLine($"{valueType.Name}:"); foreach (var prop in props) { try { object propValue = prop.GetValue(value); sb.AppendLine($" {prop.Name}: {FormatValue(propValue)}"); } catch { sb.AppendLine($" {prop.Name}: [Error reading value]"); } } return sb.ToString(); } private string FormatValue(object value) { if (value == null) return "null"; // 处理列表/数组 if (value is IList list) { return $"[{list.Count} items]"; } // 处理字典 if (value is IDictionary dict) { return $"[{dict.Count} pairs]"; } // 处理基本类型 Type valueType = value.GetType(); if (valueType.IsPrimitive || valueType == typeof(string) || valueType == typeof(decimal)) { return value.ToString(); } // 复杂对象显示类型名 return $"[{valueType.Name}]"; } } }