C# lưu vị trí
Tìm ra rồi — nhìn kỹ file mẫu TagValueList.cs, hàm ResolveFormKey của nó khác TagSwitchPulse.cs một chỗ quan trọng:
TagValueList.cs (chạy đúng):
private string ResolveFormKey(Form form)
{
if (!string.IsNullOrWhiteSpace(TagFormKey)) return TagFormKey;
if (!string.IsNullOrWhiteSpace(form.Name)) return form.Name;
return form.GetType().Name; // <-- có dự phòng
}TagSwitchPulse.cs (bị lỗi):
private string ResolveFormKey(Form form) =>
string.IsNullOrWhiteSpace(TagFormKey) ? form.Name : TagFormKey; // <-- không dự phòngVì sao đây chính là nguyên nhân: Trong code InitializeComponent() mà Visual Studio tự sinh ra, các dòng this.Controls.Add(...) (thêm control con vào Form) luôn được đặt TRƯỚC dòng this.Name = "Form1"; của chính cái Form đó (do thứ tự sắp xếp thuộc tính theo alphabet: "Controls" đứng trước "Name").
Control của bạn bind vào Form ngay lúc OnParentChanged (tức đúng lúc Controls.Add chạy) — tại thời điểm đó form.Name vẫn còn rỗng (""), vì dòng gán tên Form chưa kịp chạy. Do đó:
- Lúc load (khởi động app):
formKey = ""(rỗng) → tìm dòng cấu hình với prefix"|Name|Geometry=". - Lúc save (bạn chỉnh tọa độ lúc app đã chạy xong hoàn chỉnh):
form.Namelúc này đã có giá trị thật (vd"Form1") → ghi dòng với prefix"Form1|Name|Geometry=".
→ Hai khóa không khớp nhau, nên lần load sau không bao giờ tìm thấy dòng đã lưu!
TagValueList.cs "vô tình" tránh được lỗi này vì nó fallback về form.GetType().Name (tên class, luôn có sẵn ngay cả khi Name property chưa gán) — và trùng hợp giá trị đó thường giống hệt form.Name sau khi Form được gán tên theo mặc định của VS.
Mình sửa ResolveFormKey trong TagSwitchPulse.cs giống hệt bản đã chạy đúng:
Lưu ý khi test: file cấu hình cũ (tag_switch_pulse_bindings.txt) đang có sẵn các dòng lưu với formKey rỗng (dạng "|tagSwitchPulse1|Geometry=...") — những dòng này sẽ không còn khớp với key mới nữa. Bạn nên xóa file cấu hình cũ đi (hoặc xóa riêng các dòng có prefix rỗng |) rồi chỉnh lại tọa độ 1 lần nữa để nó ghi lại với key đúng — từ đó về sau tắt/bật app sẽ giữ nguyên vị trí.

