unit pixel_classes;

interface

uses
 System.SysUtils, Rtti, TypInfo;

{$TYPEINFO ON}

type
 TZString = type string;

type
  TNullValue = record
  end;

  TNullable<T{: record}> = record
  public
    Value: T;
    HasValue: Boolean;

    class operator Implicit(const AValue: T): TNullable<T>;
    class operator Implicit(const AValue: TNullable<T>): T;
    class operator Implicit(const AValue: TNullValue): TNullable<T>;
    class operator Explicit(const AValue: T): TNullable<T>;

    // add these...
    class operator Equal(const A: TNullable<T>; const B: TNullValue): Boolean;
    class operator NotEqual(const A: TNullable<T>; const B: TNullValue): Boolean;
  end;

var NullValue: TNullValue;

implementation

class operator TNullable<T>.Implicit(const AValue: T): TNullable<T>;
begin
  Result.Value := AValue;
  Result.HasValue := True;
end;

class operator TNullable<T>.Implicit(const AValue: TNullable<T>): T;
begin
  if AValue.HasValue then
    Result := AValue.Value
  else
    Result := Default(T); // or raise an exception
end;

class operator TNullable<T>.Implicit(const AValue: TNullValue): TNullable<T>;
begin
  Result.Value := Default(T);
  Result.HasValue := False;
end;

class operator TNullable<T>.Explicit(const AValue: T): TNullable<T>;
begin
  Result.Value := AValue;
  Result.HasValue := True;
end;

class operator TNullable<T>.Equal(const A: TNullable<T>; const B: TNullValue): Boolean;
begin
  Result := not A.HasValue;
end;

class operator TNullable<T>.NotEqual(const A: TNullable<T>; const B: TNullValue): Boolean;
begin
  Result := A.HasValue;
end;

end.


-----------

unit orm_binding;

interface

uses
  System.Classes,
  System.Rtti,
  System.Generics.Collections,
  orm_utils,
  AdvCGrid,
  Variants,
  SysUtils,
  StrUtils,
  Dialogs,
  pixel_classes,
  interposer_class;

type
  /// <summary>
  /// Manages the binding between a TAdvColumnGrid and a TQryRecArray<TQryRecInfo> for bidirectional synchronization.
  /// </summary>
  TORMBinding = class
  private
    FGrid: TAdvColumnGrid;
    FData: TQryRecArray<TQryRecInfo>;
    FColumnFields: TDictionary<Integer, string>; // Maps grid column indices to ORM property names
    FColumnTypes: TDictionary<string, TRttiType>; // Maps ORM property names to their RTTI types
    FRowMapping: TDictionary<Integer, TQryRecInfo>; // Maps grid row indices to ORM records
    FCtx: TRttiContext; // RTTI context for dynamic property access

    /// <summary>
    /// Binds grid columns to ORM properties based on the ORMField property.
    /// </summary>
    procedure BindColumns;

    /// <summary>
    /// Populates the grid with data from TQryRecArray and maps each row to its corresponding ORM record.
    /// </summary>
    procedure PopulateAndMapGrid;

    /// <summary>
    /// Sets up event handlers for synchronizing changes between the grid and ORM data.
    /// </summary>
    procedure SetupEventHandlers;

    /// <summary>
    /// Event handler triggered when a grid cell edit is completed.
    /// Updates the corresponding ORM property.
    /// </summary>
    procedure GridCellChanged(Sender: TObject; ACol, ARow: Integer);

    /// <summary>
    /// Event handler triggered when an ORM property is changed.
    /// Updates the corresponding grid cell.
    /// </summary>
    procedure ORMPropertyChanged(Sender: TQryRecInfo; const PropertyName: string);

    /// <summary>
    /// Sets the ORM property based on the grid cell change.
    /// </summary>
    procedure SetORMProperty(ORMRec: TQryRecInfo; const FieldName: string; const Value: TValue);

    /// <summary>
    /// Updates a specific grid cell based on ORM changes.
    /// </summary>
    procedure UpdateGridCell(ACol: Integer; ARow: Integer);
  public
    /// <summary>
    /// Creates an instance of TORMBinding and initializes the binding between the grid and data array.
    /// </summary>
    constructor Create(AGrid: TAdvColumnGrid; AData: TQryRecArray<TQryRecInfo>);

    /// <summary>
    /// Frees resources and unsubscribes from events.
    /// </summary>
    destructor Destroy; override;

    /// <summary>
    /// Initializes the binding by populating the grid and setting up mappings.
    /// </summary>
    procedure InitializeBinding;
  end;

implementation

{ TORMBinding }

