퀀텀 그리드에서 DB를 연결하지 않고 데이타를 출력하는 방법에는 Unbound Mode( 일반 String Grid 처럼 입력하는 방법 ) 와 Provider Mode( 사용자가 임의로 CustomerDataSource를 생성하여 구성하는 방법 )이 있습니다.
가장 많이 사용되는 방법은 Unbound Mode입니다. 하지만, 가끔은 Provider Mode로 작업을 해야 하는 경우가 있습니다.
Provider Mode는 일반 DBGrid처럼 DataSource를 연결하는 방식이 아니라 사용자가 직접 TcxCustomDataSource라는 Class를 상속받아 데이타를 직접 관리하는 방법입니다.
이번 내용도 역시 본 제작자가 이동 또는 게시하지 않는 경우를 제외하고는 이곳 외에 다른 곳에서 볼 수 없습니다. 이를 어 길시 정말 법적 제제가 가해질 수 있습니다.
Provider Mode는 사용자가 제작한 Class와 퀀텀에서 제공하는 TcxCustomDataSource를 상속받아 데이타를 표현하는 방법이므로 처음에는 그리 만만한 작업이 아닙니다. 특히 Unbound모드에 비해 코딩량이 꽤나 많이 필요로 합니다.
Provider Mode는 데이타를 클래스로 표현하는 방법이기때문에 아래와 같은 작업 순서가 필요합니다.
- Column을 담을 Class를 생성합니다.
- Record들을 담을 Class를 생성합니다.
- TcxCustomDataSource를 상속받아 데이타를 관리할 Classs를 생성합니다.
아래 예제는 DBDemos의 Animals.dbf의 내용을 표현하는 용도로 만들어진 Class들입니다.
Column을 담을 Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
TAnimal = class private fFieldList : Array of Variant; function GetFieldList(Fieldidx: Integer): Variant; procedure SetFieldList(Fieldidx: Integer; const Value: Variant); protected public constructor Create( FieldCnt : Integer ); destructor Destroy; override; property FieldList[ Fieldidx : Integer ] : Variant read GetFieldList write SetFieldList; end; ... { TAnimal } constructor TAnimal.Create( FieldCnt : Integer ); begin Inherited Create; SetLength( fFieldList, FieldCnt ); end; destructor TAnimal.Destroy; var I: Integer; begin SetLength( fFieldList , 0 ); inherited Destroy; end; function TAnimal.GetFieldList(Fieldidx: Integer): variant; begin result := fFieldList[ Fieldidx ]; end; procedure TAnimal.SetFieldList(Fieldidx: Integer; const Value: variant); begin fFieldList[ Fieldidx ] := Value; end; |
컬럼을 담을 Class이지만 정확히 따지고 보면, 한레코드를 담을 class입니다.
여기서 fFieldList라는 변수를 가변형 배열로 선언하고 Class가 생성될때 배열의 크기를 정합니다.
Variant형을 선언하여 어떤 내용이던 담을수 있도록 하였습니다.
레코드들을 담을 Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
TAnimalList = class private FList: TList; FFieldCnt: Integer; procedure ReleaseAll; procedure Release(AIndex: Integer); function GetAnimal(AIndex: Integer): TAnimal; function GetCount: Integer; procedure SetFieldCnt(const Value: Integer); public constructor Create; destructor Destroy; override; procedure Clear; function Add : TAnimal; overload; function Add(Animal: TAnimal): Integer; overload; procedure Delete(AIndex: Integer); procedure Insert(AIndex: integer; Animal: TAnimal); property Animals[Index: Integer]: TAnimal read GetAnimal; default; property Count: Integer read GetCount; property FieldCnt : Integer read FFieldCnt write SetFieldCnt; end; ... { TAnimalList } function TAnimalList.Add(Animal: TAnimal): Integer; begin Result := FList.Add(Animal); end; function TAnimalList.Add: TAnimal; begin Result := TAnimal.Create( FFieldCnt ); FList.Add( Result ); end; procedure TAnimalList.Clear; begin ReleaseAll; end; constructor TAnimalList.Create; begin inherited Create; FList := TList.Create; end; procedure TAnimalList.Delete(AIndex: Integer); begin Release(AIndex); FList.Delete(AIndex); end; destructor TAnimalList.Destroy; begin ReleaseAll; FList.Free; inherited Destroy; end; function TAnimalList.GetCount: Integer; begin Result := FList.Count; end; function TAnimalList.GetAnimal(AIndex: Integer): TAnimal; begin Result := TAnimal(FList[AIndex]); end; procedure TAnimalList.Insert(AIndex: integer; Animal: TAnimal); begin FList.Insert(AIndex, Animal); end; procedure TAnimalList.ReleaseALL; var I : Integer; begin for I := 0 to Count -1 do Release(I); FList.Clear; end; procedure TAnimalList.Release(AIndex: Integer); begin TAnimal( FList[AIndex] ).Free; end; procedure TAnimalList.SetFieldCnt(const Value: Integer); begin FFieldCnt := Value; end; |
TAnimalList Class의 용도는 TAnimal Class를 담고 있을 Class입니다.
핵심은 TList를 이용하여 TAnimal Class를 담아관리 한다는 것입니다.
TAnimal Class는 한개의 레코드이고 TAnimalList는 레코드를 모아놓은 Table과 같은 역활을 합니다.
그래서, TAnimalList Class를 보면 FFieldCnt를 이용하여 TAnimal Class에 Record의 Field개수를 정의하고, TAnimal Class를 TList에 저장합니다.
데이타를 담을 클래스를 정의하고 난뒤에 이를 관리할 Class를 생성합니다. TcxCustomDataSource는 기본원형을 퀀텀에서 제공하고 아래 표시된 Function과 Procedure를 정의 해야 합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
TAnimalDataSource = class(TcxCustomDataSource) private FAnimals: TAnimalList; FModified: boolean; protected function InsertRecord(ARecordHandle: TcxDataRecordHandle): TcxDataRecordHandle; override; function AppendRecord: TcxDataRecordHandle; override; procedure DeleteRecord(ARecordHandle: TcxDataRecordHandle); override; function GetRecordCount: Integer; override; function GetValue(ARecordHandle: TcxDataRecordHandle; AItemHandle: TcxDataItemHandle): Variant; override; procedure SetValue(ARecordHandle: TcxDataRecordHandle; AItemHandle: TcxDataItemHandle; const AValue: Variant); override; public constructor Create( AAnimals: TAnimalList); property Modified: boolean read FModified; end; |
Protected에 표시된 내용은 반드시 정의해야 하는 내용으로 이 이상 정의할것도 별로 없습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
{ TAnimalDataSource } // 데이타를 추가할경우 TAnimal을 생성하고 FAnimals에 담는다. function TAnimalDataSource.AppendRecord: TcxDataRecordHandle; var AAnimal: TAnimal; begin AAnimal := TAnimal.Create( FAnimals.FieldCnt ); Result := TcxDataRecordHandle( FAnimals.Add(AAnimal) ); DataChanged; // Refresh와 같은 역활을 한다. if not Modified then FModified := True; end; constructor TAnimalDataSource.Create(AAnimals: TAnimalList); begin inherited Create; FAnimals := AAnimals; end; // 데이타를 삭제할경우 FAnimals에서 지정된 Index를 삭제한다. procedure TAnimalDataSource.DeleteRecord(ARecordHandle: TcxDataRecordHandle); begin FAnimals.Delete(Integer(ARecordHandle)); DataChanged; if not Modified then FModified := True; end; // 데이타를 삽입할경우 TAnimal을 생성하고 FAnimals에 삽입한다. function TAnimalDataSource.InsertRecord( ARecordHandle: TcxDataRecordHandle): TcxDataRecordHandle; var AAnimal: TAnimal; begin AAnimal := TAnimal.Create( FAnimals.FieldCnt ); FAnimals.Insert( Integer(ARecordHandle), AAnimal ); Result := TcxDataRecordHandle(ARecordHandle); DataChanged; if not Modified then FModified := True; end; // FAnimals의 갯수( 실제 전체 레코드의 갯수 )를 나타낸다. function TAnimalDataSource.GetRecordCount: Integer; begin Result := FAnimals.Count; end; // 데이타를 가져온다.( ARecordHandle : Record Index, AItemHandle : Column Index ) function TAnimalDataSource.GetValue(ARecordHandle: TcxDataRecordHandle; AItemHandle: TcxDataItemHandle): Variant; var AAnimal: TAnimal; begin AAnimal := FAnimals[Integer(ARecordHandle)]; Result := AAnimal.FieldList[ GetDefaultItemID(Integer(AItemHandle)) ]; end; // 데이타를 넣는다.( ARecordHandle : Record Index, AItemHandle : Column Index ) procedure TAnimalDataSource.SetValue(ARecordHandle: TcxDataRecordHandle; AItemHandle: TcxDataItemHandle; const AValue: Variant); var AAnimal: TAnimal; begin AAnimal := FAnimals[Integer(ARecordHandle)]; AAnimal.FieldList[ GetDefaultItemID(Integer(AItemHandle)) ] := AValue; if not Modified then FModified := True; end; |
생각보다는 어렵지 않습니다.
Table+DataSource = Class + TcxCustomDataSource = TAnimalList + TcxCustomDataSource 라는 등식이 성립됩니다. 결국 레코드를 만들고( TAnimal ) 레코드를 모아 테이블( TAnimalList )을 만들고 TcxCsutomDataSource를 통해 관리 하는겁니다.
이제 데이타를 넣고 컬럼을 만드는 작업을 살펴보도록 하겠습니다.
우선 변수를 선언합니다. 물론 아래 내용은 전역 또는 DataModule 또는 TForm안에 선언 되어야 할것입니다.
1 2 3 |
var AnimalList : TAnimalList; CustomerDataSource : TAnimalDataSource; |
그리고 컬럼을 만듭니다. 구찮아서 컬럼과 데이타를 한번에 만들고 불러오도록 만들었습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
procedure TForm1.LoadData; var i: Integer; Animal : TAnimal; begin With Table1 do begin // 컬럼을 모두 지운다. cxGrid1TableView1.ClearItems; // 반드시 해준다. 그래야 TAnimal에 컬럼이 만들어지므로. AnimalList.FieldCnt := FieldCount; // 컬럼을 생성한다. Unbound Mode와 100% 동일하다. for i := 0 to FieldCount - 1 do begin With cxGrid1TableView1.CreateColumn do begin Caption := Fields[i].FieldName; case Fields[i].DataType of ftString : DataBinding.ValueTypeClass := TcxStringValueType; ftDate, ftDateTime : DataBinding.ValueTypeClass := TcxDateTimeValueType; ftBoolean : DataBinding.ValueTypeClass := TcxBooleanValueType; ftBlob, ftTypedBinary, ftGraphic : begin DataBinding.ValueTypeClass := TcxVariantValueType; PropertiesClass := TcxImageProperties; end; ftSmallint, ftInteger, ftWord : DataBinding.ValueTypeClass := TcxIntegerValueType; else begin DataBinding.ValueTypeClass := TcxFloatValueType; end; end; // <-- Case end; // <-- With end; // For // Provider Mode설정. cxGrid1TableView1.DataController.CustomDataSource := CustomerDataSource; // 값을 넣는다. First; while not eof do begin Animal := AnimalList.Add; for i := 0 to FieldCount - 1 do Animal.FieldList[i] := Fields[i].Value; Next; end; // <-- While not eof... CustomerDataSource.DataChanged; // Refresh end; // <-- With Table1... end; |
복잡해 보이지만 실제로는 그리 복잡해 보이지 않습니다.
요기까지 Provider Mode를 알아 보았습니다. Mater-Detail 방법은 퀀텀 그리드의 데모를 참고해 주시기바랍니다.
최신 댓글