using System.ComponentModel; using System.Runtime.CompilerServices; using InABox.Clients; using InABox.Core; namespace InABox.Avalonia { public abstract class Shell : INotifyPropertyChanged, IShell, IShell where TParent : ICoreRepository where TEntity : Entity, IPersistent, IRemotable, new() { private object? _tag; public object? Tag { get => _tag; set => SetProperty(ref _tag, value); } private bool _isSelected; public bool IsSelected { get => _isSelected; set { if(_isSelected != value) { _isSelected = value; DoPropertyChanged(nameof(IsSelected)); } } } #region INotifyPropertyChanged public event PropertyChangedEventHandler? PropertyChanged; protected void DoPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetProperty(ref T field, T value, [CallerMemberName] string propertyName = "") { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; DoPropertyChanged(propertyName); return true; } #endregion private TEntity? _entity; private TEntity CheckEntity() { _entity ??= Row.ToObject(); return _entity; } public TEntity Entity => CheckEntity(); protected virtual void RowChanged() { } public bool IsChanged() => _entity?.IsChanged() ?? false; public virtual void Save(string auditmessage) { if (_entity != null) { new Client().Save(_entity, auditmessage); _entity.CommitChanges(); } } public Task SaveAsync(string auditMessage) { return Task.Run(() => Save(auditMessage)); } public void Cancel() { _entity?.CancelChanges(); } private CoreRow _row = null!; public CoreRow Row { get => _row; set { _row = value; RowChanged(); } } public TParent Parent { get; set; } ICoreRepository IShell.Parent => this.Parent; public Guid ID => Get(); public virtual string[] TextSearchValues() => []; public bool Match(string? text) { if (string.IsNullOrWhiteSpace(text)) return true; foreach (var value in TextSearchValues()) { if (value.Contains(text, StringComparison.InvariantCultureIgnoreCase)) return true; } return false; } #region Row Get/Set Caching // We do this for three reasons: // 1. Rather than define properties in once class and columns in another, // we can define and link properties and columns in the one class, // using a _static_ constructor, which reduces complexity // 2. By caching based on the property name, we eliminate the need to convert // expressions to strings (expensive), while still retaining type-safety // 3. Using the Get/Set helper functions reduces code complexity when defining // shell properties, and distinguishes between data and calculated properties private static ShellColumns? _columns; public ShellColumns Columns { get { if (_columns == null) { _columns = new ShellColumns(); _columns.Map(nameof(ID), x => x.ID); ConfigureColumns(_columns); } return _columns; } } protected abstract void ConfigureColumns(ShellColumns columns); protected virtual T Get([CallerMemberName] string property = null) { if (_entity != null) { return (T)CoreUtils.GetPropertyValue( _entity, CoreUtils.GetFullPropertyName(Columns[property], ".") ); } if (_row != null) { var col = _columns?.IndexOf(property); if (col != null) return Row.Get(col.Value); //() Row.Values[]; } return CoreUtils.GetDefault(); //return value != null ? (T)CoreUtils.ChangeType(value, typeof(T)) : CoreUtils.GetDefault(); } protected virtual void Set(T value, bool notify = true, [CallerMemberName] string property = null) { CheckEntity(); CoreUtils.SetPropertyValue( _entity, CoreUtils.GetFullPropertyName(Columns[property], "."), value ); //Row.Values[Columns.IndexOf(property)] = value; if (notify) DoPropertyChanged(property); } #endregion } }