constructor TORMBinding.Create(AGrid: TAdvColumnGrid; AData: TQryRecArray<TQryRecInfo>);
begin
  inherited Create;
  FGrid := AGrid;
  FData := AData;
  FColumnFields := TDictionary<Integer, string>.Create;
  FColumnTypes := TDictionary<string, TRttiType>.Create;
  FRowMapping := TDictionary<Integer, TQryRecInfo>.Create;
  FCtx := TRttiContext.Create;

  BindColumns;
  InitializeBinding;
  SetupEventHandlers;
end;

destructor TORMBinding.Destroy;
var
  ORMRec: TQryRecInfo;
begin
  // Unsubscribe from ORM property changed events to prevent memory leaks
  for ORMRec in FRowMapping.Values do
    ORMRec.OnPropertyChanged := nil;

  FRowMapping.Free;
  FColumnFields.Free;
  FColumnTypes.Free;
  FCtx.Free;
  inherited;
end;

/// <summary>
/// Binds grid columns to ORM properties based on the ORMField property.
/// </summary>
procedure TORMBinding.BindColumns;
var
  i: Integer;
  PropName: string;
  RttiProp: TRttiProperty;
  ORMRec: TQryRecInfo;
begin
  if FData.RecCount = 0 then
    Exit; // No data to bind

  ORMRec := FData.Rec(0); // Assuming all records are of the same type

  for i := 0 to FGrid.ColCount - 1 do
  begin
    PropName := FGrid.ORMField[i];
    if PropName <> '' then
    begin
      if not FColumnFields.ContainsKey(i) then
        FColumnFields.Add(i, PropName)
      else
        FColumnFields[i] := PropName; // Update if already exists

      // Use RTTI to get the property type
      RttiProp := FCtx.GetType(ORMRec.ClassType).GetProperty(PropName);
      if Assigned(RttiProp) then
        FColumnTypes.AddOrSetValue(PropName, RttiProp.PropertyType)
      else
        raise Exception.CreateFmt('Property "%s" not found in class "%s".', [PropName, ORMRec.ClassName]);
    end;
  end;
end;

/// <summary>
/// Initializes the binding by populating the grid and setting up row mappings.
/// </summary>
procedure TORMBinding.InitializeBinding;
begin
  PopulateAndMapGrid;
end;

/// <summary>
/// Populates the grid with data from TQryRecArray and maps each row to its corresponding ORM record.
/// </summary>
procedure TORMBinding.PopulateAndMapGrid;
var
  i, j: Integer;
  ORMRec: TQryRecInfo;
  PropName: string;
  RttiProp: TRttiProperty;
  Value: TValue;
begin
  // Set grid row count (+1 if there's a header row; adjust as needed)
  FGrid.RowCount := FData.RecCount + 1;
  FRowMapping.Clear;

  for i := 0 to FData.RecCount - 1 do
  begin
    ORMRec := FData.Rec(i);
    FRowMapping.Add(i, ORMRec);

    // Subscribe to ORM property changed event
    ORMRec.OnPropertyChanged := ORMPropertyChanged;

    for j := 0 to FGrid.ColCount - 1 do
    begin
      if FColumnFields.TryGetValue(j, PropName) then
      begin
        RttiProp := FCtx.GetType(ORMRec.ClassType).GetProperty(PropName);
        if Assigned(RttiProp) then
        begin
          Value := ORMRec.GetPropertyValue(PropName);
          case RttiProp.PropertyType.TypeKind of
            tkInteger, tkInt64:
              FGrid.Cells[j, i + 1] := Value.ToString; // Adjust for header row
            tkFloat:
              FGrid.Cells[j, i + 1] := FloatToStr(Value.AsExtended);
            tkString, tkUString, tkLString, tkWString:
              FGrid.Cells[j, i + 1] := Value.AsString;
            tkEnumeration:
              if RttiProp.PropertyType.Handle = TypeInfo(Boolean) then
                FGrid.Cells[j, i + 1] := IfThen(Value.AsBoolean, 'True', 'False')
              else
                FGrid.Cells[j, i + 1] := Value.ToString;
            tkVariant:
              FGrid.Cells[j, i + 1] := VarToStr(Value.AsVariant);
            else
              FGrid.Cells[j, i + 1] := Value.ToString;
          end;
        end
        else
          FGrid.Cells[j, i + 1] := ''; // Clear cell if property not found
      end
      else
        FGrid.Cells[j, i + 1] := ''; // Clear cell if no mapping exists
    end;
  end;
end;

/// <summary>
/// Sets up event handlers for synchronizing changes between the grid and ORM data.
/// </summary>
procedure TORMBinding.SetupEventHandlers;
begin
  // Assign the grid's OnEditCellDone event to handle cell changes
  FGrid.OnEditCellDone := GridCellChanged;
