From 0ea214a23f9d6aaad32adac7ad64f1a8c2088870 Mon Sep 17 00:00:00 2001
From: lrc <571244399@qq.com>
Date: Sat, 27 Jun 2026 10:27:56 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A1=A8=E6=A0=BC=E5=8D=95?=
=?UTF-8?q?=E5=85=83=E8=BD=AC=E6=8D=A2=E5=B7=A5=E5=85=B7=E3=80=82=20?=
=?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A1=A8=E6=A0=BC=E6=95=B0=E6=8D=AE=E7=BB=93?=
=?UTF-8?q?=E6=9E=84=E4=BF=9D=E5=AD=98=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../XTable/XericUIActionTableSceneEditor.cs | 24 +---
Runtime/XTable/Core/XTableBlock.cs | 22 ++-
Runtime/XTable/Core/XTableData.cs | 59 +++++++--
.../XTable/Mapping/XTableCoordinateFormat.cs | 125 ++++++++++++++++++
.../Mapping/XTableCoordinateFormat.cs.meta | 11 ++
.../Rendering/Component/XericUIActionTable.cs | 35 ++---
6 files changed, 226 insertions(+), 50 deletions(-)
create mode 100644 Runtime/XTable/Mapping/XTableCoordinateFormat.cs
create mode 100644 Runtime/XTable/Mapping/XTableCoordinateFormat.cs.meta
diff --git a/Editor/XTable/XericUIActionTableSceneEditor.cs b/Editor/XTable/XericUIActionTableSceneEditor.cs
index 78b6151..2283bdc 100644
--- a/Editor/XTable/XericUIActionTableSceneEditor.cs
+++ b/Editor/XTable/XericUIActionTableSceneEditor.cs
@@ -153,8 +153,7 @@ namespace XericUIEditor.XTable
/// 更新 s_EditText 为当前焦点格的单元格内容
private static void UpdateEditText(XericUIActionTable table, int row, int col)
{
- var data = table.GetCellData(row, col);
- s_EditText = data != null ? data.Text ?? "" : "";
+ s_EditText = table.GetCellData(row, col).Text ?? "";
}
#endregion
@@ -399,30 +398,17 @@ namespace XericUIEditor.XTable
alignment = TextAnchor.MiddleCenter
};
- // 位置坐标显示当前选中(焦点格)
- string cellLabel = XTableCoordinateFormat.CellToText(
- table.SelectedRow, table.SelectedCol);
- GUI.Label(new Rect(barRect.x + 4, barRect.y, 55, barRect.height),
+ // 单元格坐标(单选 "A0",多选 "A0:B1")
+ string cellLabel = table.GetSelectionText();
+ GUI.Label(new Rect(barRect.x + 4, barRect.y, 75, barRect.height),
cellLabel, labelStyle);
- var rangeStyle = new GUIStyle(GUI.skin.label)
- {
- fontSize = 10,
- normal = { textColor = new Color(0.5f, 0.6f, 0.8f) },
- alignment = TextAnchor.MiddleLeft
- };
-
- // 多选范围文本
- string rangeText = table.GetSelectionText();
- GUI.Label(new Rect(barRect.x + 62, barRect.y, 100, barRect.height),
- rangeText, rangeStyle);
-
var inputStyle = new GUIStyle(GUI.skin.textField)
{
fontSize = 12,
margin = new RectOffset(2, 2, 2, 2)
};
- Rect inputRect = new Rect(barRect.x + 165, barRect.y + 2, 200, barRect.height - 4);
+ Rect inputRect = new Rect(barRect.x + 85, barRect.y + 2, 200, barRect.height - 4);
// 回车键提交
if (Event.current.isKey && Event.current.keyCode == KeyCode.Return
diff --git a/Runtime/XTable/Core/XTableBlock.cs b/Runtime/XTable/Core/XTableBlock.cs
index 8165fdd..13a3ca1 100644
--- a/Runtime/XTable/Core/XTableBlock.cs
+++ b/Runtime/XTable/Core/XTableBlock.cs
@@ -1,12 +1,16 @@
+using System;
using System.Collections.Generic;
+using UnityEngine;
+
namespace XericUI.XTable.Core
{
///
/// 单个数据块——保存 BlockSizeX × BlockSizeY 个单元格数据。
/// 数据使用线性数组存储,索引 = localRow * BlockSizeX + localCol。
///
- public class XTableBlock
+ [Serializable]
+ public class XTableBlock : ISerializationCallbackReceiver
{
/// 块在全局块坐标系中的行号
public int BlockRow;
@@ -86,5 +90,21 @@ namespace XericUI.XTable.Core
}
return null;
}
+
+ #region 序列化回调
+
+ public void OnBeforeSerialize() { }
+
+ public void OnAfterDeserialize()
+ {
+ // 反序列化时确保 BlockSize 有效(构造函数被跳过)
+ if (BlockSizeX <= 0) BlockSizeX = XTableData.DefaultBlockSize;
+ if (BlockSizeY <= 0) BlockSizeY = XTableData.DefaultBlockSize;
+ // 确保数组和列表非空
+ if (Cells == null) Cells = new XTableCellData[BlockSizeX * BlockSizeY];
+ if (MergeDescriptors == null) MergeDescriptors = new List();
+ }
+
+ #endregion
}
}
diff --git a/Runtime/XTable/Core/XTableData.cs b/Runtime/XTable/Core/XTableData.cs
index 6658399..b632356 100644
--- a/Runtime/XTable/Core/XTableData.cs
+++ b/Runtime/XTable/Core/XTableData.cs
@@ -1,5 +1,8 @@
+using System;
using System.Collections.Generic;
+using UnityEngine;
+
using XericUI.XTable.Mapping;
namespace XericUI.XTable.Core
@@ -8,14 +11,18 @@ namespace XericUI.XTable.Core
/// 表格数据主类——持有块字典,提供单元格级别的读写接口。
/// 块按需创建(SetCell 时自动创建不存在的块),通过 Z 曲线索引映射。
///
- public class XTableData
+ [Serializable]
+ public class XTableData : ISerializationCallbackReceiver
{
+ /// 全系统唯一默认块尺寸——禁止硬编码字面量
+ public const int DefaultBlockSize = 32;
+
#region 属性
- /// 块内列数(默认 32)
+ /// 块内列数(默认 )
public int BlockSizeX { get; private set; }
- /// 块内行数(默认 32)
+ /// 块内行数(默认 )
public int BlockSizeY { get; private set; }
///
@@ -24,6 +31,29 @@ namespace XericUI.XTable.Core
///
public Dictionary BlockMap { get; private set; }
+ ///
+ /// 获取或创建单元格——如果单元格不存在则自动创建空数据。
+ /// 在编辑器和运行时都是安全的,保证不会返回 null。
+ ///
+ public XTableCellData GetOrCreateCell(int row, int col)
+ {
+ var data = GetCell(row, col);
+ if (data == null)
+ {
+ data = new XTableCellData();
+ SetCell(row, col, data);
+ }
+ return data;
+ }
+
+ ///
+ /// 判断指定单元格是否有数据
+ ///
+ public bool HasCell(int row, int col)
+ {
+ return GetCell(row, col) != null;
+ }
+
#endregion
#region 构造函数
@@ -31,17 +61,30 @@ namespace XericUI.XTable.Core
///
/// 创建表格数据实例
///
- /// 块内列数,默认 32
- /// 块内行数,默认 32
- public XTableData(int blockSizeX = 32, int blockSizeY = 32)
+ /// 块内列数,默认
+ /// 块内行数,默认
+ public XTableData(int blockSizeX = DefaultBlockSize, int blockSizeY = DefaultBlockSize)
{
- BlockSizeX = blockSizeX > 0 ? blockSizeX : 32;
- BlockSizeY = blockSizeY > 0 ? blockSizeY : 32;
+ BlockSizeX = blockSizeX > 0 ? blockSizeX : DefaultBlockSize;
+ BlockSizeY = blockSizeY > 0 ? blockSizeY : DefaultBlockSize;
BlockMap = new Dictionary();
}
#endregion
+ #region 序列化回调
+
+ public void OnBeforeSerialize() { }
+
+ public void OnAfterDeserialize()
+ {
+ if (BlockSizeX <= 0) BlockSizeX = DefaultBlockSize;
+ if (BlockSizeY <= 0) BlockSizeY = DefaultBlockSize;
+ if (BlockMap == null) BlockMap = new Dictionary();
+ }
+
+ #endregion
+
#region 核心访问方法
///
diff --git a/Runtime/XTable/Mapping/XTableCoordinateFormat.cs b/Runtime/XTable/Mapping/XTableCoordinateFormat.cs
new file mode 100644
index 0000000..7829742
--- /dev/null
+++ b/Runtime/XTable/Mapping/XTableCoordinateFormat.cs
@@ -0,0 +1,125 @@
+using System.Text;
+
+namespace XericUI.XTable.Mapping
+{
+ ///
+ /// 表格坐标文本格式化工具——
+ /// 列索引 ↔ 字母(A..Z, AA..ZZ)、单元格文本 ↔ (row,col)、范围文本 ↔ 矩形选区。
+ ///
+ public static class XTableCoordinateFormat
+ {
+ private const string ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ #region 列字母转换
+
+ /// 列索引转 Excel 风格字母(0→A, 25→Z, 26→AA)
+ public static string ColumnIndexToLetter(int col)
+ {
+ if (col < 0) return string.Empty;
+
+ var sb = new StringBuilder(4);
+ do
+ {
+ sb.Insert(0, ALPHABET[col % 26]);
+ col = col / 26 - 1;
+ } while (col >= 0);
+ return sb.ToString();
+ }
+
+ /// Excel 字母转列索引(A→0, Z→25, AA→26)
+ public static int LetterToColumnIndex(string letter)
+ {
+ if (string.IsNullOrEmpty(letter)) return -1;
+
+ int result = 0;
+ foreach (char c in letter.ToUpperInvariant())
+ {
+ if (c < 'A' || c > 'Z') return -1;
+ result = result * 26 + (c - 'A' + 1);
+ }
+ return result - 1;
+ }
+
+ #endregion
+
+ #region 单元格文本 ↔ 索引
+
+ /// 单元格索引 → "A0" 格式文本
+ public static string CellToText(int row, int col)
+ {
+ return $"{ColumnIndexToLetter(col)}{row}";
+ }
+
+ /// "A0" 格式文本 → (row, col),解析失败返回 false
+ public static bool TryParseCell(string text, out int row, out int col)
+ {
+ row = -1;
+ col = -1;
+ if (string.IsNullOrEmpty(text)) return false;
+
+ // 分离字母前缀和数字后缀
+ int i = 0;
+ while (i < text.Length && char.IsLetter(text[i])) i++;
+ if (i == 0 || i == text.Length) return false;
+
+ string colPart = text.Substring(0, i);
+ string rowPart = text.Substring(i);
+
+ col = LetterToColumnIndex(colPart);
+ if (col < 0) return false;
+ if (!int.TryParse(rowPart, out row) || row < 0) return false;
+ return true;
+ }
+
+ #endregion
+
+ #region 范围文本 ↔ 矩形选区
+
+ /// 矩形选区 → "A0:B1" 格式(统一使用左上:右下)
+ public static string RangeToText(int startRow, int startCol, int endRow, int endCol)
+ {
+ NormalizeRange(ref startRow, ref startCol, ref endRow, ref endCol);
+ return $"{ColumnIndexToLetter(startCol)}{startRow}:{ColumnIndexToLetter(endCol)}{endRow}";
+ }
+
+ /// "A0:B1" 格式文本 → 矩形选区参数,解析失败返回 false
+ public static bool TryParseRange(string text,
+ out int startRow, out int startCol,
+ out int endRow, out int endCol)
+ {
+ startRow = -1; startCol = -1; endRow = -1; endCol = -1;
+ if (string.IsNullOrEmpty(text)) return false;
+
+ int colon = text.IndexOf(':');
+ if (colon < 0)
+ {
+ // 无冒号,尝试作为单个单元格解析
+ if (TryParseCell(text, out startRow, out startCol))
+ {
+ endRow = startRow;
+ endCol = startCol;
+ return true;
+ }
+ return false;
+ }
+
+ string left = text.Substring(0, colon);
+ string right = text.Substring(colon + 1);
+
+ if (!TryParseCell(left, out startRow, out startCol)) return false;
+ if (!TryParseCell(right, out endRow, out endCol)) return false;
+
+ return true;
+ }
+
+ /// 将命名范围标准化(确保 start ≤ end),返回 (minR, minC, maxR, maxC)
+ public static void NormalizeRange(ref int startRow, ref int startCol,
+ ref int endRow, ref int endCol)
+ {
+ if (startRow > endRow) (startRow, endRow) = (endRow, startRow);
+ if (startCol > endCol) (startCol, endCol) = (endCol, startCol);
+ }
+
+ #endregion
+ }
+}
diff --git a/Runtime/XTable/Mapping/XTableCoordinateFormat.cs.meta b/Runtime/XTable/Mapping/XTableCoordinateFormat.cs.meta
new file mode 100644
index 0000000..0302610
--- /dev/null
+++ b/Runtime/XTable/Mapping/XTableCoordinateFormat.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7ec7f819ca6470d42b7ebf1d054f499d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
index fa09922..b938538 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
@@ -178,12 +178,10 @@ namespace XericUI.XTable.Rendering.Component
#region 默认示例数据
- [System.NonSerialized] private bool m_DefaultDataCreated;
-
private void CreateDefaultSampleData()
{
- if (m_DefaultDataCreated) return;
- m_DefaultDataCreated = true;
+ // 仅当数据完全为空时才创建示例数据(首次使用或数据损坏后恢复)
+ if (m_TableData.BlockCount > 0) return;
string[,] sample = new string[2, 3]
{
@@ -193,19 +191,10 @@ namespace XericUI.XTable.Rendering.Component
for (int r = 0; r < 2; r++)
for (int c = 0; c < 3; c++)
- SetCellTextInternal(r, c, sample[r, c], skipDirty: true);
- }
-
- private void SetCellTextInternal(int row, int col, string text, bool skipDirty)
- {
- var data = TableData.GetCell(row, col);
- if (data == null)
- {
- data = new XTableCellData();
- TableData.SetCell(row, col, data);
- }
- data.Text = text;
- if (!skipDirty) MarkDirty(TableDirtyType.DataChanged);
+ {
+ var data = m_TableData.GetOrCreateCell(r, c);
+ data.Text = sample[r, c];
+ }
}
#endregion
@@ -257,11 +246,13 @@ namespace XericUI.XTable.Rendering.Component
public void SetCellText(int row, int col, string text)
{
- SetCellTextInternal(row, col, text, skipDirty: false);
+ var data = TableData.GetOrCreateCell(row, col);
+ data.Text = text;
+ MarkDirty(TableDirtyType.DataChanged);
}
public XTableCellData GetCellData(int row, int col)
- => TableData.GetCell(row, col);
+ => TableData.GetOrCreateCell(row, col);
public void MergeCells(int startRow, int startCol, int rowSpan, int colSpan)
{
@@ -349,11 +340,12 @@ namespace XericUI.XTable.Rendering.Component
return true;
}
- /// 获取当前选区的文本描述(无多选返回 "A0",有多选返回 "A0:B1")
+ /// 获取当前选区的文本描述(单格 "A0",多格 "A0:B1")
public string GetSelectionText()
{
if (m_SelectedRow < 0 || m_SelectedCol < 0) return string.Empty;
- if (HasMultiSelection)
+ if (HasMultiSelection &&
+ (m_SelectStartRow != m_SelectEndRow || m_SelectStartCol != m_SelectEndCol))
{
return XTableCoordinateFormat.RangeToText(
m_SelectStartRow, m_SelectStartCol,
@@ -429,7 +421,6 @@ namespace XericUI.XTable.Rendering.Component
public void FullRebuild()
{
m_Assembler?.DestroyAll();
- m_DefaultDataCreated = false;
if (m_BackgroundGo != null)
{