89 lines
2.8 KiB
C#
89 lines
2.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Reflection;
|
|
using Godot;
|
|
|
|
public sealed class FormBinder<TViewModel> where TViewModel : INotifyPropertyChanged
|
|
{
|
|
private readonly TViewModel _vm;
|
|
private readonly Control _root;
|
|
private readonly Dictionary<LineEdit, PropertyInfo> _lineToProp = new();
|
|
private readonly Dictionary<string, LineEdit> _propNameToLine = new(StringComparer.OrdinalIgnoreCase);
|
|
private bool _suppress;
|
|
|
|
public FormBinder(TViewModel vm, Control root)
|
|
{
|
|
_vm = vm;
|
|
_root = root;
|
|
}
|
|
|
|
public FormBinder<TViewModel> Bind()
|
|
{
|
|
Discover();
|
|
_vm.PropertyChanged += OnPropertyChanged;
|
|
foreach (var (line, prop) in _lineToProp)
|
|
{
|
|
line.TextChanged += (string text) =>
|
|
{
|
|
if (_suppress) return;
|
|
if (prop.PropertyType == typeof(string) || prop.PropertyType == typeof(string))
|
|
prop.SetValue(_vm, text);
|
|
};
|
|
}
|
|
// Initial sync UI <- VM
|
|
PushAllFromVm();
|
|
return this;
|
|
}
|
|
|
|
private void Discover()
|
|
{
|
|
var props = typeof(TViewModel).GetProperties(BindingFlags.Instance | BindingFlags.Public);
|
|
var propMap = new Dictionary<string, PropertyInfo>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var p in props) if (p.CanRead || p.CanWrite) propMap[p.Name] = p;
|
|
|
|
void Walk(Node n)
|
|
{
|
|
if (n is LineEdit le)
|
|
{
|
|
// Prefer explicit metadata "vm_prop" to map to a property name; fallback to node name.
|
|
string propName = le.HasMeta("vm_prop") ? (string)le.GetMeta("vm_prop") : StripSuffix(le.Name, "LineEdit");
|
|
if (propMap.TryGetValue(propName, out var pi))
|
|
{
|
|
_lineToProp[le] = pi;
|
|
_propNameToLine[propName] = le;
|
|
}
|
|
}
|
|
foreach (var c in n.GetChildren())
|
|
if (c is Node child) Walk(child);
|
|
}
|
|
|
|
Walk(_root);
|
|
}
|
|
|
|
private static string StripSuffix(string s, string suffix) =>
|
|
s.EndsWith(suffix, StringComparison.Ordinal) ? s[..^suffix.Length] : s;
|
|
|
|
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == null) return;
|
|
if (_propNameToLine.TryGetValue(e.PropertyName, out var le))
|
|
{
|
|
_suppress = true;
|
|
try { le.Text = _lineToProp[le].GetValue(_vm)?.ToString() ?? string.Empty; }
|
|
finally { _suppress = false; }
|
|
}
|
|
}
|
|
|
|
public void PushAllFromVm()
|
|
{
|
|
_suppress = true;
|
|
try
|
|
{
|
|
foreach (var (line, prop) in _lineToProp)
|
|
line.Text = prop.GetValue(_vm)?.ToString() ?? string.Empty;
|
|
}
|
|
finally { _suppress = false; }
|
|
}
|
|
}
|