本页给出一个完整的 Table + Pagination 数据管理页范例。重点不是堆叠控件,而是展示一条稳定的数据管线:
System.Data.DataTable。Table.DataSource 只绑定当前页 DataTable。范例使用 aardio 设计器推荐模式:每个 AntdUI 控件放在独立 custom 宿主中,窗口布局、DPI 缩放、边距固定交给 /*DSG{{*/ ... /*}}*/ 维护。不要把大量 .NET SetBounds 绝对坐标当作普通业务窗口模板。
import win.ui;
/*DSG{{*/
var winform = win.form(text="设备运维数据台";right=980;bottom=640;bgcolor=0xF7F8FA)
winform.add(
pageHost={cls="custom";left=20;top=592;right=960;bottom=626;db=1;dl=1;dr=1;z=6};
queryHost={cls="custom";left=20;top=18;right=260;bottom=54;dl=1;dt=1;edge=1;z=1};
resetHost={cls="custom";text="重置";left=430;top=18;right=520;bottom=54;dl=1;dt=1;frame=1;z=3};
statusHost={cls="custom";left=270;top=18;right=420;bottom=54;dl=1;dt=1;edge=1;z=2};
summaryHost={cls="custom";left=530;top=18;right=960;bottom=54;dl=1;dr=1;dt=1;edge=1;z=4};
tableHost={cls="custom";left=20;top=68;right=960;bottom=580;db=1;dl=1;dr=1;dt=1;edge=1;z=5}
)
/*}}*/
import dotNet.AntdUI;
import System;
import System.Data;
var query = AntdUI.Input(winform.queryHost);
query.PlaceholderText = "搜索设备名称或编号";
query.AllowClear = true;
query.PrefixText = "搜索";
var statusPicker = AntdUI.Select(winform.statusHost);
statusPicker.PlaceholderText = "状态";
statusPicker.AllowClear = false;
var statusOptions = [
{text="全部状态";value=""},
{text="运行";value="运行"},
{text="维护";value="维护"},
{text="离线";value="离线"}
];
for i,item in statusOptions {
statusPicker.Items.Add(AntdUI.SelectItem(item.text,item.value));
}
statusPicker.SelectedIndex = 0;
var resetButton = AntdUI.Button(winform.resetHost);
resetButton.Type = AntdUI.TTypeMini.Default;
var summary = AntdUI.Alert(winform.summaryHost);
summary.TextTitle = "当前视图";
summary.Text = "准备加载数据";
summary.Icon = AntdUI.TType.Info;
summary.CloseIcon = false;
summary.Radius = 6;
var grid = AntdUI.Table(winform.tableHost);
grid.Bordered = true;
grid.EmptyText = "没有匹配的设备";
grid.RowHeight = 42;
grid.RowHeightHeader = 42;
grid.FixedHeader = true;
grid.EditMode = AntdUI.TEditMode.DoubleClick;
grid.Columns.Add(AntdUI.ColumnCheck("selected","选择"));
var colId = AntdUI.Column("id","编号");
colId.Width = "90";
grid.Columns.Add(colId);
var colName = AntdUI.Column("name","设备名称");
colName.Width = "180";
colName.SortOrder = true;
grid.Columns.Add(colName);
grid.Columns.Add(AntdUI.Column("status","状态"));
grid.Columns.Add(AntdUI.Column("progress","巡检进度"));
grid.Columns.Add(AntdUI.ColumnSwitch("enabled","启用"));
var colOps = AntdUI.Column("ops","操作");
colOps.Width = "100";
grid.Columns.Add(colOps);
var pager = AntdUI.Pagination(winform.pageHost);
pager.PageSize = 6;
pager.Current = 1;
pager.Total = 0;
pager.ShowSizeChanger = true;
// 业务数据永远保存在 aardio 数组中,Table 只显示当前页 DataTable。
var sourceItems = [
{id=1001;name="东区网关 A";status="运行";progress=0.92;enabled=true;selected=false},
{id=1002;name="西区传感器 B";status="维护";progress=0.35;enabled=true;selected=false},
{id=1003;name="仓库温控 C";status="离线";progress=0.12;enabled=false;selected=false},
{id=1004;name="主楼门禁 D";status="运行";progress=0.76;enabled=true;selected=false},
{id=1005;name="机房 UPS E";status="维护";progress=0.58;enabled=true;selected=false},
{id=1006;name="车库摄像头 F";status="运行";progress=0.88;enabled=true;selected=false},
{id=1007;name="屋顶气象站 G";status="离线";progress=0.2;enabled=false;selected=false},
{id=1008;name="南门访客机 H";status="运行";progress=0.67;enabled=true;selected=false}
];
var pageItems = [];
var isRefreshing = false;
var sortKey = "id";
var sortDesc = false;
var textOrEmpty = function(v){
// aardio 中 0 与 false 为假,不能写 v || "",否则会吞掉 0/false。
if(v === null) return "";
return tostring(v);
}
var statusCell = function(text){
select(text) {
case "运行" {
return AntdUI.CellTag("运行",AntdUI.TTypeMini.Success);
}
case "维护" {
return AntdUI.CellTag("维护",AntdUI.TTypeMini.Warn);
}
case "离线" {
return AntdUI.CellTag("离线",AntdUI.TTypeMini.Error);
}
}
return AntdUI.CellTag(text,AntdUI.TTypeMini.Default);
}
var buildTable = function(items){
var dt = System.Data.DataTable();
// System.Object 列可以承载 CellTag、CellProgress、CellButton 等复杂单元格对象。
dt.Columns.Add("selected",System.Boolean);
dt.Columns.Add("id",System.Object);
dt.Columns.Add("name",System.Object);
dt.Columns.Add("status",System.Object);
dt.Columns.Add("progress",System.Object);
dt.Columns.Add("enabled",System.Boolean);
dt.Columns.Add("ops",System.Object);
for i,item in items {
var row = dt.NewRow();
row["selected"] = item.selected;
row["id"] = item.id;
row["name"] = item.name;
row["status"] = statusCell(item.status);
row["progress"] = AntdUI.CellProgress(item.progress); // 0 到 1,不是 0 到 100。
row["enabled"] = item.enabled;
row["ops"] = AntdUI.CellButton("detail","详情",AntdUI.TTypeMini.Primary);
dt.Rows.Add(row);
}
return dt;
}
var getStatusFilter = function(){
var idx = statusPicker.SelectedIndex;
if(idx >= 0 && statusOptions[idx + 1]){
return statusOptions[idx + 1].value;
}
return "";
}
var filterAndSort = function(){
var q = query.Text;
if(q === null) q = "";
q = string.lower(string.trim(q));
var statusFilter = getStatusFilter();
var result = [];
for i,item in sourceItems {
var hit = true;
if(#q){
var hay = string.lower(item.name + " " + item.id);
hit = string.indexOf(hay,q) !== null;
}
if(hit && #statusFilter){
hit = item.status == statusFilter;
}
if(hit) table.push(result,item);
}
table.sort(result,function(next){
var a = owner[sortKey];
var b = next[sortKey];
if(sortDesc) return tostring(a) > tostring(b);
return tostring(a) < tostring(b);
});
return result;
}
var refreshData = function(resetPage){
if(isRefreshing) return;
isRefreshing = true;
if(resetPage) pager.Current = 1;
var filtered = filterAndSort();
pager.Total = #filtered;
var maxPage = math.max(1,math.ceil(#filtered / pager.PageSize));
if(pager.Current > maxPage) pager.Current = maxPage;
pageItems = [];
if(#filtered){
var startAt = (pager.Current - 1) * pager.PageSize + 1;
var endAt = math.min(startAt + pager.PageSize - 1,#filtered);
pageItems = table.slice(filtered,startAt,endAt);
}
grid.DataSource = buildTable(pageItems);
summary.Text = "共 " + #filtered + " 台设备,当前第 " + pager.Current + " 页 / " + maxPage + " 页";
isRefreshing = false;
}
var showMessage = function(text,typ){
// grid.Parent 是 AntdUI.BaseForm,可作为 Message 的 owner。
var cfg = AntdUI.Message.Config(grid.Parent,text,typ);
cfg.AutoClose = 2;
AntdUI.Message.open(cfg);
}
query.TextChanged = function(sender,e){
// 在 .NET 回调中重建 DataSource,建议延后到消息循环,避免递归刷新或焦点闪动。
winform.setTimeout(function(){
refreshData(true);
},0);
}
statusPicker.SelectedIndexChanged = function(sender,e){
refreshData(true);
}
resetButton.Click = function(sender,e){
query.Text = "";
statusPicker.SelectedIndex = 0;
refreshData(true);
}
pager.ValueChanged = function(sender,e){
if(isRefreshing) return;
refreshData(false);
}
grid.SortModeChanged = function(sender,e){
if(e.Column){
sortKey = e.Column.Key;
sortDesc = e.SortMode === 2;
winform.setTimeout(function(){
refreshData(false);
},0);
}
return true;
}
grid.CheckedChanged = function(sender,e){
// e.RowIndex 是 .NET 行索引,从 0 开始;aardio 数组 pageItems 从 1 开始。
var item = pageItems[e.RowIndex + 1];
if(item && e.Column){
if(e.Column.Key == "selected" || e.Column.Key == "enabled"){
item[e.Column.Key] = e.Value;
}
}
}
grid.CellEndEdit = function(sender,e){
var item = pageItems[e.RowIndex + 1];
if(item && e.Column && e.Column.Key == "name"){
item.name = textOrEmpty(e.Value);
showMessage("已更新设备名称",AntdUI.TType.Success);
}
return true;
}
grid.CellButtonClick = function(sender,e){
var item = pageItems[e.RowIndex + 1];
if(item){
showMessage("打开详情:" + item.id + " / " + item.name,AntdUI.TType.Info);
}
}
refreshData(true);
winform.show();
win.loopMessage();
DataTable 不只能放普通文本。只要把列类型设为 System.Object,字段值就可以放入 AntdUI 的复杂单元格对象,例如:
dt.Columns.Add("status",System.Object);
dt.Columns.Add("progress",System.Object);
dt.Columns.Add("ops",System.Object);
row["status"] = AntdUI.CellTag("运行",AntdUI.TTypeMini.Success);
row["progress"] = AntdUI.CellProgress(0.76);
row["ops"] = AntdUI.CellButton("detail","详情",AntdUI.TTypeMini.Primary);
这适合“主要数据来自 DataTable,但少数列需要标签、进度条、按钮”的业务表格。若某个单元格要放多个链接/按钮,AntItem[] 行模型会更灵活,详见 TableCell 表格单元格。
Pagination 不会自动切换 Table.DataSource。推荐把 filterAndSort()、refreshData() 这类函数写清楚:
refreshData(true),回到第一页。refreshData(false),保留当前筛选条件。pager.Current,再构造 DataTable。pager.Total、pager.Current 可能触发分页事件,使用 isRefreshing 防止递归。| 事件 | 常用字段 | 说明 |
|---|---|---|
CellButtonClick |
e.Btn、e.Record、e.RowIndex、e.Column |
点击 CellButton / CellLink。 |
CheckedChanged |
e.Value、e.Record、e.RowIndex、e.Column |
复选框列、开关列或可勾选单元格变化。 |
CellEndEdit |
e.Value、e.Record、e.RowIndex、e.Column |
编辑结束;通常返回 true 表示应用修改。 |
SortModeChanged |
e.SortMode、e.Column |
列排序模式变化;建议在业务数组中排序后重建当前页。 |
注意:这些事件里的 RowIndex 是 .NET 0 基索引;如果要访问 aardio 数组,需要 pageItems[e.RowIndex + 1]。
"" 为真、0 为假;不要用 v || "" 处理表格值,否则会把 0 / false 替换掉。CellProgress.Value 是 0~1 比例,不是 0~100。Message 时,owner 建议传 grid.Parent 或显式保存的 AntdUI.BaseForm,不要传普通 aardio win.form 或 custom 控件。DataSource 可能导致递归或焦点问题;复杂刷新可用 winform.setTimeout(...,0) 延后执行。