#1 2012-04-17 10:33:08

coblongpamor
Member
From: Bali-Indonesia
Registered: 2010-11-07
Posts: 130
Website

Need Help about SQLite3UIEdit

SI there anyone of you have been using SQLite3UIEdit?
Parameters of the procedure SetRecord FieldNamesWidth make all the same size field. While I like different size of the fields.
I tried adding a variable AnsiChar on SetRecord procedure by taking the value of the parameter RibbonParams.

 
... 
fFieldCaption [i]: = aCaption; 
if RibbonParams <> nil then begin 
ac: = RibbonParams ^. FieldWidth [i 1]; 
if ac in ['a' .. 'z'] then 
dec (ac, 32); 
fFieldLengthMean [i]: = (Ord (ac)) 200; 
/ / FieldNamesWidth: = fFieldLengthMean [i]; 
end; 
... 

if C.InheritsFrom (TCheckBox) then / / trick to avoid black box around 
  CC.SetBounds (FieldNamesWidth, Y, CW, CC.Height) else 
if C.InheritsFrom (TDateTimePicker) then begin 
  ... 
end else 
  / / C.SetBounds (FieldNamesWidth, Y, 200.22); 
  C.SetBounds (FieldNamesWidth, Y, fFieldLengthMean [i], 22); 
fFieldComponents [i]: = C; 

Field length but not as expected (maybe i put it in the wrong place).

I also do not understand, why aClient always Nil?
so that this code was never executed

 
sftID: 
if aClient <> nil then begin 
/ / ID field (TSQLRecord descendant) is handled by a component TComboBox 
/ / With all possible values of the corresponding descendant TSQLRecord 
IDClass: = TSQLRecordClass (P ^. PropType ^ ^. Classtype ^. Classtype); 
CB: = TComboBox.Create (Scroll); 
CB.Parent: = Scroll; / / need parent now for access CB.Items 
CB.Style: = csDropDownList; 
AID: = P ^. GetOrdValue (aRecord); 
with IDClass.RecordProps do 
if MainField [true]> = 0 then begin 
  aClient.OneFieldValues (IDClass, FieldsName [MainField [true]],'', CB.Items, @ AID); 
  CB.ItemIndex: = Aid; / / @ AID now contains the found index of AID 
end; 
end; 

one another, how to use events TOnComponentCreated, TOnComponentCreate, TOnComponentValidate?
also, I do not understand the usefulness and how to use SQLite3UIOptions.

I'm very grateful if anyone willing to help.

Thank you.

Offline

#2 2012-04-17 11:29:09

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,669
Website

Re: Need Help about SQLite3UIEdit

FieldWidth parameter is used for Grid display, and result into proportional computation of fFieldLengthMean[] values.
So you should not use these values for field display on edit screen.

As stated by the documentation, you have to call TRecordEditForm.SetRecord() to set the parameters of the window.
This is where the Client property is defined.

About event handlers:

    /// this event is used to customize screen text of property names
    OnCaptionName: TOnCaptionName;
    /// this event is used to customize the input components creation
    // - this event is also triggerred once at the creation of the Option window,
    // with Obj=Prop=nil and Parent=TOptionsForm: the event must
    // call method Parent.AddEditors() / Parent.SetRecord() to add fields to the
    // Option (this is not mandatory to the Record Edit window)
    // - this event is triggered once for every object, with Prop=nil,
    // and should return nil if the object is to be added to the dialog,
    // and something not nil if the object is to be ignored
    // (same as a runtime-level _Name object)
    // - this is the only mandatory event of this component, for TOptionsForm
    // - this event is not mandatory for TRecordEditForm (you can call
    // its SetRecord method directly)
    OnComponentCreate: TOnComponentCreate;
    /// this event is used to customize the input components after creation
    // - triggered when the component has been created
    // - can be used to disabled the component if user don't have the right
    // to modify its value; but he/she will still be able to view it
    OnComponentCreated: TOnComponentCreated;

Offline

#3 2012-04-17 13:11:04

coblongpamor
Member
From: Bali-Indonesia
Registered: 2010-11-07
Posts: 130
Website

Re: Need Help about SQLite3UIEdit

i'm sorry, but with

TRecordEditForm.SetRecord()

call, have error message

This form of method call only alowed for class method'

.
i'd read the documentation many times, but still don't understand.
I learned a lot about RTTI and Object Oriented Programming from this framework, prior my knowledge about it is very weak. Now it's getting better. but still not enough to understand the existing documentation on SQLite3UIEdit and SQLite3UIOption.

before, i call the RecordEditForm like this:

RecordEditForm:= TRecordEditForm.Create(Self);
  with RecordEditForm do try
    RecordEditForm.SetRecord(Client,aRec,nil,Ribbon);
    Result := ShowModal= mrOk;
  finally
    Free;
  end;

could you give me a small piece of code?

thank you.

Offline

#4 2012-04-18 05:29:07

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,669
Website

Re: Need Help about SQLite3UIEdit