end;

/// <summary>
/// Event handler triggered when a grid cell edit is completed.
/// Updates the corresponding ORM property.
/// </summary>
procedure TORMBinding.GridCellChanged(Sender: TObject; ACol, ARow: Integer);
var
  NewValue: string;
  ORMRec: TQryRecInfo;
  PropName: string;
  RttiProp: TRttiProperty;
  TValueToSet: TValue;
begin
  // Ignore header row or invalid rows
  if ARow < 1 then Exit; // Assuming first row (0) is header

  if not FRowMapping.TryGetValue(ARow - 1, ORMRec) then Exit; // Adjust for header row
  if not FColumnFields.TryGetValue(ACol, PropName) then Exit;

  RttiProp := FCtx.GetType(ORMRec.ClassType).GetProperty(PropName);
  if not Assigned(RttiProp) then Exit;

  // Get the new value from the grid
  NewValue := FGrid.Cells[ACol, ARow];

  // Convert the string to the appropriate TValue based on property type
  case RttiProp.PropertyType.TypeKind of
    tkInteger:
      TValueToSet := TValue.From<Integer>(StrToIntDef(NewValue, 0));
    tkInt64:
      TValueToSet := TValue.From<Int64>(StrToInt64Def(NewValue, 0));
    tkFloat:
      TValueToSet := TValue.From<Extended>(StrToFloatDef(NewValue, 0.0));
    tkString, tkUString, tkLString, tkWString:
      TValueToSet := TValue.From<string>(NewValue);
    tkEnumeration:
      if RttiProp.PropertyType.Handle = TypeInfo(Boolean) then
        TValueToSet := TValue.From<Boolean>(SameText(NewValue, 'True') or SameText(NewValue, '1'))
      else
        TValueToSet := TValue.From<string>(NewValue);
    tkVariant:
      TValueToSet := TValue.FromVariant(NewValue);
    else
      TValueToSet := TValue.From<string>(NewValue); // Default to string for unsupported types
  end;

  // Update the ORM property using the integrated SetORMProperty procedure
  SetORMProperty(ORMRec, PropName, TValueToSet);
end;

/// <summary>
/// Event handler triggered when an ORM property is changed.
/// Updates the corresponding grid cell.
/// </summary>
procedure TORMBinding.ORMPropertyChanged(Sender: TQryRecInfo; const PropertyName: string);
var
  ARow: Integer;
  ACol: Integer;
  RttiProp: TRttiProperty;
  Value: TValue;
begin
  // Find the row corresponding to the ORM record
  ARow := -1;
  for var Key in FRowMapping.Keys do
  begin
    if FRowMapping[Key] = Sender then
    begin
      ARow := Key;
      Break;
    end;
  end;

  if ARow = -1 then Exit; // Record not found

  // Find the column corresponding to the property
  ACol := -1;
  for var Col in FColumnFields.Keys do
  begin
    if SameText(FColumnFields[Col], PropertyName) then
    begin
      ACol := Col;
      Break;
    end;
  end;

  if ACol = -1 then Exit; // Column not found

  RttiProp := FCtx.GetType(Sender.ClassType).GetProperty(PropertyName);
  if not Assigned(RttiProp) then Exit;

  // Get the new value from ORM
  Value := Sender.GetPropertyValue(PropertyName);

  // Update the grid cell based on the property type
  case RttiProp.PropertyType.TypeKind of
    tkInteger, tkInt64:
      FGrid.Cells[ACol, ARow + 1] := Value.ToString; // Adjust for header row
    tkFloat:
      FGrid.Cells[ACol, ARow + 1] := FloatToStr(Value.AsExtended);
    tkString, tkUString, tkLString, tkWString:
      FGrid.Cells[ACol, ARow + 1] := Value.AsString;
    tkEnumeration:
      if RttiProp.PropertyType.Handle = TypeInfo(Boolean) then
        FGrid.Cells[ACol, ARow + 1] := IfThen(Value.AsBoolean, 'True', 'False')
      else
        FGrid.Cells[ACol, ARow + 1] := Value.ToString;
    tkVariant:
      FGrid.Cells[ACol, ARow + 1] := VarToStr(Value.AsVariant);
    else
      FGrid.Cells[ACol, ARow + 1] := Value.ToString;
  end;
end;

/// <summary>
/// Sets the ORM property based on the grid cell change.
/// </summary>
procedure TORMBinding.SetORMProperty(ORMRec: TQryRecInfo; const FieldName: string; const Value: TValue);
var
  Ctx: TRttiContext;
  Prop: TRttiProperty;
  ORMFieldName: string;
  ObjType: TRttiType;
