MeowmentDesign/Assets/Editor/CustomTools/BytesViewer/BytesViewerWindow.cs
2026-02-01 15:37:46 +08:00

1059 lines
40 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// Bytes文件查看器 - 将Thrift bytes数据以表格形式展示
/// </summary>
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<string> columnNames = new List<string>();
private List<object> rowDataList = new List<object>();
private List<object> filteredRowDataList = new List<object>();
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<GlobalSearchResult> globalSearchResults = new List<GlobalSearchResult>();
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("蹊径/Bytes查看器")]
public static void ShowWindow()
{
BytesViewerWindow window = GetWindow<BytesViewerWindow>("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<TextAsset>(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<TextAsset>(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 TBinaryProtocol(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<string> configNames = new List<string>();
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}]";
}
}
}