Of course, TRecordEditForm.SetRecord() is not to be called as this: this is not a class method.
I referred to this method as TRecordEditForm.SetRecord, since it is defined as such in the source code implementation.

The way you are using TRecordEditForm with a temporary RecordEditForm variable is perfectly correct.

Offline

#5 2012-04-18 05:56:08

coblongpamor
Member
From: Bali-Indonesia
Registered: 2010-11-07
Posts: 130
Website

Re: Need Help about SQLite3UIEdit

thank you. but with this way the aClient is still always nil.

Offline

#6 2012-04-18 06:00:36

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,669
Website

Re: Need Help about SQLite3UIEdit

That is because your "Client" variable is nil when you call SetRecord(Client,...), I suppose!

Offline

#7 2012-04-18 06:13:06

coblongpamor
Member
From: Bali-Indonesia
Registered: 2010-11-07
Posts: 130
Website

Re: Need Help about SQLite3UIEdit

no. i'd toggled breakpoint (F5) at

RecordEditForm.SetRecord(Client,aRec,nil,Ribbon);

in this breakpoint, Client not nil.
i also toggled breakpoint in the SQLite3UIEdit.pas at this line:

..
fRec := aRecord;
fClient := aClient; //here aClient is nil.
CW := Scroll.ClientWidth;
...

thank you.

Offline

#8 2012-04-18 06:54:56

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,669
Website

Re: Need Help about SQLite3UIEdit

So what occurs in-between to the aClient variable?

If it is non nil when entering the method, why is it nil some steps later?

Offline

#9 2012-04-18 09:31:33

coblongpamor
Member
From: Bali-Indonesia
Registered: 2010-11-07
Posts: 130
Website

Re: Need Help about SQLite3UIEdit

that is what i don't understand.
at this time

RecordEditForm.SetRecord(Client,aRec,nil,Ribbon);

debuger show me hint something like

