Search code examples
delphilivebindings

How to write live binding expression that control TEdit.PasswordChar based on TCheckBox.Checked?


I have 2 controls on a form, TCheckBox and TEdit.

I want to use Live Binding to perform this:

  1. When TCheckBox.Checked = True, set TEdit.PasswordChar = *
  2. When TCheckBox.Checked = False, set TEdit.PasswordChar = #0

How may I write ControlExpression to achieve this? It would be great if I can avoid register custom method.


Solution

  • Here's a simple example. I couldn't find a boolean expression evaluator so I registered a new one, and also a string-to-char converter (seems to be missing, too).

    The form:

    object Form1: TForm1
      Left = 0
      Top = 0
      Caption = 'Form1'
      ClientHeight = 282
      ClientWidth = 418
      Color = clBtnFace
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -11
      Font.Name = 'Tahoma'
      Font.Style = []
      OldCreateOrder = False
      PixelsPerInch = 96
      TextHeight = 13
      object CheckBox1: TCheckBox
        Left = 24
        Top = 24
        Width = 97
        Height = 17
        Caption = 'CheckBox1'
        TabOrder = 0
        OnClick = CheckBox1Click
      end
      object Edit1: TEdit
        Left = 24
        Top = 56
        Width = 121
        Height = 21
        TabOrder = 1
        Text = 'Edit1'
      end
      object BindingsList1: TBindingsList
        Methods = <>
        OutputConverters = <>
        UseAppManager = True
        Left = 212
        Top = 13
        object BindExpression1: TBindExpression
          Category = 'Binding Expressions'
          ControlComponent = Edit1
          SourceComponent = CheckBox1
          SourceExpression = 'iif(Checked, '#39'*'#39', '#39#39')'
          ControlExpression = 'PasswordChar'
          NotifyOutputs = True
          Direction = dirSourceToControl
        end
      end
    end
    

    and the code:

    unit Unit1;
    
    interface
    
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Data.Bind.EngExt, Vcl.Bind.DBEngExt, System.Rtti,
      Vcl.Bind.Editors, Data.Bind.Components, System.Bindings.Outputs;
    
    type
      TForm1 = class(TForm)
        CheckBox1: TCheckBox;
        Edit1: TEdit;
        BindingsList1: TBindingsList;
        BindExpression1: TBindExpression;
        procedure CheckBox1Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    uses
      System.TypInfo,
      System.Bindings.EvalProtocol,
      System.Bindings.Methods;
    
    resourcestring
      sIifArgError = 'Expected three variables for Iif() call';
      sIifExpectedBoolean = 'First argument to Iif() must be a boolean';
    
    function MakeIif: IInvokable;
    begin
      Result := MakeInvokable(
        function(Args: TArray<IValue>): IValue
        var
          V: IValue;
          B: Boolean;
        begin
          if Length(Args) <> 3 then
            raise EEvaluatorError.Create(sIifArgError);
          V := Args[0];
          if (V.GetType^.Kind <> tkEnumeration) or (V.GetType^.Name <> 'Boolean') then
            raise EEvaluatorError.Create(sIifExpectedBoolean);
    
          B := V.GetValue.AsBoolean;
          if B then
            Result := TValueWrapper.Create(Args[1].GetValue)
          else
            Result := TValueWrapper.Create(Args[2].Getvalue);
        end
      );
    end;
    
    procedure TForm1.CheckBox1Click(Sender: TObject);
    begin
      BindingsList1.Notify(CheckBox1, 'Checked');
    end;
    
    initialization
      TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(MakeIif, 'iif', 'iif', '', True, '', nil));
      TValueRefConverterFactory.RegisterConversion(TypeInfo(string), TypeInfo(Char),
        TConverterDescription.Create(
          procedure(const I: TValue; var O: TValue)
          var
            S: string;
          begin
            S := I.AsString;
            if Length(S) = 1 then
              O := S[1]
            else
              O := #0;
          end,
          'StringToChar', 'StringToChar', '', True, '', nil));
    
    finalization
      TValueRefConverterFactory.UnRegisterConversion('StringToChar');
      TBindingMethodsFactory.UnRegisterMethod('iif');
    
    end.