教程:TODO App
完整示例位于 samples/Todo/:
dotnet run --project samples/Todo/Todo.csproj
该示例不使用 NullRenderHost。运行需要 WebKitGTK 6.0、JavaScriptCoreGTK 6.0、
GTK 4 和可用的 DISPLAY 或 WAYLAND_DISPLAY。
1. 配置 CSX
<ProjectReference Include="..\..\src\UltimateUI.Components.SourceGen\UltimateUI.Components.SourceGen.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<AdditionalFiles Include="TodoView.uui.csx" />
2. 编写 TodoView.uui.csx
TodoShell 是 TODO showcase 的根组件:它在组件内部管理 draft、todo 列表和
事件处理,并直接在 CSX 子节点里展开列表。TodoRow 只负责单行 item。CSX 文件
仍然使用普通 C# 外壳,只把方法里的 return (<.../>); 当成扩展语法。子节点里的
{todos.Value.Select(...)} 会把 C# 构造出的 IEnumerable<Element> 插入到当前父节点。
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateUI;
using UltimateUI.Components;
using UltimateUI.Events;
namespace UltimateUI.Samples.Todo;
public static partial class TodoView
{
public static Element TodoShell(ComponentContext context)
{
var draft = context.UseState("Read the docs");
var todos = context.UseState<IEnumerable<TodoItem>>(() =>
new[]
{
new TodoItem(1, "Build component runtime", Done: true),
new TodoItem(2, "Ship TODO example", Done: false),
});
EventHandler<InputEventArgs> onDraftInput = (_, e) => draft.Set(e.Value);
EventHandler<ClickEventArgs> onAdd = (_, _) =>
{
var title = draft.Value.Trim();
if (title.Length == 0)
{
return;
}
var nextId = todos.Value.Any() ? todos.Value.Max(static item => item.Id) + 1 : 1;
todos.Set(todos.Value.Concat(new[] { new TodoItem(nextId, title, Done: false) }));
draft.Set(string.Empty);
};
var openCount = todos.Value.Count(static item => !item.Done);
return (
<div className="todo-shell">
<h1>TODO</h1>
<p>Open items: {openCount}</p>
<div className="todo-row">
<input className="todo-input" placeholder="New task" value={draft.Value} onInput={onDraftInput} />
<button className="todo-button" onClick={onAdd}>Add</button>
</div>
{todos.Value.Select(item => TodoRow(
context,
item.Id,
item.Title,
item.Done,
(_, _) => todos.Set(todos.Value
.Select(candidate => candidate.Id == item.Id ? candidate with { Done = !candidate.Done } : candidate))))}
</div>
);
}
public static Element TodoRow(ComponentContext context, int id, string title, bool done, EventHandler<ClickEventArgs>? onToggle)
{
return (
<div className="todo-row" key={id}>
<button className="todo-button" onClick={onToggle}>{done ? "Undo" : "Done"}</button>
<span className={done ? "todo-title todo-done" : "todo-title"}>{title}</span>
</div>
);
}
}
TodoItem 是同一命名空间下的普通 C# record,可以放在 Program.cs 末尾或单独的
.cs 文件里:
internal readonly record struct TodoItem(int Id, string Title, bool Done);
3. 在 TodoShell 中管理状态
var draft = context.UseState("Read the docs");
var todos = context.UseState<IEnumerable<TodoItem>>(() =>
new[]
{
new TodoItem(1, "Build component runtime", Done: true),
new TodoItem(2, "Ship TODO example", Done: false),
});
4. 绑定事件并 inline 渲染列表
EventHandler<InputEventArgs> onDraftInput = (_, e) => draft.Set(e.Value);
EventHandler<ClickEventArgs> onAdd = (_, _) => { /* append todo */ };
{todos.Value.Select(item => TodoRow(
context,
item.Id,
item.Title,
item.Done,
(_, _) => { /* toggle todo */ }))}
5. 挂载到 WebKitGTK
var builder = UltimateAppBuilder.CreateDefaultBuilder();
var app = builder.Build();
var window = app.MainWindow = new Window();
window.AttachGlobalStyle(styles);
window.Render(TodoView.TodoShell);
return await app.RunAsync();