Client=(fCache:nil;fTransactionActive:0;fTransactionCriticalSession:(DebugInfo:$1F4CD8;.............

but, in the SQLite3UIEdit

..
fRec := aRecord;
fClient := aClient; //here debuger shown hint aClient = nil.
CW := Scroll.ClientWidth;
...

Offline

#10 2012-04-18 11:54:17

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,669
Website

Re: Need Help about SQLite3UIEdit

I'm not able to reproduce the issue here.

Step into the SetRecord method, adding a watchpoint about aClient.

Use step by step debugging (F8) to guess where aClient is set to nil.

There may be a stack buffer overflow issue somewhere.

Offline

#11 2012-04-18 15:29:43

coblongpamor
Member
From: Bali-Indonesia
Registered: 2010-11-07
Posts: 130
Website

Re: Need Help about SQLite3UIEdit

ok, thank you. i'll try to debug more carefully.

Offline

#12 2012-04-19 18:00:25

coblongpamor
Member
From: Bali-Indonesia
Registered: 2010-11-07
Posts: 130
Website

Re: Need Help about SQLite3UIEdit

hi. i solved the problem. should be

RecordEditForm.SetRecord(frmMain.Client,aRec,nil,Ribbon);

because Client variable exists in the RecordEditForm and frmMain form.

But, i have another issue with RecordEditForm.
How is the best way to implement the following scenario:
1. When RecordEditForm in mode to be Create, and everything is Ok to add a Record, I wish the form is still showing, and ready to receive new input from the user (all editor is blank).
- For that, I did it by adding a parameter CRUDAction: (caCreate, caRead, caUpdate, caDelete) on Procedure SetRecord(..)

if CRUDAction = caCreate then begin
	if Client.Add (Rec, True)> 0 then begin
		Rec.ClearProperties; //sometimes i get AV in this.
		Self.Hide;
		SetRecord (Client, Rec, caCreate, nil, fRibbon);
		Self.Show;
	end;
end else
	ModalResult: = mrOK;// Update Mode (CRUDAction = caUpdate)

2. When user click the Cancel button, and if there are field(s) had changed, firstly, I want ask to user, whether the changes they made to the save or not.
- For this purpose, I've tried doing a litle test as follows:

...
sftID: begin
           SetIndex if <0 then
             AID: = 0 else
           begin
             AID: = PtrInt (CB.Items.Objects [SetIndex]);
             if AID <> StrToInt (Rec.GetFieldValue (S2U (P ^. Name))) then / / this value is changed
               MessageBox (0, PAnsiChar (Rec.GetFieldValue (S2U (P ^. Name))), PAnsiChar (IntToStr (AID)), 0);
           end;
           P ^. SetOrdValue (Rec, AID);
           Include (ModifiedFields, FieldIndex);
         end;
...

but, likely I need to split BtnSaveClick procedure;

I need your advice.

Offline

#13 2012-04-20 05:04:35

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,669
Website

Re: Need Help about SQLite3UIEdit

I suspect you'll have to implement this workflow outside SQLite3UIEdit.

Just call the form several times with several ShowModal, depending on your CRUD state.

Offline

#14 2012-04-20 06:41:46

coblongpamor
Member
From: Bali-Indonesia
Registered: 2010-11-07
Posts: 130
Website

Re: Need Help about SQLite3UIEdit

ok. i'll try it. thank you.

Offline

#15 2012-05-01 16:20:50

coblongpamor
Member
From: Bali-Indonesia
Registered: 2010-11-07
Posts: 130
Website

Re: Need Help about SQLite3UIEdit

I tried to use the event OnComponentCreated on RecordEditForm to create two buttons on the right control for the sftid field type.

procedure TBPBaseEditForm.DOOnComponentCreated(Obj: TObject; Prop: PPropInfo; Comp: TWinControl);
var
  SQLFieldType: TSQLFieldType;
  P: PPropInfo;
  ButtonAdd, ButtonEdit: TSynButton;
begin
  P:= Prop;
  SQLFieldType:= P^.PropType^^.SQLFieldType;
  case SQLFieldType of
    sftID: begin
      ButtonAdd := TSynButton.Create(Comp.Parent);
      with ButtonAdd do begin
        Parent := Comp.Parent;
        SetBounds(Comp.Left + Comp.Width + 1,Comp.Top - 1,50,Comp.Height + 2);
        Caption := 'Add';
//      B.OnClick := BClick;
        SetBitmap(BitmapArrow);
        Anchors := [akLeft, akTop];
      end;
      ButtonEdit:= TSynButton.Create(Comp.Parent);
      with ButtonEdit do begin
        Parent := Comp.Parent;
        SetBounds(ButtonAdd.Left + ButtonAdd.Width + 1,ButtonAdd.Top,50,ButtonAdd.Height);
        Caption := 'Edit';
//      B.OnClick := BClick;
        SetBitmap(BitmapArrow);
        Anchors := [akLeft, akTop];
      end;
    end;
  end;
end;

but, I had trouble in adjusting the position of the buttons.
unless I move the trigger event, to the bottom.

....
fFieldComponents[i] := C;
//i move it to here
if Assigned(OnComponentCreated) then // allow component customization
  OnComponentCreated(aRecord,P,C); // e.g. set C.Enabled := false
inc(Y,aHeight);
...

one more, can you add BitmapAdd and BitmapEdit to the SynTaskDialog.res?

thank you

Offline

#16 2012-05-02 14:02:32

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,669
Website

Re: Need Help about SQLite3UIEdit

You should better use a resourcestring for the captions (e.g. sAdd and sEdit).

I've moved OnComponentCreated event call after all component initialization, to allow adding paired components on purpose, as you wanted to.
See http://synopse.info/fossil/info/8d2b799f3f

The sftID kind of field should better handle "Add" and "Edit" buttons by the unit, you are right.
A more generic implementation is needed. But perhaps the new form add or edit should also be calling an automated created form like SQLite3UIEdit.
In this case, generic bitmaps for those actions do make sense.

Offline

#17 2012-05-02 14:59:57

coblongpamor
Member
From: Bali-Indonesia
Registered: 2010-11-07
Posts: 130
Website

Re: Need Help about SQLite3UIEdit

thank you.
I tried to call the form automatically, by creating an instance of TSynButton.

TSQLRecordButtonClass = class of TSQLRecordButton;

  TSQLRecordButton = class(TSynButton)
  private
    fProp: PPropInfo;
    fRecordClass: TSQLRecordClass;
    fAssociateComponent: TWinControl;
  protected
  public
    property Prop: PPropInfo read fProp write fProp;
    property RecordClass: TSQLRecordClass read fRecordClass;
    property AssociateComponent: TWinControl read fAssociateComponent;

    constructor Create(aOwner: TComponent; P: PPropInfo; Comp: TWinControl); reintroduce;
    class function CreateButton(aOwner: TComponent; P: PPropInfo;
      Comp: TWinControl; RecordButtonClass: TSQLRecordButtonClass): TSQLRecordButton;
  end;

  TSQLRecordAddButton = class(TSQLRecordButton);

  TSQLRecordEditButton = class(TSQLRecordButton);

I'm not sure, if this is a good idea or not. but so far it's working fine,
except I can not get RibbonTabParameters, because not all of RecordClass listed on the ribbon.
I'm hoping to get suggestions for a better way.

The following is a complete unit of BPBaseEditForm.

unit BPBaseEdit;

interface

uses
  Windows,
  Messages,
  SysUtils,
  Variants,
  Classes,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  StdCtrls,
  ExtCtrls,
  ComCtrls,
  SynCommons,
  SQLite3Commons,
  SQLite3i18n,
  SQLite3ToolBar,
  SQLite3UIEdit,
  SQLite3UILogin,
  SQLBaseModel,
  BPFrame,
  ImgList,
  SynTaskDialog;
type
  TSQLRecordButtonClass = class of TSQLRecordButton;

  TSQLRecordButton = class(TSynButton)
  private
    fProp: PPropInfo;
    fRecordClass: TSQLRecordClass;
    fAssociateComponent: TWinControl;
  protected
  public
    property Prop: PPropInfo read fProp write fProp;
    property RecordClass: TSQLRecordClass read fRecordClass;
    property AssociateComponent: TWinControl read fAssociateComponent;

    constructor Create(aOwner: TComponent; P: PPropInfo; Comp: TWinControl); reintroduce;
    class function CreateButton(aOwner: TComponent; P: PPropInfo;
      Comp: TWinControl; RecordButtonClass: TSQLRecordButtonClass): TSQLRecordButton;
  end;

  TSQLRecordAddButton = class(TSQLRecordButton);

  TSQLRecordEditButton = class(TSQLRecordButton);

  TBPBaseEditForm = class(TRecordEditForm)
    pnlFrame: TPanel;
    il16: TImageList;
    il32: TImageList;
    procedure BtnSaveClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    fModified: Boolean;
    fPagerTop: TSynPager;
    fTB: TSQLCustomToolBar;
    fDetail: TFrameDetail;
  protected
    procedure CRUDActionClick(Sender: TObject);
    procedure CRUDManyActionClick(Sender: TObject);
    procedure DOOnChange(Sender: TObject);
    procedure SetDetail(aDetail:TFrameDetail);
  public
    { Public declarations }
    function DOOnComponentCreate(Obj: TObject; Prop: PPropInfo; Parent: TWinControl): TWinControl;
    procedure DOOnComponentCreated(Obj: TObject; Prop: PPropInfo; Comp: TWinControl);

    property Modified: Boolean read fModified write fModified;
    property Detail: TFrameDetail read fDetail write SetDetail;
  end;

//var BPBaseEditForm: TBPBaseEditForm;

implementation

uses Contnrs;

{$R *.dfm}

{TSQLRecordButton}
constructor TSQLRecordButton.Create(aOwner: TComponent; P: PPropInfo; Comp: TWinControl);
begin
  fProp:= P;
  fRecordClass:= TSQLRecordClass(fProp^.Proptype^^.ClassType^.Classtype);
  fAssociateComponent:= Comp;
  Inherited Create(aOwner);
end;

class function TSQLRecordButton.CreateButton(aOwner: TComponent; P: PPropInfo;
  Comp: TWinControl; RecordButtonClass: TSQLRecordButtonClass): TSQLRecordButton;
begin
  if (aOwner = nil) or (P = nil) then
    Result:= nil
  else Result:= RecordButtonClass.Create(aOwner,P,Comp);
end;

{TBPBaseEditForm}
procedure TBPBaseEditForm.CRUDActionClick(Sender: TObject);
var
  aRec: TSQLRecord;
  BaseEditForm: TBPBaseEditForm;
  aID, SetIndex: Integer;
  CB: TComboBox;
  IsOK: Boolean;

  function CRUD(aRec: TSQLRecord): Boolean;
  begin
    BaseEditForm.SetRecord(Client,aRec);
    Result := BaseEditForm.ShowModal= mrOk;
  end;

  procedure RefreshComboBox(aClient: TSQLRestClient; P: PPropInfo);
  var
    IDClass: TSQLRecordClass;
    SourceID,OldIndex: Integer;
  begin
    if aClient <> nil then begin
      OldIndex:= CB.ItemIndex;
      IDClass := TSQLRecordClass(P^.PropType^^.ClassType^.ClassType);
      SourceID := P^.GetOrdValue(Rec);
      with IDClass.RecordProps do
      if MainField[True] >= 0 then begin
        aClient.OneFieldValues(IDClass,FieldsName[MainField[True]],'',CB.Items,@SourceID);
        CB.ItemIndex:= OldIndex;
      end;
    end;
  end;
begin
  with TSQLRecordButton(Sender) do
  begin
    aRec:= RecordClass.Create;
    CB:= TComboBox(AssociateComponent);
    SetIndex:= CB.ItemIndex;
    if SetIndex < 0 then
      aID := 0
    else aID := PtrInt(CB.Items.Objects[SetIndex]);
  end;
  if aRec = nil then
    Exit;
  BaseEditForm:= TBPBaseEditForm.Create(Application);
  try
    if Sender.InheritsFrom(TSQLRecordAddButton) then
      while CRUD(aRec) do
      try
        aID:= Client.Add(aRec,True);
        if aID > 0 then begin
          aRec.ClearProperties;
        end;
      finally
        Client.UnLock(aRec);
      end else
    if Sender.InheritsFrom(TSQLRecordEditButton) then
    try
      if Client.Retrieve(aID,aRec,False) then
      try
        IsOK:= CRUD(aRec);
        if IsOK then
          Client.Update(aRec);
      finally
        Client.UnLock(aRec);
      end;
    finally
    end;
    RefreshComboBox(Client, TSQLRecordButton(Sender).Prop);
  finally
    aRec.Free;
    BaseEditForm.Free;
  end;
end;


procedure TBPBaseEditForm.CRUDManyActionClick(Sender: TObject);
var
  aRec: TSQLRecord;
  aMany: TSQLRecordMany;
  BaseEditForm: TBPBaseEditForm;
  aID,aTag: Integer;
  function CRUD(aRec: TSQLRecord): Boolean;
  begin
    BaseEditForm.SetRecord(Client,aRec);
    Result := BaseEditForm.ShowModal= mrOk;
  end;
begin
  aTag:= TSynToolButton(Sender).Tag;
  case aTag of
    Ord(caCreate)..Ord(caUpdate):begin
      BaseEditForm:= TBPBaseEditForm.Create(Application);
      try
        aRec:= Detail.CurrentPage.Dest;
        if aRec = nil then
          Exit;
        aMany:= Detail.CurrentPage.Many;
        if aTag = Ord(caCreate) then
          while CRUD(aRec) do
          try
            aID:= Client.Add(aRec,True);
            if aID > 0 then begin
              aMany.ManyAdd(Client,Rec.ID,aID,True);
              aRec.ClearProperties;
              Detail.RefreshCurrentPage;
            end;
          finally
            Client.UnLock(aRec);
          end else
        if aTag = Ord(caUpdate) then
        try
        finally
        end;
      finally
      end;
    end;
  end;
end;

procedure TBPBaseEditForm.DOOnChange(Sender: TObject);
begin
  Modified:= True;
end;

procedure TBPBaseEditForm.SetDetail(aDetail: TFrameDetail);
begin
  if aDetail <> nil then
    fDetail:= aDetail
  else
    fDetail:= TFrameDetail.Create(Self,Client,Rec);
end;

function TBPBaseEditForm.DOOnComponentCreate(Obj: TObject; Prop: PPropInfo; Parent: TWinControl): TWinControl;
var
  SQLFieldType: TSQLFieldType;
  P: PPropInfo;
begin
  Result:= nil;
  P:= Prop;
  SQLFieldType:= P^.PropType^^.SQLFieldType;
  case SQLFieldType of
    sftMany: begin
      Result := TComboBox.Create(Parent);
    end;
  end;
end;

procedure TBPBaseEditForm.DOOnComponentCreated(Obj: TObject; Prop: PPropInfo; Comp: TWinControl);
const
  W = 50;
var
  P: PPropInfo;
  SQLFieldType: TSQLFieldType;
  L: Integer;
begin
  P:= Prop;
  SQLFieldType:= P^.PropType^^.SQLFieldType;
  case SQLFieldType of
    sftID: begin
      with TSQLRecordButton.CreateButton(Comp.Parent,Prop,Comp,TSQLRecordAddButton) do begin
        Tag:= 0;
        Parent := Comp.Parent;
        L:= Comp.Left + Comp.Width + 1;
        SetBounds(L,Comp.Top - 1,W,Comp.Height + 2);
        Caption := 'Add';
        OnClick := CRUDActionClick;
        SetBitmap(BitmapArrow);
        Anchors := [akLeft, akTop];
      end;

      with TSQLRecordButton.CreateButton(Comp.Parent,Prop,Comp,TSQLRecordEditButton) do begin
        Tag:= 1;
        Parent := Comp.Parent;
        L:= L + W + 1;
        SetBounds(L,Comp.Top - 1,W,Comp.Height + 2);
        Caption := 'Edit';
        OnClick := CRUDActionClick;
        SetBitmap(BitmapArrow);
        Anchors := [akLeft, akTop];
      end;
    end;
  end;

  if Comp.InheritsFrom(TLabeledEdit) then begin
    if P^.Name = 'Name' then
      Comp.Width:= Comp.Width + 100;
    TLabeledEdit(Comp).OnChange:= DOOnChange;
  end else
  if Comp is TDateTimePicker then begin
    TDateTimePicker(Comp).OnChange:= DOOnChange;
  end else
  if Comp is TComboBox then begin
    TComboBox(Comp).OnChange:= DOOnChange;
  end;
end;

procedure TBPBaseEditForm.BtnSaveClick(Sender: TObject);
begin
  Modified:= False;
  inherited BtnSaveClick(Sender);
end;

resourcestring
  sSave= 'Save Changes ?';

procedure TBPBaseEditForm.FormClose(Sender: TObject;
  var Action: TCloseAction);
var
  aAnswer: Integer;
begin
  inherited;
  if Modified then begin
    aAnswer:= MessageDlg(sSave,mtConfirmation,mbYesNoCancel,0);
    case aAnswer of
      mrYes: BtnSaveClick(Sender);
      mrNo:;
      mrCancel:Action:= caNone;
    end;
  end;
end;

procedure TBPBaseEditForm.FormCreate(Sender: TObject);
begin
  inherited;
//  OnComponentCreate:= DOOnComponentCreate;
  OnComponentCreated:= DOOnComponentCreated;

  BottomPanel.BevelOuter:= bvNone;
  BottomPanel.Height:= 40;
  BtnSave.SetBounds(BtnSave.Parent.Width - 220,5,100,27);
  BtnCancel.SetBounds(BtnSave.Left + BtnSave.Width,5,100,27);
  BtnSave.OnClick:= BtnSaveClick;
end;

[b]
procedure TBPBaseEditForm.FormShow(Sender: TObject);
begin
  with Rec.RecordProps do
    if Pointer(ManyFields) <> nil then try
      Detail:= TFrameDetail.Create(pnlFrame,Client,Rec);
      Detail.Parent:= pnlFrame;

      fPagerTop:= TSynPager.Create(Detail);
      fPagerTop.Parent:= Detail;
      fPagerTop.Align:= alTop;
      fPagerTop.Height:= 40;
      fTB.Init(fPagerTop,TypeInfo(TCRUDAction),CRUDManyActionClick,nil,'');
      fTB.AddToolBar('');
      fTB.Toolbars[0].Align:= alClient;
    except
    end;
  pnlFrame.Visible:= Detail <> nil;
  if pnlFrame.Visible then begin
    ClientHeight:= ClientHeight + pnlFrame.Height;
  end;
  SetStyle(Self);
  inherited;
end;
[/b]

procedure TBPBaseEditForm.FormDestroy(Sender: TObject);
begin
  if fPagerTop <> nil then
    fPagerTop.Free;
  if fDetail <> nil then
    fDetail.Free;

  inherited;
end;

end.

I also tried to handle sftMany SQLFieldType.
please note the FormShow () and CRUDManyActionClick ().
It also works pretty well, but I'm still not satisfied with the results.
I feel this way is not true.
The following is what I am trying to do on the unit FrameDetai

unit BPFrame;

interface

uses
  Windows,
  Messages,
  SysUtils,
  Variants,
  Classes,
  Graphics,
  Controls,
  ComCtrls,
  Forms,
  Dialogs,
  SynCommons,
  SQLite3Commons,
  SQLite3UI,
  SQLite3ToolBar,
  SQLite3UILogin,
{$IFDEF USENEXTPACK}
  NxColumnClasses,
  NxColumns,
  NxScrollControl,
  NxCustomGridControl,
  NxCustomGrid,
  NxGrid,
{$ELSE}
  Grids,
{$ENDIF}
  ExtCtrls;
type
  TPagerMany= class;

  TPageMany= class(TSynPage)
  private
    fDest: TSQLRecord;
    fMany: TSQLRecordMany;
    fTable: TSQLTable;
    {$IFDEF USENEXTPACK}
    fGrid: TNextGrid;
    {$ELSE}
    fTableToGrid: TSQLTableToGrid;
    fGrid: TDrawGrid;
    {$ENDIF}
    fSourceCSVFieldNames: PUTF8Char;
    fDestCSVFieldNames: PUTF8Char;
  protected
    function CreateGrid:{$IFDEF USENEXTPACK}TNextGrid{$ELSE}TDrawGrid{$ENDIF};
  public
    class function CreatePageMany(aPager: TPagerMany; aMany: TSQLRecordMany;
      SourceCSVFieldNames: PUTF8Char=nil; DestCSVFieldNames: PUTF8Char=nil): TPageMany;

    property Dest: TSQLRecord read fDest write fDest;
    property Many: TSQLRecordMany read fMany;
    property Table: TSQLTable read fTable write fTable;
    property SourceCSVFieldNames: PUTF8Char read fSourceCSVFieldNames;
    property DestCSVFieldNames: PUTF8Char read fDestCSVFieldNames;
    {$IFDEF USENEXTPACK}
    property Grid: TNextGrid read fGrid write fGrid;
    {$ELSE}
    property TableToGrid: TSQLTableToGrid read fTableToGrid write fTableToGrid;
    property Grid: TDrawGrid read fGrid write fGrid;
    {$ENDIF}

    destructor Destroy; override;
  end;

  TPagerMany= class(TSynPager)
  private
    fClient: TSQLRestClient;
    fSource: TSQLRecord;
    function GetActivePageIndex: integer;
    procedure SetActivePageIndex(const Value: integer);
  protected
    procedure Change; override;
    function GetPageMany(aIndex: integer): TPageMany; {$ifdef HASINLINE}inline;{$endif}
    function GetSQLMany(aIndex: integer): TSQLRecordMany; {$ifdef HASINLINE}inline;{$endif}
  public
    class function CreatePagerMany(aOwner: TComponent; aClient: TSQLRestClient; aSource: TSQLRecord): TPagerMany;
    /// add a page instance
    function AddPage(aPage: TPageMany): integer; overload;
    /// create a new page with the specified caption
    function AddPage: integer; overload;
    /// mimic TTabSheet.Pages property
    property Pages[aIndex: Integer]: TPageMany read GetPageMany;
    /// force OnChange event to be triggered
    property ActivePageIndex: integer read GetActivePageIndex write SetActivePageIndex;

    property Client: TSQLRestClient read fClient;
    property Source: TSQLRecord read fSource;
  published
    property OnChange;
  end;

  TFrameDetail = class(TFrame)
  private
    { Private declarations }
    fClient: TSQLRestClient;
    fSource: TSQLRecord;
    fCurrentPage: TPageMany;
  protected
    function GetActivePage: TPageMany;
    procedure DoPageChange(Sender: TObject);
  public
    { Public declarations }
    PagerMany: TPagerMany;

    property Client: TSQLRestClient read fClient;
    property Source: TSQLRecord read fSource;
    property CurrentPage: TPageMany read GetActivePage write fCurrentPage;

    procedure RefreshCurrentPage;
    constructor Create(aOwner: TComponent; aClient: TSQLRestClient; aSource: TSQLRecord); reintroduce;
    destructor Destroy; override;
  end;

implementation

{$R *.dfm}

{TPageMany}
function TPageMany.CreateGrid:{$IFDEF USENEXTPACK}TNextGrid{$ELSE}TDrawGrid{$ENDIF};
var
  {$IFDEF USENEXTPACK}
  CC: TNxColumnClass;
  {$ENDIF}
  C,R,aID: Integer;
  S: String;
  aClient: TSQLRestClient;
  aSource: TSQLRecord;
  FieldTableClass, IDClass: TSQLRecordClass;
  aRecord: TSQLRecord;
  P: PPropInfo;
  NeedCreation: Boolean;
begin
  with TPagerMany(Self.PageControl) do begin
    aClient:= fClient;
    aSource:= fSource;
  end;
  
  NeedCreation:= Self.Grid = nil;

  if NeedCreation then begin
    Grid:= {$IFDEF USENEXTPACK}TNextGrid.Create(Self){$ELSE}TDrawGrid.Create(Self){$ENDIF};
  end;
  with Grid do try
    if NeedCreation then begin
      Parent:= Self;
      Align:= alClient;
    end;
    Table:= Many.DestGetJoinedTable(aClient,'',aSource.ID,jkDestFields);
    if Table <> nil then try
      {$IFDEF USENEXTPACK}
      if NeedCreation then begin
        for C := 0 to Table.FieldCount - 1 do begin
          case Table.FieldType(C,nil) of
            sftAnsiText,sftUTF8Text: CC:= TNxTextColumn;
            sftEnumerate,sftSet: CC:= TNxCheckBoxColumn;
            sftInteger: CC:= TNxIncrementColumn;
            sftID: CC:= TNxComboBoxColumn;
            sftDateTime,sftTimeLog,sftCreateTime,sftModTime: CC:= TNxDateColumn;
          end;
          with Columns.Add(CC, Table.GetCaption(0,C)) do begin
            Options:= [coCanInput];
          end;
        end;
        AppearanceOptions:= [aoBoldTextSelection,aoIndicateSortedColumn];
        Options:= [goDisableColumnMoving,goFooter,goGrid,goHeader,goInplaceEditEvents,goLockFixedCols,goIndicator,goInput];
      end;

      ClearRows;
      AddRow(Table.RowCount);
      for R := 1 to Table.RowCount do
        for C := 0 to Table.FieldCount - 1 do begin
          case Table.ExpandAsString(R,C,aClient,S) of
            sftID:
            if aClient <> nil then try
              FieldTableClass:= TSQLRecordClass(Table.FieldTable(C));
              aRecord:= FieldTableClass.Create(aClient,StrToInt(S));
              P:= FieldTableClass.RecordProps.Fields[C+1];
//              S:= FieldTableClass.RecordProps.Fields[C+1].Name;
              IDClass := TSQLRecordClass(P^.PropType^^.ClassType^.ClassType);
              aID := P^.GetOrdValue(aRecord);
              with IDClass.RecordProps do
              if MainField[true]>=0 then begin
                aClient.OneFieldValues(IDClass,FieldsName[MainField[True]],'',
                  TNxComboBoxColumn(Grid.Columns[C]).Items,@aID);
                TNxComboBoxColumn(Grid.Columns[C]).Index:= aID;
                if aID > 0 then
                  S:= TNxComboBoxColumn(Grid.Columns[C]).Items[aID];
              end;
            finally
              aRecord.Free;
            end;
          end;
          Cell[C,R-1].AsString:= S;
        end;
      {$ELSE}
        RowCount:= 1;
        ColCount:= 1;
      TableToGrid:= TSQLTableToGrid.Create(Grid,Table,TSQLRestClientURI(aClient));
      {$ENDIF};
    finally
    end;
  finally
  end;
  Result:= Grid;
end;

class function TPageMany.CreatePageMany(aPager: TPagerMany; aMany: TSQLRecordMany;
  SourceCSVFieldNames: PUTF8Char=nil; DestCSVFieldNames: PUTF8Char=nil): TPageMany;
var
  PM: TPageMany;
  DestClass: TSQLRecordClass;
begin
  PM:= TPageMany.Create(aPager);
  PM.Caption:= FormatUTF8('[%]',[aMany.SQLTableName]);
  aPager.AddPage(PM);
  PM.Name := 'P'+IntToStr(Random(GetTickCount));
  PM.Tag := PtrInt(PM);
  PM.fMany:= aMany;
  DestClass:= PM.fMany.RecordProps.RecordManyDestClass;
  PM.Dest:= DestClass.Create;
  PM.PageControl := aPager;
  PM.Grid:= PM.CreateGrid;
  SetStyle(PM);
  Result:= PM;
end;

destructor TPageMany.Destroy;
begin
  fDest.Free;
  fMany.Free;
  fTableToGrid.Free;
  fGrid.Free;
  inherited Destroy;
end;

{ TPagerMany }
function TPagerMany.AddPage(aPage: TPageMany): Integer;
begin
  aPage.PageControl := Self;
  Result := PageCount-1;
end;

function TPagerMany.AddPage: Integer;
var aPage: TPageMany;
begin
  aPage := TPageMany.Create(Self);
  aPage.Parent := Self;
  Result := AddPage(aPage);
end;

function TPagerMany.GetPageMany(aIndex: integer): TPageMany;
begin
  result := inherited Pages[aIndex] as TPageMany;
end;

function TPagerMany.GetSQLMany(aIndex: integer): TSQLRecordMany;
begin
  Result:= Pages[aIndex].Many;
end;

function TPagerMany.GetActivePageIndex: integer;
begin
  Result := inherited ActivePageIndex;
end;

procedure TPagerMany.SetActivePageIndex(const Value: integer);
begin
  inherited ActivePageIndex := Value;
  Change;
  if Assigned(OnChange) then
    OnChange(Self);
end;

procedure TPagerMany.Change;
begin
  inherited Change;
end;

class function TPagerMany.CreatePagerMany(aOwner: TComponent; aClient: TSQLRestClient; aSource: TSQLRecord): TPagerMany;
begin
  Result:= TPagerMany.Create(aOwner);
  if aOwner is TWinControl then
    Result.Parent := TWinControl(aOwner);
  Result.fClient:= aClient;
  Result.fSource:= aSource;
  Result.HotTrack := True;
  Result.ControlStyle := Result.ControlStyle+[csClickEvents]; // enable OnDblClick
  Result.Align := alClient;
  Result.Font.Style:= Result.Font.Style + [fsBold];
  Result.MultiLine:= True;
  Result.TabWidth:= 75;
  Result.OwnerDraw:= True;
  SetStyle(Result);
end;

{TFrameDetail}
function TFrameDetail.GetActivePage: TPageMany;
begin
  with PagerMany do
    Result:= Pages[GetActivePageIndex];
end;

procedure TFrameDetail.DoPageChange(Sender: TObject);
begin
  if not(Sender.InheritsFrom(TPageControl)) then
    Exit;
  CurrentPage:= GetActivePage;
  CurrentPage.CreateGrid;
end;

procedure TFrameDetail.RefreshCurrentPage;
begin
  CurrentPage.CreateGrid; //will do all the magiq
end;

constructor TFrameDetail.Create(aOwner: TComponent; aClient: TSQLRestClient; aSource: TSQLRecord);
var
  i: Integer;
  P: PPropInfo;
  aMany: TSQLRecordMany;
  IDClass: TSQLRecordClass;
begin
  inherited Create(aOwner);
  fClient:= aClient;
  fSource:= aSource;

  with Source.RecordProps do
  if PagerMany = nil then begin
    PagerMany:= TPagerMany.CreatePagerMany(Self,Client,Source);
    PagerMany.OnChange:= DoPageChange;
    for i := 0 to High(ManyFields) do begin
      aMany := TSQLRecordManyClass(ManyFields[i]^.PropType^^.ClassType^.ClassType).Create;
      TPageMany.CreatePageMany(PagerMany,aMany);
    end;
  end;

  if aOwner is TWinControl then
    Self.Parent:= TWinControl(aOwner);
  Self.Align:= alClient;
  SetStyle(Self);
end;

destructor TFrameDetail.Destroy;
begin
//  PagerTop.Free;
//  PagerMany.Free;
//  fSource.Free;
//  fClient.Free;
  inherited Destroy;
end;

end.

Offline

#18 2012-05-02 15:35:30

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,669
Website

Re: Need Help about SQLite3UIEdit

Nice post.

I'll look at this.
Worth updating the official mORMot code, to include your sftID handling, on request.

Offline

#19 2012-05-03 03:41:59

coblongpamor
Member
From: Bali-Indonesia
Registered: 2010-11-07
Posts: 130
Website

Re: Need Help about SQLite3UIEdit

yes please, i need it, and thanks.
a also plan to add Browse Button, to show Browse form that containing TSQLRecordClass data related to sftID.
in this form (browse form) i plan to use facility of SQLite3UIQuery.
but with filtering (not marking) the match row.
i mean, only the matches rows will be displayed on the grid.
is it possible?

Offline

#20 2012-05-03 06:04:11

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,669
Website

Re: Need Help about SQLite3UIEdit

Yes, this is feasible, of course.

And adding a Browse button does make sense, in case of a huge list.
In fact, using a TComboBox (like in the default implementation) would lead into something slow and difficult to use, in case of a huge number of rows.
In this case, a popup grid window, with UIQuery optionally, could be more handy.

Offline

Board footer

Powered by FluxBB