begin
  Ctx := TRttiContext.Create;
  try
    ObjType := Ctx.GetType(ORMRec.ClassType);
    Prop := ObjType.GetProperty(FieldName);

    if Assigned(Prop) and Prop.IsWritable then
    begin
      ORMFieldName := '_' + FieldName;

      if ObjType.GetField(ORMFieldName) = nil then
        Exit; // Backing field not found, skip

      if Prop.PropertyType.TypeKind = tkRecord then
      begin
        // Check if the property is a TNullable<T>
        if Prop.PropertyType.Name.StartsWith('TNullable<') then
        begin
          if Value.IsEmpty or VarIsNull(Value.AsVariant) then
          begin
            // Set the property to null
            if Prop.PropertyType.Name = 'TNullable<System.string>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<string>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.Integer>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Integer>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.Int64>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Int64>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.Extended>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Extended>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.Currency>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Currency>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.TDateTime>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<TDateTime>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.Boolean>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Boolean>>(NullValue))
            else if Prop.PropertyType.Name = 'TNullable<System.Variant>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Variant>>(NullValue));
          end
          else
          begin
            // Set the property with the actual value, adjust types
            if Prop.PropertyType.Name = 'TNullable<System.string>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<string>>(Value.AsString))
            else if Prop.PropertyType.Name = 'TNullable<System.Integer>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Integer>>(Value.AsInteger))
            else if Prop.PropertyType.Name = 'TNullable<System.Int64>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Int64>>(Value.AsInt64))
            else if Prop.PropertyType.Name = 'TNullable<System.Extended>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Extended>>(Value.AsExtended))
            else if Prop.PropertyType.Name = 'TNullable<System.Currency>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Currency>>(Value.AsCurrency))
            else if Prop.PropertyType.Name = 'TNullable<System.TDateTime>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<TDateTime>>(Value.AsType<TDateTime>))
            else if Prop.PropertyType.Name = 'TNullable<System.Boolean>' then
              Prop.SetValue(Pointer(ORMRec), TValue.From<TNullable<Boolean>>(Value.AsBoolean))
            // No need to handle TNullable<Variant> since no conversion required except as fallback
            else
              raise EORMUtilsException.CreateFmt('Unsupported nullable type: %s', [Prop.PropertyType.Name]);
          end;
        end
        else
          raise EORMUtilsException.CreateFmt('Unsupported record type: %s', [Prop.PropertyType.Name]);
      end
      else
      begin
        // Handle non-nullable types
        case Prop.PropertyType.TypeKind of
          tkString, tkLString, tkWString, tkUString:
            ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsString);
          tkInteger:
            ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsInteger);
          tkInt64:
            ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsInt64);
          tkFloat:
            if Prop.PropertyType.Handle = TypeInfo(Currency) then
              ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsCurrency)
            else if Prop.PropertyType.Handle = TypeInfo(TDateTime) then
              ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), TValue.From<TDateTime>(Value.AsType<TDateTime>))
            else
              ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsExtended);
          tkEnumeration:
            if Prop.PropertyType.Handle = TypeInfo(Boolean) then
              ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsBoolean)
            else
              ObjType.GetField(ORMFieldName).SetValue(Pointer(ORMRec), Value.AsString);
        else
          raise EORMUtilsException.CreateFmt('Unsupported type for field "%s" in record "%s".', [FieldName, ORMRec.ClassName]);
        end;
      end;

      // After setting the ORM property, find the associated component and update it
      var Component := ORMManager.FindComponent(ORMRec, FieldName);
      if Assigned(Component) then
      begin
        ORMManager.UpdateComponentFromORM(Component, ORMRec, FieldName);
      end;

    end
    else
      raise EORMUtilsException.CreateFmt('Property "%s" not found or not writable in record "%s".', [FieldName, ORMRec.ClassName]);
  finally
    // TRttiContext is a record and managed automatically
  end;
end;

end.

--------------
    


    Core Improvements:

    Introduce column behavior configuration (read-only, editable, custom edit)
    Add column metadata storage with display names and formatting options
    Implement event handlers for validation and editing control

    New Features:

    Filtering capability with real-time updates
    Sorting functionality by column
    CSV import/export functionality
    Record management (add, delete, move)
    Auto-generation of columns with customizable options

    Enhanced Type Support:

    Better handling of nullable types
    Improved type conversion and validation
    Support for custom edit masks
    Column alignment options


Based on the included information, please carefully review everything and suggest and implement enhancements on orm_binding unit. Give me the enhanced orm_binding unit without any gaps.