2

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.

Chau Chee Yang
  • 18,422
  • 16
  • 68
  • 132
  • 4
    Better assign checkbox to an action and write "native" code on execute. Maybe in some cases using live bindings have a reason, but they definitely are not for lazy programmers ;) – Marcodor Jul 20 '12 at 05:54

1 Answers1

2

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.
Ondrej Kelle
  • 36,941
  • 2
  • 65
  • 128
  • 5
    This example shows how ridiculous the livebinding design is. Write tons of code and **still** have to implement the OnClick event. – Stefan Glienke Jul 20 '12 at 10:10
  • You don't **have to** implement the `OnClick` event handler as shown here. There are other possibilities (e.g. `Application.OnIdle` with generic code to automatically re-evaluate all active bindings, similar to Actions). – Ondrej Kelle Jul 20 '12 at 10:25
  • Even worse. Components should be binding aware and in theory they are since they support the bindlink which is used when working with datasets afaik. But it does not work properly with component to component or component to simple data object bindings. – Stefan Glienke Jul 20 '12 at 11:02
  • Agreed, a better way would be to have all bindings evaluated automatically, when the source component changes. Perhaps this could be done generically with some kind of RTTI hook. – Ondrej Kelle Jul 20 '12 at 11:05
  • Writing OnChange event handler is fine as it may have great chance to shared by many controls. The binding method promote code re-use and may greatly reduce the code in .pas files to focus on logics other than UI controls interaction. – Chau Chee Yang Jul 21 '12 at 00:05