{********************************************************************} { } { Developer Express Visual Component Library } { ExpressDataController } { } { Copyright (c) 1998-2008 Developer Express Inc. } { ALL RIGHTS RESERVED } { } { The entire contents of this file is protected by U.S. and } { International Copyright Laws. Unauthorized reproduction, } { reverse-engineering, and distribution of all or any portion of } { the code contained in this file is strictly prohibited and may } { result in severe civil and criminal penalties and will be } { prosecuted to the maximum extent possible under the law. } { } { RESTRICTIONS } { } { THIS SOURCE CODE AND ALL RESULTING INTERMEDIATE FILES } { (DCU, OBJ, DLL, ETC.) ARE CONFIDENTIAL AND PROPRIETARY TRADE } { SECRETS OF DEVELOPER EXPRESS INC. THE REGISTERED DEVELOPER IS } { LICENSED TO DISTRIBUTE THE EXPRESSDATACONTROLLER AND ALL } { ACCOMPANYING VCL CONTROLS AS PART OF AN EXECUTABLE PROGRAM ONLY. } { } { THE SOURCE CODE CONTAINED WITHIN THIS FILE AND ALL RELATED } { FILES OR ANY PORTION OF ITS CONTENTS SHALL AT NO TIME BE } { COPIED, TRANSFERRED, SOLD, DISTRIBUTED, OR OTHERWISE MADE } { AVAILABLE TO OTHER INDIVIDUALS WITHOUT EXPRESS WRITTEN CONSENT } { AND PERMISSION FROM DEVELOPER EXPRESS INC. } { } { CONSULT THE END USER LICENSE AGREEMENT FOR INFORMATION ON } { ADDITIONAL RESTRICTIONS. } { } {********************************************************************} unit cxDBData; {$I cxVer.inc} interface uses SysUtils, Classes, {$IFDEF DELPHI6}Variants, {$ENDIF} DB, cxDataUtils, cxCustomData, cxDataStorage, cxVariants, cxFilter, cxDBFilter, cxDB; type TcxDBDataController = class; TcxDBDataProvider = class; TcxDBDataModeController = class; TcxDBDataFilterCriteria = class; TGetListProc = procedure(AList: TList) of object; { TcxDBProviderDetailFilterAdapter } TcxDBProviderDetailFilterAdapter = class(TcxDBAdapterItem) public function IsCurrentQuery(ADataSet: TDataSet; const AParamNames: string; const AParamValues: Variant): Boolean; virtual; procedure ReopenSQL(ADataSet: TDataSet; const AParamNames: string; const AParamValues: Variant; var AReopened: Boolean); virtual; procedure SetFilter(ADataSet: TDataSet; const AMasterDetailKeyFieldNames: string; const AMasterDetailKeyValues: Variant; AIsSQLMode: Boolean; var AReopened: Boolean); virtual; end; { TcxDBDataLink } TcxDBDataLink = class(TDataLink) private {$IFDEF DELPHI7} FLayoutChangedFlag: Boolean; FPreventLayoutChanged: Boolean; {$ENDIF} FProvider: TcxDBDataProvider; procedure DoInsertingRecord; protected procedure ActiveChanged; override; {$IFDEF DELPHI7} procedure DataEvent(Event: TDataEvent; Info: Integer); override; {$ENDIF} procedure DataSetChanged; override; procedure DataSetScrolled(Distance: Integer); override; procedure EditingChanged; override; procedure FocusControl(Field: TFieldRef); override; procedure LayoutChanged; override; procedure RecordChanged(Field: TField); override; procedure UpdateData; override; property Provider: TcxDBDataProvider read FProvider; public constructor Create(AProvider: TcxDBDataProvider); end; { TcxDBDataProvider } TcxDBDataProviderSavePosInfo = record Bookmark: TBookmarkStr; Bof: Boolean; // conflicts with C++ macro Eof: Boolean; // conflicts with C++ macro ActiveRecord: Integer; end; TcxDBDataAssignedFields = class private FFields: TList; public constructor Create; destructor Destroy; override; procedure AddField(AField: TField); procedure AddFieldsByFieldList(AFieldList: TList); procedure AddFieldsByFieldNames(ADataSet: TDataSet; const AFieldNames: string); procedure Clear; function IsEmpty: Boolean; function IsFieldExists(AField: TField): Boolean; end; TcxDBDataProvider = class(TcxCustomDataProvider) private FAssignedFields: TcxDBDataAssignedFields; FDataLink: TcxDBDataLink; FInCanInitEditing: Boolean; FInSetDataSource: Boolean; FIncrementalSearching: Boolean; FInFirst: Boolean; FInInsert: Boolean; FInsertOperation: Boolean; FInUpdateGridMode: Boolean; FIsDataSetCurrent: Boolean; FInNotify: Boolean; FListeners: TList; FPostLocateFlag: Boolean; FRecordIndex: Integer; // FNearestRecordID: Variant; FSavePosInfo: TcxDBDataProviderSavePosInfo; FUpdatingPrevFocusedRecordIndex: Integer; FUpdatingRecordIndex: Integer; FUpdatingRecordIsInserting: Boolean; FUpdatingNewRecordIndex: Integer; procedure AddNewRecord(AOperation: TDataOperation); function GetDataController: TcxDBDataController; function GetDataSet: TDataSet; function GetDataSource: TDataSource; function GetIsDataSetCurrent: Boolean; function GetIsSmartRefresh: Boolean; procedure RemoveFromListeners; procedure SetDataSource(Value: TDataSource); procedure SetIsDataSetCurrent(Value: Boolean); protected function IsCustomDataSourceSupported: Boolean; override; function IsGridMode: Boolean; override; function IsDataSource: Boolean; override; function IsKeyNavigation: Boolean; function IsOtherInsert: Boolean; override; function IsSyncInsert: Boolean; function IsSyncMode: Boolean; override; // DataSet function DataSet_ActiveRecord: Integer; function DataSet_BookmarkAvailable: Boolean; function DataSet_BufferCount: Integer; // State function IsActive: Boolean; override; function IsActiveDataSet: Boolean; override; function IsBOF: Boolean; override; function IsChanging: Boolean; override; function IsEditing: Boolean; override; function IsEOF: Boolean; override; function IsGridModeUpdating: Boolean; override; function IsInserting: Boolean; override; function IsModified: Boolean; override; function IsDataSetModified: Boolean; virtual; // Navigation function BookmarkValid(const ABookmark: TBookmarkStr): Boolean; virtual; function CompareBookmarks(const AItem1, AItem2: TBookmarkStr): Integer; virtual; procedure CorrectRecordIndex(ARecordIndex: Integer); override; procedure First; override; procedure Prev; override; procedure Next; override; procedure Last; override; procedure MoveBy(ADistance: Integer); override; procedure Scroll(ADistance: Integer); override; procedure SavePos; override; procedure RestorePos; override; // Editing function CanDelete: Boolean; override; function CanInitEditing(ARecordIndex: Integer): Boolean; override; function CanModify: Boolean; override; procedure Append; override; procedure InternalCancel; virtual; procedure Cancel; override; procedure Delete; override; procedure DeleteRecords(AList: TList); override; procedure DeleteSelection; override; procedure Edit; override; function GetEditValue(ARecordIndex: Integer; AField: TcxCustomDataField; AEditValueSource: TcxDataEditValueSource): Variant; override; procedure Insert; override; procedure Post; override; procedure PostEditingData; override; function SetEditValue(ARecordIndex: Integer; AField: TcxCustomDataField; const AValue: Variant; AEditValueSource: TcxDataEditValueSource): Boolean; override; procedure BeginDeleting; override; procedure EndDeleting; override; procedure AssignItemValue(ARecordIndex: Integer; AField: TcxCustomDataField; const AValue: Variant); override; procedure DoInitInsertingRecord(AInsertingRecordIndex: Integer; const AGroupValues: TcxDataSummaryValues); override; // Notification procedure ActiveChanged(AActive: Boolean); override; procedure AddListener(AProvider: TcxDBDataProvider); procedure BeginLocate; override; procedure DataScrolled(ADistance: Integer); override; procedure Freeze; override; procedure LayoutChanged(ADataLayoutChanges: TcxDataLayoutChanges); override; procedure RecordChanged(Field: TField); virtual; procedure ResetPostLocateFlag; procedure ResetSmartRefresh; procedure SetPostLocateFlag; procedure Unfreeze; override; // Smart Refesh procedure SmartDataChanged; // GridMode procedure CheckExpandedDetails; function DoLocate(const ASubText: string; AForward, ANext: Boolean): Boolean; function GetDataBufferCount: Integer; function GetDataRecordCount: Integer; function GetExternalDataDisplayText(ARecordIndex: Integer; AField: TcxCustomDataField): string; override; function GetExternalDataValue(ARecordIndex: Integer; AField: TcxCustomDataField): Variant; override; procedure LoadDataBuffer; override; procedure UpdateGridMode; // Master Detail function GetDetailHasChildren(ARecordIndex: Integer; ARelationIndex: Integer): Boolean; override; // Structure function GetRecordIndex: Integer; override; function GetValueDefReaderClass: TcxValueDefReaderClass; override; property DataController: TcxDBDataController read GetDataController; property DataLink: TcxDBDataLink read FDataLink; property UpdatingRecordIndex: Integer read FUpdatingRecordIndex; public constructor Create(ADataController: TcxCustomDataController); override; destructor Destroy; override; function IsCanInitEditingBusy: Boolean; function IsDataSetBusy: Boolean; function IsDetailDataSetBusy: Boolean; function IsOtherDataControllerUpdating: Boolean; function IsOtherDetailDataSetInInsert: Boolean; function IsOtherDetailChanged: Boolean; function IsUniDirectional: Boolean; property DataSet: TDataSet read GetDataSet; property DataSource: TDataSource read GetDataSource write SetDataSource; property IsDataSetCurrent: Boolean read GetIsDataSetCurrent write SetIsDataSetCurrent; property IsSmartRefresh: Boolean read GetIsSmartRefresh; end; TcxValueDefDBReader = class(TcxValueDefReader) public function GetDisplayText(AValueDef: TcxValueDef): string; override; function GetValue(AValueDef: TcxValueDef): Variant; override; function IsInternal(AValueDef: TcxValueDef): Boolean; override; end; { TcxDBDataField } TcxDBDataField = class(TcxCustomDataField) private FField: TField; FFieldName: string; FFields: TList; FIsCurrency: Boolean; FParentField: TcxDBDataField; function GetDataController: TcxDBDataController; function GetField: TField; function GetFieldItem(Index: Integer): TcxDBDataField; function GetFieldCount: Integer; function GetIsLookup: Boolean; function GetProvider: TcxDBDataProvider; procedure SetField(Value: TField); protected procedure AddField(ADataField: TcxDBDataField); procedure ClearFields; procedure DoPropertiesChanged; procedure Reassign(ASource: TcxCustomDataField); override; procedure RemoveField(ADataField: TcxDBDataField); procedure RemoveNotification(AComponent: TComponent); override; procedure SetPropertiesByField(AField: TField; AFieldChanged: Boolean); property DataController: TcxDBDataController read GetDataController; property Field: TField read GetField write SetField; property FieldCount: Integer read GetFieldCount; property FieldName: string read FFieldName write FFieldName; property Fields[Index: Integer]: TcxDBDataField read GetFieldItem; default; property IsCurrency: Boolean read FIsCurrency; property IsLookup: Boolean read GetIsLookup; property ParentField: TcxDBDataField read FParentField; property Provider: TcxDBDataProvider read GetProvider; public destructor Destroy; override; function CanModify(AEditValueSource: TcxDataEditValueSource): Boolean; override; function IsUnbound: Boolean; override; function IsValueDefInternal: Boolean; override; end; { TcxDBDataRelation } TcxDBDataRelation = class(TcxCustomDataRelation) private FMasterKeyField: TcxDBDataField; function GetDataController: TcxDBDataController; function GetDetailKeyFieldNames: string; function GetMasterKeyFieldNames: string; protected procedure GetMasterKeyFields(AList: TList); function IsLinked: Boolean; virtual; procedure RemoveDataField(ADataField: TcxCustomDataField); override; procedure RemoveMasterKeyField; procedure UpdateMasterDetailKeyFieldNames; public destructor Destroy; override; function GetMasterRecordID(ARecordIndex: Integer): Variant; property DataController: TcxDBDataController read GetDataController; property DetailKeyFieldNames: string read GetDetailKeyFieldNames; property MasterKeyFieldNames: string read GetMasterKeyFieldNames; end; { DB Filter } TcxDBDataFilterCriteriaItem = class(TcxDataFilterCriteriaItem) private function GetDataController: TcxDBDataController; function GetDBField: TcxDBDataField; protected function GetFilterOperatorClass: TcxFilterOperatorClass; override; public property DataController: TcxDBDataController read GetDataController; property DBField: TcxDBDataField read GetDBField; end; TcxDBDataFilterBeforeChangeEvent = procedure(Sender: TcxDBDataFilterCriteria; ADataSet: TDataSet; const AFilterText: string) of object; TcxDBDataFilterFormatFilterTextValueEvent = procedure(Sender: TcxDBDataFilterCriteria; const AValue: Variant; var ADisplayValue: string) of object; TcxDBDataFilterCriteria = class(TcxDataFilterCriteria) private FAutoDataSetFilter: Boolean; FIgnoreOrigin: Boolean; FOnBeforeChange: TcxDBDataFilterBeforeChangeEvent; FOnFormatFilterTextValue: TcxDBDataFilterFormatFilterTextValueEvent; function GetDataController: TcxDBDataController; procedure SetAutoDataSetFilter(Value: Boolean); procedure SetIgnoreOrigin(Value: Boolean); protected procedure FormatFilterTextValue(AItem: TcxFilterCriteriaItem; const AValue: Variant; var ADisplayValue: string); override; function GetItemClass: TcxFilterCriteriaItemClass; override; procedure Update; override; public constructor Create(ADataController: TcxCustomDataController); override; procedure Assign(Source: TPersistent; AIgnoreItemNames: Boolean = False); override; procedure AssignEvents(Source: TPersistent); override; function IsAutoDataSetFilter: Boolean; function IsFiltering: Boolean; override; procedure RestoreDefaults; override; property DataController: TcxDBDataController read GetDataController; published // TODO: add property CustomFiltering -> IsFiltering = False! property AutoDataSetFilter: Boolean read FAutoDataSetFilter write SetAutoDataSetFilter default False; property DateTimeFormat; property IgnoreOrigin: Boolean read FIgnoreOrigin write SetIgnoreOrigin default True; property TranslateBetween; property TranslateIn; property TranslateLike; property SupportedLike; property OnBeforeChange: TcxDBDataFilterBeforeChangeEvent read FOnBeforeChange write FOnBeforeChange; property OnFormatFilterTextValue: TcxDBDataFilterFormatFilterTextValueEvent read FOnFormatFilterTextValue write FOnFormatFilterTextValue; end; { DB Summary } TcxDBDataSummaryItem = class(TcxDataSummaryItem) private FDataField: TcxDBDataField; FFieldName: string; function GetDBDataController: TcxDBDataController; procedure SetFieldName(const Value: string); protected function IsCurrency(AVarType: TVarType): Boolean; override; public procedure Assign(Source: TPersistent); override; property DataController: TcxDBDataController read GetDBDataController; function DataField: TcxCustomDataField; override; published property FieldName: string read FFieldName write SetFieldName; end; { TcxDBDataModeController } TcxDBDataModeControllerDetailIsCurrentQueryEvent = function(Sender: TcxDBDataModeController; ADataSet: TDataSet; const AMasterDetailKeyFieldNames: string; const AMasterDetailKeyValues: Variant): Boolean of object; TcxDBDataModeControllerDetailFirstEvent = procedure(Sender: TcxDBDataModeController; ADataSet: TDataSet; const AMasterDetailKeyFieldNames: string; const AMasterDetailKeyValues: Variant; var AReopened: Boolean) of object; TcxDBDataModeController = class(TPersistent) private FDataController: TcxDBDataController; FDetailInSQLMode: Boolean; FDetailInSyncMode: Boolean; FGridMode: Boolean; FGridModeBufferCount: Integer; FSmartRefresh: Boolean; FSyncInsert: Boolean; FSyncMode: Boolean; FOnDetailFirst: TcxDBDataModeControllerDetailFirstEvent; FOnDetailIsCurrentQuery: TcxDBDataModeControllerDetailIsCurrentQueryEvent; procedure SetGridMode(Value: Boolean); procedure SetGridModeBufferCount(Value: Integer); procedure SetSmartRefresh(Value: Boolean); procedure SetSyncMode(Value: Boolean); protected function DetailIsCurrentQuery(const AMasterDetailKeyFieldNames: string; const AMasterDetailKeyValues: Variant): Boolean; virtual; procedure DoDetailFirst(const AMasterDetailKeyFieldNames: string; const AMasterDetailKeyValues: Variant; var AReopened: Boolean); virtual; property DetailInSyncMode: Boolean read FDetailInSyncMode write FDetailInSyncMode default True; public constructor Create(ADataController: TcxDBDataController); procedure Assign(Source: TPersistent); override; property DataController: TcxDBDataController read FDataController; property SyncInsert: Boolean read FSyncInsert write FSyncInsert default True; published property DetailInSQLMode: Boolean read FDetailInSQLMode write FDetailInSQLMode default False; property GridMode: Boolean read FGridMode write SetGridMode default False; property GridModeBufferCount: Integer read FGridModeBufferCount write SetGridModeBufferCount default 0; property SmartRefresh: Boolean read FSmartRefresh write SetSmartRefresh default False; property SyncMode: Boolean read FSyncMode write SetSyncMode default True; property OnDetailFirst: TcxDBDataModeControllerDetailFirstEvent read FOnDetailFirst write FOnDetailFirst; property OnDetailIsCurrentQuery: TcxDBDataModeControllerDetailIsCurrentQueryEvent read FOnDetailIsCurrentQuery write FOnDetailIsCurrentQuery; end; { TcxDBDataSelection } TcxDBDataSelection = class(TcxDataSelection) private FAnchorBookmark: TBookmarkStr; FBookmarks: TStrings; FInSelectAll: Boolean; function GetDataController: TcxDBDataController; protected procedure ClearAnchor; override; function CompareBookmarks(const AItem1, AItem2: TBookmarkStr): Integer; procedure InternalAdd(AIndex, ARowIndex, ARecordIndex, ALevel: Integer); override; procedure InternalClear; override; procedure InternalDelete(AIndex: Integer); override; function FindBookmark(const ABookmark: TBookmarkStr; var AIndex: Integer): Boolean; function GetRowBookmark(ARowIndex: Integer): TBookmarkStr; function RefreshBookmarks: Boolean; procedure SyncCount; public constructor Create(ADataController: TcxCustomDataController); override; destructor Destroy; override; function FindByRowIndex(ARowIndex: Integer; var AIndex: Integer): Boolean; override; procedure SelectAll; procedure SelectFromAnchor(AToBookmark: TBookmarkStr; AKeepSelection: Boolean); property DataController: TcxDBDataController read GetDataController; end; { TcxDBDataController } TcxDBDataDetailHasChildrenEvent = procedure(Sender: TcxDBDataController; ARecordIndex, ARelationIndex: Integer; const AMasterDetailKeyFieldNames: string; const AMasterDetailKeyValues: Variant; var HasChildren: Boolean) of object; TcxDBDataController = class(TcxCustomDataController) private FBookmark: TBookmarkStr; FCreatedDataController: TcxCustomDataController; FDataModeController: TcxDBDataModeController; FDetailKeyFieldNames: string; FInCheckBrowseMode: Boolean; FInCheckCurrentQuery: Boolean; FInResetDataSetCurrent: Boolean; FInUnboundCopy: Boolean; FInUpdateGridModeBufferCount: Boolean; FKeyField: TcxDBDataField; FKeyFieldNames: string; FLoaded: Boolean; FMasterDetailKeyFields: TList; FMasterDetailKeyValues: Variant; FMasterKeyFieldNames: string; FResetDBFields: Boolean; FUpdateDataSetPos: Boolean; FOnDetailHasChildren: TcxDBDataDetailHasChildrenEvent; function AddInternalDBField: TcxDBDataField; function GetDataSet: TDataSet; function GetDataSetRecordCount: Integer; function GetDataSource: TDataSource; function GetDBField(Index: Integer): TcxDBDataField; function GetDBSelection: TcxDBDataSelection; function GetFilter: TcxDBDataFilterCriteria; function GetMasterDetailKeyFieldNames: string; function GetMasterDetailKeyFields: TList; function GetProvider: TcxDBDataProvider; function GetRecNo: Integer; procedure MasterDetailKeyFieldsRemoveNotification(AComponent: TComponent); procedure RemoveKeyField; procedure SetDataModeController(Value: TcxDBDataModeController); procedure SetDataSource(Value: TDataSource); procedure SetDetailKeyFieldNames(const Value: string); procedure SetFilter(Value: TcxDBDataFilterCriteria); procedure SetKeyFieldNames(const Value: string); procedure SetMasterKeyFieldNames(const Value: string); procedure SetRecNo(Value: Integer); procedure SyncDataSetPos; function SyncMasterDetail: TcxCustomDataController; procedure SyncMasterDetailDataSetPos; procedure UpdateRelationFields; protected function CanChangeDetailExpanding(ARecordIndex: Integer; AExpanded: Boolean): Boolean; override; function CanFocusRecord(ARecordIndex: Integer): Boolean; override; procedure CheckDataSetCurrent; override; function CheckMasterBrowseMode: Boolean; override; procedure ClearMasterDetailKeyFields; procedure CorrectAfterDelete(ARecordIndex: Integer); override; procedure DoDataSetCurrentChanged(AIsCurrent: Boolean); virtual; procedure DoDataSourceChanged; virtual; procedure DoInitInsertingRecord(AInsertingRecordIndex: Integer); virtual; function DoSearchInGridMode(const ASubText: string; AForward, ANext: Boolean): Boolean; override; function FindRecordIndexInGridMode(const AKeyFieldValues: Variant): Integer; function GetActiveRecordIndex: Integer; override; function GetDataProviderClass: TcxCustomDataProviderClass; override; function GetDataSelectionClass: TcxDataSelectionClass; override; function GetDefaultGridModeBufferCount: Integer; virtual; function GetFieldClass: TcxCustomDataFieldClass; override; function GetFilterCriteriaClass: TcxDataFilterCriteriaClass; override; procedure GetKeyFields(AList: TList); override; function GetRelationClass: TcxCustomDataRelationClass; override; function GetSummaryItemClass: TcxDataSummaryItemClass; override; function InternalCheckBookmark(ADeletedRecordIndex: Integer): Boolean; override; procedure InternalClearBookmark; override; procedure InternalGotoBookmark; override; function InternalSaveBookmark: Boolean; override; procedure InvalidateDataBuffer; virtual; function IsDataField(AField: TcxCustomDataField): Boolean; override; function IsKeyNavigation: Boolean; override; function IsOtherDetailChanged: Boolean; function IsOtherDetailCreating: Boolean; function IsProviderDataSource: Boolean; override; function IsSmartRefresh: Boolean; override; procedure LoadStorage; override; function LocateRecordIndex(AGetFieldsProc: TGetListProc): Integer; virtual; function LockOnAfterSummary: Boolean; override; procedure NotifyDataControllers; override; procedure NotifyDetailAfterFieldsRecreating(ADataController: TcxCustomDataController); procedure NotifyDetailsAfterFieldsRecreating(ACreatingLinkObject: Boolean); procedure PrepareField(AField: TcxCustomDataField); override; procedure RemoveNotification(AComponent: TComponent); override; procedure ResetDataSetCurrent(ADataController: TcxCustomDataController); procedure ResetDBFields; procedure RestructData; override; procedure ResyncDBFields; procedure RetrieveField(ADataField: TcxDBDataField; AIsLookupKeyOnly: Boolean); function TryFocusRecord(ARecordIndex: Integer): Boolean; virtual; procedure UpdateEditingRecord; procedure UpdateField(ADataField: TcxDBDataField; const AFieldNames: string; AIsLookup: Boolean); procedure UpdateFields; override; procedure UpdateFocused; override; procedure UpdateInternalKeyFields(const AFieldNames: string; var AField: TcxDBDataField); procedure UpdateLookupFields; procedure UpdateRelations(ARelation: TcxCustomDataRelation); override; procedure UpdateScrollBars; virtual; // Locate procedure BeginReadRecord; override; procedure EndReadRecord; override; property DBFields[Index: Integer]: TcxDBDataField read GetDBField; property DBSelection: TcxDBDataSelection read GetDBSelection; property KeyField: TcxDBDataField read FKeyField; property MasterDetailKeyFieldNames: string read GetMasterDetailKeyFieldNames; property MasterDetailKeyFields: TList read GetMasterDetailKeyFields; property MasterDetailKeyValues: Variant read FMasterDetailKeyValues; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Assign(Source: TPersistent); override; // Actions function ExecuteAction(Action: TBasicAction): Boolean; override; function UpdateAction(Action: TBasicAction): Boolean; override; // Structure procedure ChangeFieldName(AItemIndex: Integer; const AFieldName: string); procedure ChangeValueTypeClass(AItemIndex: Integer; AValueTypeClass: TcxValueTypeClass); override; function GetItemByFieldName(const AFieldName: string): TObject; function GetItemField(AItemIndex: Integer): TField; function GetItemFieldName(AItemIndex: Integer): string; function IsDisplayFormatDefined(AItemIndex: Integer; AIgnoreSimpleCurrency: Boolean): Boolean; override; procedure Loaded; override; // Data procedure BeginLocate; procedure EndLocate; procedure DoUpdateRecord(ARecordIndex: Integer); function GetGroupValue(ARecordIndex: Integer; AField: TcxCustomDataField): Variant; override; procedure GetKeyDBFields(AList: TList); function GetKeyFieldsValues: Variant; function GetRecordId(ARecordIndex: Integer): Variant; override; procedure UpdateGridModeBufferCount; // Data Editing procedure CheckBrowseMode; override; function DataChangedNotifyLocked: Boolean; override; procedure RefreshExternalData; override; // Navigation procedure SetFocus; override; // Bookmark function IsBookmarkAvailable: Boolean; override; function IsBookmarkRow(ARowIndex: Integer): Boolean; override; // Filter function GetFilterDataValue(ARecordIndex: Integer; AField: TcxCustomDataField): Variant; override; function GetFilterItemFieldName(AItem: TObject): string; override; // Search function FindRecordIndexByKey(const AKeyFieldValues: Variant): Integer; function LocateByKey(const AKeyFieldValues: Variant): Boolean; // Master-Detail procedure CheckCurrentQuery; function GetDetailFilterAdapter: TcxDBProviderDetailFilterAdapter; virtual; procedure SetMasterRelation(AMasterRelation: TcxCustomDataRelation; AMasterRecordIndex: Integer); override; // MultiSelect in GridMode function GetRowId(ARowIndex: Integer): Variant; override; function GetSelectedBookmark(Index: Integer): TBookmarkStr; function GetSelectedRowIndex(Index: Integer): Integer; override; function GetSelectionAnchorBookmark: TBookmarkStr; function GetSelectionAnchorRowIndex: Integer; override; function IsSelectionAnchorExist: Boolean; override; procedure SelectAll; override; procedure SelectFromAnchor(ARowIndex: Integer; AKeepSelection: Boolean); override; procedure SetSelectionAnchor(ARowIndex: Integer); override; // Export function FocusSelectedRow(ASelectedIndex: Integer): Boolean; override; // View Data procedure ForEachRow(ASelectedRows: Boolean; AProc: TcxDataControllerEachRowProc); override; function IsSequenced: Boolean; property DataModeController: TcxDBDataModeController read FDataModeController write SetDataModeController; property DataSet: TDataSet read GetDataSet; property DataSource: TDataSource read GetDataSource write SetDataSource; property DetailKeyFieldNames: string read FDetailKeyFieldNames write SetDetailKeyFieldNames; property Filter: TcxDBDataFilterCriteria read GetFilter write SetFilter; property KeyFieldNames: string read FKeyFieldNames write SetKeyFieldNames; property MasterKeyFieldNames: string read FMasterKeyFieldNames write SetMasterKeyFieldNames; property Provider: TcxDBDataProvider read GetProvider; property RecNo: Integer read GetRecNo write SetRecNo; // Sequenced property DataSetRecordCount: Integer read GetDataSetRecordCount; // Sequenced property OnDetailHasChildren: TcxDBDataDetailHasChildrenEvent read FOnDetailHasChildren write FOnDetailHasChildren; end; var cxDetailFilterControllers: TcxDBAdapterList; function CanCallDataSetLocate(ADataSet: TDataSet; const AKeyFieldNames: string; const AValue: Variant): Boolean; function GetValueTypeClassByField(AField: TField): TcxValueTypeClass; implementation uses {$IFDEF DELPHI9}Windows, {$ENDIF} TypInfo, Contnrs, cxDataConsts ; type TDataSetAccess = class(TDataSet); var DBDataProviders: TList; procedure GetInternalKeyFields(ADataField: TcxDBDataField; AList: TList); var I: Integer; begin if Assigned(ADataField) then begin if ADataField.FieldCount = 0 then AList.Add(ADataField) else for I := 0 to ADataField.FieldCount - 1 do AList.Add(ADataField.Fields[I]); end; end; function CanCallDataSetLocate(ADataSet: TDataSet; const AKeyFieldNames: string; const AValue: Variant): Boolean; function TryGetFieldList(ADataSet: TDataSet; const AFieldNames: WideString; AList: TList): Boolean; var AField: TField; APos: Integer; begin Result := True; APos := 1; while APos <= Length(AFieldNames) do begin AField := ADataSet.FindField(ExtractFieldName(AFieldNames, APos)); Result := AField <> nil; if not Result then Break; AList.Add(AField); end; end; function IsNullValidToLocate(AField: TField): Boolean; begin Result := not (AField is TAutoIncField); end; var AArrayLowBound, I: Integer; AField: TField; AFieldList: TObjectList; begin if VarIsArray(AValue) then begin AFieldList := TObjectList.Create(False); try AArrayLowBound := VarArrayLowBound(AValue, 1); Result := TryGetFieldList(ADataSet, AKeyFieldNames, AFieldList) and (AFieldList.Count = VarArrayHighBound(AValue, 1) - AArrayLowBound + 1); if Result then for I := 0 to AFieldList.Count - 1 do begin Result := not VarIsNull(AValue[I + AArrayLowBound]) or IsNullValidToLocate(TField(AFieldList[I])); if not Result then Break; end; finally AFieldList.Free; end; end else begin AField := nil; if Pos(';', AKeyFieldNames) = 0 then AField := ADataSet.FindField(AKeyFieldNames); Result := (AField <> nil) and (not VarIsNull(AValue) or IsNullValidToLocate(AField)); end; end; function GetValueTypeClassByField(AField: TField): TcxValueTypeClass; begin if AField = nil then Result := TcxStringValueType else begin case AField.DataType of ftString: Result := TcxStringValueType; ftWideString: Result := TcxWideStringValueType; ftSmallint: Result := TcxSmallintValueType; ftInteger, ftAutoInc: Result := TcxIntegerValueType; ftWord: Result := TcxWordValueType; ftBoolean: Result := TcxBooleanValueType; ftCurrency, ftFloat: Result := TcxFloatValueType; {ftCurrency, }ftBCD: Result := TcxCurrencyValueType; ftDate, ftTime, ftDateTime: Result := TcxDateTimeValueType; {$IFDEF DELPHI6} ftFMTBcd: Result := TcxFMTBcdValueType; ftLargeint: Result := TcxLargeIntValueType; ftTimeStamp: Result := TcxSQLTimeStampValueType; {$ENDIF} else Result := TcxVariantValueType; end; end; end; {$IFNDEF DELPHI5} function IsPublishedProp(Instance: TObject; const PropName: string): Boolean; begin Result := GetPropInfo(Instance.ClassInfo, PropName) <> nil; end; function GetObjectProp(Instance: TObject; const PropName: string): TObject; var PropInfo: PPropInfo; begin Result := nil; PropInfo := TypInfo.GetPropInfo(Instance.ClassInfo, PropName); if (PropInfo <> nil) and (PropInfo^.PropType^.Kind = tkClass) then Result := TObject(GetOrdProp(Instance, PropInfo)); end; {$ENDIF} { TcxDBProviderDetailFilterAdapter } function TcxDBProviderDetailFilterAdapter.IsCurrentQuery(ADataSet: TDataSet; const AParamNames: string; const AParamValues: Variant): Boolean; var AParams: TParams; begin Result := False; if IsPublishedProp(ADataSet, 'Params') then begin AParams := GetObjectProp(ADataSet, 'Params') as TParams; if AParams <> nil then begin if VarEquals(AParams.ParamValues[AParamNames], AParamValues) then Result := True; end; end; end; procedure TcxDBProviderDetailFilterAdapter.ReopenSQL(ADataSet: TDataSet; const AParamNames: string; const AParamValues: Variant; var AReopened: Boolean); var AParams: TParams; begin if IsPublishedProp(ADataSet, 'Params') then begin AParams := GetObjectProp(ADataSet, 'Params') as TParams; if AParams <> nil then begin if VarEquals(AParams.ParamValues[AParamNames], AParamValues) then ADataSet.First // TODO: Update method - for Query with Post else begin ADataSet.DisableControls; try ADataSet.Active := False; AParams.ParamValues[AParamNames] := AParamValues; ADataSet.Active := True; finally ADataSet.EnableControls; end; AReopened := True; // set Flag if Query reopened end; end; end; end; procedure TcxDBProviderDetailFilterAdapter.SetFilter(ADataSet: TDataSet; const AMasterDetailKeyFieldNames: string; const AMasterDetailKeyValues: Variant; AIsSQLMode: Boolean; var AReopened: Boolean); begin if AIsSQLMode then ReopenSQL(ADataSet, AMasterDetailKeyFieldNames, AMasterDetailKeyValues, AReopened) else ADataSet.Locate(AMasterDetailKeyFieldNames, AMasterDetailKeyValues, []); // must be sort by Detail Keys end; { TcxDBDataModeController } constructor TcxDBDataModeController.Create(ADataController: TcxDBDataController); begin inherited Create; FDetailInSyncMode := True; FDataController := ADataController; FGridModeBufferCount := 0; FSyncInsert := True; FSyncMode := True; end; procedure TcxDBDataModeController.Assign(Source: TPersistent); begin if Source is TcxDBDataModeController then begin DetailInSQLMode := TcxDBDataModeController(Source).DetailInSQLMode; DetailInSyncMode := TcxDBDataModeController(Source).DetailInSyncMode; GridMode := TcxDBDataModeController(Source).GridMode; GridModeBufferCount := TcxDBDataModeController(Source).GridModeBufferCount; SmartRefresh := TcxDBDataModeController(Source).SmartRefresh; SyncInsert := TcxDBDataModeController(Source).SyncInsert; SyncMode := TcxDBDataModeController(Source).SyncMode; OnDetailFirst := TcxDBDataModeController(Source).OnDetailFirst; OnDetailIsCurrentQuery := TcxDBDataModeController(Source).OnDetailIsCurrentQuery; end else inherited Assign(Source); end; function TcxDBDataModeController.DetailIsCurrentQuery(const AMasterDetailKeyFieldNames: string; const AMasterDetailKeyValues: Variant): Boolean; var ADetailFilterAdapter: TcxDBProviderDetailFilterAdapter; begin if Assigned(FOnDetailIsCurrentQuery) then Result := FOnDetailIsCurrentQuery(Self, DataController.DataSet, AMasterDetailKeyFieldNames, AMasterDetailKeyValues) else begin ADetailFilterAdapter := DataController.GetDetailFilterAdapter; Result := ADetailFilterAdapter.IsCurrentQuery(DataController.DataSet, AMasterDetailKeyFieldNames, AMasterDetailKeyValues); end; end; procedure TcxDBDataModeController.DoDetailFirst(const AMasterDetailKeyFieldNames: string; const AMasterDetailKeyValues: Variant; var AReopened: Boolean); var ADetailFilterAdapter: TcxDBProviderDetailFilterAdapter; begin AReopened := False; if Assigned(FOnDetailFirst) then FOnDetailFirst(Self, DataController.DataSet, AMasterDetailKeyFieldNames, AMasterDetailKeyValues, AReopened) else begin ADetailFilterAdapter := DataController.GetDetailFilterAdapter; ADetailFilterAdapter.SetFilter(DataController.DataSet, AMasterDetailKeyFieldNames, AMasterDetailKeyValues, DetailInSQLMode, AReopened); end; end; procedure TcxDBDataModeController.SetGridMode(Value: Boolean); var APrevGridMode: Boolean; begin APrevGridMode := DataController.IsGridMode; if FGridMode <> Value then begin FGridMode := Value; if DataController.IsGridMode <> APrevGridMode then DataController.RestructData; end; end; procedure TcxDBDataModeController.SetGridModeBufferCount(Value: Integer); begin if Value < 0 then Value := 0; if FGridModeBufferCount <> Value then begin FGridModeBufferCount := Value; DataController.UpdateGridModeBufferCount; end; end; procedure TcxDBDataModeController.SetSmartRefresh(Value: Boolean); begin if FSmartRefresh <> Value then begin FSmartRefresh := Value; if not DataController.Provider.IsGridMode then DataController.Provider.DataScrolled(0); end; end; procedure TcxDBDataModeController.SetSyncMode(Value: Boolean); begin if FSyncMode <> Value then begin FSyncMode := Value; if DataController.Provider.IsActive then DataController.Provider.DataScrolled(0); end; end; { TcxDBDataSelection } constructor TcxDBDataSelection.Create(ADataController: TcxCustomDataController); begin inherited Create(ADataController); FBookmarks := TStringList.Create; end; destructor TcxDBDataSelection.Destroy; begin FBookmarks.Free; FBookmarks := nil; inherited Destroy; end; function TcxDBDataSelection.FindByRowIndex(ARowIndex: Integer; var AIndex: Integer): Boolean; begin if DataController.IsGridMode then Result := FindBookmark(GetRowBookmark(ARowIndex), AIndex) else Result := inherited FindByRowIndex(ARowIndex, AIndex); end; procedure TcxDBDataSelection.SelectAll; begin if DataController.IsGridMode and Assigned(DataController.DataSet) then begin with DataController.Provider do begin Freeze; try SavePos; First; InternalClear; try while not IsEOF do begin if DataController.CanSelectRow(-1) then FBookmarks.Add(DataSet.Bookmark); Next; end; SyncCount; except FBookmarks.Clear; SyncCount; raise; end; RestorePos; finally Unfreeze; Changed; DataController.DataControllerInfo.CheckChanges; DataController.SyncMasterPos; end; end; end; end; procedure TcxDBDataSelection.SelectFromAnchor(AToBookmark: TBookmarkStr; AKeepSelection: Boolean); function CheckBookmarks(const ABkm1, ABkm2: TBookmarkStr; ADirectionDown: Boolean): Boolean; begin Result := False; if AKeepSelection or (FBookmarks.Count = 0) then Exit; with DataController.Provider do begin if CompareBookmarks(ABkm1, ABkm2) = 0 then begin FBookmarks.Clear; if DataController.CanSelectRow(-1) then FBookmarks.Add(ABkm1); Result := True; end else if ADirectionDown then begin if (FBookmarks.Count > 1) and (CompareBookmarks(FBookmarks[FBookmarks.Count - 2], ABkm2) = 0) then // dec last begin if CompareBookmarks(FBookmarks[0], ABkm1) = 0 then begin FBookmarks.Delete(FBookmarks.Count - 1); Result := True; end; end else begin DataSet.Bookmark := ABkm2; DataSet.Prior; if CompareBookmarks(FBookmarks[FBookmarks.Count - 1], DataSet.Bookmark) = 0 then // inc begin if CompareBookmarks(FBookmarks[0], ABkm1) = 0 then begin DataSet.Bookmark := ABkm2; // for OnCanSelectRow event if DataController.CanSelectRow(-1) then FBookmarks.Add(ABkm2); Result := True; end; end; end; end else begin if (FBookmarks.Count > 1) and (CompareBookmarks(FBookmarks[1], ABkm1) = 0) then // dec top begin if CompareBookmarks(FBookmarks[FBookmarks.Count - 1], ABkm2) = 0 then begin FBookmarks.Delete(0); Result := True; end; end else begin DataSet.Bookmark := ABkm1; DataSet.Next; if CompareBookmarks(FBookmarks[0], DataSet.Bookmark) = 0 then // inc begin if CompareBookmarks(FBookmarks[FBookmarks.Count - 1], ABkm2) = 0 then begin DataSet.Bookmark := ABkm1; // for OnCanSelectRow event if DataController.CanSelectRow(-1) then FBookmarks.Insert(0, ABkm1); Result := True; end; end; end; end; end; end; procedure LoadBookmarks(const ABkm1, ABkm2: TBookmarkStr); var ABkm: TBookmarkStr; ACheckNeeded, AFound: Boolean; I, C, AIndex1, AIndex2: Integer; begin with DataController.Provider do begin if not AKeepSelection then FBookmarks.Clear; try ACheckNeeded := FBookmarks.Count > 0; if ACheckNeeded then begin FindBookmark(ABkm1, AIndex1); FindBookmark(ABkm2, AIndex2); if AIndex1 = FBookmarks.Count then ACheckNeeded := False; end; DataSet.Bookmark := ABkm1; while not IsEOF do begin ABkm := DataSet.Bookmark; if ACheckNeeded then begin AFound := False; for I := AIndex1 to AIndex2 do begin if I >= FBookmarks.Count then Break; C := CompareBookmarks(ABkm, FBookmarks[I]); if C < 0 then begin if DataController.CanSelectRow(-1) then FBookmarks.Insert(I, ABkm); Inc(AIndex1); Inc(AIndex2); AFound := True; Break; end else if C = 0 then begin Inc(AIndex1); AFound := True; Break; end; end; if not AFound then begin if DataController.CanSelectRow(-1) then FBookmarks.Add(ABkm); end; end else begin if DataController.CanSelectRow(-1) then FBookmarks.Add(ABkm); end; if CompareBookmarks(ABkm, ABkm2) = 0 then Break; Next; end; except FBookmarks.Clear; SyncCount; raise; end; end; end; var ABkm1, ABkm2: TBookmarkStr; ADirectionDown: Boolean; begin // TODO: optimize - Do Selection (up/down) if DataController.IsGridMode and Assigned(DataController.DataSet) then begin with DataController.Provider do begin if (DataSet.State = dsInsert) and DataSet.Eof then Exit; Freeze; try SavePos; ABkm1 := FAnchorBookmark; ABkm2 := AToBookmark; ADirectionDown := CompareBookmarks(ABkm1, ABkm2) < 0; if not ADirectionDown then begin ABkm2 := FAnchorBookmark; ABkm1 := AToBookmark; end; // check - select shift + up/down if not CheckBookmarks(ABkm1, ABkm2, ADirectionDown) then LoadBookmarks(ABkm1, ABkm2); SyncCount; RestorePos; finally Unfreeze; Changed; DataController.DataControllerInfo.CheckChanges; DataController.SyncMasterPos; end; end; end; end; procedure TcxDBDataSelection.ClearAnchor; begin inherited ClearAnchor; FAnchorBookmark := ''; end; function TcxDBDataSelection.CompareBookmarks(const AItem1, AItem2: TBookmarkStr): Integer; begin Result := DataController.Provider.CompareBookmarks(AItem1, AItem2); end; procedure TcxDBDataSelection.InternalAdd(AIndex, ARowIndex, ARecordIndex, ALevel: Integer); begin inherited InternalAdd(AIndex, ARowIndex, ARecordIndex, ALevel); if not DataController.IsGridMode or FInSelectAll then Exit; FBookmarks.Insert(AIndex, GetRowBookmark(ARowIndex)); end; procedure TcxDBDataSelection.InternalClear; begin inherited InternalClear; if FBookmarks <> nil then FBookmarks.Clear; end; procedure TcxDBDataSelection.InternalDelete(AIndex: Integer); begin inherited InternalDelete(AIndex); if not DataController.IsGridMode or FInSelectAll then Exit; if CompareBookmarks(FBookmarks[AIndex], FAnchorBookmark) = 0 then ClearAnchor; FBookmarks.Delete(AIndex); end; function TcxDBDataSelection.FindBookmark(const ABookmark: TBookmarkStr; var AIndex: Integer): Boolean; var L, H, I, C: Integer; begin AIndex := 0; Result := False; L := 0; H := FBookmarks.Count - 1; if L <= H then repeat I := (L + H) div 2; C := CompareBookmarks(FBookmarks[I], ABookmark); if C = 0 then begin AIndex := I; Result := True; Break; end else if C < 0 then L := I + 1 else H := I - 1; if L > H then begin AIndex := L; Break; end; until False; end; function TcxDBDataSelection.GetRowBookmark(ARowIndex: Integer): TBookmarkStr; var APrevActiveRecord: Integer; begin Result := ''; if not DataController.IsGridMode then Exit; APrevActiveRecord := DataController.Provider.DataLink.ActiveRecord; try DataController.Provider.DataLink.ActiveRecord := ARowIndex; Result := DataController.DataSet.Bookmark; finally DataController.Provider.DataLink.ActiveRecord := APrevActiveRecord; end; end; function TcxDBDataSelection.RefreshBookmarks: Boolean; var I: Integer; ADataSet: TDataSet; begin Result := False; if not DataController.IsGridMode then Exit; ADataSet := DataController.DataSet; if Assigned(ADataSet) then for I := Count - 1 downto 0 do begin if (FBookmarks[I] = '') or not DataController.Provider.BookmarkValid(FBookmarks[I]) then begin Result := True; Delete(I); end; end; end; procedure TcxDBDataSelection.SyncCount; begin FInSelectAll := True; try SetInternalCount(FBookmarks.Count); finally FInSelectAll := False; end; end; function TcxDBDataSelection.GetDataController: TcxDBDataController; begin Result := inherited DataController as TcxDBDataController; end; { TcxDBDataLink } constructor TcxDBDataLink.Create(AProvider: TcxDBDataProvider); begin inherited Create; FProvider := AProvider; VisualControl := True; end; procedure TcxDBDataLink.ActiveChanged; begin Provider.ActiveChanging := True; try Provider.ActiveChanged(Active); if Provider.DataController.IsLoading then Provider.DataController.FLoaded := True; finally Provider.ActiveChanging := False; end; Provider.FInSetDataSource := Assigned(DataSet) and (DataSet.State = dsInsert); {$IFDEF DELPHI7} if not Active then FLayoutChangedFlag := False; {$ENDIF} end; {$IFDEF DELPHI7} procedure TcxDBDataLink.DataEvent(Event: TDataEvent; Info: Integer); begin inherited DataEvent(Event, Info); if (Event = deDisabledStateChange) and not Boolean(Info) then FLayoutChangedFlag := True; end; {$ENDIF} procedure TcxDBDataLink.DataSetChanged; begin {$IFDEF DELPHI7} if FLayoutChangedFlag then begin LayoutChanged; Exit; end; {$ENDIF} if Provider.IsOtherDataControllerUpdating then Exit; if Provider.IsOtherDetailChanged then Exit; { if Provider.IsGridMode and Provider.DataController.IsDetailMode and not VarEquals(GetDataSetValues(DataSet, Provider.DataController.MasterDetailKeyFields), Provider.DataController.MasterDetailKeyValues) then Exit; } if Provider.IsDataSetBusy then DataSetScrolled(0) else begin if Provider.IsGridMode then begin Provider.FInUpdateGridMode := True; try Provider.DataController.BeginFullUpdate; try Provider.UpdateGridMode; if Provider.DataController.MultiSelect then Provider.DataController.DBSelection.RefreshBookmarks; Provider.DataController.CheckBookmarkValid(-1); if DataSet.State = dsInsert then DoInsertingRecord; finally Provider.DataController.EndFullUpdate; end; Provider.CheckExpandedDetails; finally Provider.FInUpdateGridMode := False; end; // if DataSet.State = dsInsert then // Provider.InsertingRecord(Provider.IsEOF); end else begin if not (DataSet.State in dsEditModes) then begin if Provider.IsSmartRefresh then begin Provider.SmartDataChanged; DataSetScrolled(0); end else begin Provider.DataChanged(dcTotal, -1, -1); DataSetScrolled(0); // AB16340 (alternative: Provider.DataController.UpdateFocused;) end; Provider.ResetSmartRefresh; end else if (DataSet.State = dsInsert) and not Provider.FInserting then begin if (not Provider.DataController.IsDetailMode or Provider.IsDataSetCurrent) and Provider.IsSyncInsert and not Provider.FInInserting then DoInsertingRecord; end; end; end; end; procedure TcxDBDataLink.DataSetScrolled(Distance: Integer); begin Provider.DataScrolled(Distance); end; procedure TcxDBDataLink.EditingChanged; begin if not Provider.IsSyncMode then Exit; if not Active or not (DataSet.State in dsEditModes) then begin if {not Provider.IsGridMode and} Provider.IsSmartRefresh and (Provider.EditingRecordIndex <> cxNullEditingRecordIndex) and (Provider.EditingRecordIndex = Provider.DataController.NewItemRecordIndex) then begin if Provider.FUpdatingRecordIsInserting then begin Provider.DataController.FInUnboundCopy := True; // !!! try Provider.FUpdatingNewRecordIndex := Provider.DataController.AppendRecord; Provider.DataController.CopyRecord(Provider.DataController.NewItemRecordIndex, Provider.FUpdatingNewRecordIndex); finally Provider.DataController.FInUnboundCopy := False; end; end; end; Provider.ResetEditing; end else begin if DataSet.State = dsEdit then begin Provider.DataController.FInUnboundCopy := TRue; try Provider.EditingRecord; finally Provider.DataController.FInUnboundCopy := False; end; end else if (DataSet.State = dsInsert) and not DataSet.ControlsDisabled and (Provider.FInSetDataSource or Provider.DataController.FInUpdateGridModeBufferCount) then begin if Provider.IsGridMode and not Provider.FInInsert then DoInsertingRecord; end; end; Provider.FInSetDataSource := False; end; procedure TcxDBDataLink.FocusControl(Field: TFieldRef); function FindItemIndex(AField: TField): Integer; var I: Integer; ADataField: TcxDBDataField; begin Result := -1; for I := 0 to Provider.DataController.Fields.Count - 1 do begin ADataField := Provider.DataController.DBFields[I]; if not ADataField.IsInternal and (ADataField.Field = AField) then begin Result := I; Break; end; end; end; var ADone: Boolean; AItemIndex: Integer; begin if Assigned(Field) and Assigned(Field^) then begin ADone := False; AItemIndex := FindItemIndex(Field^); if (AItemIndex <> -1) and (AItemIndex < Provider.DataController.GetItemCount) then begin Provider.DataController.FocusControl(AItemIndex, ADone); if ADone then Field^ := nil; end; end; end; procedure TcxDBDataLink.LayoutChanged; begin {$IFDEF DELPHI7} // bug in Delphi 7 with update pack 1 if (DataSet.State = dsInsert) or (Provider.IsSmartRefresh and FPreventLayoutChanged) then begin DataSetChanged; Exit; end; {$ENDIF} Provider.LayoutChanged([lcStructure, lcData]); {$IFDEF DELPHI7} FLayoutChangedFlag := False; {$ENDIF} end; procedure TcxDBDataLink.RecordChanged(Field: TField); begin Provider.RecordChanged(Field); end; procedure TcxDBDataLink.UpdateData; begin Provider.DoUpdateData; // A2136!!! // if Provider.IsSmartRefresh then RecordChanged(nil); Provider.ResetChanging; end; procedure TcxDBDataLink.DoInsertingRecord; begin Provider.FInInserting := True; try Provider.InsertingRecord(Provider.IsEOF and not Provider.FInsertOperation); finally Provider.FInInserting := False; end; end; { TcxDBDataAssignedFieldNames } constructor TcxDBDataAssignedFields.Create; begin inherited; FFields := TList.Create; end; destructor TcxDBDataAssignedFields.Destroy; begin FFields.Free; FFields := nil; inherited Destroy; end; procedure TcxDBDataAssignedFields.AddField(AField: TField); begin if FFields.IndexOf(AField) = -1 then FFields.Add(AField); end; procedure TcxDBDataAssignedFields.AddFieldsByFieldList(AFieldList: TList); var I: Integer; begin for I := 0 to AFieldList.Count - 1 do if AFieldList[I] <> nil then FFields.Add(AFieldList[I]); end; procedure TcxDBDataAssignedFields.AddFieldsByFieldNames(ADataSet: TDataSet; const AFieldNames: string); begin ADataSet.GetFieldList(FFields, AFieldNames); end; procedure TcxDBDataAssignedFields.Clear; begin FFields.Clear; end; function TcxDBDataAssignedFields.IsEmpty: Boolean; begin Result := FFields.Count = 0; end; function TcxDBDataAssignedFields.IsFieldExists(AField: TField): Boolean; begin Result := FFields.IndexOf(AField) <> -1; end; { TcxDBDataProvider } constructor TcxDBDataProvider.Create(ADataController: TcxCustomDataController); begin inherited Create(ADataController); FAssignedFields := TcxDBDataAssignedFields.Create; if DBDataProviders = nil then DBDataProviders := TList.Create; DBDataProviders.Add(Self); FDataLink := TcxDBDataLink.Create(Self); FUpdatingRecordIndex := cxNullEditingRecordIndex; FUpdatingNewRecordIndex := -1; FListeners := TList.Create; end; destructor TcxDBDataProvider.Destroy; begin RemoveFromListeners; FListeners.Free; FListeners := nil; FDataLink.Free; FDataLink := nil; DBDataProviders.Remove(Self); if DBDataProviders.Count = 0 then begin DBDataProviders.Free; DBDataProviders := nil; end; FAssignedFields.Free; FAssignedFields := nil; inherited Destroy; end; function TcxDBDataProvider.IsCanInitEditingBusy: Boolean; var I: Integer; ADBDataProvider: TcxDBDataProvider; begin Result := False; for I := 0 to DBDataProviders.Count - 1 do begin ADBDataProvider := TcxDBDataProvider(DBDataProviders[I]); if (ADBDataProvider.DataSet = Self.DataSet) and ADBDataProvider.FInCanInitEditing then begin Result := True; Break; end; end; end; function TcxDBDataProvider.IsDataSetBusy: Boolean; var I: Integer; ADBDataProvider: TcxDBDataProvider; begin Result := False; for I := 0 to DBDataProviders.Count - 1 do begin ADBDataProvider := TcxDBDataProvider(DBDataProviders[I]); if {(ADBDataProvider <> Self) and} (ADBDataProvider.DataSet = Self.DataSet) and ((ADBDataProvider.LockCount <> 0) or ADBDataProvider.FInNotify or ((Self.IsKeyNavigation or Self.DataController.IsGridMode) and (ADBDataProvider.LocateCount <> 0))) then begin if ADBDataProvider.FInNotify then begin if not FPostLocateFlag then Result := True; end else if not FPostLocateFlag then begin Result := True; if Self.DataController.IsGridMode and (Self.DataController.LockGridModeNotifyCount <> 0) then Result := False; end; Break; end; end; end; function TcxDBDataProvider.IsDetailDataSetBusy: Boolean; var I: Integer; ADBDataProvider: TcxDBDataProvider; begin Result := False; for I := 0 to DBDataProviders.Count - 1 do begin ADBDataProvider := TcxDBDataProvider(DBDataProviders[I]); if (ADBDataProvider.DataSet = Self.DataSet) and ((ADBDataProvider.LockCount <> 0){ or ADBDataProvider.FInFirst}) and ADBDataProvider.DataController.IsDetailMode {ADBDataProvider.FInFirst} then begin Result := True; Break; end; end; end; function TcxDBDataProvider.IsOtherDataControllerUpdating: Boolean; var I: Integer; ADBDataProvider: TcxDBDataProvider; begin Result := False; for I := 0 to DBDataProviders.Count - 1 do begin ADBDataProvider := TcxDBDataProvider(DBDataProviders[I]); if (ADBDataProvider <> Self) and (ADBDataProvider.DataSet = Self.DataSet) and ((ADBDataProvider.LockCount <> 0) or (Self.IsKeyNavigation and (ADBDataProvider.LocateCount <> 0))) then begin if ADBDataProvider.DataController.LockGridModeNotifyCount = 0 then ADBDataProvider.AddListener(Self); Result := True; Break; end; end; end; function TcxDBDataProvider.IsOtherDetailDataSetInInsert: Boolean; var I: Integer; ADBDataProvider: TcxDBDataProvider; begin Result := False; for I := 0 to DBDataProviders.Count - 1 do begin ADBDataProvider := TcxDBDataProvider(DBDataProviders[I]); if (ADBDataProvider <> Self) and (ADBDataProvider.DataSet = Self.DataSet) and ADBDataProvider.FInInsert then begin Result := True; Break; end; end; end; function TcxDBDataProvider.IsOtherDetailChanged: Boolean; begin Result := DataController.IsOtherDetailChanged; end; function TcxDBDataProvider.IsUniDirectional: Boolean; begin {$IFDEF DELPHI6} if Assigned(DataSet) and DataSet.IsUniDirectional then Result := True else {$ENDIF} Result := False; end; function TcxDBDataProvider.IsCustomDataSourceSupported: Boolean; begin Result := False; end; function TcxDBDataProvider.IsGridMode: Boolean; begin Result := DataController.DataModeController.GridMode and not IsUniDirectional and not FLoadAllNeeded and {not DataController.IsDetailMode} (DataController.DetailMode <> dcdmPattern); end; function TcxDBDataProvider.IsDataSource: Boolean; begin Result := (DataSet <> nil) and (DataSet.State <> dsInactive); end; function TcxDBDataProvider.IsKeyNavigation: Boolean; begin Result := DataController.IsKeyNavigation; end; function TcxDBDataProvider.IsOtherInsert: Boolean; var I: Integer; ADBDataProvider: TcxDBDataProvider; begin Result := False; for I := 0 to DBDataProviders.Count - 1 do begin ADBDataProvider := TcxDBDataProvider(DBDataProviders[I]); if (ADBDataProvider <> Self) and (ADBDataProvider.DataSet = Self.DataSet) and ADBDataProvider.FInInsert then begin Result := True; Break; end; end; end; function TcxDBDataProvider.IsSyncInsert: Boolean; begin Result := not DataController.IsPattern and DataController.DataModeController.SyncInsert and IsSyncMode; end; function TcxDBDataProvider.IsSyncMode: Boolean; begin Result := Assigned(DataSet) and DataSet.Active and (IsGridMode or DataController.DataModeController.SyncMode) and not IsUniDirectional; if DataController.IsDetailMode then begin if not DataController.DataModeController.DetailInSyncMode then Result := False else if DataController.DataModeController.DetailInSQLMode then begin if not IsDataSetCurrent then Result := False; end else if not (IsKeyNavigation or IsGridMode) then Result := False; end; end; function TcxDBDataProvider.DataSet_ActiveRecord: Integer; begin Result := TDataSetAccess(DataSet).ActiveRecord; end; function TcxDBDataProvider.DataSet_BookmarkAvailable: Boolean; begin Result := TDataSetAccess(DataSet).BookmarkAvailable; end; function TcxDBDataProvider.DataSet_BufferCount: Integer; begin Result := TDataSetAccess(DataSet).BufferCount; end; function TcxDBDataProvider.IsActive: Boolean; begin Result := Assigned(DataSet) and DataSet.Active; // WARNING: DataLink.Active <> DataSet.Active! Result := Result and not DataSet.ControlsDisabled; end; function TcxDBDataProvider.IsActiveDataSet: Boolean; begin Result := Assigned(DataSet) and (DataSet.FieldCount > 0) and (DataSet.Active or not DataSet.DefaultFields); end; function TcxDBDataProvider.IsBOF: Boolean; begin if Assigned(DataSet) then Result := DataSet.BOF or not DataLink.Active else Result := inherited IsBOF; end; function TcxDBDataProvider.IsChanging: Boolean; begin Result := inherited IsChanging; end; function TcxDBDataProvider.IsEditing: Boolean; begin if IsUnboundColumnMode then Result := inherited IsEditing else Result := Assigned(DataSet) and (DataSet.State in dsEditModes); end; function TcxDBDataProvider.IsEOF: Boolean; begin if Assigned(DataSet) then begin with DataController do begin if IsDetailMode and not IsGridMode then begin if not VarIsEmpty(MasterDetailKeyValues) then Result := DataSet.EOF or (not (DataSet.State in dsEditModes) and not VarEquals(GetDataSetValues(DataSet, MasterDetailKeyFields), MasterDetailKeyValues)) else Result := True; end else Result := DataSet.EOF or not DataLink.Active; end; end else Result := inherited IsEOF; end; function TcxDBDataProvider.IsGridModeUpdating: Boolean; begin Result := IsGridMode and FInUpdateGridMode; end; function TcxDBDataProvider.IsInserting: Boolean; begin Result := Assigned(DataSet) and (DataSet.State = dsInsert); end; function TcxDBDataProvider.IsModified: Boolean; begin Result := inherited IsModified {for Unbound column} or IsDataSetModified; end; function TcxDBDataProvider.IsDataSetModified: Boolean; var I: Integer; begin Result := Assigned(DataSet) and DataSet.Modified; if Result and not FAssignedFields.IsEmpty then // check Null values begin Result := False; for I := 0 to DataSet.FieldCount - 1 do if not FAssignedFields.IsFieldExists(DataSet.Fields[I]) and not VarIsNull(GetFieldValue(DataSet.Fields[I])) then begin Result := True; Break; end; end; // check unbound column if not Result and (inherited IsModified) then begin for I := 0 to DataController.ItemCount - 1 do if DataController.Fields[I].IsUnbound and not VarIsNull(DataController.GetEditValue(I, evsValue)) then begin Result := True; Break; end; end; end; function TcxDBDataProvider.BookmarkValid(const ABookmark: TBookmarkStr): Boolean; begin Result := Dataset.BookmarkValid(TBookmark(ABookmark)); end; function TcxDBDataProvider.CompareBookmarks(const AItem1, AItem2: TBookmarkStr): Integer; begin if not DataLink.Active then Result := -1 else Result := Dataset.CompareBookmarks(TBookmark(AItem1), TBookmark(AItem2)); end; procedure TcxDBDataProvider.CorrectRecordIndex(ARecordIndex: Integer); begin if Assigned(DataSet) then begin if not IsKeyNavigation and (FSavePosInfo.Bookmark <> '') and (CompareBookmarks(FSavePosInfo.Bookmark, DataSet.Bookmark) = 0) then FRecordIndex := ARecordIndex; end; end; procedure TcxDBDataProvider.First; var AReopened: Boolean; begin FInFirst := True; try inherited First; if Assigned(DataSet) then with DataController do if IsDetailMode and not IsGridMode then begin if not VarIsEmpty(MasterDetailKeyValues) then begin DataModeController.DoDetailFirst(MasterDetailKeyFieldNames, MasterDetailKeyValues, AReopened); if AReopened then SavePos; IsDataSetCurrent := True; if RecreatedFieldsAfterFirst then NotifyDetailsAfterFieldsRecreating(not DataController.FInCheckCurrentQuery); end; end else if DataSet.Active then DataSet.First; finally FInFirst := False; end; end; procedure TcxDBDataProvider.Prev; begin if Assigned(DataSet) then DataSet.Prior; end; procedure TcxDBDataProvider.Next; begin if Assigned(DataSet) then DataSet.Next; end; procedure TcxDBDataProvider.Last; begin if Assigned(DataSet) and DataSet.Active then DataSet.Last; end; procedure TcxDBDataProvider.MoveBy(ADistance: Integer); begin if Assigned(DataSet) then DataSet.MoveBy(ADistance); end; procedure TcxDBDataProvider.Scroll(ADistance: Integer); var ARecordIndex: Integer; begin if not IsGridMode or (ADistance = 0) or not IsActive then Exit; if ADistance < 0 then begin ARecordIndex := (ADistance - DataLink.ActiveRecord); DataSet.MoveBy(ARecordIndex); end else begin ARecordIndex := DataLink.RecordCount - DataLink.ActiveRecord - 1 + ADistance; DataSet.MoveBy(ARecordIndex); end; DataController.CorrectPrevSelectionChangedInfo; DataController.SyncSelected(True); end; procedure TcxDBDataProvider.SavePos; begin if Assigned(DataSet) then with DataSet do begin if DataSet_BookmarkAvailable then FSavePosInfo.Bookmark := Bookmark else FSavePosInfo.Bookmark := ''; FSavePosInfo.ActiveRecord := DataSet_ActiveRecord; // save offset in DataSet buffer FSavePosInfo.BOF := BOF; FSavePosInfo.EOF := EOF; end; end; procedure TcxDBDataProvider.RestorePos; begin if Assigned(DataSet) then with DataSet do begin if (FSavePosInfo.Bookmark <> '') and Self.BookmarkValid(FSavePosInfo.Bookmark) then begin try Bookmark := FSavePosInfo.Bookmark; // restore offset in DataSet buffer if DataSet_ActiveRecord > FSavePosInfo.ActiveRecord then begin MoveBy(DataSet_BufferCount - DataSet_ActiveRecord - 1 + DataSet_ActiveRecord - FSavePosInfo.ActiveRecord); MoveBy(FSavePosInfo.ActiveRecord - DataSet_BufferCount + 1); end else if DataSet_ActiveRecord < FSavePosInfo.ActiveRecord then begin MoveBy(-DataSet_ActiveRecord + DataSet_ActiveRecord - FSavePosInfo.ActiveRecord); MoveBy(FSavePosInfo.ActiveRecord); end; // BOF and EOF if FSavePosInfo.BOF and not BOF then Prior; if FSavePosInfo.EOF and not EOF then Next; except on EDatabaseError do; end; end; end; end; function TcxDBDataProvider.CanDelete: Boolean; begin Result := CanModify and not DataSet.IsEmpty; end; function TcxDBDataProvider.CanInitEditing(ARecordIndex: Integer): Boolean; begin if IsUnboundColumnMode then Result := inherited CanInitEditing(ARecordIndex) else begin FInCanInitEditing := True; try // bug in Delphi 7 with update pack 1 if DataController.UseNewItemRowForEditing and DataController.NewItemRowFocused and not IsInserting then begin Insert; Result := True; end else Result := DataLink.Edit; if Result then begin SetChanging; // Update Navigator if DataController.UseNewItemRowForEditing and DataController.NewItemRowFocused then DataController.Change([dccUpdateRecord]); end; finally FInCanInitEditing := False; end; end; end; function TcxDBDataProvider.CanModify: Boolean; begin Result := Assigned(DataSet) and DataSet.Active and DataSet.CanModify and IsSyncMode; end; procedure TcxDBDataProvider.Append; begin if Assigned(DataSet) then AddNewRecord(DataSet.Append); end; procedure TcxDBDataProvider.InternalCancel; var ARecordIndex: Integer; ARecordId: Variant; begin if not Assigned(DataSet) then Exit; if DataController.IsDetailMode and IsEOF then // Appending in M-D begin DataSet.DisableControls; try if (DataController.GetRowCount - 2) >= 0 then begin ARecordIndex := DataController.GetRowInfo(DataController.GetRowCount - 2).RecordIndex; ARecordId := DataController.GetRecordId(ARecordIndex); end else ARecordIndex := -1; DataSet.Cancel; if ARecordIndex <> -1 then DataSet.Locate(DataController.KeyFieldNames, ARecordId, []); finally DataSet.EnableControls; end; end else DataSet.Cancel; end; procedure TcxDBDataProvider.Cancel; begin if IsUnboundColumnMode then inherited Cancel else begin if DataController.IsSmartRefresh then // !!! begin DataController.FInUnboundCopy := True; try inherited Cancel; finally DataController.FInUnboundCopy := False; end; end; InternalCancel; end; end; procedure TcxDBDataProvider.Delete; begin if Assigned(DataSet) then begin if DataController.GetFocusedRecordIndex <> -1 then begin if IsSmartRefresh then FUpdatingPrevFocusedRecordIndex := DataController.GetFocusedRecordIndex; DataSet.DisableControls; try DataSet.Delete; {$IFDEF DELPHI7} // bug in Delphi 7 with update pack 1 DataLink.FPreventLayoutChanged := True; {$ENDIF} if DataController.FNearestRecordIndex <> -1 then DataController.TryFocusRecord(DataController.FNearestRecordIndex); finally DataSet.EnableControls; {$IFDEF DELPHI7} // bug in Delphi 7 with update pack 1 DataLink.FPreventLayoutChanged := False; {$ENDIF} end; if IsSmartRefresh then begin if FUpdatingPrevFocusedRecordIndex < DataController.RecordCount then DataController.DeleteRecord(FUpdatingPrevFocusedRecordIndex); DataController.UpdateFocused; end; end; end; end; procedure TcxDBDataProvider.DeleteRecords(AList: TList); procedure InternalDelete(AUpdateFocused: Boolean); var ARecordIndex: Integer; begin if IsSmartRefresh then begin ARecordIndex := DataController.LocateRecordIndex(DataController.GetKeyFields); if ARecordIndex <> -1 then begin DataSet.Delete; DataController.DeleteRecord(ARecordIndex); end; end else DataSet.Delete; end; procedure DeleteWithLocate; var AKeyList: TList; AFieldList: TList; I: Integer; P: PVariant; begin AKeyList := TList.Create; AFieldList := TList.Create; try DataController.GetKeyFields(AFieldList); for I := 0 to AList.Count - 1 do begin New(P); P^ := DataController.GetInternalRecordId(Integer(AList[I]), AFieldList); AKeyList.Add(P); end; for I := AKeyList.Count - 1 downto 0 do begin P := PVariant(AKeyList[I]); if DataSet.Locate(DataController.KeyFieldNames, P^, []) then InternalDelete(False); end; if IsSmartRefresh then DataController.UpdateFocused; finally for I := 0 to AKeyList.Count - 1 do begin P := AKeyList[I]; Dispose(P); end; AKeyList.Free; AFieldList.Free; end; end; procedure DeleteWithMoveBy; var I, AAnchorRecordIndex, J, ARecordCount: Integer; begin AAnchorRecordIndex := Integer(AList[AList.Count - 1]); ARecordCount := DataController.RecordCount; J := 0; if DataController.CanFocusRecord(AAnchorRecordIndex) then for I := AList.Count - 1 downto 0 do begin DataSet.MoveBy(Integer(AList[I]) - AAnchorRecordIndex + J); DataSet.Delete; AAnchorRecordIndex := Integer(AList[I]); if AAnchorRecordIndex = (ARecordCount - 1) then J := 1 else J := 0; Dec(ARecordCount); end; end; begin if Assigned(DataSet) then begin DataSet.DisableControls; try if DataController.IsSmartRefresh then DataController.BeginFullUpdate; try if IsKeyNavigation then DeleteWithLocate else DeleteWithMoveBy; finally if DataController.IsSmartRefresh then begin if DataController.FInDeleteSelection then DataController.ClearSelection; DataController.EndFullUpdate; end else if not DataController.IsKeyNavigation then DataController.ClearSelection; end; finally DataSet.EnableControls; end; end; end; procedure TcxDBDataProvider.DeleteSelection; var I: Integer; ASelection: TcxDBDataSelection; begin if IsGridMode then begin if Assigned(DataSet) then begin ASelection := DataController.DBSelection; DataSet.DisableControls; try for I := ASelection.Count - 1 downto 0 do begin DataSet.Bookmark := ASelection.FBookmarks[I]; DataSet.Delete; ASelection.Delete(I); end; finally DataSet.EnableControls; end; end; end else inherited DeleteSelection; end; procedure TcxDBDataProvider.Edit; begin if IsUnboundColumnMode then inherited Edit else if Assigned(DataSet) then DataSet.Edit; end; function TcxDBDataProvider.GetEditValue(ARecordIndex: Integer; AField: TcxCustomDataField; AEditValueSource: TcxDataEditValueSource): Variant; var ADataSetField: TField; begin Result := Null; ADataSetField := TcxDBDataField(AField).Field; if Assigned(ADataSetField) then begin if AEditValueSource = evsText then begin if ADataSetField.IsNull and not Assigned(ADataSetField.OnGetText) then Result := Null else Result := ADataSetField.Text; end else if (AEditValueSource = evsKey) and (ADataSetField.KeyFields <> '') then Result := ADataSetField.DataSet.FieldValues[ADataSetField.KeyFields] else if not ADataSetField.IsNull then Result := ADataSetField.Value; // evsValue end else if AField.IsUnbound then Result := inherited GetEditValue(ARecordIndex, AField, AEditValueSource); end; procedure TcxDBDataProvider.Insert; begin if Assigned(DataSet) then begin FInsertOperation := True; try AddNewRecord(DataSet.Insert); finally FInsertOperation := False; end; end; end; procedure TcxDBDataProvider.Post; var ALock: Boolean; begin if IsUnboundColumnMode then inherited Post else if Assigned(DataSet) then begin ALock := not IsSmartRefresh; if ALock then DataController.BeginFullUpdate; try DataSet.CheckBrowseMode; // DataSet.Post; finally if ALock then DataController.EndFullUpdate; end; end; end; procedure TcxDBDataProvider.PostEditingData; begin if DataLink.Active then DataLink.UpdateData; end; procedure TcxDBDataProvider.AssignItemValue(ARecordIndex: Integer; AField: TcxCustomDataField; const AValue: Variant); var ADBField: TcxDBDataField; ADataSetField: TField; begin ADBField := AField as TcxDBDataField; ADataSetField := ADBField.Field; if Assigned(ADataSetField) then begin if ADBField.IsLookup and (ADataSetField.KeyFields <> '') then begin ADataSetField.DataSet.FieldValues[ADataSetField.KeyFields] := AValue; FAssignedFields.AddFieldsByFieldNames(ADataSetField.DataSet, ADataSetField.KeyFields); end else begin ADataSetField.Value := AValue; // SetFieldValue(ADataSetField, AValue);? FAssignedFields.AddField(ADataSetField); end; end; end; procedure TcxDBDataProvider.DoInitInsertingRecord(AInsertingRecordIndex: Integer; const AGroupValues: TcxDataSummaryValues); begin if not (FInInsert or FInCanInitEditing or IsOtherInsert) then begin DataController.UpdateEditingRecord; Exit; end; FAssignedFields.Clear; // reset assigned field names inherited DoInitInsertingRecord(AInsertingRecordIndex, AGroupValues); if (dcoAssignMasterDetailKeys in DataController.Options) and DataController.IsDetailMode then begin SetDataSetValues(DataSet, DataController.MasterDetailKeyFields, DataController.MasterDetailKeyValues); FAssignedFields.AddFieldsByFieldList(DataController.MasterDetailKeyFields); end; DataController.DoInitInsertingRecord(AInsertingRecordIndex); DataController.UpdateEditingRecord; end; function TcxDBDataProvider.SetEditValue(ARecordIndex: Integer; AField: TcxCustomDataField; const AValue: Variant; AEditValueSource: TcxDataEditValueSource): Boolean; var ADataSetField: TField; begin Result := False; ADataSetField := TcxDBDataField(AField).Field; if Assigned(ADataSetField) then begin if (AEditValueSource = evsText) or Assigned(ADataSetField.OnSetText) then ADataSetField.Text := VarToStr(AValue) else if (AEditValueSource = evsKey) and (ADataSetField.KeyFields <> '') then ADataSetField.DataSet.FieldValues[ADataSetField.KeyFields] := AValue else SetFieldValue(ADataSetField, AValue); Result := True; end else if AField.IsUnbound then Result := inherited SetEditValue(ARecordIndex, AField, AValue, AEditValueSource); end; procedure TcxDBDataProvider.BeginDeleting; //var // ARowIndex: Integer; begin inherited; { if IsKeyNavigation and (DataController.FocusedRowIndex <> -1) then begin ARowIndex := DataController.GetNearestRowIndex(DataController.FocusedRowIndex, ASelection); if ARowIndex <> -1 then begin DataController.FDeletingRecordIndex := DataController.GetRowInfo(ARowIndex).RecordIndex; FNearestRecordID := DataController.GetRecordId(DataController.FDeletingRecordIndex); end; end; } end; procedure TcxDBDataProvider.EndDeleting; begin try if DataController.FNearestRecordIndex <> -1 then begin if DataController.FNearestRecordIndex < DataController.RecordCount then DataController.ChangeFocusedRecordIndex(DataController.FNearestRecordIndex); end; finally inherited; end; { DataController.FDeletingRecordIndex := -1; FNearestRecordID := Unassigned; } end; procedure TcxDBDataProvider.ActiveChanged(AActive: Boolean); begin inherited ActiveChanged(AActive); if Assigned(DataSet) and not DataSet.Active and DataSet.DefaultFields then DataController.ResetDBFields; end; procedure TcxDBDataProvider.AddListener(AProvider: TcxDBDataProvider); begin if FListeners.IndexOf(AProvider) = -1 then FListeners.Add(AProvider); end; procedure TcxDBDataProvider.BeginLocate; begin inherited BeginLocate; if Assigned(DataSet) and (DataSet.State in dsEditModes) then SetPostLocateFlag; end; procedure TcxDBDataProvider.DataScrolled(ADistance: Integer); begin if IsGridMode then begin FRecordIndex := DataLink.ActiveRecord; DataController.BeginUpdate; try if ADistance <> 0 then DataController.InvalidateDataBuffer; if not DataController.Relations.IsEmpty then DataController.CollapseDetails; finally DataController.EndUpdate; end; end else Inc(FRecordIndex, ADistance); inherited DataScrolled(ADistance); if IsGridMode and DataController.IsSequenced then DataController.UpdateScrollBars; end; procedure TcxDBDataProvider.Freeze; begin inherited Freeze; if Assigned(DataSet) then begin DataSet.DisableControls; if DataSet.Active and (DataSet.State in dsEditModes) then begin SetPostLocateFlag; DataSet.Cancel; end; end; end; procedure TcxDBDataProvider.LayoutChanged(ADataLayoutChanges: TcxDataLayoutChanges); begin if IsDetailDataSetBusy then begin if IsActive then DataController.ResyncDBFields; Exit; end; inherited LayoutChanged(ADataLayoutChanges); end; procedure TcxDBDataProvider.RecordChanged(Field: TField); var AIsDataSetCurrent: Boolean; begin if DataController.IsDetailMode and DataController.DataModeController.DetailInSyncMode and not IsDataSetCurrent then AIsDataSetCurrent := False else AIsDataSetCurrent := True; if IsEditing and AIsDataSetCurrent then DataController.UpdateEditingRecord; ResetChanging; end; procedure TcxDBDataProvider.ResetPostLocateFlag; begin FPostLocateFlag := False; end; procedure TcxDBDataProvider.ResetSmartRefresh; begin FUpdatingRecordIndex := cxNullEditingRecordIndex; FUpdatingNewRecordIndex := -1; FUpdatingRecordIsInserting := False; end; procedure TcxDBDataProvider.SetPostLocateFlag; var I: Integer; ADBDataProvider: TcxDBDataProvider; begin for I := 0 to DBDataProviders.Count - 1 do begin ADBDataProvider := TcxDBDataProvider(DBDataProviders[I]); if (ADBDataProvider.DataSet = Self.DataSet) and (ADBDataProvider.DataController.DetailMode <> dcdmPattern) then ADBDataProvider.FPostLocateFlag := True; end; end; procedure TcxDBDataProvider.Unfreeze; begin if Assigned(DataSet) then DataSet.EnableControls; inherited Unfreeze; end; procedure TcxDBDataProvider.SmartDataChanged; function IsKeyFieldAutoInc: Boolean; var AKeyFields: TList; begin Result := False; AKeyFields := TList.Create; try DataController.GetKeyDBFields(AKeyFields); if (AKeyFields.Count = 1) and (TField(AKeyFields[0]).DataType = ftAutoInc) then Result := True; finally AKeyFields.Free; end; end; function IsNewRecordInserted(const AFocusedRecordValues, AKeyFieldsValues: Variant): Boolean; begin Result := False; if not FUpdatingRecordIsInserting then Exit; // check auto inc if VarEquals(AFocusedRecordValues, Null) and IsKeyFieldAutoInc then begin if DataController.FindRecordIndexByKey(AKeyFieldsValues) = -1 then Result := True; end; // new item ? end; var AFocusedRecordIndex, ARecordIndex: Integer; AFocusedRecordValues, AKeyFieldsValues: Variant; ADataChangeInfo: TcxDataChangeInfo; begin if FUpdatingRecordIndex <> cxNullEditingRecordIndex then begin AFocusedRecordIndex := DataController.GetFocusedRecordIndex; if AFocusedRecordIndex = FUpdatingRecordIndex then begin if (AFocusedRecordIndex < 0) and (FUpdatingNewRecordIndex <> -1) then AFocusedRecordIndex := FUpdatingNewRecordIndex; AFocusedRecordValues := DataController.GetRecordId(AFocusedRecordIndex); AKeyFieldsValues := DataController.GetKeyFieldsValues; if not VarEquals(AFocusedRecordValues, AKeyFieldsValues) then begin if IsNewRecordInserted(AFocusedRecordValues, AKeyFieldsValues) then begin if DataController.NewItemRowFocused then begin if FUpdatingNewRecordIndex <> -1 then AFocusedRecordIndex := FUpdatingNewRecordIndex else begin AFocusedRecordIndex := DataController.AppendRecord; DataController.CopyRecord(DataController.NewItemRecordIndex, AFocusedRecordIndex); end; end; DataController.DoUpdateRecord(AFocusedRecordIndex); end else begin ARecordIndex := DataController.FindRecordIndexByKey(AFocusedRecordValues); if (ARecordIndex = -1) or (ARecordIndex = AFocusedRecordIndex) then begin if AFocusedRecordIndex <> -1 then DataController.DeleteRecord(AFocusedRecordIndex); DataController.UpdateFocused; end; end; end else DataController.DoUpdateRecord(AFocusedRecordIndex); end; // record changed notify if AFocusedRecordIndex <> -1 then begin ADataChangeInfo := DataController.FDataChangeInfo; ADataChangeInfo.Kind := dcRecord; ADataChangeInfo.RecordIndex := AFocusedRecordIndex; DataController.FDataChangeInfo := ADataChangeInfo; end; end; DataController.FilterChanged; DataController.SummaryChanged(False); ResetPostLocateFlag; // ? end; procedure TcxDBDataProvider.CheckExpandedDetails; var I, AFocusedRecordIndex: Integer; begin AFocusedRecordIndex := DataController.FocusedRecordIndex; for I := 0 to DataController.RecordCount - 1 do if (I <> AFocusedRecordIndex) and DataController.IsDetailDataControllerExist(I, -1) then begin DataController.CollapseDetails; Break; end; end; function TcxDBDataProvider.DoLocate(const ASubText: string; AForward, ANext: Boolean): Boolean; var ASearchItemIndex: Integer; AStartPos: TBookmarkStr; AExit, AStartEOF: Boolean; function IsCurrentEqual: Boolean; var S: string; begin FIncrementalSearching := True; try S := DataController.GetIncrementalSearchText(-1{set FIncrementalSearching flag!}, ASearchItemIndex); Result := DataCompareText(S, ASubText, True); finally FIncrementalSearching := False; end; end; procedure CalcNextRecord; begin with DataSet do if not AForward then begin Prior; if BOF then begin if not ANext then Last else AExit := True; AStartPos := FSavePosInfo.Bookmark; end; end else begin Next; if EOF then begin if not ANext then First else AExit := True; AStartPos := FSavePosInfo.Bookmark; end; end; end; begin Result := False; if not Assigned(DataSet) or IsEditing then Exit; with DataSet do begin if Active and not (BOF and EOF) then begin ASearchItemIndex := DataController.Search.ItemIndex; Result := not ANext and IsCurrentEqual; if Result then DataController.UpdateFocused else begin Freeze; try SavePos; AStartPos := ''; AExit := False; AStartEOF := False; if ANext then CalcNextRecord else if EOF then AStartEOF := True; while (not EOF or ANext or AStartEOF) and ((AStartPos = '') or (Self.CompareBookmarks(Bookmark, AStartPos) <> 0)) do begin AStartEOF := False; if IsCurrentEqual then begin Result := True; Break; end; CalcNextRecord; if AExit then Break; end; if not Result then RestorePos; finally Unfreeze; // Invalidate disabled end; if Result then begin DataController.CorrectPrevSelectionChangedInfo; DataController.Change([dccLayout]); // Invalidate end; DataController.SyncMasterPos; end; end; end; end; function TcxDBDataProvider.GetDataBufferCount: Integer; begin if DataController.DataModeController.GridModeBufferCount = 0 then Result := DataController.GetDefaultGridModeBufferCount else Result := DataController.DataModeController.GridModeBufferCount; end; function TcxDBDataProvider.GetDataRecordCount: Integer; begin Result := DataLink.RecordCount; end; function TcxDBDataProvider.GetExternalDataDisplayText(ARecordIndex: Integer; AField: TcxCustomDataField): string; var ADataSetField: TField; APrevActiveRecord: Integer; begin ADataSetField := TcxDBDataField(AField).Field; if Assigned(ADataSetField) then begin if (0 <= ARecordIndex) and (ARecordIndex < DataLink.RecordCount) then begin APrevActiveRecord := DataLink.ActiveRecord; try DataLink.ActiveRecord := ARecordIndex; Result := ADataSetField.DisplayText; finally DataLink.ActiveRecord := APrevActiveRecord; end; end else if IsInserting or FIncrementalSearching then Result := ADataSetField.DisplayText else Result := ''; end else Result := inherited GetExternalDataDisplayText(ARecordIndex, AField); end; function TcxDBDataProvider.GetExternalDataValue(ARecordIndex: Integer; AField: TcxCustomDataField): Variant; function GetFieldValue(AField: TField): Variant; begin if AField.IsNull then Result := Null else Result := AField.Value; end; var ADataSetField: TField; APrevActiveRecord: Integer; begin ADataSetField := TcxDBDataField(AField).Field; if Assigned(ADataSetField) then begin if (0 <= ARecordIndex) and (ARecordIndex < DataLink.RecordCount) then begin APrevActiveRecord := DataLink.ActiveRecord; try DataLink.ActiveRecord := ARecordIndex; Result := GetFieldValue(ADataSetField); finally DataLink.ActiveRecord := APrevActiveRecord; end; end else if IsInserting or FIncrementalSearching then Result := GetFieldValue(ADataSetField) else Result := Null; end else Result := inherited GetExternalDataValue(ARecordIndex, AField); end; procedure TcxDBDataProvider.LoadDataBuffer; begin if DataLink.Active and IsGridMode then begin DataLink.BufferCount := GetDataBufferCount; DataController.DataStorage.Clear(False); DataController.DataStorage.RecordCount := GetDataRecordCount; FRecordIndex := DataLink.ActiveRecord; end; inherited LoadDataBuffer; end; procedure TcxDBDataProvider.UpdateGridMode; var APrevRecordCount: Integer; begin if DataLink.Active and IsGridMode then begin APrevRecordCount := DataController.RecordCount; DataLink.BufferCount := GetDataBufferCount; FRecordIndex := DataLink.ActiveRecord; if ((APrevRecordCount <> GetDataRecordCount) or IsInserting) {and not DataController.FInUpdateGridModeBufferCount} then // !!! begin DataChanged(dcTotal, -1, -1); if DataController.FInUpdateGridModeBufferCount and (DataSet.State in dsEditModes) and not DataLink.ReadOnly then DataLink.EditingChanged; end else begin if not IsInserting then DataChanged(dcRecord, -1, -1); DataController.UpdateFocused; end; end; end; function TcxDBDataProvider.GetDetailHasChildren(ARecordIndex: Integer; ARelationIndex: Integer): Boolean; var ARelation: TcxDBDataRelation; APatternDataController, ADetailDataController: TcxCustomDataController; ADataController: TcxDBDataController; AMasterDetailKeyValues: Variant; begin APatternDataController := DataController.GetPatternDataController; if APatternDataController <> nil then ADetailDataController := APatternDataController.Relations[ARelationIndex].DetailDataController else ADetailDataController := nil; //!!! if ADetailDataController is TcxDBDataController then begin Result := True; ADataController := TcxDBDataController(ADetailDataController); ARelation := TcxDBDataRelation(DataController.Relations[ARelationIndex]); // event AMasterDetailKeyValues := ARelation.GetMasterRecordID(ARecordIndex); if Assigned(DataController.FOnDetailHasChildren) then DataController.FOnDetailHasChildren(DataController, ARecordIndex, ARelationIndex, ADataController.MasterDetailKeyFieldNames, AMasterDetailKeyValues, Result) else if not (ADataController.IsGridMode or ADataController.DataModeController.DetailInSQLMode) then begin if Assigned(ADataController.DataSet) and ADataController.DataSet.Active then begin ADataController.Provider.Freeze; try ADataController.Provider.SavePos; try Result := ADataController.DataSet.Locate(ADataController.MasterDetailKeyFieldNames, AMasterDetailKeyValues, []); // must be sort by Detail Keys finally ADataController.Provider.RestorePos; end; finally ADataController.Provider.Unfreeze; ADataController.SyncMasterPos; end; end else Result := False; end; end else Result := inherited GetDetailHasChildren(ARecordIndex, ARelationIndex); end; function TcxDBDataProvider.GetRecordIndex: Integer; begin if not FInUpdateGridMode and IsInserting then Result := EditingRecordIndex else if FInUpdateGridMode and IsInserting and DataController.NewItemRowFocused then Result := DataController.NewItemRecordIndex else Result := FRecordIndex; end; function TcxDBDataProvider.GetValueDefReaderClass: TcxValueDefReaderClass; begin Result := TcxValueDefDBReader; end; procedure TcxDBDataProvider.AddNewRecord(AOperation: TDataOperation); procedure CheckDataSetBrowseMode; begin DataSet.CheckBrowseMode; SmartDataChanged; ResetSmartRefresh; ResetEditing; end; begin if Assigned(DataSet) then begin FInInsert := True; try if DataSet.State in dsEditModes then begin if not IsSmartRefresh and (DataSet.State = dsInsert) then DataController.CheckBrowseMode; if DataSet.State in dsEditModes then DataLink.UpdateRecord; if IsSmartRefresh then DataSet.DisableControls; try if IsSmartRefresh then CheckDataSetBrowseMode; AOperation; finally if IsSmartRefresh then DataSet.EnableControls; end; end else AOperation; finally FInInsert := False; end; end; end; function TcxDBDataProvider.GetDataController: TcxDBDataController; begin Result := inherited DataController as TcxDBDataController; end; function TcxDBDataProvider.GetDataSet: TDataSet; begin if Assigned(DataLink) then Result := DataLink.DataSet else Result := nil; end; function TcxDBDataProvider.GetDataSource: TDataSource; begin if Assigned(DataLink) then Result := DataLink.DataSource else Result := nil; end; function TcxDBDataProvider.GetIsDataSetCurrent: Boolean; begin if DataController.IsDetailMode and not DataController.DataModeController.DetailInSQLMode and IsSyncMode then Result := FInCanInitEditing or ((FInInsert or (((DataController.GetFocusedRowIndex <> -1) or FIsDataSetCurrent) and not IsOtherDetailDataSetInInsert) or (DataController.UseNewItemRowForEditing and DataController.NewItemRowFocused and IsInserting)) and not IsCanInitEditingBusy ) else Result := FIsDataSetCurrent; end; function TcxDBDataProvider.GetIsSmartRefresh: Boolean; begin Result := IsKeyNavigation and not IsGridMode and DataController.DataModeController.SmartRefresh; end; procedure TcxDBDataProvider.RemoveFromListeners; var I: Integer; ADBDataProvider: TcxDBDataProvider; begin if DataSet = nil then Exit; for I := 0 to DBDataProviders.Count - 1 do begin ADBDataProvider := TcxDBDataProvider(DBDataProviders[I]); if (ADBDataProvider <> Self) and (ADBDataProvider.DataSet = Self.DataSet) then ADBDataProvider.FListeners.Remove(Self); end; end; procedure TcxDBDataProvider.SetDataSource(Value: TDataSource); begin if Assigned(DataLink) and (DataLink.DataSource <> Value) then begin // FInSetDataSource := True; try DataLink.DataSource := Value; finally // FInSetDataSource := False; end; end; end; procedure TcxDBDataProvider.SetIsDataSetCurrent(Value: Boolean); begin if FIsDataSetCurrent <> Value then begin if Value then DataController.ForEachDetail(DataController.GetMasterRelation, DataController.ResetDataSetCurrent); FIsDataSetCurrent := Value; // Reopen? DataController.DoDataSetCurrentChanged(Value); end; end; { TcxValueDefDBReader } function TcxValueDefDBReader.GetDisplayText(AValueDef: TcxValueDef): string; var AField: TField; begin AField := TcxDBDataField(AValueDef.LinkObject).Field; if Assigned(AField) then Result := AField.DisplayText else Result := inherited GetDisplayText(AValueDef); end; function TcxValueDefDBReader.GetValue(AValueDef: TcxValueDef): Variant; var AField: TField; begin AField := TcxDBDataField(AValueDef.LinkObject).Field; if Assigned(AField) then Result := GetFieldValue(AField) else Result := inherited GetValue(AValueDef); end; function TcxValueDefDBReader.IsInternal(AValueDef: TcxValueDef): Boolean; begin Result := TcxDBDataField(AValueDef.LinkObject).IsValueDefInternal; end; { TcxDBDataField } destructor TcxDBDataField.Destroy; begin if Assigned(ParentField) then ParentField.RemoveField(Self); ClearFields; inherited Destroy; end; function TcxDBDataField.CanModify(AEditValueSource: TcxDataEditValueSource): Boolean; function IsLookupCanModify: Boolean; var I: Integer; begin Result := True; for I := 0 to FieldCount - 1 do if not Fields[I].CanModify(evsValue) then begin Result := False; Break; end; end; begin Result := inherited CanModify(AEditValueSource); if not IsUnbound then begin // Result := inherited CanModify(AEditValueSource) and Assigned(Field); Result := Result and Assigned(Field); if Result then begin if IsLookup then Result := (AEditValueSource = evsKey) and IsLookupCanModify else Result := IsFieldCanModify(Field, AEditValueSource = evsValue); end; end; end; function TcxDBDataField.IsUnbound: Boolean; begin Result := not IsInternal and (FieldName = '') and (Field = nil) and (ValueDef <> nil); end; function TcxDBDataField.IsValueDefInternal: Boolean; begin Result := ((Field = nil) or DataController.IsGridMode) and (not (IsUnbound and DataController.FInUnboundCopy)); end; procedure TcxDBDataField.AddField(ADataField: TcxDBDataField); begin if FFields = nil then FFields := TList.Create; if FFields.IndexOf(ADataField) = -1 then begin if ADataField.FParentField <> nil then InvalidOperation('ADataField.FParentField <> nil'); FFields.Add(ADataField); ADataField.FParentField := Self; end; end; procedure TcxDBDataField.ClearFields; var I: Integer; begin if FFields <> nil then begin for I := FFields.Count - 1 downto 0 do TcxDBDataField(FFields[I]).Free; FFields.Free; FFields := nil; end; end; procedure TcxDBDataField.DoPropertiesChanged; procedure NotifyReferenceFields; var I: Integer; begin for I := 0 to FieldList.Count - 1 do if (FieldList[I].ReferenceField = Self) and not FieldList[I].IsInternal then DataController.DoValueTypeClassChanged(I); end; begin if not IsInternal then DataController.DoValueTypeClassChanged(Index); NotifyReferenceFields; end; procedure TcxDBDataField.Reassign(ASource: TcxCustomDataField); begin inherited Reassign(ASource); if ASource is TcxDBDataField then begin FField := TcxDBDataField(ASource).FField; FFields := TcxDBDataField(ASource).FFields; TcxDBDataField(ASource).FField := nil; TcxDBDataField(ASource).FFields := nil; end; end; procedure TcxDBDataField.RemoveField(ADataField: TcxDBDataField); begin if FFields <> nil then FFields.Remove(ADataField); ADataField.FParentField := nil; end; procedure TcxDBDataField.RemoveNotification(AComponent: TComponent); begin if Assigned(ReferenceField) then Exit; inherited RemoveNotification(AComponent); if Field = AComponent then begin if Provider.FInFirst and Provider.DataLink.Active and not Provider.DataSet.Active then Provider.RecreatedFieldsAfterFirst := True; Field := nil; Changed; end; end; procedure TcxDBDataField.SetPropertiesByField(AField: TField; AFieldChanged: Boolean); var ATextStored, AIsCurrency, AValueTypeClassChanged: Boolean; AValueTypeClass: TcxValueTypeClass; AChanged: Boolean; begin if Assigned(ReferenceField) then Exit; repeat if not IsInternal then ATextStored := IsFieldFormatted(AField, DataController.GetItemValueSource(Index) = evsText) else ATextStored := IsFieldFormatted(AField, False); AValueTypeClass := GetValueTypeClassByField(AField); AIsCurrency := IsSimpleCurrencyField(AField); AValueTypeClassChanged := (ValueTypeClass <> AValueTypeClass) or (IsCurrency <> AIsCurrency); AChanged := AValueTypeClassChanged or (TextStored <> ATextStored); if AChanged then begin BeginRecreateData; try TextStored := ATextStored; ValueTypeClass := AValueTypeClass; FIsCurrency := AIsCurrency; finally EndRecreateData; end; if AValueTypeClassChanged then begin DoPropertiesChanged; AFieldChanged := False; end; end else if AFieldChanged then DoPropertiesChanged; until not AChanged; end; function TcxDBDataField.GetDataController: TcxDBDataController; begin Result := inherited DataController as TcxDBDataController; end; function TcxDBDataField.GetField: TField; begin if Assigned(ReferenceField) then Result := (ReferenceField as TcxDBDataField).Field else Result := FField; end; function TcxDBDataField.GetFieldItem(Index: Integer): TcxDBDataField; begin if Assigned(ReferenceField) then Result := (ReferenceField as TcxDBDataField).Fields[Index] else Result := TcxDBDataField(FFields[Index]); end; function TcxDBDataField.GetFieldCount: Integer; begin if Assigned(ReferenceField) then Result := (ReferenceField as TcxDBDataField).FieldCount else if FFields <> nil then Result := FFields.Count else Result := 0; end; function TcxDBDataField.GetIsLookup: Boolean; begin Result := Assigned(Field) and Field.Lookup; end; function TcxDBDataField.GetProvider: TcxDBDataProvider; begin Result := DataController.Provider; end; procedure TcxDBDataField.SetField(Value: TField); begin if Assigned(ReferenceField) then Exit; if (Value <> nil) and (csDestroying in Value.ComponentState) then Value := nil; if FField <> Value then begin FField := Value; if Assigned(Value) then Value.FreeNotification(Notifier); ClearFields; end; end; { TcxDBDataRelation } destructor TcxDBDataRelation.Destroy; begin RemoveMasterKeyField; inherited Destroy; end; function TcxDBDataRelation.GetMasterRecordID(ARecordIndex: Integer): Variant; var AList: TList; begin DataController.CheckRecordRange(ARecordIndex); Result := Unassigned; if IsLinked then begin AList := TList.Create; try GetMasterKeyFields(AList); Result := DataController.GetInternalRecordId(ARecordIndex, AList); finally AList.Free; end; end; end; procedure TcxDBDataRelation.GetMasterKeyFields(AList: TList); begin GetInternalKeyFields(FMasterKeyField, AList); end; function TcxDBDataRelation.IsLinked: Boolean; begin Result := Assigned(FMasterKeyField) and (DetailKeyFieldNames <> ''); end; procedure TcxDBDataRelation.RemoveDataField(ADataField: TcxCustomDataField); begin inherited RemoveDataField(ADataField); if FMasterKeyField = ADataField then FMasterKeyField := nil; end; procedure TcxDBDataRelation.RemoveMasterKeyField; begin FMasterKeyField.Free; FMasterKeyField := nil; end; procedure TcxDBDataRelation.UpdateMasterDetailKeyFieldNames; begin if DataController.DetailMode = dcdmClone then Exit; Changed; end; function TcxDBDataRelation.GetDataController: TcxDBDataController; begin Result := inherited DataController as TcxDBDataController; end; function TcxDBDataRelation.GetDetailKeyFieldNames: string; var ADataController: TcxCustomDataController; begin ADataController := DataController.GetPatternDataController; if ADataController <> nil then ADataController := ADataController.Relations[Self.Index].DetailDataController; if (ADataController <> nil) and (ADataController is TcxDBDataController) then Result := (ADataController as TcxDBDataController).DetailKeyFieldNames else Result := ''; end; function TcxDBDataRelation.GetMasterKeyFieldNames: string; var ADataController: TcxCustomDataController; begin ADataController := DataController.GetPatternDataController; if ADataController <> nil then ADataController := ADataController.Relations[Self.Index].DetailDataController; if (ADataController <> nil) and (ADataController is TcxDBDataController) then Result := (ADataController as TcxDBDataController).MasterKeyFieldNames else Result := ''; end; { TcxDBDataFilterCriteriaItem } function TcxDBDataFilterCriteriaItem.GetFilterOperatorClass: TcxFilterOperatorClass; var ADataSet: TDataSet; AOperatorAdapter: TcxDBFilterOperatorAdapter; begin Result := inherited GetFilterOperatorClass; ADataSet := DataController.Provider.DataSet; AOperatorAdapter := cxGetFilterOperatorAdapter(ADataSet); if Assigned(AOperatorAdapter) then AOperatorAdapter.PrepareOperatorClass(Self, ADataSet, Result); end; function TcxDBDataFilterCriteriaItem.GetDataController: TcxDBDataController; begin Result := inherited DataController as TcxDBDataController; end; function TcxDBDataFilterCriteriaItem.GetDBField: TcxDBDataField; begin Result := Field as TcxDBDataField; end; { TcxDBDataFilterCriteria } constructor TcxDBDataFilterCriteria.Create(ADataController: TcxCustomDataController); begin inherited Create(ADataController); FIgnoreOrigin := True; end; procedure TcxDBDataFilterCriteria.Assign(Source: TPersistent; AIgnoreItemNames: Boolean = False); begin if Source is TcxDBDataFilterCriteria then begin BeginUpdate; try inherited; AutoDataSetFilter := TcxDBDataFilterCriteria(Source).AutoDataSetFilter; IgnoreOrigin := TcxDBDataFilterCriteria(Source).IgnoreOrigin; finally EndUpdate; end; end else inherited; end; procedure TcxDBDataFilterCriteria.AssignEvents(Source: TPersistent); begin inherited AssignEvents(Source); if Source is TcxDBDataFilterCriteria then begin OnBeforeChange := TcxDBDataFilterCriteria(Source).OnBeforeChange; OnFormatFilterTextValue := TcxDBDataFilterCriteria(Source).OnFormatFilterTextValue; end; end; function TcxDBDataFilterCriteria.IsAutoDataSetFilter: Boolean; begin Result := AutoDataSetFilter and not DataController.Provider.IsSmartRefresh; end; function TcxDBDataFilterCriteria.IsFiltering: Boolean; begin Result := inherited IsFiltering and not (Assigned(FOnBeforeChange) or IsAutoDataSetFilter); end; procedure TcxDBDataFilterCriteria.RestoreDefaults; begin BeginUpdate; try inherited RestoreDefaults; AutoDataSetFilter := False; IgnoreOrigin := True; finally EndUpdate; end; end; procedure TcxDBDataFilterCriteria.FormatFilterTextValue(AItem: TcxFilterCriteriaItem; const AValue: Variant; var ADisplayValue: string); begin if Assigned(FOnFormatFilterTextValue) then FOnFormatFilterTextValue(Self, AValue, ADisplayValue); end; function TcxDBDataFilterCriteria.GetItemClass: TcxFilterCriteriaItemClass; begin Result := TcxDBDataFilterCriteriaItem; end; procedure TcxDBDataFilterCriteria.Update; var ADataSet: TDataSet; AFilterText: string; AFilterOptions: TFilterOptions; AFilterActive: Boolean; begin if IsInternal then Exit; if not IsDestroying then begin ADataSet := DataController.Provider.DataSet; if Assigned(ADataSet) and (ADataSet.State in dsEditModes) then try DataController.CheckBrowseMode; except Active := False; raise; end; if {Active and }Assigned(FOnBeforeChange) and not IsLoading then FOnBeforeChange(Self, ADataSet, FilterText); if IsAutoDataSetFilter and Assigned(ADataSet) then begin AFilterText := FilterText; AFilterOptions := []; if fcoCaseInsensitive in Options then AFilterOptions := AFilterOptions + [foCaseInsensitive] else AFilterOptions := AFilterOptions - [foCaseInsensitive]; AFilterActive := Active and (AFilterText <> ''); if (ADataSet.Filter <> AFilterText) or (ADataSet.FilterOptions <> AFilterOptions) or (ADataSet.Filtered <> AFilterActive) then begin ADataSet.DisableControls; try ADataSet.Filter := AFilterText; ADataSet.FilterOptions := AFilterOptions; ADataSet.Filtered := AFilterActive; finally ADataSet.EnableControls; end; end; end; end; inherited Update; if not IsDestroying and DataController.Provider.IsSyncMode then DataController.CheckFocusedRow; end; function TcxDBDataFilterCriteria.GetDataController: TcxDBDataController; begin Result := inherited DataController as TcxDBDataController; end; procedure TcxDBDataFilterCriteria.SetAutoDataSetFilter(Value: Boolean); begin if FAutoDataSetFilter <> Value then begin FAutoDataSetFilter := Value; Changed; end; end; procedure TcxDBDataFilterCriteria.SetIgnoreOrigin(Value: Boolean); begin if FIgnoreOrigin <> Value then begin FIgnoreOrigin := Value; Changed; end; end; { TcxDBDataSummaryItem } procedure TcxDBDataSummaryItem.Assign(Source: TPersistent); begin if Source is TcxDBDataSummaryItem then begin BeginUpdate; try inherited Assign(Source); FieldName := TcxDBDataSummaryItem(Source).FieldName; finally EndUpdate; end; end else inherited Assign(Source); end; function TcxDBDataSummaryItem.DataField: TcxCustomDataField; begin if Assigned(FDataField) then Result := FDataField else Result := inherited DataField; end; function TcxDBDataSummaryItem.IsCurrency(AVarType: TVarType): Boolean; begin Result := inherited IsCurrency(AVarType); if (DataField <> nil) and (DataController.DBFields[DataField.Index].Field <> nil) then Result := DataController.DBFields[DataField.Index].IsCurrency; end; function TcxDBDataSummaryItem.GetDBDataController: TcxDBDataController; begin Result := inherited DataController as TcxDBDataController; end; procedure TcxDBDataSummaryItem.SetFieldName(const Value: string); begin if FFieldName <> Value then begin FFieldName := Value; DataController.UpdateInternalKeyFields(FFieldName, FDataField); end; end; { TcxDBDataController } constructor TcxDBDataController.Create(AOwner: TComponent); begin inherited Create(AOwner); FDataModeController := TcxDBDataModeController.Create(Self); end; destructor TcxDBDataController.Destroy; begin Destroying; // for CLR ClearMasterDetailKeyFields; RemoveKeyField; FDataModeController.Free; FDataModeController := nil; inherited Destroy; end; procedure TcxDBDataController.Assign(Source: TPersistent); begin inherited Assign(Source); if Source is TcxDBDataController then begin DataModeController := TcxDBDataController(Source).DataModeController; DataSource := TcxDBDataController(Source).DataSource; KeyFieldNames := TcxDBDataController(Source).KeyFieldNames; DetailKeyFieldNames := TcxDBDataController(Source).DetailKeyFieldNames; MasterKeyFieldNames := TcxDBDataController(Source).MasterKeyFieldNames; OnDetailHasChildren := TcxDBDataController(Source).OnDetailHasChildren; end; end; function TcxDBDataController.ExecuteAction(Action: TBasicAction): Boolean; begin Result := inherited ExecuteAction(Action) or Provider.DataLink.ExecuteAction(Action); end; function TcxDBDataController.UpdateAction(Action: TBasicAction): Boolean; begin Result := inherited UpdateAction(Action) or Provider.DataLink.UpdateAction(Action); end; function TcxDBDataController.CanChangeDetailExpanding(ARecordIndex: Integer; AExpanded: Boolean): Boolean; begin if IsGridMode and AExpanded then begin ChangeFocusedRecordIndex(ARecordIndex); if FocusedRecordIndex <> ARecordIndex then // scroll by begin ChangeFocusedRecordIndex(ARecordIndex); if FocusedRecordIndex <> ARecordIndex then begin Result := False; Exit; end; end; end; Result := inherited CanChangeDetailExpanding(ARecordIndex, AExpanded); end; function TcxDBDataController.CanFocusRecord(ARecordIndex: Integer): Boolean; var AInResetDataSetCurrent: Boolean; begin if (Provider.IsInserting and (ARecordIndex = Provider.EditingRecordIndex)) or (UseNewItemRowForEditing and (ARecordIndex < 0)) then begin if not Provider.IsInserting and (ARecordIndex < 0) then Post; Result := True; Exit; end; Provider.BeginLocate; try AInResetDataSetCurrent := False; if IsDetailMode and DataModeController.DetailInSyncMode then begin if FInResetDataSetCurrent then AInResetDataSetCurrent := True else CheckCurrentQuery; end; if not AInResetDataSetCurrent and Provider.IsSyncMode then Result := TryFocusRecord(ARecordIndex) else Result := inherited CanFocusRecord(ARecordIndex); finally Provider.EndLocate; end; SyncMasterPos; end; procedure TcxDBDataController.CheckDataSetCurrent; begin if FocusedRecordIndex <> -1 then begin FInResetDataSetCurrent := True; try Provider.IsDataSetCurrent := True; finally FInResetDataSetCurrent := False; end; end; end; function TcxDBDataController.CheckMasterBrowseMode: Boolean; var AMasterRelation: TcxDBDataRelation; AMasterDataController: TcxDBDataController; ADataController: TcxDBDataController; ALink, AMasterLink: TcxDataListenerLink; begin if FInResetDataSetCurrent then begin Result := True; Exit; end; AMasterRelation := GetMasterRelation as TcxDBDataRelation; if AMasterRelation <> nil then begin Result := False; AMasterDataController := AMasterRelation.DataController; if AMasterDataController.CheckMasterBrowseMode then begin ADataController := Self; ALink := AddListenerLink(ADataController); AMasterLink := AddListenerLink(AMasterDataController); try AMasterDataController.CheckBrowseMode; if ALink.Ref <> nil then begin ADataController.CheckBrowseMode; if (ALink.Ref <> nil) and (AMasterLink.Ref <> nil) then Result := True; end; finally RemoveListenerLink(AMasterLink); RemoveListenerLink(ALink); end; end; end else Result := True; end; procedure TcxDBDataController.ClearMasterDetailKeyFields; begin FMasterDetailKeyFields.Free; FMasterDetailKeyFields := nil; end; procedure TcxDBDataController.CorrectAfterDelete(ARecordIndex: Integer); begin inherited CorrectAfterDelete(ARecordIndex); if Provider.FUpdatingRecordIndex = ARecordIndex then Provider.ResetSmartRefresh; if Provider.FUpdatingPrevFocusedRecordIndex = ARecordIndex then Provider.FUpdatingPrevFocusedRecordIndex := -1; end; procedure TcxDBDataController.DoDataSetCurrentChanged(AIsCurrent: Boolean); begin end; procedure TcxDBDataController.DoDataSourceChanged; begin end; procedure TcxDBDataController.DoInitInsertingRecord(AInsertingRecordIndex: Integer); begin end; function TcxDBDataController.DoSearchInGridMode(const ASubText: string; AForward, ANext: Boolean): Boolean; begin Search.Lock; try Result := Provider.DoLocate(ASubText, AForward, ANext); finally Search.Unlock; end; end; function TcxDBDataController.FindRecordIndexInGridMode(const AKeyFieldValues: Variant): Integer; var AFieldList: TList; I: Integer; begin Result := -1; AFieldList := TList.Create; try GetKeyFields(AFieldList); for I := 0 to RecordCount - 1 do begin if VarEquals(GetInternalRecordId(I, AFieldList), AKeyFieldValues) then begin Result := I; Break; end; end; finally AFieldList.Free; end; end; procedure TcxDBDataController.DoUpdateRecord(ARecordIndex: Integer); var AValueDefReader: TcxValueDefReader; begin AValueDefReader := Provider.GetValueDefReaderClass.Create; try DataStorage.ReadRecord(ARecordIndex, AValueDefReader); DoReadRecord(ARecordIndex); finally AValueDefReader.Free; end; if not Provider.FInCanInitEditing then Change([dccUpdateRecord]); end; function TcxDBDataController.GetActiveRecordIndex: Integer; begin if IsGridMode then Result := Provider.DataLink.ActiveRecord else Result := inherited GetActiveRecordIndex; end; function TcxDBDataController.GetDataProviderClass: TcxCustomDataProviderClass; begin Result := TcxDBDataProvider; end; function TcxDBDataController.GetDataSelectionClass: TcxDataSelectionClass; begin Result := TcxDBDataSelection; end; function TcxDBDataController.GetDefaultGridModeBufferCount: Integer; begin Result := 100; end; function TcxDBDataController.GetFieldClass: TcxCustomDataFieldClass; begin Result := TcxDBDataField; end; function TcxDBDataController.GetFilterCriteriaClass: TcxDataFilterCriteriaClass; begin Result := TcxDBDataFilterCriteria; end; procedure TcxDBDataController.UpdateEditingRecord; begin if Provider.EditingRecordIndex = cxNullEditingRecordIndex then Exit; Provider.FUpdatingRecordIndex := Provider.EditingRecordIndex; Provider.FUpdatingRecordIsInserting := Provider.IsInserting; DoUpdateRecord(Provider.EditingRecordIndex); end; procedure TcxDBDataController.UpdateField(ADataField: TcxDBDataField; const AFieldNames: string; AIsLookup: Boolean); function CheckExistField(const AFieldName: string): Boolean; var I: Integer; ACurrentField: TcxDBDataField; APrevValueTypeClass: TcxValueTypeClass; begin APrevValueTypeClass := ADataField.ValueTypeClass; ADataField.ReferenceField := nil; Result := False; if ADataField.FieldName = '' then ADataField.ValueTypeClass := nil else begin for I := 0 to Fields.Count - 1 do begin ACurrentField := DBFields[I]; if ACurrentField <> ADataField then begin if IsEqualFieldNames(ACurrentField.FieldName, ADataField.FieldName) then begin ADataField.ReferenceField := ACurrentField; Result := True; Break; end; end; end; end; if (ADataField.ValueTypeClass <> APrevValueTypeClass) and not ADataField.IsInternal then DoValueTypeClassChanged(ADataField.Index); end; var APos: Integer; ASubDataField: TcxDBDataField; begin ADataField.ClearFields; if not AIsLookup then begin if ADataField.FieldName <> AFieldNames then Fields.ReassignFields(ADataField); ADataField.FieldName := AFieldNames; end; if AIsLookup or IsMultipleFieldNames(AFieldNames) then begin BeginUpdate; try ADataField.ReferenceField := nil; APos := 1; while APos <= Length(AFieldNames) do begin ASubDataField := AddInternalDBField; ADataField.AddField(ASubDataField); {$WARNINGS OFF} UpdateField(ASubDataField, ExtractFieldName(AFieldNames, APos), False); {$WARNINGS ON} end; finally EndUpdate; end; end else begin if CheckExistField(AFieldNames) then Change([dccData]) else LayoutChanged([lcStructure]); end; end; procedure TcxDBDataController.ChangeFieldName(AItemIndex: Integer; const AFieldName: string); begin CheckItemRange(AItemIndex); if GetItemFieldName(AItemIndex) <> AFieldName then UpdateField(DBFields[AItemIndex], AFieldName, False); end; function TcxDBDataController.GetItemByFieldName(const AFieldName: string): TObject; var I: Integer; begin Result := nil; for I := 0 to ItemCount - 1 do if AnsiCompareText(GetItemFieldName(I), AFieldName) = 0 then begin Result := GetItem(I); Break; end; end; function TcxDBDataController.GetItemField(AItemIndex: Integer): TField; begin CheckItemRange(AItemIndex); Result := DBFields[AItemIndex].Field; end; function TcxDBDataController.GetItemFieldName(AItemIndex: Integer): string; begin CheckItemRange(AItemIndex); Result := DBFields[AItemIndex].FieldName; end; function TcxDBDataController.IsDisplayFormatDefined(AItemIndex: Integer; AIgnoreSimpleCurrency: Boolean): Boolean; begin Result := inherited IsDisplayFormatDefined(AItemIndex, AIgnoreSimpleCurrency); if Result and AIgnoreSimpleCurrency and (GetItemField(AItemIndex) <> nil) and IsSimpleCurrencyField(GetItemField(AItemIndex)) then Result := False; end; procedure TcxDBDataController.Loaded; begin if not FLoaded and Provider.IsActiveDataSet then LayoutChanged([lcStructure]); inherited; FLoaded := True; end; procedure TcxDBDataController.BeginLocate; begin Provider.BeginLocate; end; procedure TcxDBDataController.EndLocate; begin Provider.EndLocate; end; function TcxDBDataController.GetGroupValue(ARecordIndex: Integer; AField: TcxCustomDataField): Variant; var ADBField: TcxDBDataField; I: Integer; V: array of Variant; begin ADBField := AField as TcxDBDataField; if ADBField.IsLookup then begin if ADBField.FieldCount = 1 then Result := inherited GetGroupValue(ARecordIndex, ADBField[0]) else begin SetLength(V, ADBField.FieldCount); for I := 0 to ADBField.FieldCount - 1 do V[I] := inherited GetGroupValue(ARecordIndex, ADBField[I]); Result := V; end; end else Result := inherited GetGroupValue(ARecordIndex, AField); end; procedure TcxDBDataController.GetKeyDBFields(AList: TList); var I: Integer; begin GetKeyFields(AList); for I := 0 to AList.Count - 1 do begin AList[I] := TcxDBDataField(AList[I]).Field; if AList[I] = nil then InvalidOperation(cxSDBKeyFieldNotFound); end; end; function TcxDBDataController.GetKeyFieldsValues: Variant; var AKeyFields: TList; begin AKeyFields := TList.Create; try GetKeyDBFields(AKeyFields); Result := GetDataSetValues(DataSet, AKeyFields); finally AKeyFields.Free; end; end; function TcxDBDataController.GetRecordId(ARecordIndex: Integer): Variant; var AList: TList; begin CheckRecordRange(ARecordIndex); AList := TList.Create; try GetKeyFields(AList); Result := GetInternalRecordId(ARecordIndex, AList); finally AList.Free; end; end; procedure TcxDBDataController.UpdateGridModeBufferCount; begin if IsGridMode then begin FInUpdateGridModeBufferCount := True; try Provider.UpdateGridMode; finally FInUpdateGridModeBufferCount := False; end; end; end; procedure TcxDBDataController.CheckBrowseMode; begin FInCheckBrowseMode := True; try if Assigned(DataSet) then begin // if IsSmartRefresh or IsDetaimMOde then // !!! if True then // !!! begin if DataSet.State <> dsInactive then TDataSetAccess(DataSet).DataEvent(deCheckBrowseMode, 0); if DataSet.State in [dsEdit, dsInsert] then begin DataSet.UpdateRecord; if Provider.IsDataSetModified {DataSet.Modified} then begin if not IsSmartRefresh and not IsGridMode then DataSet.DisableControls; // support AfterPost Requery try DataSet.Post; finally if not IsSmartRefresh and not IsGridMode then DataSet.EnableControls; end; end else Cancel; end; end else DataSet.CheckBrowseMode; end; CheckDetailsBrowseMode; finally FInCheckBrowseMode := False; end; end; function TcxDBDataController.DataChangedNotifyLocked: Boolean; begin Result := inherited DataChangedNotifyLocked; if not Result and IsGridMode and Assigned(DataSet) and (DataSet.State = dsInsert) then Result := True; end; procedure TcxDBDataController.RefreshExternalData; begin if Assigned(DataSet) then DataSet.Refresh; end; procedure TcxDBDataController.SetFocus; begin if IsDetailMode and DataModeController.DetailInSyncMode then CheckCurrentQuery; end; function TcxDBDataController.IsBookmarkAvailable: Boolean; begin if IsGridMode then Result := FBookmark <> '' else Result := inherited IsBookmarkAvailable; end; function TcxDBDataController.IsBookmarkRow(ARowIndex: Integer): Boolean; var ABookmark: TBookmarkStr; begin if IsGridMode then begin Result := False; if IsBookmarkAvailable and Assigned(DataSet) then begin ABookmark := DBSelection.GetRowBookmark(ARowIndex); if Provider.CompareBookmarks(FBookmark, ABookmark) = 0 then Result := True; end; end else Result := inherited IsBookmarkRow(ARowIndex); end; function TcxDBDataController.GetFilterDataValue(ARecordIndex: Integer; AField: TcxCustomDataField): Variant; var ADBField: TcxDBDataField; I, AFieldCount: Integer; begin ADBField := AField as TcxDBDataField; if Assigned(ADBField) and ADBField.IsLookup then begin AFieldCount := ADBField.FieldCount; if AFieldCount > 0 then begin if AFieldCount = 1 then Result := GetFilterDataValue(ARecordIndex, ADBField[0]) else begin // Multiply Lookup Keys Result := VarArrayCreate([0, AFieldCount - 1], varVariant); for I := 0 to AFieldCount - 1 do Result[I] := GetFilterDataValue(ARecordIndex, ADBField[I]); end; end else Result := Null; end else Result := inherited GetFilterDataValue(ARecordIndex, AField); end; function TcxDBDataController.GetFilterItemFieldName(AItem: TObject): string; var ADBField: TcxDBDataField; I, AFieldCount: Integer; begin Result := ''; ADBField := Fields.FieldByItem(AItem) as TcxDBDataField; if Assigned(ADBField) then if Assigned(ADBField.Field) then if ADBField.IsLookup then begin AFieldCount := ADBField.FieldCount; if ADBField.FieldCount > 0 then if AFieldCount = 1 then Result := GetFilterFieldName(ADBField[0].Field, Filter.IgnoreOrigin) else begin // Multiply Lookup Keys Result := GetFilterFieldName(ADBField[0].Field, Filter.IgnoreOrigin); for I := 1 to AFieldCount - 1 do Result := Result + ';' + GetFilterFieldName(ADBField[I].Field, Filter.IgnoreOrigin); end else Result := ''; end else Result := GetFilterFieldName(ADBField.Field, Filter.IgnoreOrigin) else Result := ADBField.FieldName; end; function TcxDBDataController.FindRecordIndexByKey(const AKeyFieldValues: Variant): Integer; function GetVarArrayElementCount(const AVarArray: Variant): Integer; begin Result := VarArrayHighBound(AVarArray, 1) - VarArrayLowBound(AVarArray, 1) + 1; end; var ALocateObject: TcxDataControllerKeyLocateObject; AValueDefReader: TcxValueDefUnboundReader; I: Integer; begin if IsGridMode then Result := FindRecordIndexInGridMode(AKeyFieldValues) else begin ALocateObject := TcxDataControllerKeyLocateObject.Create(Self); try AValueDefReader := TcxValueDefUnboundReader.Create; try GetKeyFields(ALocateObject.FieldList); if (ALocateObject.FieldList.Count = 1) and VarIsArray(AKeyFieldValues) or (ALocateObject.FieldList.Count <> 1) and (not VarIsArray(AKeyFieldValues) or (GetVarArrayElementCount(AKeyFieldValues) < ALocateObject.FieldList.Count)) then begin Result := -1; Exit; end; if ALocateObject.FieldList.Count = 1 then AValueDefReader.SetValue(TcxDBDataField(ALocateObject.FieldList[0]).ValueDef, AKeyFieldValues) else begin for I := 0 to ALocateObject.FieldList.Count - 1 do AValueDefReader.SetValue(TcxDBDataField(ALocateObject.FieldList[I]).ValueDef, AKeyFieldValues[I]); end; ALocateObject.ReadData(AValueDefReader); Result := ALocateObject.FindRecordIndex; finally AValueDefReader.Free; end; finally ALocateObject.Free; end; end; end; function TcxDBDataController.LocateByKey(const AKeyFieldValues: Variant): Boolean; function TryLocateInGridMode: Boolean; begin Result := False; BeginLocate; try if DataSet <> nil then Result := CanCallDataSetLocate(DataSet, KeyFieldNames, AKeyFieldValues) and DataSet.Locate(KeyFieldNames, AKeyFieldValues, []); finally EndLocate; end; end; var ARecordIndex, ARowIndex: Integer; begin ARecordIndex := FindRecordIndexByKey(AKeyFieldValues); if (ARecordIndex = -1) and IsGridMode then begin Result := TryLocateInGridMode; if Result then SyncSelected(True); Exit; end; ARowIndex := -1; if ARecordIndex <> -1 then ARowIndex := GetRowIndexByRecordIndex(ARecordIndex, True); ChangeFocusedRowIndex(ARowIndex); Result := ARowIndex <> -1; end; procedure TcxDBDataController.CheckCurrentQuery; begin if DataModeController.DetailInSQLMode and not DataModeController.DetailIsCurrentQuery(MasterDetailKeyFieldNames, MasterDetailKeyValues) then with Provider do begin Freeze; FInCheckCurrentQuery := True; try First; SavePos; RestorePos; FRecordIndex := 0; finally Unfreeze; FInCheckCurrentQuery := False; end; end; end; function TcxDBDataController.GetDetailFilterAdapter: TcxDBProviderDetailFilterAdapter; var AIndex: Integer; begin if cxDetailFilterControllers.FindAdapter(TDataSetClass(DataSet.ClassType), AIndex) then Result := cxDetailFilterControllers[AIndex] as TcxDBProviderDetailFilterAdapter else begin Result := nil; InvalidOperation(cxSDBDetailFilterControllerNotFound); end; end; procedure TcxDBDataController.SetMasterRelation(AMasterRelation: TcxCustomDataRelation; AMasterRecordIndex: Integer); var ARelation: TcxDBDataRelation; begin BeginUpdate; try inherited SetMasterRelation(AMasterRelation, AMasterRecordIndex); if (AMasterRelation <> nil) and (AMasterRecordIndex <> -1) and (AMasterRelation is TcxDBDataRelation) then begin ARelation := AMasterRelation as TcxDBDataRelation; FMasterDetailKeyValues := ARelation.GetMasterRecordID(AMasterRecordIndex); LayoutChanged([lcData]); end; finally EndUpdate; end; end; function TcxDBDataController.GetRowId(ARowIndex: Integer): Variant; begin if not IsGridMode or (ARowIndex < 0) then Result := inherited GetRowId(ARowIndex) else Result := DBSelection.GetRowBookmark(ARowIndex) end; function TcxDBDataController.GetSelectedBookmark(Index: Integer): TBookmarkStr; begin if not IsGridMode then InvalidOperation(cxSDBNotInGridMode); if IsFocusedSelectedMode then Result := DBSelection.GetRowBookmark(FocusedRowIndex) else Result := DBSelection.FBookmarks[Index]; end; function TcxDBDataController.GetSelectedRowIndex(Index: Integer): Integer; var ASelection: TcxDBDataSelection; I, ARowCount: Integer; ABookmark: TBookmarkStr; begin if not IsGridMode or IsFocusedSelectedMode then Result := inherited GetSelectedRowIndex(Index) else begin Result := -1; ASelection := DBSelection; ARowCount := GetRowCount; ABookmark := ASelection.FBookmarks[Index]; // bug in BDEDataSet.CachedUpdates mode if (ASelection.CompareBookmarks(ASelection.GetRowBookmark(0), ABookmark) <= 0) and (ASelection.CompareBookmarks(ABookmark, ASelection.GetRowBookmark(ARowCount - 1)) <= 0) then begin for I := 0 to ARowCount - 1 do if ASelection.CompareBookmarks(ASelection.GetRowBookmark(I), ABookmark) = 0 then begin Result := I; Break; end; end; end; end; function TcxDBDataController.GetSelectionAnchorBookmark: TBookmarkStr; begin if not IsGridMode then InvalidOperation(cxSDBNotInGridMode); Result := DBSelection.FAnchorBookmark; end; function TcxDBDataController.GetSelectionAnchorRowIndex: Integer; begin if not IsGridMode then Result := inherited GetSelectionAnchorRowIndex else Result := -1; end; function TcxDBDataController.IsSelectionAnchorExist: Boolean; begin if not IsGridMode then Result := inherited IsSelectionAnchorExist else Result := GetSelectionAnchorBookmark <> ''; end; procedure TcxDBDataController.SelectAll; begin if not IsGridMode then inherited SelectAll else DBSelection.SelectAll; end; procedure TcxDBDataController.SelectFromAnchor(ARowIndex: Integer; AKeepSelection: Boolean); var ASelectionAnchorBookmark: TBookmarkStr; ASelection: TcxDBDataSelection; begin if not IsGridMode then inherited SelectFromAnchor(ARowIndex, AKeepSelection) else begin DataControllerInfo.CheckRowIndex(ARowIndex); ASelectionAnchorBookmark := GetSelectionAnchorBookmark; if ASelectionAnchorBookmark <> '' then begin ASelection := DBSelection; ASelection.SelectFromAnchor(ASelection.GetRowBookmark(ARowIndex), AKeepSelection); end; end; end; procedure TcxDBDataController.SetSelectionAnchor(ARowIndex: Integer); var ASelection: TcxDBDataSelection; begin if not IsGridMode then inherited SetSelectionAnchor(ARowIndex) else begin DataControllerInfo.CheckRowIndex(ARowIndex); ASelection := DBSelection; ASelection.FAnchorBookmark := ASelection.GetRowBookmark(ARowIndex); end; end; function TcxDBDataController.FocusSelectedRow(ASelectedIndex: Integer): Boolean; begin Result := False; if Assigned(DataSet) then // DataSet_BookmarkAvailable begin DataSet.Bookmark := GetSelectedBookmark(ASelectedIndex); Result := True; end; end; procedure TcxDBDataController.ForEachRow(ASelectedRows: Boolean; AProc: TcxDataControllerEachRowProc); procedure DoProc; var ARowIndex: Integer; begin ARowIndex := GetFocusedRowIndex; AProc(ARowIndex, GetRowInfo(ARowIndex)); end; var I: Integer; begin if not IsGridMode then inherited ForEachRow(ASelectedRows, AProc) else if DataSet <> nil then begin if ASelectedRows then begin if IsFocusedSelectedMode then DoProc else for I := 0 to GetSelectedCount - 1 do begin DataSet.Bookmark := GetSelectedBookmark(I); DoProc; end; end else begin DataSet.First; while not DataSet.EOF do begin DoProc; DataSet.Next; end; end; end; end; function TcxDBDataController.IsSequenced: Boolean; begin Result := Assigned(DataSet) and DataSet.IsSequenced; end; procedure TcxDBDataController.ChangeValueTypeClass(AItemIndex: Integer; AValueTypeClass: TcxValueTypeClass); begin CheckItemRange(AItemIndex); if not Assigned(DBFields[AItemIndex].Field) then inherited ChangeValueTypeClass(AItemIndex, AValueTypeClass); end; procedure TcxDBDataController.GetKeyFields(AList: TList); begin GetInternalKeyFields(FKeyField, AList); end; function TcxDBDataController.GetRelationClass: TcxCustomDataRelationClass; begin Result := TcxDBDataRelation; end; function TcxDBDataController.GetSummaryItemClass: TcxDataSummaryItemClass; begin Result := TcxDBDataSummaryItem; end; procedure TcxDBDataController.InvalidateDataBuffer; begin Change([dccLayout]); end; function TcxDBDataController.InternalCheckBookmark(ADeletedRecordIndex: Integer): Boolean; begin if IsGridMode then Result := not (Assigned(DataSet) and Provider.BookmarkValid(FBookmark)) else Result := inherited InternalCheckBookmark(ADeletedRecordIndex); end; procedure TcxDBDataController.InternalClearBookmark; begin FBookmark := ''; inherited InternalClearBookmark; end; procedure TcxDBDataController.InternalGotoBookmark; begin if IsGridMode then begin if Assigned(DataSet) then begin DataSet.Bookmark := FBookmark; CorrectPrevSelectionChangedInfo; end; end else inherited InternalGotoBookmark; end; function TcxDBDataController.InternalSaveBookmark: Boolean; var ABookmark: TBookmarkStr; begin if IsGridMode then begin Result := False; if Assigned(DataSet) then begin ABookmark := DataSet.Bookmark; if (FBookmark = '') or (Provider.CompareBookmarks(FBookmark, ABookmark) <> 0) then FBookmark := ABookmark; end; end else Result := inherited InternalSaveBookmark; end; function TcxDBDataController.IsDataField(AField: TcxCustomDataField): Boolean; begin Result := inherited IsDataField(AField) or (TcxDBDataField(AField).Field <> nil); end; function TcxDBDataController.IsKeyNavigation: Boolean; begin Result := (FKeyField <> nil) and not IsGridMode; end; function TcxDBDataController.LocateRecordIndex(AGetFieldsProc: TGetListProc): Integer; var ALocateObject: TcxDataControllerKeyLocateObject; AValueDefReader: TcxValueDefReader; begin ALocateObject := TcxDataControllerKeyLocateObject.Create(Self); try AGetFieldsProc(ALocateObject.FieldList); AValueDefReader := Provider.GetValueDefReaderClass.Create; try ALocateObject.ReadData(AValueDefReader); finally AValueDefReader.Free; end; Result := ALocateObject.FindRecordIndex; finally ALocateObject.Free; end; end; function TcxDBDataController.LockOnAfterSummary: Boolean; begin Result := inherited LockOnAfterSummary or FInUpdateGridModeBufferCount or ((DataSet <> nil) and not DataSet.Active and DataSet.DefaultFields and ((Provider.ActiveChanging and FResetDBFields) or DataSet.ControlsDisabled)); { if not Result and IsGridMode and IsDetailMode and not IsPattern and DataSet.Active then begin if not VarEquals(GetDataSetValues(DataSet, MasterDetailKeyFields), MasterDetailKeyValues) then Result := True; end; } end; procedure TcxDBDataController.NotifyDataControllers; var I: Integer; begin if Provider.FInNotify then Exit; Provider.FInNotify := True; try for I := Provider.FListeners.Count - 1 downto 0 do begin TcxDBDataProvider(Provider.FListeners[I]).DataLink.DataSetChanged; Provider.FListeners.Delete(I); end; finally Provider.FInNotify := False; end; end; procedure TcxDBDataController.NotifyDetailAfterFieldsRecreating(ADataController: TcxCustomDataController); var ADBDataController: TcxDBDataController; begin if ADataController = FCreatedDataController then Exit; ADBDataController := ADataController as TcxDBDataController; ADBDataController.UpdateFields; ADBDataController.Change([dccData]); // for Grid end; procedure TcxDBDataController.NotifyDetailsAfterFieldsRecreating(ACreatingLinkObject: Boolean); begin if ACreatingLinkObject then FCreatedDataController := Self else FCreatedDataController := nil; ForEachDetail(GetMasterRelation, NotifyDetailAfterFieldsRecreating); end; procedure TcxDBDataController.PrepareField(AField: TcxCustomDataField); begin inherited PrepareField(AField); RetrieveField(AField as TcxDBDataField, False); end; procedure TcxDBDataController.RemoveNotification(AComponent: TComponent); begin inherited RemoveNotification(AComponent); MasterDetailKeyFieldsRemoveNotification(AComponent); end; procedure TcxDBDataController.ResetDataSetCurrent(ADataController: TcxCustomDataController); var ADBDataController: TcxDBDataController; begin ADBDataController := ADataController as TcxDBDataController; if ADBDataController.FInResetDataSetCurrent then Exit; ADBDataController.FInResetDataSetCurrent := True; try ADBDataController.Provider.IsDataSetCurrent := False; ADBDataController.ChangeFocusedRecordIndex(-1); finally ADBDataController.FInResetDataSetCurrent := False; end; end; procedure TcxDBDataController.ResetDBFields; begin FResetDBFields := True; try UpdateFields; finally FResetDBFields := False; end; end; procedure TcxDBDataController.RestructData; begin if not IsGridMode then Provider.DataLink.BufferCount := 1; inherited RestructData; end; procedure TcxDBDataController.ResyncDBFields; begin UpdateFields; end; function TcxDBDataController.IsOtherDetailChanged: Boolean; begin Result := IsDetailMode and DataModeController.DetailInSQLMode and (not DataModeController.DetailInSyncMode or (not Provider.IsDataSetCurrent and not VarIsEmpty(MasterDetailKeyValues) and not VarEquals(GetDataSetValues(DataSet, MasterDetailKeyFields), MasterDetailKeyValues)) ); end; function TcxDBDataController.IsOtherDetailCreating: Boolean; var I: Integer; ADBDataProvider: TcxDBDataProvider; begin Result := False; for I := 0 to DBDataProviders.Count - 1 do begin ADBDataProvider := TcxDBDataProvider(DBDataProviders[I]); if {(ADBDataProvider <> Provider) and} (ADBDataProvider.DataSet = Provider.DataSet) then begin if ADBDataProvider.DataController.FInCheckCurrentQuery or ((ADBDataProvider.DataController.GetMasterDataController <> nil) and ADBDataProvider.DataController.GetMasterDataController.IsCreatingLinkObject) then begin Result := True; Break; end; end; end; end; function TcxDBDataController.IsProviderDataSource: Boolean; begin Result := True; end; function TcxDBDataController.IsSmartRefresh: Boolean; begin Result := Provider.IsSmartRefresh; end; procedure TcxDBDataController.LoadStorage; begin SaveKeys; try inherited LoadStorage; Provider.ResetPostLocateFlag; finally RestoreKeys; end; end; procedure TcxDBDataController.RetrieveField(ADataField: TcxDBDataField; AIsLookupKeyOnly: Boolean); function IsLookupKeyField(ADataField: TcxDBDataField): Boolean; begin Result := Assigned(ADataField.ParentField) and Assigned(ADataField.ParentField.Field) and ADataField.ParentField.Field.Lookup; end; var APrevField: TField; begin if Assigned(ADataField.ReferenceField) then Exit; if AIsLookupKeyOnly and not IsLookupKeyField(ADataField) then Exit; if Assigned(Provider.DataSet) then begin APrevField := ADataField.Field; if not FResetDBFields then begin ADataField.Field := Provider.DataSet.Fields.FindField(ADataField.FieldName); if ADataField.Field = nil then ADataField.Field := Provider.DataSet.AggFields.FindField(ADataField.FieldName); end else ADataField.Field := nil; if Assigned(ADataField.Field) then begin if (ADataField.Field = APrevField) and IsOtherDetailCreating then Exit; ADataField.SetPropertiesByField(ADataField.Field, ADataField.Field <> APrevField); if ADataField.Field.Lookup then UpdateField(ADataField, ADataField.Field.KeyFields, True); end else ADataField.DoPropertiesChanged; end; end; function TcxDBDataController.TryFocusRecord(ARecordIndex: Integer): Boolean; var ARecordId: Variant; begin if ARecordIndex < 0 then Result := False else begin if IsKeyNavigation then begin // check Equals before Locate ARecordId := GetRecordId(ARecordIndex); if VarEquals(ARecordId, GetKeyFieldsValues) and (Provider.FRecordIndex = ARecordIndex) then begin CheckBrowseMode; // !!!!! Result := True; end else begin CheckBrowseMode; // !!!!! Result := Provider.DataSet.Locate(KeyFieldNames, ARecordId, []); end; // Result := (VarEquals(ARecordId, GetKeyFieldsValues) and (Provider.FRecordIndex = ARecordIndex)) or // Provider.DataSet.Locate(KeyFieldNames, GetRecordId(ARecordIndex), []); end else begin CheckBrowseMode; // !!!!! Provider.MoveBy(ARecordIndex - Provider.FRecordIndex); Result := True; end; end; end; function TcxDBDataController.AddInternalDBField: TcxDBDataField; begin Result := AddInternalField as TcxDBDataField; end; procedure TcxDBDataController.UpdateFields; begin inherited UpdateFields; UpdateLookupFields; end; procedure TcxDBDataController.UpdateFocused; begin BeginUpdate; try inherited UpdateFocused; if Provider.IsSyncMode then SyncDataSetPos; finally EndUpdate; end; end; procedure TcxDBDataController.UpdateInternalKeyFields(const AFieldNames: string; var AField: TcxDBDataField); begin if AFieldNames = '' then begin AField.Free; AField := nil; Change([dccData]); end else begin if AField = nil then AField := AddInternalDBField; UpdateField(AField, AFieldNames, False); end; end; procedure TcxDBDataController.UpdateLookupFields; var I: Integer; begin for I := 0 to Fields.Count - 1 do RetrieveField(DBFields[I], True); end; procedure TcxDBDataController.UpdateRelations(ARelation: TcxCustomDataRelation); begin inherited UpdateRelations(ARelation); UpdateRelationFields; end; procedure TcxDBDataController.UpdateScrollBars; begin end; procedure TcxDBDataController.BeginReadRecord; begin FInUnboundCopy := True; end; procedure TcxDBDataController.EndReadRecord; begin FInUnboundCopy := False; end; function TcxDBDataController.GetDataSet: TDataSet; begin Result := Provider.DataSet; end; function TcxDBDataController.GetDataSetRecordCount: Integer; begin if Assigned(DataSet) and DataSet.Active then Result := DataSet.RecordCount else Result := 0; end; function TcxDBDataController.GetDataSource: TDataSource; begin Result := Provider.DataSource; end; function TcxDBDataController.GetDBField(Index: Integer): TcxDBDataField; begin Result := Fields[Index] as TcxDBDataField; end; function TcxDBDataController.GetDBSelection: TcxDBDataSelection; begin Result := DataControllerInfo.Selection as TcxDBDataSelection; end; function TcxDBDataController.GetFilter: TcxDBDataFilterCriteria; begin Result := inherited Filter as TcxDBDataFilterCriteria; end; function TcxDBDataController.GetMasterDetailKeyFieldNames: string; begin if MasterKeyFieldNames <> '' then Result := DetailKeyFieldNames else Result := ''; end; function TcxDBDataController.GetMasterDetailKeyFields: TList; var I: Integer; begin if FMasterDetailKeyFields = nil then begin FMasterDetailKeyFields := TList.Create; if Assigned(Provider.DataSet) then begin Provider.DataSet.GetFieldList(FMasterDetailKeyFields, MasterDetailKeyFieldNames); for I := 0 to FMasterDetailKeyFields.Count - 1 do if FMasterDetailKeyFields[I] <> nil then TField(FMasterDetailKeyFields[I]).FreeNotification(Notifier); end; end; Result := FMasterDetailKeyFields; end; function TcxDBDataController.GetProvider: TcxDBDataProvider; begin Result := inherited Provider as TcxDBDataProvider; end; function TcxDBDataController.GetRecNo: Integer; begin if DataSet.Active then Result := DataSet.RecNo else Result := -1; end; procedure TcxDBDataController.MasterDetailKeyFieldsRemoveNotification(AComponent: TComponent); begin if Assigned(FMasterDetailKeyFields) and (FMasterDetailKeyFields.IndexOf(AComponent) <> -1) then ClearMasterDetailKeyFields; end; procedure TcxDBDataController.RemoveKeyField; begin FKeyField.Free; FKeyField := nil; end; procedure TcxDBDataController.SetDataModeController(Value: TcxDBDataModeController); begin FDataModeController.Assign(Value); end; procedure TcxDBDataController.SetDataSource(Value: TDataSource); var APrevDataSource: TDataSource; begin APrevDataSource := DataSource; Provider.DataSource := Value; if DataSource <> APrevDataSource then DoDataSourceChanged; end; procedure TcxDBDataController.SetDetailKeyFieldNames(const Value: string); begin if FDetailKeyFieldNames <> Value then begin FDetailKeyFieldNames := Value; if GetMasterRelation <> nil then (GetMasterRelation as TcxDBDataRelation).UpdateMasterDetailKeyFieldNames; end; end; procedure TcxDBDataController.SetFilter(Value: TcxDBDataFilterCriteria); begin inherited Filter := Value; end; procedure TcxDBDataController.SetKeyFieldNames(const Value: string); begin if FKeyFieldNames <> Value then begin FKeyFieldNames := Value; UpdateInternalKeyFields(FKeyFieldNames, FKeyField); end; end; procedure TcxDBDataController.SetMasterKeyFieldNames(const Value: string); begin if FMasterKeyFieldNames <> Value then begin FMasterKeyFieldNames := Value; if GetMasterRelation <> nil then (GetMasterRelation as TcxDBDataRelation).UpdateMasterDetailKeyFieldNames; end; end; procedure TcxDBDataController.SetRecNo(Value: Integer); begin DataSet.RecNo := Value; end; procedure TcxDBDataController.SyncDataSetPos; var APrevRecordIndex: Integer; begin if DetailMode = dcdmPattern then begin if not IsLoading and not Provider.ActiveChanging and not Provider.IsDataSetBusy then // !!! SyncMasterDetailDataSetPos; end else if IsKeyNavigation then begin APrevRecordIndex := Provider.FRecordIndex; Provider.FRecordIndex := LocateRecordIndex(GetKeyFields); if APrevRecordIndex <> Provider.FRecordIndex then UpdateFocused; end; end; function TcxDBDataController.SyncMasterDetail: TcxCustomDataController; function ExpandDetails(ADataController: TcxCustomDataController; ARelationIndex: Integer; const AMasterKeyValues: Variant): TcxCustomDataController; var ARowIndex, ARecordIndex, ACurRelationIndex: Integer; ACurDetailDataController: TcxDBDataController; ACurMasterRelation: TcxDBDataRelation; ACurMasterDetailKeyValues: Variant; begin Result := nil; ARowIndex := ADataController.GetFocusedRowIndex; if ARowIndex <> -1 then begin ARecordIndex := ADataController.GetRowInfo(ARowIndex).RecordIndex; ADataController.ChangeDetailExpanding(ARecordIndex, True); if ADataController.GetDetailExpanding(ARecordIndex) then begin // Check current detail ACurRelationIndex := ADataController.GetDetailActiveRelationIndex(ARecordIndex); if ARelationIndex <> ACurRelationIndex then begin ACurDetailDataController := ADataController.GetDetailDataController(ARecordIndex, ACurRelationIndex) as TcxDBDataController; ACurMasterRelation := ACurDetailDataController.GetMasterRelation as TcxDBDataRelation; ACurMasterDetailKeyValues := ACurDetailDataController.Provider.DataSet.FieldValues[ACurMasterRelation.DetailKeyFieldNames]; if VarEquals(AMasterKeyValues, ACurMasterDetailKeyValues) then begin Result := ACurDetailDataController; Exit; end; end; ADataController.ChangeDetailActiveRelationIndex(ARecordIndex, ARelationIndex); Result := ADataController.GetDetailDataController(ARecordIndex, ARelationIndex) end; end; end; function IsMastersInSyncMode(AMasterDataController: TcxDBDataController): Boolean; var AMasterRelation: TcxDBDataRelation; begin Result := AMasterDataController.Provider.IsSyncMode and AMasterDataController.IsKeyNavigation and not AMasterDataController.Provider.IsEditing; if Result then begin AMasterRelation := AMasterDataController.GetMasterRelation as TcxDBDataRelation; if AMasterRelation <> nil then Result := IsMastersInSyncMode(AMasterRelation.DataController); end; end; var AMasterRelation: TcxDBDataRelation; AMasterDetailKeyValues, AMasterKeyValues: Variant; AMasterDataController: TcxDBDataController; ADataController: TcxCustomDataController; AEqualFlag: Boolean; begin if (GetMasterRelation <> nil) and (MasterDetailKeyFieldNames <> '') then begin Result := nil; if not (GetMasterRelation is TcxDBDataRelation) then Exit; AMasterRelation := GetMasterRelation as TcxDBDataRelation; AMasterDataController := AMasterRelation.DataController; if Provider.IsSyncMode and IsMastersInSyncMode(AMasterDataController) and not AMasterDataController.IsCreatingLinkObject then begin AMasterDataController.FUpdateDataSetPos := True; try AMasterDetailKeyValues := Provider.DataSet.FieldValues[AMasterRelation.DetailKeyFieldNames]; AMasterKeyValues := AMasterDataController.Provider.DataSet.FieldValues[AMasterRelation.MasterKeyFieldNames]; AEqualFlag := VarEquals(AMasterKeyValues, AMasterDetailKeyValues); if not (AEqualFlag and AMasterDataController.FInFocusDetails) then // !!! begin if not AEqualFlag then begin AMasterDataController.Provider.BeginLocate; try AEqualFlag := AMasterDataController.Provider.DataSet.Locate(AMasterRelation.MasterKeyFieldNames, AMasterDetailKeyValues, []); finally AMasterDataController.Provider.EndLocate; end; end; if AEqualFlag then begin ADataController := AMasterDataController.SyncMasterDetail; if ADataController <> nil then Result := ExpandDetails(ADataController, AMasterRelation.Index, AMasterKeyValues); SyncMasterPos; end; end; finally AMasterDataController.FUpdateDataSetPos := False; end; end; end else Result := Self; end; procedure TcxDBDataController.SyncMasterDetailDataSetPos; begin if FUpdateDataSetPos or FInCheckBrowseMode then Exit; SyncMasterDetail; end; procedure TcxDBDataController.UpdateRelationFields; var I: Integer; AMasterKeyFieldNames: string; AMasterKeyField: TcxDBDataField; begin for I := 0 to Relations.Count - 1 do if Relations[I] is TcxDBDataRelation then begin AMasterKeyFieldNames := (Relations[I] as TcxDBDataRelation).MasterKeyFieldNames; AMasterKeyField := (Relations[I] as TcxDBDataRelation).FMasterKeyField; UpdateInternalKeyFields(AMasterKeyFieldNames, AMasterKeyField); (Relations[I] as TcxDBDataRelation).FMasterKeyField := AMasterKeyField; end; end; initialization cxDetailFilterControllers := TcxDBAdapterList.Create; cxDetailFilterControllers.RegisterAdapter(TDataSet, TcxDBProviderDetailFilterAdapter); finalization cxDetailFilterControllers.Free; cxDetailFilterControllers := nil; end.