// COPYRIGHT (C) Tom. ALL RIGHTS RESERVED. // THE AntdUI PROJECT IS AN WINFORM LIBRARY LICENSED UNDER THE Apache-2.0 License. // LICENSED UNDER THE Apache License, VERSION 2.0 (THE "License") // YOU MAY NOT USE THIS FILE EXCEPT IN COMPLIANCE WITH THE License. // YOU MAY OBTAIN A COPY OF THE LICENSE AT // // http://www.apache.org/licenses/LICENSE-2.0 // // UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, SOFTWARE // DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. // SEE THE LICENSE FOR THE SPECIFIC LANGUAGE GOVERNING PERMISSIONS AND // LIMITATIONS UNDER THE License. // GITEE: https://gitee.com/antdui/AntdUI // GITHUB: https://github.com/AntdUI/AntdUI // CSDN: https://blog.csdn.net/v_132 // QQ: 17379620 using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; namespace AntdUI { partial class Table { protected override void OnFontChanged(EventArgs e) { LoadLayout(); Invalidate(); base.OnFontChanged(e); } protected override void OnCreateControl() { base.OnCreateControl(); if (dataSource == null) return; if (dataOne) LoadLayout(); } string? show_oldrect = null; protected override void OnSizeChanged(EventArgs e) { var rect = ClientRectangle; if (IsHandleCreated && rect.Width > 1 && rect.Height > 1) { string show_rect = rect.Width + "_" + rect.Height; if (show_oldrect == show_rect) return; show_oldrect = show_rect; LoadLayout(rect); base.OnSizeChanged(e); } } internal RowTemplate[]? rows = null; internal List rows_Expand = new List(); Rectangle[] dividers = new Rectangle[0], dividerHs = new Rectangle[0]; MoveHeader[] moveheaders = new MoveHeader[0]; public void LoadLayout() { if (IsHandleCreated) { var rect = ClientRectangle; if (rect.Width > 1 && rect.Height > 1) LoadLayout(rect); else show_oldrect = null; } } void LoadLayout(Rectangle rect_t) { var rect = LayoutDesign(rect_t); ScrollBar.SizeChange(rect); } bool has_check = false; Rectangle rect_read, rect_divider; Rectangle LayoutDesign(Rectangle rect) { has_check = false; if (dataTmp == null) { ThreadState?.Dispose(); ThreadState = null; if (visibleHeader && emptyHeader && columns != null && columns.Count > 0) { var _rows = LayoutDesign(new TempTable(new TempiColumn[0], new IRow[0]), out var _columns, out int processing, out var col_width, out int KeyTreeINDEX); rows = LayoutDesign(rect, _rows, _columns, col_width, KeyTreeINDEX, out int x, out int y, out bool is_exceed); ScrollBar.SetVrSize(is_exceed ? x : 0, y); return rect; } else { ScrollBar.SetVrSize(0, 0); dividers = new Rectangle[0]; rows = null; } } else { var _rows = LayoutDesign(dataTmp, out var _columns, out int processing, out var col_width, out int KeyTreeINDEX); if (visibleHeader && EmptyHeader && _rows.Count == 0) { rows = LayoutDesign(rect, _rows, _columns, col_width, KeyTreeINDEX, out int x, out int y, out bool is_exceed); ScrollBar.SetVrSize(is_exceed ? x : 0, y); ThreadState?.Dispose(); ThreadState = null; return rect; } else if (_rows.Count > 0) { rows = LayoutDesign(rect, _rows, _columns, col_width, KeyTreeINDEX, out int x, out int y, out bool is_exceed); ScrollBar.SetVrSize(is_exceed ? x : 0, y); if (processing == 0) { ThreadState?.Dispose(); ThreadState = null; } else { if (Config.Animation && ThreadState == null) { ThreadState = new ITask(this, i => { AnimationStateValue = i; Invalidate(); }, 50, 1F, 0.05F); } } return rect; } else { ThreadState?.Dispose(); ThreadState = null; ScrollBar.SetVrSize(0, 0); dividers = new Rectangle[0]; rows = null; } } return Rectangle.Empty; } List LayoutDesign(TempTable dataTmp, out List Columns, out int Processing, out Dictionary ColWidth, out int KeyTreeIndex) { var _rows = new List(dataTmp.rows.Length); var _columns = new List(dataTmp.columns.Length); int processing = 0; var col_width = new Dictionary(); string? KeyTree = null; int KeyTreeINDEX = -1; if (columns == null) { if (SortHeader == null) { foreach (var it in dataTmp.columns) _columns.Add(new Column(it.key, it.key) { INDEX = _columns.Count }); } else { foreach (var i in SortHeader) { var it = dataTmp.columns[i]; _columns.Add(new Column(it.key, it.key) { INDEX = i }); } } } else { int x = 0; ForColumn(columns, it => { int INDEX = _columns.Count; _columns.Add(it); ColumnWidth(it, ref col_width, x); x++; if (it.KeyTree != null) { foreach (var item in dataTmp.columns) { if (item.key == it.KeyTree) { KeyTree = it.KeyTree; break; } } } return INDEX; }); if (KeyTree != null) { foreach (var it in _columns) { if (it.KeyTree == KeyTree) KeyTreeINDEX = it.INDEX; } } } if (KeyTree == null) { ForRow(dataTmp, row => { var cells = new List(_columns.Count); foreach (var column in _columns) AddRows(ref cells, ref processing, column, row, column.Key); if (cells.Count > 0) AddRows(ref _rows, cells.ToArray(), row.record); }); } else { ForRow(dataTmp, row => { var cells = new List(_columns.Count); foreach (var column in _columns) AddRows(ref cells, ref processing, column, row, column.Key); if (cells.Count > 0) ForTree(ref _rows, ref processing, AddRows(ref _rows, cells.ToArray(), row.record), row, _columns, KeyTree, KeyTreeINDEX, 0, true); }); } Columns = _columns; Processing = processing; ColWidth = col_width; KeyTreeIndex = KeyTreeINDEX; dataOne = false; return _rows; } RowTemplate[] LayoutDesign(Rectangle rect, List _rows, List _columns, Dictionary col_width, int KeyTreeINDEX, out int _x, out int _y, out bool _is_exceed) { if (rows != null) { List dir_Select = new List(rows.Length), dir_Hover = new List(1); foreach (var item in rows) { if (item.Select) dir_Select.Add(item.RECORD); if (item.Hover) dir_Hover.Add(item.RECORD); } foreach (var item in _rows) { if (dir_Select.Contains(item.RECORD)) item.Select = true; if (dir_Hover.Contains(item.RECORD)) item.Hover = true; } } #region 添加表头 var _cols = new List(_columns.Count); foreach (var it in _columns) _cols.Add(new TCellColumn(this, it)); AddRows(ref _rows, _cols.ToArray(), dataSource); #endregion #region 计算坐标 int x = 0, y = 0; bool is_exceed = false; rect_read.X = rect.X; rect_read.Y = rect.Y; Helper.GDI(g => { var dpi = Config.Dpi; int check_size = (int)(_checksize * dpi), switchsize = (int)(_switchsize * dpi), treesize = (int)(TreeButtonSize * dpi), gap = (int)(_gap * dpi), gap2 = gap * 2, split = (int)(1F * dpi), split2 = split / 2, split_move = (int)(6F * dpi), split_move2 = split_move / 2; check_radius = check_size * .12F * dpi; check_border = check_size * .04F * dpi; #region 布局高宽 var read_width_cell = new Dictionary(_rows[0].cells.Length); for (int cel_i = 0; cel_i < _rows[0].cells.Length; cel_i++) read_width_cell.Add(cel_i, new AutoWidth()); for (int row_i = 0; row_i < _rows.Count; row_i++) { var row = _rows[row_i]; row.INDEX = row_i; if (row.ShowExpand) { float max_height = 0; if (row.IsColumn) { for (int cel_i = 0; cel_i < row.cells.Length; cel_i++) { var it = row.cells[cel_i]; it.INDEX = cel_i; if (it is TCellCheck check && check.NoTitle) { if (max_height < gap2) max_height = gap2; read_width_cell[cel_i].value = -1; } else { var text_size = it.GetSize(g, columnfont ?? Font, rect.Width, gap, gap2); int width = (int)Math.Ceiling(text_size.Width); if (max_height < text_size.Height) max_height = text_size.Height; if (read_width_cell[cel_i].value < width) read_width_cell[cel_i].value = width; if (read_width_cell[cel_i].minvalue < it.MinWidth) read_width_cell[cel_i].minvalue = it.MinWidth; } } } else { for (int cel_i = 0; cel_i < row.cells.Length; cel_i++) { var it = row.cells[cel_i]; it.INDEX = cel_i; if (it is TCellCheck check) { if (check.NoTitle) { if (max_height < gap2) max_height = gap2; read_width_cell[cel_i].value = -1; } } else { var text_size = it.GetSize(g, Font, rect.Width, gap, gap2); int width = (int)Math.Ceiling(text_size.Width); if (it.ROW.CanExpand && _rows[0].cells[cel_i].INDEX == KeyTreeINDEX) width += treesize + gap2 + (treesize * it.ROW.ExpandDepth); if (max_height < text_size.Height) max_height = text_size.Height; if (read_width_cell[cel_i].value < width) read_width_cell[cel_i].value = width; } } } row.Height = (int)Math.Round(max_height) + gap2; } } foreach (var it in read_width_cell) { var maxWidth = _columns[it.Key].MaxWidth; if (maxWidth != null) { if (maxWidth.EndsWith("%") && float.TryParse(maxWidth.TrimEnd('%'), out var f)) { int max = (int)(rect.Width * f / 100F); if (it.Value.value > max) it.Value.value = max; } else if (int.TryParse(maxWidth, out var i)) { int max = (int)(i * Config.Dpi); if (it.Value.value > max) it.Value.value = max; } } } rect_read.Width = rect.Width; rect_read.Height = rect.Height; var width_cell = CalculateWidth(rect, col_width, read_width_cell, check_size, ref is_exceed); #endregion #region 最终坐标 int use_y; if (visibleHeader) use_y = rect.Y; else use_y = rect.Y - _rows[0].Height; foreach (var row in _rows) { if (row.ShowExpand) { int use_x = rect.X; row.RECT = new Rectangle(rect.X, use_y, rect_read.Width, row.Height); for (int i = 0; i < row.cells.Length; i++) { var it = row.cells[i]; var _rect = new Rectangle(use_x, use_y, width_cell[i], row.RECT.Height); int ox = 0; if (row.INDEX > 0 && _rows[0].cells[i].INDEX == KeyTreeINDEX) { int x = gap + (treesize * row.ExpandDepth); ox = x + gap + treesize / 2; row.RectExpand = new Rectangle(use_x + x + split_move, use_y + (row.Height - treesize) / 2, treesize, treesize); } if (it is TCellCheck check) check.SetSize(_rect, check_size); else if (it is TCellRadio radio) radio.SetSize(_rect, check_size); else if (it is TCellSwitch _switch) _switch.SetSize(_rect, switchsize); else if (it is TCellColumn column) { it.SetSize(g, Font, _rect, ox, gap, gap2); if (column.column is ColumnCheck columnCheck && columnCheck.NoTitle) { column.column.SortOrder = false; columnCheck.PARENT = this; //全选 column.rect = new Rectangle(_rect.X + (_rect.Width - check_size) / 2, _rect.Y + (_rect.Height - check_size) / 2, check_size, check_size); } else { if (column.column.SortOrder) column.rect = new Rectangle(_rect.X + gap, _rect.Y + gap, _rect.Width - gap2 - column.SortWidth, _rect.Height - gap2); else column.rect = new Rectangle(_rect.X + gap, _rect.Y + gap, _rect.Width - gap2, _rect.Height - gap2); if (x < column.rect.Right) x = column.rect.Right; } } else it.SetSize(g, Font, _rect, ox, gap, gap2); if (x < _rect.Right) x = _rect.Right; if (y < _rect.Bottom) y = _rect.Bottom; use_x += width_cell[i]; } use_y += row.Height; } } #endregion List _dividerHs = new List(), _dividers = new List(); var MoveHeaders = new List(); int last_index = _rows.Count - 1; var last_row = _rows[last_index]; while (!last_row.ShowExpand) { last_index--; last_row = _rows[last_index]; } var last = last_row.cells[last_row.cells.Length - 1]; bool iseg = emptyHeader && _rows.Count == 1; if ((rect.Y + rect.Height) > last.RECT.Bottom && !iseg) rect_read.Height = last.RECT.Bottom - rect.Y; int sp2 = split * 2; rect_divider = new Rectangle(rect_read.X + split, rect_read.Y + split, rect_read.Width - sp2, rect_read.Height - sp2); var moveheaders_dir = new Dictionary(moveheaders.Length); foreach (var item in moveheaders) moveheaders_dir.Add(item.i, item); foreach (var row in _rows) { if (row.IsColumn) { if (EnableHeaderResizing) { for (int i = 0; i < row.cells.Length; i++) { var it = (TCellColumn)row.cells[i]; MoveHeaders.Add(new MoveHeader(moveheaders_dir, new Rectangle(it.RECT.Right - split_move2, rect.Y, split_move, it.RECT.Height), i, it.RECT.Width, it.MinWidth)); } } if (bordered) { if (iseg) { for (int i = 0; i < row.cells.Length - 1; i++) { var it = (TCellColumn)row.cells[i]; _dividerHs.Add(new Rectangle(it.RECT.Right - split2, rect.Y, split, it.RECT.Height)); } } else { for (int i = 0; i < row.cells.Length - 1; i++) { var it = (TCellColumn)row.cells[i]; _dividerHs.Add(new Rectangle(it.RECT.Right - split2, rect.Y, split, rect_read.Height)); } } if (visibleHeader) _dividers.Add(new Rectangle(rect.X, row.RECT.Bottom - split2, rect_read.Width, split)); } else { for (int i = 0; i < row.cells.Length - 1; i++) { var it = (TCellColumn)row.cells[i]; _dividerHs.Add(new Rectangle(it.RECT.Right - split2, it.rect.Y, split, it.rect.Height)); } } } else { if (bordered) _dividers.Add(new Rectangle(rect.X, row.RECT.Bottom - split2, rect_read.Width, split)); else _dividers.Add(new Rectangle(row.RECT.X, row.RECT.Bottom - split2, row.RECT.Width, split)); } } if (bordered && !iseg) _dividers.RemoveAt(_dividers.Count - 1); dividerHs = _dividerHs.ToArray(); dividers = _dividers.ToArray(); moveheaders = MoveHeaders.ToArray(); }); #endregion _x = x; _y = y; _is_exceed = is_exceed; return _rows.ToArray(); } #region 通用循环 void ForColumn(ColumnCollection columns, Func action) { if (SortHeader == null) { foreach (var it in columns) { it.PARENT = this; if (it.Visible) it.INDEX = action(it); } } else { var dir = new Dictionary(); foreach (var it in columns) { if (it.Visible) dir.Add(dir.Count, it); } foreach (var index in SortHeader) { var it = dir[index]; it.PARENT = this; it.INDEX = index; if (it.Visible) action(it); } } } void ForRow(TempTable data_temp, Action action) { if (SortData == null || SortData.Length != data_temp.rows.Length) { foreach (var row in data_temp.rows) action(row); } else { foreach (var i in SortData) action(data_temp.rows[i]); } } bool ForTree(ref List _rows, ref int processing, RowTemplate row_new, IRow row, List _columns, string KeyTree, int KeyTreeINDEX, int depth, bool show) { if (DefaultExpand && dataOne) { if (!rows_Expand.Contains(row.record)) rows_Expand.Add(row.record); } row_new.ShowExpand = show; row_new.ExpandDepth = depth; row_new.KeyTreeINDEX = KeyTreeINDEX; row_new.Expand = rows_Expand.Contains(row.record); int count = 0; var list_tree = ForTreeValue(row, KeyTree); if (list_tree != null) { show = show && row_new.Expand; row_new.CanExpand = true; count++; for (int i = 0; i < list_tree.Count; i++) { var item_tree = GetRow(list_tree[i], _columns.Count); if (item_tree.Count > 0) { var row_tree = new IRow(i, list_tree[i], item_tree); var cells_tree = new List(_columns.Count); foreach (var column in _columns) AddRows(ref cells_tree, ref processing, column, row_tree, column.Key); if (ForTree(ref _rows, ref processing, AddRows(ref _rows, cells_tree.ToArray(), row_tree.record), row_tree, _columns, KeyTree, KeyTreeINDEX, depth + 1, show)) count++; } } } return count > 0; } static IList? ForTreeValue(IRow row, string KeyTree) { if (row.cells.ContainsKey(KeyTree)) { var ov_tree = row.cells[KeyTree]; if (ov_tree is AntItem item) { var value_tree = item.value; if (value_tree is IList list_tree && list_tree.Count > 0) { var value = new List(list_tree.Count); foreach (var it in list_tree) value.Add(it); return value.ToArray(); } } else if (ov_tree is PropertyDescriptor prop) { var value_tree = prop.GetValue(row.record); if (value_tree is IList list_tree && list_tree.Count > 0) return list_tree; } } return null; } #endregion Dictionary tmpcol_width = new Dictionary(0); Dictionary CalculateWidth(Rectangle rect, Dictionary col_width, Dictionary read_width, int check_size, ref bool is_exceed) { int use_width = rect.Width; float max_width = 0; foreach (var it in read_width) { if (tmpcol_width.TryGetValue(it.Key, out var tw)) max_width += tw; else if (col_width.TryGetValue(it.Key, out var value)) { if (value is int val_int) { if (val_int == -1) max_width += it.Value.value; else if (val_int == -2) max_width += it.Value.minvalue; else max_width += val_int; } if (value is float val_float) max_width += rect.Width * val_float; } else if (it.Value.value == -1F) { int size = check_size * 2;//复选框大小 max_width += size; use_width -= size; } else max_width += it.Value.value; } var width_cell = new Dictionary(read_width.Count); if (max_width > rect.Width) { is_exceed = true; foreach (var it in read_width) { if (tmpcol_width.TryGetValue(it.Key, out var tw)) width_cell.Add(it.Key, tw); else if (col_width.TryGetValue(it.Key, out var value)) { if (value is int val_int) { if (val_int == -1) width_cell.Add(it.Key, it.Value.value); else if (val_int == -2) width_cell.Add(it.Key, it.Value.value); else width_cell.Add(it.Key, val_int); } else if (value is float val_float) width_cell.Add(it.Key, (int)Math.Ceiling(rect.Width * val_float)); } else if (it.Value.value == -1F) { int _check_size = check_size * 2; width_cell.Add(it.Key, _check_size); } else width_cell.Add(it.Key, it.Value.value); } } else { var fill_count = new List(); foreach (var it in read_width) { if (tmpcol_width.TryGetValue(it.Key, out var tw)) width_cell.Add(it.Key, tw); else if (col_width.TryGetValue(it.Key, out var value)) { if (value is int val_int) { if (val_int == -1) width_cell.Add(it.Key, it.Value.value); else if (val_int == -2) fill_count.Add(it.Key); else width_cell.Add(it.Key, val_int); } else if (value is float val_float) width_cell.Add(it.Key, (int)Math.Ceiling(rect.Width * val_float)); } else if (it.Value.value == -1F) { int _check_size = check_size * 2; width_cell.Add(it.Key, _check_size); } else width_cell.Add(it.Key, (int)Math.Ceiling(use_width * (it.Value.value / max_width))); } int sum_wi = 0; foreach (var it in width_cell) sum_wi += it.Value; if (fill_count.Count > 0) { int width = (rect.Width - sum_wi) / fill_count.Count; foreach (var it in fill_count) width_cell.Add(it, width); sum_wi = rect.Width; } if (rect_read.Width > sum_wi) { if (AutoSizeColumnsMode == ColumnsMode.Fill) { //填充 var percentage = new Dictionary(width_cell.Count); foreach (var it in width_cell) { percentage.Add(it.Key, (int)Math.Round(rect_read.Width * (it.Value * 1.0) / sum_wi)); } width_cell = percentage; } else rect_read.Width = sum_wi; } } return width_cell; } void ColumnWidth(Column it, ref Dictionary col_width, int x) { if (it.Width != null) { if (it.Width.EndsWith("%") && float.TryParse(it.Width.TrimEnd('%'), out var f)) col_width.Add(x, f / 100F); else if (int.TryParse(it.Width, out var i)) col_width.Add(x, (int)(i * Config.Dpi)); else if (it.Width.Contains("fill")) col_width.Add(x, -2);//填充剩下的 else col_width.Add(x, -1); //AUTO } } #region 动画 ITask? ThreadState = null; internal float AnimationStateValue = 0; #endregion float check_radius = 0F, check_border = 1F; void AddRows(ref List cells, ref int processing, Column column, IRow row, string key) { if (row.cells.TryGetValue(key, out var ov)) { var value = OGetValue(ov, row.record, out var property, out var rv); if (column.Render == null) AddRows(ref cells, ref processing, column, rv, value, property); else AddRows(ref cells, ref processing, column, rv, column.Render?.Invoke(value, row.record, row.i), property); } else AddRows(ref cells, ref processing, column, null, column.Render?.Invoke(null, row.record, row.i), null); } /// /// 添加行 /// /// 列 /// 动画 /// 表头 /// 原始值 /// 真值 /// 反射 void AddRows(ref List cells, ref int processing, Column column, object? ov, object? value, PropertyDescriptor? prop) { if (value == null) cells.Add(new TCellText(this, prop, ov, column, null)); else { if (column is ColumnCheck columnCheck) { //复选框 has_check = true; bool value_check = false; if (value is bool check) value_check = check; AddRows(ref cells, new TCellCheck(this, prop, ov, value_check, columnCheck)); } else if (column is ColumnRadio columnRadio) { //单选框 has_check = true; bool value_check = false; if (value is bool check) value_check = check; AddRows(ref cells, new TCellRadio(this, prop, ov, value_check, columnRadio)); } else if (column is ColumnSwitch columnSwitch) { //开关 bool value_check = false; if (value is bool check) value_check = check; AddRows(ref cells, new TCellSwitch(this, prop, ov, value_check, columnSwitch)); } else if (value is IList icells) AddRows(ref cells, new Template(this, prop, ov, column, ref processing, icells)); else if (value is ICell icell) AddRows(ref cells, new Template(this, prop, ov, column, ref processing, new ICell[] { icell })); else cells.Add(new TCellText(this, prop, ov, column, value.ToString())); } if (ov is INotifyPropertyChanged notify) { notify.PropertyChanged -= Notify_PropertyChanged; notify.PropertyChanged += Notify_PropertyChanged; } } void AddRows(ref List cells, TCell data) { cells.Add(data); var data_temp = data; if (data is Template template) { foreach (var it in template.value) { it.Value.Changed = layout => { if (layout) LoadLayout(); Invalidate(); }; } } } RowTemplate AddRows(ref List rows, TCell[] cells, object? record) { var row = new RowTemplate(this, cells, record); foreach (var it in row.cells) it.ROW = row; rows.Add(row); return row; } RowTemplate AddRows(ref List rows, TCellColumn[] cells, object? record) { var row = new RowTemplate(this, cells, record) { IsColumn = true }; for (int i = 0; i < row.cells.Length; i++) { var it = row.cells[i]; if (it is TCellColumn column && column.column is ColumnCheck checkColumn && checkColumn.NoTitle) { if (rows.Count > 0) { int t_count = rows.Count, check_count = 0; for (int row_i = 0; row_i < rows.Count; row_i++) { var cell = rows[row_i].cells[i]; if (cell is TCellCheck checkCell && checkCell.Checked) check_count++; } if (t_count == check_count) checkColumn.CheckState = System.Windows.Forms.CheckState.Checked; else if (check_count > 0) checkColumn.CheckState = System.Windows.Forms.CheckState.Indeterminate; else checkColumn.CheckState = System.Windows.Forms.CheckState.Unchecked; } else checkColumn.CheckState = System.Windows.Forms.CheckState.Unchecked; } it.ROW = row; } rows.Insert(0, row); return row; } string? OGetValue(TempTable data_temp, int i_r, string key) { var value = data_temp.rows[i_r].cells[key]; if (value is AntItem item) { var val = item.value; if (val is IList icells) { var vals = new List(icells.Count); foreach (var cell in icells) { var str = cell.ToString(); if (!string.IsNullOrEmpty(str)) vals.Add(str); } return string.Join(" ", vals); } else return val?.ToString(); } else if (value is PropertyDescriptor prop) { var val = prop.GetValue(data_temp.rows[i_r].record); if (val is IList icells) { var vals = new List(icells.Count); foreach (var cell in icells) { var str = cell.ToString(); if (!string.IsNullOrEmpty(str)) vals.Add(str); } return string.Join(" ", vals); } else return val?.ToString(); } else return value?.ToString(); } object? OGetValue(object? ov, object record, out PropertyDescriptor? property, out object? value) { value = ov; property = null; if (ov is AntItem cell) return cell.value; else if (ov is PropertyDescriptor prop) { value = record; property = prop; return prop.GetValue(record); } return ov; } #region MVVM private void Notify_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (sender == null || e.PropertyName == null) return; PropertyChanged(sender, e.PropertyName); } void PropertyChanged(object data, string key) { if (rows == null || key == null) return; for (int cel_i = 0; cel_i < rows[0].cells.Length; cel_i++) { var column = (TCellColumn)rows[0].cells[cel_i]; if (key == column.column.Key) { if (data is AntItem item) { foreach (var row in rows) { if (row.RECORD is IList items && items.Contains(item)) { RefreshItem(rows, column, row, row.cells[cel_i], cel_i, item.value); return; } } } else { foreach (var row in rows) { if (row.RECORD == data) { var cel = row.cells[cel_i]; if (cel.PROPERTY != null) RefreshItem(rows, column, row, cel, cel_i, cel.PROPERTY.GetValue(data)); return; } } } return; } } } void RefreshItem(RowTemplate[] rows, TCellColumn column, RowTemplate row, TCell cel, int cel_i, object? value) { if (cel is Template template) { int count = 0; if (value == null) count++; else if (value is IList cells) { if (template.value.Count == cells.Count) { for (int i = 0; i < template.value.Count; i++) { if (template.value[i].SValue(cells[i])) count++; } } else count++; } else if (value is ICell cell) { if (template.value.Count == 1) { if (template.value[0].SValue(cell)) count++; } else count++; } else count++; if (count > 0) { LoadLayout(); Invalidate(); } } else if (cel is TCellText text) { if (value is IList || value is ICell) LoadLayout(); else text.value = value?.ToString(); Invalidate(); } else if (cel is TCellCheck check) { if (value is bool b) check.Checked = b; row.Select = RowISelect(row); if (column.column is ColumnCheck checkColumn && checkColumn.NoTitle) { int t_count = rows.Length - 1, check_count = 0; for (int row_i = 1; row_i < rows.Length; row_i++) { var it = rows[row_i]; var cell = it.cells[cel_i]; if (cell is TCellCheck checkCell && checkCell.Checked) check_count++; } if (t_count == check_count) checkColumn.CheckState = System.Windows.Forms.CheckState.Checked; else if (check_count > 0) checkColumn.CheckState = System.Windows.Forms.CheckState.Indeterminate; else checkColumn.CheckState = System.Windows.Forms.CheckState.Unchecked; } Invalidate(); } else if (cel is TCellRadio radio) { if (value is bool b) radio.Checked = b; row.Select = RowISelect(row); Invalidate(); } else if (cel is TCellSwitch _switch) { if (value is bool b) _switch.Checked = b; Invalidate(); } else { LoadLayout(); Invalidate(); } } internal void CheckAll(int i_cel, ColumnCheck columnCheck, bool value) { if (rows == null) return; int count = 0, nocount = 0; for (int i_row = 1; i_row < rows.Length; i_row++) { var item = rows[i_row].cells[i_cel]; if (item is TCellCheck checkCell) { count++; if (checkCell.Checked != value) { checkCell.Checked = value; SetValue(item, checkCell.Checked); CheckedChanged?.Invoke(this, new TableCheckEventArgs(value, rows[i_row].RECORD, i_row, i_cel)); } else nocount++; } } if (count > 0 && nocount == count) columnCheck.Checked = value; } void SetValue(TCell cel, object? value) { if (cel.PROPERTY == null) { if (cel.VALUE is AntItem arow) arow.value = value; } else cel.PROPERTY.SetValue(cel.VALUE, value); } bool RowISelect(RowTemplate row) { for (int cel_i = 0; cel_i < row.cells.Length; cel_i++) { var cel = row.cells[cel_i]; if (cel is TCellCheck check) { if (check.Checked) return true; } else if (cel is TCellRadio radio) { if (radio.Checked) return true; } } return false; } #endregion } }