完成了 Server 端,接著請 AI 幫忙畫了下 UI 介面並提供 .proto
檔請他完成
可以說完成度是非常高,幾乎是貼上即可使用(還是有零星 Bug)
只能說在 AI 協作之下,這種規格導向開發是快速許多
整個 Form1.cs
如下
public partial class Form1 : Form
{
private TodoService.TodoServiceClient _client;
private TodoItem _selectedTodo;
public Form1()
{
InitializeComponent();
InitializeGrpcClient();
LoadTodos();
}
private void InitializeGrpcClient()
{
// 假設 gRPC Server 跑在 localhost:50051
var channel = GrpcChannel.ForAddress("https://localhost:7204");
_client = new TodoService.TodoServiceClient(channel);
}
private async void LoadTodos()
{
try
{
var req = new ListTodosReq();
if (chkFilterCompleted.Checked)
{
req.FilterCompleted = false;
}
var res = await _client.ListTodosAsync(req);
dgvTodos.DataSource = res.Todos.Select(t => new
{
t.Id,
t.Title,
t.Description,
IsCompleted = t.IsCompleted ? "✅" : "❌",
CreatedTime = t.CreatedTime?.ToDateTime() ?? DateTime.MinValue
}).ToList();
// 設定欄位名稱與格式
dgvTodos.Columns["Id"].HeaderText = "ID";
dgvTodos.Columns["Title"].HeaderText = "標題";
dgvTodos.Columns["Description"].HeaderText = "描述";
dgvTodos.Columns["IsCompleted"].HeaderText = "完成";
dgvTodos.Columns["CreatedTime"].HeaderText = "建立時間";
dgvTodos.Columns["CreatedTime"].DefaultCellStyle.Format = "yyyy-MM-dd HH:mm:ss";
dgvTodos.ClearSelection();
_selectedTodo = null;
ResetForm();
}
catch (Exception ex)
{
MessageBox.Show($"載入失敗:{ex.Message}", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void ResetForm()
{
txtTitle.Clear();
txtDescription.Clear();
chkIsCompleted.Checked = false;
btnUpdate.Enabled = false;
btnDelete.Enabled = false;
}
private void dgvTodos_SelectionChanged(object sender, EventArgs e)
{
if (dgvTodos.SelectedRows.Count > 0)
{
var row = dgvTodos.SelectedRows[0];
var id = (int)row.Cells["Id"].Value;
// 重新取得最新資料(避免 UI 與資料不同步)
_ = Task.Run(async () =>
{
try
{
var res = await _client.GetTodoAsync(new GetTodoReq { Id = id });
if (res.Todo != null)
{
Invoke((Action)(() =>
{
_selectedTodo = res.Todo;
txtTitle.Text = _selectedTodo.Title;
txtDescription.Text = _selectedTodo.Description;
chkIsCompleted.Checked = _selectedTodo.IsCompleted;
btnUpdate.Enabled = true;
btnDelete.Enabled = true;
}));
}
}
catch (Exception ex)
{
Invoke((Action)(() =>
{
MessageBox.Show($"取得資料失敗:{ex.Message}", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
}));
}
});
}
else
{
ResetForm();
}
}
private async void btnCreate_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtTitle.Text))
{
MessageBox.Show("標題不可為空", "輸入錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
try
{
var req = new CreateTodoReq
{
Title = txtTitle.Text.Trim(),
Description = txtDescription.Text.Trim()
};
var res = await _client.CreateTodoAsync(req);
MessageBox.Show($"新增成功!ID: {res.Todo.Id}", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
LoadTodos();
}
catch (Exception ex)
{
MessageBox.Show($"新增失敗:{ex.Message}", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async void btnUpdate_Click(object sender, EventArgs e)
{
if (_selectedTodo == null)
{
MessageBox.Show("請先選擇項目", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
try
{
var req = new UpdateTodoReq
{
Id = _selectedTodo.Id,
Title = txtTitle.Text.Trim(),
Description = txtDescription.Text.Trim(),
IsCompleted = chkIsCompleted.Checked
};
var res = await _client.UpdateTodoAsync(req);
MessageBox.Show($"更新成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
LoadTodos();
}
catch (Exception ex)
{
MessageBox.Show($"更新失敗:{ex.Message}", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async void btnDelete_Click(object sender, EventArgs e)
{
if (_selectedTodo == null)
{
MessageBox.Show("請先選擇項目", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var confirm = MessageBox.Show($"確定刪除「{_selectedTodo.Title}」?", "確認刪除", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (confirm != DialogResult.Yes) return;
try
{
var req = new DeleteTodoReq { Id = _selectedTodo.Id };
var res = await _client.DeleteTodoAsync(req);
if (res.Success)
{
MessageBox.Show("刪除成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
LoadTodos();
}
else
{
throw new Exception("伺服器回傳刪除失敗");
}
}
catch (Exception ex)
{
MessageBox.Show($"刪除失敗:{ex.Message}", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void chkFilterCompleted_CheckedChanged(object sender, EventArgs e)
{
LoadTodos();
}
}