TableViewer.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. #if UNITY_2019_4_OR_NEWER
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using UnityEditor;
  7. using UnityEngine;
  8. using UnityEditor.UIElements;
  9. using UnityEngine.UIElements;
  10. namespace YooAsset.Editor
  11. {
  12. /// <summary>
  13. /// Unity2022版本以上推荐官方类:MultiColumnListView组件
  14. /// </summary>
  15. //[UxmlElement]
  16. public partial class TableViewer : VisualElement
  17. {
  18. private readonly Toolbar _toolbar;
  19. private readonly ListView _listView;
  20. private readonly List<TableColumn> _columns = new List<TableColumn>(10);
  21. private List<ITableData> _itemsSource;
  22. private List<ITableData> _sortingDatas;
  23. // 排序相关
  24. private string _sortingHeader;
  25. private bool _descendingSort = true;
  26. /// <summary>
  27. /// 数据源
  28. /// </summary>
  29. public List<ITableData> itemsSource
  30. {
  31. get
  32. {
  33. return _itemsSource;
  34. }
  35. set
  36. {
  37. if (CheckItemsSource(value))
  38. {
  39. _itemsSource = value;
  40. _sortingDatas = value;
  41. }
  42. }
  43. }
  44. /// <summary>
  45. /// 选中的数据列表
  46. /// </summary>
  47. public List<ITableData> selectedItems
  48. {
  49. get
  50. {
  51. #if UNITY_2020_3_OR_NEWER
  52. return _listView.selectedItems.Cast<ITableData>().ToList();
  53. #else
  54. List<ITableData> result = new List<ITableData>();
  55. result.Add(_listView.selectedItem as ITableData);
  56. return result;
  57. #endif
  58. }
  59. }
  60. /// <summary>
  61. /// 单元视图交互事件
  62. /// </summary>
  63. public Action<PointerDownEvent, ITableData> ClickTableDataEvent;
  64. /// <summary>
  65. /// 单元视图交互事件
  66. /// </summary>
  67. public Action<TableColumn> ClickTableHeadEvent;
  68. /// <summary>
  69. /// 单元视图变化事件
  70. /// </summary>
  71. public Action<ITableData> SelectionChangedEvent;
  72. public TableViewer()
  73. {
  74. this.style.flexShrink = 1f;
  75. this.style.flexGrow = 1f;
  76. // 定义标题栏
  77. _toolbar = new Toolbar();
  78. // 定义列表视图
  79. _listView = new ListView();
  80. _listView.style.flexShrink = 1f;
  81. _listView.style.flexGrow = 1f;
  82. _listView.makeItem = MakeListViewElement;
  83. _listView.bindItem = BindListViewElement;
  84. _listView.selectionType = SelectionType.Multiple;
  85. _listView.RegisterCallback<PointerDownEvent>(OnClickListItem);
  86. #if UNITY_2022_3_OR_NEWER
  87. _listView.selectionChanged += OnSelectionChanged;
  88. #elif UNITY_2020_1_OR_NEWER
  89. _listView.onSelectionChange += OnSelectionChanged;
  90. #else
  91. _listView.onSelectionChanged += OnSelectionChanged;
  92. #endif
  93. this.Add(_toolbar);
  94. this.Add(_listView);
  95. }
  96. /// <summary>
  97. /// 获取标题UI元素
  98. /// </summary>
  99. public VisualElement GetHeaderElement(string elementName)
  100. {
  101. return _toolbar.Q<ToolbarButton>(elementName);
  102. }
  103. /// <summary>
  104. /// 添加单元列
  105. /// </summary>
  106. public void AddColumn(TableColumn column)
  107. {
  108. var toolbarBtn = new ToolbarButton();
  109. toolbarBtn.userData = column;
  110. toolbarBtn.name = column.ElementName;
  111. toolbarBtn.text = column.HeaderTitle;
  112. toolbarBtn.style.flexGrow = 0;
  113. toolbarBtn.style.width = column.ColumnStyle.Width;
  114. toolbarBtn.style.minWidth = column.ColumnStyle.Width;
  115. toolbarBtn.style.maxWidth = column.ColumnStyle.Width;
  116. toolbarBtn.clickable.clickedWithEventInfo += OnClickTableHead;
  117. SetCellElementStyle(toolbarBtn);
  118. _toolbar.Add(toolbarBtn);
  119. _columns.Add(column);
  120. // 可伸缩控制柄
  121. if (column.ColumnStyle.Stretchable)
  122. {
  123. int handleWidth = 3;
  124. int minWidth = (int)column.ColumnStyle.MinWidth.value;
  125. int maxWidth = (int)column.ColumnStyle.MaxWidth.value;
  126. var resizeHandle = new ResizeHandle(handleWidth, toolbarBtn, minWidth, maxWidth);
  127. resizeHandle.ResizeChanged += (float value) =>
  128. {
  129. float width = Mathf.Clamp(value, column.ColumnStyle.MinWidth.value, column.ColumnStyle.MaxWidth.value);
  130. column.ColumnStyle.Width = width;
  131. foreach (var element in column.CellElements)
  132. {
  133. element.style.width = width;
  134. element.style.minWidth = width;
  135. element.style.maxWidth = width;
  136. }
  137. };
  138. _toolbar.Add(resizeHandle);
  139. }
  140. // 计算索引值
  141. column.ColumnIndex = _columns.Count - 1;
  142. if (column.ColumnStyle.Sortable == false)
  143. toolbarBtn.SetEnabled(false);
  144. }
  145. /// <summary>
  146. /// 添加单元列集合
  147. /// </summary>
  148. public void AddColumns(IList<TableColumn> columns)
  149. {
  150. foreach (var column in columns)
  151. {
  152. AddColumn(column);
  153. }
  154. }
  155. /// <summary>
  156. /// 重建表格视图
  157. /// </summary>
  158. public void RebuildView()
  159. {
  160. if (_itemsSource == null)
  161. return;
  162. var itemsSource = _sortingDatas.Where(row => row.Visible);
  163. _listView.Clear();
  164. _listView.ClearSelection();
  165. _listView.itemsSource = itemsSource.ToList();
  166. _listView.Rebuild();
  167. // 刷新标题栏
  168. RefreshToobar();
  169. }
  170. private void RefreshToobar()
  171. {
  172. // 设置为原始标题
  173. foreach (var column in _columns)
  174. {
  175. var toobarButton = _toolbar.Q<ToolbarButton>(column.ElementName);
  176. toobarButton.text = column.HeaderTitle;
  177. }
  178. // 设置元素数量
  179. foreach (var column in _columns)
  180. {
  181. if (column.ColumnStyle.Counter)
  182. {
  183. var toobarButton = GetHeaderElement(column.ElementName) as ToolbarButton;
  184. toobarButton.text = $"{toobarButton.text} ({itemsSource.Count()})";
  185. }
  186. }
  187. // 设置展示单位
  188. foreach (var column in _columns)
  189. {
  190. if (string.IsNullOrEmpty(column.ColumnStyle.Units) == false)
  191. {
  192. var toobarButton = GetHeaderElement(column.ElementName) as ToolbarButton;
  193. toobarButton.text = $"{toobarButton.text} ({column.ColumnStyle.Units})";
  194. }
  195. }
  196. // 设置升降符号
  197. if (string.IsNullOrEmpty(_sortingHeader) == false)
  198. {
  199. var _toobarButton = _toolbar.Q<ToolbarButton>(_sortingHeader);
  200. if (_descendingSort)
  201. _toobarButton.text = $"{_toobarButton.text} ↓";
  202. else
  203. _toobarButton.text = $"{_toobarButton.text} ↑";
  204. }
  205. }
  206. /// <summary>
  207. /// 清空所有数据
  208. /// </summary>
  209. public void ClearAll(bool clearColumns, bool clearSource)
  210. {
  211. if (clearColumns)
  212. {
  213. _columns.Clear();
  214. _toolbar.Clear();
  215. }
  216. if (clearSource)
  217. {
  218. if (_itemsSource != null)
  219. _itemsSource.Clear();
  220. if (_sortingDatas != null)
  221. _sortingDatas.Clear();
  222. _listView.Clear();
  223. _listView.ClearSelection();
  224. }
  225. }
  226. private void OnClickListItem(PointerDownEvent evt)
  227. {
  228. var selectData = _listView.selectedItem as ITableData;
  229. if (selectData == null)
  230. return;
  231. ClickTableDataEvent?.Invoke(evt, selectData);
  232. }
  233. private void OnClickTableHead(EventBase eventBase)
  234. {
  235. if (_itemsSource == null)
  236. return;
  237. ToolbarButton toolbarBtn = eventBase.target as ToolbarButton;
  238. var clickedColumn = toolbarBtn.userData as TableColumn;
  239. if (clickedColumn == null)
  240. return;
  241. ClickTableHeadEvent?.Invoke(clickedColumn);
  242. if (clickedColumn.ColumnStyle.Sortable == false)
  243. return;
  244. if (_sortingHeader != clickedColumn.ElementName)
  245. {
  246. _sortingHeader = clickedColumn.ElementName;
  247. _descendingSort = false;
  248. }
  249. else
  250. {
  251. _descendingSort = !_descendingSort;
  252. }
  253. // 升降排序
  254. if (_descendingSort)
  255. _sortingDatas = _itemsSource.OrderByDescending(tableData => tableData.Cells[clickedColumn.ColumnIndex]).ToList();
  256. else
  257. _sortingDatas = _itemsSource.OrderBy(tableData => tableData.Cells[clickedColumn.ColumnIndex]).ToList();
  258. // 刷新数据表
  259. RebuildView();
  260. }
  261. private void OnSelectionChanged(IEnumerable<object> items)
  262. {
  263. foreach (var item in items)
  264. {
  265. var tableData = item as ITableData;
  266. SelectionChangedEvent?.Invoke(tableData);
  267. break;
  268. }
  269. }
  270. private bool CheckItemsSource(List<ITableData> itemsSource)
  271. {
  272. if (itemsSource == null)
  273. return false;
  274. if (itemsSource.Count > 0)
  275. {
  276. int cellCount = itemsSource[0].Cells.Count;
  277. for (int i = 0; i < itemsSource.Count; i++)
  278. {
  279. var tableData = itemsSource[i];
  280. if (tableData == null)
  281. {
  282. Debug.LogWarning($"Items source has null instance !");
  283. return false;
  284. }
  285. if (tableData.Cells == null || tableData.Cells.Count == 0)
  286. {
  287. Debug.LogWarning($"Items source data has empty cells !");
  288. return false;
  289. }
  290. if (tableData.Cells.Count != cellCount)
  291. {
  292. Debug.LogWarning($"Items source data has inconsisten cells count ! Item index {i}");
  293. return false;
  294. }
  295. }
  296. }
  297. return true;
  298. }
  299. private VisualElement MakeListViewElement()
  300. {
  301. VisualElement listViewElement = new VisualElement();
  302. listViewElement.style.flexDirection = FlexDirection.Row;
  303. foreach (var column in _columns)
  304. {
  305. var cellElement = column.MakeCell.Invoke();
  306. cellElement.name = column.ElementName;
  307. cellElement.style.flexGrow = 0f;
  308. cellElement.style.width = column.ColumnStyle.Width;
  309. cellElement.style.minWidth = column.ColumnStyle.Width;
  310. cellElement.style.maxWidth = column.ColumnStyle.Width;
  311. SetCellElementStyle(cellElement);
  312. listViewElement.Add(cellElement);
  313. column.CellElements.Add(cellElement);
  314. }
  315. return listViewElement;
  316. }
  317. private void BindListViewElement(VisualElement listViewElement, int index)
  318. {
  319. var sourceDatas = _listView.itemsSource as List<ITableData>;
  320. var tableData = sourceDatas[index];
  321. foreach (var colum in _columns)
  322. {
  323. var cellElement = listViewElement.Q(colum.ElementName);
  324. var tableCell = tableData.Cells[colum.ColumnIndex];
  325. colum.BindCell.Invoke(cellElement, tableData, tableCell);
  326. }
  327. }
  328. private void SetCellElementStyle(VisualElement element)
  329. {
  330. StyleLength defaultStyle = new StyleLength(1f);
  331. element.style.paddingTop = defaultStyle;
  332. element.style.paddingBottom = defaultStyle;
  333. element.style.marginTop = defaultStyle;
  334. element.style.marginBottom = defaultStyle;
  335. element.style.paddingLeft = 1;
  336. element.style.paddingRight = 1;
  337. element.style.marginLeft = 0;
  338. element.style.marginRight = 0;
  339. }
  340. }
  341. }
  342. #endif