Indy 10 SSL root certificate - ssl-certificate

I am trying to verify server certificate. I use Indy 10 and OpenSSL. I specify Root RootCertFile and VerifyDepth to MaxInt. OnVerifyPeer works fine - AOk is true. I wonder how to load certificates from Windows Trusted Root Certification Authorities. There is my stripped code of a client:
uses
{Delphi}
IdSSLOpenSSL
, IdHTTP
, IdHeaderList
, System.Classes
{Project}
;
type
TUnicodeHTTPPoster = class
strict private
FidHTTP: TIdHTTP;
FLastError: string;
FCertPassword: string;
procedure OnGetPassword(var Password: string);
function OnVerifySSLPeer(Certificate: TIdX509;AOk: Boolean; ADepth, AError: Integer): Boolean;
public
constructor Create(const ASSLVersion: TIdSSLVersion; const AAccept: string = 'application/xml';
const ACharSet: string = 'utf-8'; const ACertFile: string = ''; const AKeyFile: string = '';
const ACertPassword: string = ''); reintroduce;
destructor Destroy; override;
function Post(const ACustomHeaders: TIdHeaderList; const ARawBody: TStream;
const AURL: string; out AResponse: string): integer;
end;
implementation
uses
{Delphi}
System.SysUtils
, IdURI
, IdGlobal
{Project}
;
constructor TUnicodeHTTPPoster.Create(const ASSLVersion: TIdSSLVersion; const AAccept: string = 'application/xml';
const ACharSet: string = 'utf-8'; const ACertFile: string = ''; const AKeyFile: string = '';
const ACertPassword: string = '');
var
_IdSSLIOHandlerSocketOpenSSL: TIdSSLIOHandlerSocketOpenSSL;
begin
inherited Create;
FidHTTP := TIdHTTP.Create(nil);
FidHTTP.Request.Accept := 'application/xml';
if AAccept <> '' then
FidHTTP.Request.Accept := AAccept;
FidHTTP.Request.Charset := 'utf-8';
if ACharSet <> '' then
FidHTTP.Request.Charset := ACharSet;
_IdSSLIOHandlerSocketOpenSSL := TIdSSLIOHandlerSocketOpenSSL.Create(FidHTTP);
if FileExists(ACertFile) then
_IdSSLIOHandlerSocketOpenSSL.SSLOptions.CertFile := ACertFile;
if FileExists(AKeyFile) then
_IdSSLIOHandlerSocketOpenSSL.SSLOptions.KeyFile := AKeyFile;
FCertPassword := ACertPassword;
FidHTTP.Request.BasicAuthentication := False;
_IdSSLIOHandlerSocketOpenSSL.SSLOptions.Mode := sslmClient;
_IdSSLIOHandlerSocketOpenSSL.SSLOptions.Method := ASSLVersion;
_IdSSLIOHandlerSocketOpenSSL.OnGetPassword := OnGetPassword;
_IdSSLIOHandlerSocketOpenSSL.SSLOptions.VerifyMode := [sslvrfPeer];
_IdSSLIOHandlerSocketOpenSSL.SSLOptions.VerifyDepth := MaxInt;
_IdSSLIOHandlerSocketOpenSSL.OnVerifyPeer := OnVerifySSLPeer;
_IdSSLIOHandlerSocketOpenSSL.SSLOptions.RootCertFile := 'C:\Users\ekolesnikovics\Desktop\Projects\nDentity\ndentify\Build\dc_ofisas.nsoft.lt.pem';
FidHTTP.IOHandler := _IdSSLIOHandlerSocketOpenSSL;
end;
function TUnicodeHTTPPoster.OnVerifySSLPeer(Certificate: TIdX509;AOk: Boolean; ADepth, AError: Integer): Boolean;
begin
Result := AOk;
end;
procedure TUnicodeHTTPPoster.OnGetPassword(var Password: string);
begin
Password := FCertPassword;
end;
function TUnicodeHTTPPoster.Post(const ACustomHeaders: TIdHeaderList; const ARawBody: TStream;
const AURL: string; out AResponse: string): integer;
var
_URL: string;
_ResponseStream: TStringStream;
begin
Result := 500;
FLastError := '';
try
if Trim(AURL) = '' then
raise EArgumentException.Create('URL is not provided.');
_URL := TIdURI.URLEncode(AURL, IndyTextEncoding_UTF8);
_ResponseStream := TStringStream.Create('', TEncoding.UTF8);
try
if Assigned(FidHTTP.Request.CustomHeaders) then
FidHTTP.Request.CustomHeaders.Clear;
if Assigned(ACustomHeaders) then
FidHTTP.Request.CustomHeaders := ACustomHeaders;
FidHTTP.Post(_URL, ARawBody, _ResponseStream);
_ResponseStream.Position := 0;
AResponse := _ResponseStream.DataString;
finally
FreeAndNil(_ResponseStream);
end;
Result := 200;
except
on E: EIdHTTPProtocolException do
begin
Result := E.ErrorCode;
FLastError := E.ErrorMessage;
FidHTTP.Disconnect;
end;
on E: Exception do
begin
FLastError := E.Message;
FidHTTP.Disconnect;
end;
end;
end;

I ended up using free https://github.com/magicxor/WinCryptographyAPIs
procedure TUnicodeHTTPPoster.ExportWindowsCertificateStoreToFile(const ACertFile: string);
var
_hStore: HCERTSTORE;
_CertContext: PCertContext;
_pchString: Cardinal;
_szString: string;
_CertList: TStringList;
begin
_hStore := CertOpenSystemStore(0, PChar('ROOT'));
if (_hStore = nil) then
RaiseLastOSError;
_CertList := TStringList.Create;
try
_CertContext := CertEnumCertificatesInStore(_hStore, nil);
if (_CertContext = nil) then
RaiseLastOSError;
while _CertContext <> nil do
begin
_pchString := 0;
if not CryptBinaryToString(_CertContext.pbCertEncoded,
_CertContext.cbCertEncoded, CRYPT_STRING_BASE64, nil, _pchString) then
RaiseLastOSError;
SetLength(_szString, 0);
SetLength(_szString, _pchString - 1);
if not CryptBinaryToString(_CertContext.pbCertEncoded,
_CertContext.cbCertEncoded, CRYPT_STRING_BASE64, PWideChar(_szString),
_pchString) then
RaiseLastOSError;
_CertList.Add('-----BEGIN CERTIFICATE-----');
_CertList.Add(Trim(StrPas(PWideChar(_szString))));
_CertList.Add('-----END CERTIFICATE-----');
_CertContext := CertEnumCertificatesInStore(_hStore, _CertContext);
end;
_CertList.SaveToFile(ACertFile);
finally
FreeAndNil(_CertList);
CertCloseStore(_hStore, 0);
end;
end;
_RootCertFileName := TPath.Combine(ExtractFilePath(ParamStr(0)), 'windows_cert.pem');
ExportWindowsCertificateStoreToFile(_RootCertFileName);
_IdSSLIOHandlerSocketOpenSSL.SSLOptions.RootCertFile := _RootCertFileName;

Related

SQL stored procedure to convert a blob field to XML

I have an xml file stored in the DB. this xml file is converted to a zipped byte[] and then stored in the db as shown below:
ByteArrayOutputStream byteOut = null;
ZipOutputStream out = null;
try {
byteOut = new ByteArrayOutputStream();
out = new ZipOutputStream(byteOut);
out.setLevel(level);
ZipInput entry = new ZipStringInput("string", xmlString));
out.putNextEntry(getZipEntry(encrypt, entry.getEntryName()));
InputStream in = entry.getInputStream();
byte[] buffer = new byte[BUFFER];
for (int length; (length = in.read(buffer)) != -1; ) {
out.write(buffer, 0, length);
}
out.finish();
saveToDB(byteOut.toByteArray()
In java I can easily fetch it like shown below (simplified wihtout catch, close etc.,):
byte[] xmlBytes = (byte[])blob;
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (ByteArrayInputStream byteIn = new ByteArrayInputStream(xmlBytes );) {
try (ZipInputStream zipIn = new ZipInputStream(byteIn);) {
for (ZipEntry entry; (entry = zipIn.getNextEntry()) != null;) {
byte[] buffer = new byte[32768];
for (int length; (length = zipIn.read(buffer, 0, buffer.length)) != -1;) {
out.write(buffer, 0, length);
}
}
return out.toString("UTF-8"));
}
Oracle DB: Now I have to task to do the same in a SQL stored procedure. how do I do it. I have tried combinations of lz_uncompress, lob.substr etc., I can see the bytes, but not sure how to convert it to the XML file.
CREATE OR REPLACE PROCEDURE print_clob AS
v_clob CLOB;
v_varchar VARCHAR2(32767);
v_start PLS_INTEGER := 1;
v_buffer PLS_INTEGER := 32767;
blob_in BLOB;
x XMLTYPE;
BEGIN
dbms_lob.createtemporary(v_clob, true);
FOR i IN (
SELECT
blobdata AS blob_in
FROM
myTable
WHERE
id = 123
) LOOP
FOR j IN 1..ceil(dbms_lob.getlength(i.blob_in) / v_buffer) LOOP
v_varchar := dbms_lob.substr(i.blob_in, v_buffer, v_start);
dbms_lob.writeappend(v_clob, length(v_varchar), v_varchar);
v_start := v_start + v_buffer;
END LOOP;
dbms_output.put_line(v_clob ); prints some binary stirng looking like '504B0304140008000800E0843E4200000000000000000.....'
-- dbms_output.put_line(UTL_RAW.CAST_TO_NVARCHAR2(v_clob )); -> throws error
-- dbms_output.put_line(utl_compress.lz_uncompress(v_clob )); -> throws error
--x := xmltype.createxml(v_clob); -> Throws error that the input it not an XML <, but some randoms tring
-- dbms_output.put_line(x.getclobval());
END LOOP;
END;
You are trying to substring a BLOB and stick it into a CLOB without any conversion. If the BLOB is compressed, you'll need to uncompress it first with utl_compress.lz_uncompress. Then once uncompressed, you need to convert it to a CLOB using dbms_lob.converttoclob.

how to get list of columns of an record type or list of columns for an Object type in Oracle

If we create an object type or recrod type like below
create type t_data as object(
execId varchar2(500),
description varchar2(500)
);
/
then how to get the list of columns for this type at later point?. In case of tables we could use all_tab_columns to get similar list.
You can use user_type_attrs:
SELECT *
FROM user_type_attrs
WHERE TYPE_NAME = 'T_DATA';
Column ATTR_NAME is what you are looking for.
Here is a demo
You can write a package to perform reflection on an object instance:
CREATE PACKAGE reflection IS
TYPE type_info IS RECORD(
prec PLS_INTEGER,
scale PLS_INTEGER,
len PLS_INTEGER,
csid PLS_INTEGER,
csfrm PLS_INTEGER,
schema_name VARCHAR2(30),
type_name VARCHAR2(30),
version VARCHAR2(100),
count PLS_INTEGER
);
TYPE attr_info IS RECORD(
prec PLS_INTEGER,
scale PLS_INTEGER,
len PLS_INTEGER,
csid PLS_INTEGER,
csfrm PLS_INTEGER,
attr_elt_type ANYTYPE,
aname VARCHAR2(30)
);
FUNCTION get_size(
p_anydata IN ANYDATA
) RETURN PLS_INTEGER;
FUNCTION get_attr_name_at(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN VARCHAR2;
FUNCTION get_attr_value_at(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN VARCHAR2;
FUNCTION list_attrs(
p_anydata IN ANYDATA
) RETURN SYS.ODCIVARCHAR2LIST PIPELINED;
END;
/
With the body:
CREATE PACKAGE BODY reflection IS
DEBUG BOOLEAN := FALSE;
FUNCTION get_type(
p_anydata IN ANYDATA
) RETURN ANYTYPE
IS
v_typeid PLS_INTEGER;
v_anytype ANYTYPE;
v_type_info REFLECTION.TYPE_INFO;
BEGIN
v_typeid := p_anydata.GetType( typ => v_anytype );
RETURN v_anytype;
END;
FUNCTION get_info(
p_anytype IN ANYTYPE
) RETURN type_info
IS
v_typeid PLS_INTEGER;
v_type_info REFLECTION.TYPE_INFO;
BEGIN
v_typeid := p_anytype.GetInfo (
v_type_info.prec,
v_type_info.scale,
v_type_info.len,
v_type_info.csid,
v_type_info.csfrm,
v_type_info.schema_name,
v_type_info.type_name,
v_type_info.version,
v_type_info.count
);
IF v_typeid <> DBMS_TYPES.TYPECODE_OBJECT THEN
RAISE_APPLICATION_ERROR( -20000, 'Not an object.' );
END IF;
RETURN v_type_info;
END;
FUNCTION get_size(
p_anydata IN ANYDATA
) RETURN PLS_INTEGER
IS
BEGIN
RETURN Get_Info( Get_Type( p_anydata ) ).COUNT;
END;
FUNCTION get_attr_name_at(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN VARCHAR2
IS
v_anydata ANYDATA := p_anydata;
v_anytype ANYTYPE;
v_type_info REFLECTION.TYPE_INFO;
v_output VARCHAR2(4000);
v_attr_typeid PLS_INTEGER;
v_attr_info REFLECTION.ATTR_INFO;
BEGIN
v_anytype := Get_Type( v_anydata );
v_type_info := Get_Info( v_anytype );
IF p_index < 1 OR p_index > v_type_info.COUNT THEN
RETURN NULL;
END IF;
v_anydata.PIECEWISE;
v_attr_typeid := v_anytype.getAttrElemInfo(
pos => p_index,
prec => v_attr_info.prec,
scale => v_attr_info.scale,
len => v_attr_info.len,
csid => v_attr_info.csid,
csfrm => v_attr_info.csfrm,
attr_elt_type => v_attr_info.attr_elt_type,
aname => v_attr_info.aname
);
RETURN v_attr_info.aname;
END;
FUNCTION get_attr_value_at(
p_anydata IN ANYDATA,
p_index IN PLS_INTEGER DEFAULT 1
) RETURN VARCHAR2
IS
v_anydata ANYDATA := p_anydata;
v_anytype ANYTYPE;
v_type_info REFLECTION.TYPE_INFO;
v_output VARCHAR2(4000);
BEGIN
v_anytype := Get_Type( v_anydata );
v_type_info := Get_Info( v_anytype );
IF p_index < 1 OR p_index > v_type_info.COUNT THEN
RETURN NULL;
END IF;
v_anydata.PIECEWISE;
FOR i IN 1 .. p_index LOOP
DECLARE
v_attr_typeid PLS_INTEGER;
v_attr_info REFLECTION.ATTR_INFO;
v_result_code PLS_INTEGER;
BEGIN
v_attr_typeid := v_anytype.getAttrElemInfo(
pos => i,
prec => v_attr_info.prec,
scale => v_attr_info.scale,
len => v_attr_info.len,
csid => v_attr_info.csid,
csfrm => v_attr_info.csfrm,
attr_elt_type => v_attr_info.attr_elt_type,
aname => v_attr_info.aname
);
IF DEBUG THEN
DBMS_OUTPUT.PUT_LINE(
'Attribute ' || i || ': '
|| v_attr_info.aname
|| ' (type ' || v_attr_typeid || ')'
);
END IF;
CASE v_attr_typeid
WHEN DBMS_TYPES.TYPECODE_NUMBER THEN
DECLARE
v_value NUMBER;
BEGIN
v_result_code := v_anydata.GetNumber( v_value );
IF i = p_index THEN
RETURN TO_CHAR( v_value );
END IF;
END;
WHEN DBMS_TYPES.TYPECODE_VARCHAR2 THEN
DECLARE
v_value VARCHAR2(4000);
BEGIN
v_result_code := v_anydata.GetVarchar2( v_value );
IF i = p_index THEN
RETURN v_value;
END IF;
END;
WHEN DBMS_TYPES.TYPECODE_DATE THEN
DECLARE
v_value DATE;
BEGIN
v_result_code := v_anydata.GetDate( v_value );
IF i = p_index THEN
RETURN TO_CHAR( v_value, 'YYYY-MM-DD HH24:MI:SS' );
END IF;
END;
ELSE
NULL;
END CASE;
END;
END LOOP;
RETURN NULL;
END;
FUNCTION list_attrs(
p_anydata IN ANYDATA
) RETURN SYS.ODCIVARCHAR2LIST PIPELINED
IS
BEGIN
FOR attr_no IN 1 .. REFLECTION.get_size(p_anydata)
LOOP
PIPE ROW (REFLECTION.get_attr_name_at(p_anydata, attr_no));
END LOOP;
END list_attrs;
END;
/
Then if you have the type:
CREATE TYPE test_obj AS OBJECT(
A VARCHAR2(20),
B NUMBER,
C DATE
);
You can use:
SELECT *
FROM TABLE(
REFLECTION.list_attrs(
ANYDATA.ConvertObject(test_obj('A', 1, SYSDATE))
)
);
Which outputs:
COLUMN_VALUE
A
B
C
db<>fiddle here

question about out_msg.Destination in MOESI_CMP_directory response message

In MOESI_CMP_directory-L2cache.sm--the action of j_forwardGlobalRequestToLocalOwner, the "out_msg.Requestor" of the request message is filled in the machineID of that L2 cache, so it is the correct requestor.
While, to response that message in "MOESI_CMP_directory-L1cache.sm"--the action of "ee_sendDataExclusive" ,when in_msg.RequestorMachine == MachineType:L2Cache, why is the out_msg.Destination filled in "mapAddressToRange", instead of "in_msg.Requestor"? (I think it is the "out_msg.Requestor" value of the request message.)
L2 cache:
action(j_forwardGlobalRequestToLocalOwner, "j", desc="Forward external request to local owner") {
peek(requestNetwork_in, RequestMsg) {
enqueue( localRequestNetwork_out, RequestMsg, response_latency ) {
out_msg.addr := in_msg.addr;
out_msg.Type := in_msg.Type;
out_msg.Requestor := machineID;// the machineID of the request L2
out_msg.RequestorMachine := MachineType:L2Cache;
out_msg.Destination.add(getLocalOwner(cache_entry, in_msg.addr));
out_msg.Type := in_msg.Type;
out_msg.MessageSize := MessageSizeType:Forwarded_Control;
out_msg.Acks := 0 - 1;
}
}
}
L1 cache:
action(ee_sendDataExclusive, "\e", desc="Send data from cache to requestor, don't keep a shared copy") {
peek(requestNetwork_in, RequestMsg) {
assert(is_valid(cache_entry));
if (in_msg.RequestorMachine == MachineType:L2Cache) {
enqueue(responseNetwork_out, ResponseMsg, request_latency) {
out_msg.addr := address;
out_msg.Type := CoherenceResponseType:DATA_EXCLUSIVE;
out_msg.Sender := machineID;
out_msg.SenderMachine := MachineType:L1Cache;
out_msg.Destination.add(mapAddressToRange(address, MachineType:L2Cache,
l2_select_low_bit, l2_select_num_bits, intToID(0)));//why do not filled in "in_msg.Requestor"?
out_msg.DataBlk := cache_entry.DataBlk;
out_msg.Dirty := cache_entry.Dirty;
out_msg.Acks := in_msg.Acks;
out_msg.MessageSize := MessageSizeType:Response_Data;
}
DPRINTF(RubySlicc, "Sending exclusive data to L2\n");
}
else {
enqueue(responseNetwork_out, ResponseMsg, request_latency) {
out_msg.addr := address;
out_msg.Type := CoherenceResponseType:DATA_EXCLUSIVE;
out_msg.Sender := machineID;
out_msg.SenderMachine := MachineType:L1Cache;
out_msg.Destination.add(in_msg.Requestor);
out_msg.DataBlk := cache_entry.DataBlk;
out_msg.Dirty := cache_entry.Dirty;
out_msg.Acks := in_msg.Acks;
out_msg.MessageSize := MessageSizeType:ResponseLocal_Data;
}
DPRINTF(RubySlicc, "Sending exclusive data to L1\n");
}
}
}

Delphi 7 - Changing font sub-property is not updating component

I'm having problems in design time with a StringGrid I've made. When a property called "Header" is changed, the Invalidate method works fine and the Grid is repainted in design-time. However, when a sub-property Font is added, the Grid does not update when Header's font is changed in desig-time. If I click on Grid or expand a cell after changing font, then it is updated.
Here is my code:
unit GridsEx;
interface
uses
Windows, SysUtils, Classes, Controls, Grids, Graphics, Dialogs;
const
CONST_CELL_PADDING = 4;
type
TStringGridEx = class;
THeader = class(TPersistent)
private
FGrid: TStringGridEx;
FColCount: Longint;
FColor: TColor;
FFont: TFont;
FHeight: Integer;
procedure SetColor(Value: TColor);
procedure SetColCount(Value: Longint);
procedure SetHeight(Value: Integer);
procedure SetFont(Value: TFont);
protected
public
constructor Create; overload;
constructor Create(const AGrid: TStringGridEx); overload;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
published
property ColCount: Longint read FColCount write SetColCount;
property Color: TColor read FColor write SetColor;
property Font: TFont read FFont write SetFont;
property Height: Integer read FHeight write SetHeight;
end;
TStringGridEx = class(TStringGrid)
private
FHeader: THeader;
protected
procedure DrawCell(ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); override;
property ColCount;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure AfterConstruction; override;
published
property Header: THeader read FHeader write FHeader;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Additional', [TStringGridEx]);
end;
{ THeader }
constructor THeader.Create;
begin
FColor := clBtnFace;
FColCount := 3;
FFont := TFont.Create;
FFont.Name := 'Tahoma';
FFont.Size := 9;
FFont.Color := clNavy;
FHeight := 22;
end;
procedure THeader.Assign(Source: TPersistent);
begin
inherited;
end;
constructor THeader.Create(const AGrid: TStringGridEx);
begin
Self.Create;
FGrid := AGrid;
end;
procedure THeader.SetColCount(Value: Longint);
begin
if (Value <> FColCount) then
begin
if (Value < 1) then Value := 1;
FColCount := Value;
FGrid.ColCount := FColCount;
FGrid.Invalidate;
end;
end;
procedure THeader.SetColor(Value: TColor);
begin
if (Value <> FColor) then
begin
FColor := Value;
FGrid.Invalidate;
end;
end;
procedure THeader.SetHeight(Value: Integer);
begin
if (Value <> FHeight) then
begin
if (Value < 0) then Value := 0;
FHeight := Value;
FGrid.RowHeights[0] := FHeight;
FGrid.Invalidate;
end;
end;
destructor THeader.Destroy;
begin
FreeAndNil(FFont);
inherited;
end;
procedure THeader.SetFont(Value: TFont);
begin
FFont.Assign(Value);
FGrid.Invalidate;
end;
{ TStringGridEx }
procedure TStringGridEx.AfterConstruction;
begin
inherited;
FHeader := THeader.Create(Self);
ColCount := FHeader.ColCount;
RowHeights[0] := FHeader.Height;
end;
constructor TStringGridEx.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
DefaultDrawing := False;
DefaultRowHeight := 20;
//Ctl3D := False;
FixedCols := 0;
FixedRows := 1;
Cells[0, 0] := 'Serial';
Cells[1, 0] := 'Name';
Cells[0, 1] := '00001';
Cells[1, 1] := 'Lorem Ipsum';
end;
destructor TStringGridEx.Destroy;
begin
FreeAndNil(FHeader);
inherited;
end;
procedure TStringGridEx.DrawCell(ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
var
TextRect: TRect;
TextFormat: Cardinal;
begin
inherited;
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := clWindow;
if (ARow = 0) then
begin
Canvas.Brush.Color := FHeader.Color;
Canvas.Font.Assign(FHeader.Font);
end;
Canvas.FillRect(Rect);
TextFormat := DT_SINGLELINE or DT_VCENTER or DT_LEFT or DT_END_ELLIPSIS;
TextRect := Rect;
TextRect.Left := TextRect.Left + (CONST_CELL_PADDING);
DrawText(Canvas.Handle, PAnsiChar(Cells[ACol, ARow]), Length(Cells[ACol, ARow]), TextRect, TextFormat);
end;
end.
English is not my language, so sorry for typos. Appreciate your help.
The grid doesn't update when you assign values to the Font's sub-properties because you are not assigning a TFont.OnChange event handler to invalidate the grid when any aspect of the Font changes.
Your SetFont() setter method does not get called when setting the Font's individual sub-properties. Only when setting the Font property itself. The OnChange event is fired for individual changes to the Font, so you need an event handler for it.
There are also several other bugs in your code:
you are defining 2 constructors for THeader when you only need 1 constructor.
you are not implementing THeader.Assign() to copy anything.
you are not defining a setter method for the TStringGridEx.Header property. You are taking ownership of the caller's input THeader object instead of copying property values from it, and leaking the previous THeader object that you were holding a pointer to.
you are handling your TStringGridEx initialization in AfterConstruction() instead of in the constructor, where it belongs.
Try this:
unit GridsEx;
interface
uses
Windows, SysUtils, Classes, Controls, Grids, Graphics, Dialogs;
const
CONST_CELL_PADDING = 4;
type
TStringGridEx = class;
THeader = class(TPersistent)
private
FGrid: TStringGridEx;
FColCount: Longint;
FColor: TColor;
FFont: TFont;
FHeight: Integer;
procedure FontChanged(Sender: TObject);
procedure SetColor(Value: TColor);
procedure SetColCount(Value: Longint);
procedure SetHeight(Value: Integer);
procedure SetFont(Value: TFont);
public
constructor Create(const AGrid: TStringGridEx);
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
published
property ColCount: Longint read FColCount write SetColCount;
property Color: TColor read FColor write SetColor;
property Font: TFont read FFont write SetFont;
property Height: Integer read FHeight write SetHeight;
end;
TStringGridEx = class(TStringGrid)
private
FHeader: THeader;
procedure SetHeader(AValue: THeader);
protected
procedure DrawCell(ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); override;
property ColCount;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Header: THeader read FHeader write SetHeader;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Additional', [TStringGridEx]);
end;
{ THeader }
procedure THeader.Assign(Source: TPersistent);
var
H: THeader;
begin
if Source is THeader then
begin
H := THeader(Source);
ColCount := H.ColCount;
Color := H.Color;
Font := H.Font;
Height := H.Height;
end else
inherited;
end;
constructor THeader.Create(const AGrid: TStringGridEx);
begin
inherited Create;
FGrid := AGrid;
FColor := clBtnFace;
FColCount := 3;
FFont := TFont.Create;
FFont.Name := 'Tahoma';
FFont.Size := 9;
FFont.Color := clNavy;
FFont.OnChange := FontChanged;
FHeight := 22;
end;
destructor THeader.Destroy;
begin
FFont.Free;
inherited;
end;
procedure THeader.FontChanged(Sender: TObject);
begin
FGrid.Invalidate;
end;
procedure THeader.SetColCount(Value: Longint);
begin
if (Value < 1) then Value := 1;
if (Value <> FColCount) then
begin
FColCount := Value;
FGrid.ColCount := FColCount;
FGrid.Invalidate;
end;
end;
procedure THeader.SetColor(Value: TColor);
begin
if (Value <> FColor) then
begin
FColor := Value;
FGrid.Invalidate;
end;
end;
procedure THeader.SetHeight(Value: Integer);
begin
if (Value < 0) then Value := 0;
if (Value <> FHeight) then
begin
FHeight := Value;
FGrid.RowHeights[0] := FHeight;
FGrid.Invalidate;
end;
end;
procedure THeader.SetFont(Value: TFont);
begin
FFont.Assign(Value);
end;
{ TStringGridEx }
constructor TStringGridEx.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FHeader := THeader.Create(Self);
DefaultDrawing := False;
DefaultRowHeight := 20;
//Ctl3D := False;
FixedCols := 0;
FixedRows := 1;
ColCount := FHeader.ColCount;
RowHeights[0] := FHeader.Height;
Cells[0, 0] := 'Serial';
Cells[1, 0] := 'Name';
Cells[0, 1] := '00001';
Cells[1, 1] := 'Lorem Ipsum';
end;
destructor TStringGridEx.Destroy;
begin
FHeader.Free;
inherited;
end;
procedure TStringGridEx.DrawCell(ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
var
TextRect: TRect;
TextFormat: Cardinal;
S: string;
begin
inherited;
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := clWindow;
if (ARow = 0) then
begin
Canvas.Brush.Color := FHeader.Color;
Canvas.Font.Assign(FHeader.Font);
end;
Canvas.FillRect(Rect);
TextFormat := DT_SINGLELINE or DT_VCENTER or DT_LEFT or DT_END_ELLIPSIS;
TextRect := Rect;
TextRect.Left := TextRect.Left + (CONST_CELL_PADDING);
S := Cells[ACol, ARow];
DrawText(Canvas.Handle, PChar(S), Length(S), TextRect, TextFormat);
end;
procedure TStringGridEx.SetHeader(AValue: THeader);
begin
FHeader.Assign(AValue);
end;
end.
That being said, you can remove the FColCount and FHeight members from THeader since they are delegated to TStringGridEx anyway, so just let TStringGridEx take care of them for you, you don't need to duplicate the work unnecessarily:
unit GridsEx;
interface
uses
Windows, SysUtils, Classes, Controls, Grids, Graphics, Dialogs;
const
CONST_CELL_PADDING = 4;
type
TStringGridEx = class;
THeader = class(TPersistent)
private
FGrid: TStringGridEx;
FColor: TColor;
FFont: TFont;
procedure FontChanged(Sender: TObject);
function GetColCount: Longint;
function GetHeight: Integer;
procedure SetColor(Value: TColor);
procedure SetColCount(Value: Longint);
procedure SetHeight(Value: Integer);
procedure SetFont(Value: TFont);
public
constructor Create(const AGrid: TStringGridEx);
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
published
property ColCount: Longint read GetColCount write SetColCount;
property Color: TColor read FColor write SetColor;
property Font: TFont read FFont write SetFont;
property Height: Integer read GetHeight write SetHeight;
end;
TStringGridEx = class(TStringGrid)
private
FHeader: THeader;
procedure SetHeader(AValue: THeader);
protected
procedure DrawCell(ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property ColCount default 3;
property Header: THeader read FHeader write SetHeader;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Additional', [TStringGridEx]);
end;
{ THeader }
procedure THeader.Assign(Source: TPersistent);
var
H: THeader;
begin
if Source is THeader then
begin
H := THeader(Source);
ColCount := H.ColCount;
Color := H.Color;
Font := H.Font;
Height := H.Height;
end else
inherited;
end;
constructor THeader.Create(const AGrid: TStringGridEx);
begin
inherited Create;
FGrid := AGrid;
FColor := clBtnFace;
FFont := TFont.Create;
FFont.Name := 'Tahoma';
FFont.Size := 9;
FFont.Color := clNavy;
FFont.OnChange := FontChanged;
end;
destructor THeader.Destroy;
begin
FFont.Free;
inherited;
end;
procedure THeader.FontChanged(Sender: TObject);
begin
FGrid.Invalidate;
end;
function THeader.GetColCount: Longint;
begin
Result := FGrid.ColCount;
end;
function THeader.GetHeight: Integer;
begin
Result := FGrid.RowHeights[0];
end;
procedure THeader.SetColCount(Value: Longint);
begin
FGrid.ColCount := Value;
end;
procedure THeader.SetColor(Value: TColor);
begin
if (Value <> FColor) then
begin
FColor := Value;
FGrid.Invalidate;
end;
end;
procedure THeader.SetHeight(Value: Integer);
begin
FGrid.RowHeights[0] := Value;
end;
procedure THeader.SetFont(Value: TFont);
begin
FFont.Assign(Value);
end;
{ TStringGridEx }
constructor TStringGridEx.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FHeader := THeader.Create(Self);
DefaultDrawing := False;
DefaultRowHeight := 20;
//Ctl3D := False;
FixedCols := 0;
FixedRows := 1;
ColCount := 3;
RowHeights[0] := 22;
Cells[0, 0] := 'Serial';
Cells[1, 0] := 'Name';
Cells[0, 1] := '00001';
Cells[1, 1] := 'Lorem Ipsum';
end;
destructor TStringGridEx.Destroy;
begin
FHeader.Free;
inherited;
end;
procedure TStringGridEx.DrawCell(ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
var
TextRect: TRect;
TextFormat: Cardinal;
S: string;
begin
inherited;
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := clWindow;
if (ARow = 0) then
begin
Canvas.Brush.Color := FHeader.Color;
Canvas.Font.Assign(FHeader.Font);
end;
Canvas.FillRect(Rect);
TextFormat := DT_SINGLELINE or DT_VCENTER or DT_LEFT or DT_END_ELLIPSIS;
TextRect := Rect;
TextRect.Left := TextRect.Left + (CONST_CELL_PADDING);
S := Cells[ACol, ARow];
DrawText(Canvas.Handle, PChar(S), Length(S), TextRect, TextFormat);
end;
procedure TStringGridEx.SetHeader(AValue: THeader);
begin
FHeader.Assign(AValue);
end;
end.

Bind combobox with OO

I have 2 business classes. This classes are load by database. The "product" class have a property "category" who is an instance of category.
unit Unit5;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, Generics.Collections,
FMX.Edit, FMX.ListBox, FMX.Controls.Presentation, FMX.StdCtrls,
Data.Bind.Components, Data.Bind.ObjectScope, Data.Bind.GenData,
FMX.Bind.Editors, System.Rtti, System.Bindings.Outputs,
Data.Bind.EngExt, FMX.Bind.DBEngExt;
type
TCategory = class
private
FName : String;
FShortName: String;
FID : integer;
public
constructor Create(IDFromDataBase: integer; NameFromDataBase: string);
property ID: integer read FID write FID;
property Name: String read FName write FName;
end;
TProduct = class
private
FID : integer;
FName : string;
FCategory: TCategory;
public
constructor Create(IDFromDataBase: integer; NameFromDataBase: string; Cat: TCategory);
property ID: integer read FID write FID;
property Name: string read FName write FName;
property Category: TCategory read FCategory write FCategory;
end;
TForm5 = class(TForm)
Label1: TLabel;
Category: TLabel;
ComboProductCategory: TComboBox;
EditProductName: TEdit;
PrototypeBindSourceCategory: TPrototypeBindSource;
PrototypeBindSourceProduct: TPrototypeBindSource;
procedure FormCreate(Sender: TObject);
procedure PrototypeBindSourceProductCreateAdapter(Sender: TObject; var ABindSourceAdapter: TBindSourceAdapter);
procedure PrototypeBindSourceCategoryCreateAdapter(Sender: TObject; var ABindSourceAdapter: TBindSourceAdapter);
private
{ Déclarations privées }
FListCategory: TObjectList<TCategory>;
FProduct : TProduct;
public
{ Déclarations publiques }
constructor Create(AOwner: TComponent); override;
end;
var
Form5: TForm5;
implementation
{$R *.fmx}
{ TCategory }
constructor TCategory.Create(IDFromDataBase: integer; NameFromDataBase: string);
begin
inherited Create;
FID := IDFromDataBase;
FName := NameFromDataBase;
end;
{ TProduct }
constructor TProduct.Create(IDFromDataBase: integer; NameFromDataBase: string; Cat: TCategory);
begin
inherited Create;
FID := IDFromDataBase;
FName := NameFromDataBase;
FCategory := Cat;
end;
constructor TForm5.Create(AOwner: TComponent);
begin
FListCategory := TObjectList<TCategory>.Create();
// Normaly load by query on database
FListCategory.Add(TCategory.Create(1, 'Clothe'));
FListCategory.Add(TCategory.Create(2, 'Luxury'));
FProduct := TProduct.Create(1, 'Clock', FListCategory.Items[1]);
inherited Create(AOwner);
end;
procedure TForm5.FormCreate(Sender: TObject);
var
// Bind sur un TEdit
LinkControl: TLinkControlToField;
ListLink : TLinkFillControlToField;
begin
PrototypeBindSourceProduct.Active := false;
PrototypeBindSourceCategory.Active := false;
try
// Bind product name on edit.
LinkControl := TLinkControlToField.Create(self);
LinkControl.DataSource := PrototypeBindSourceProduct;
LinkControl.FieldName := 'Name';
LinkControl.Control := EditProductName;
LinkControl.Track := true;
// Bind combo.
ListLink := TLinkFillControlToField.Create(self);
ListLink.DataSource := PrototypeBindSourceProduct;
ListLink.FieldName := 'Category.ID';
ListLink.Control := ComboProductCategory;
ListLink.FillDataSource := PrototypeBindSourceCategory;
ListLink.FillValueFieldName := 'ID';
ListLink.FillDisplayFieldName := 'Name';
ListLink.AutoFill := true;
ListLink.Track := true;
finally
PrototypeBindSourceCategory.Active := true;
PrototypeBindSourceProduct.Active := true;
end;
end;
procedure TForm5.PrototypeBindSourceCategoryCreateAdapter(Sender: TObject; var ABindSourceAdapter: TBindSourceAdapter);
begin
ABindSourceAdapter := TListBindSourceAdapter<TCategory>.Create(self, FListCategory);
end;
procedure TForm5.PrototypeBindSourceProductCreateAdapter(Sender: TObject; var ABindSourceAdapter: TBindSourceAdapter);
begin
ABindSourceAdapter := TObjectBindSourceAdapter<TProduct>.Create(self, FProduct);
end;
end.
But when I start program, the combobox it's loaded with the list of categories. But I have not the selected item.
I think the problem it here :
ListLink.FieldName := 'Category.ID';
I don't want create an "CategoryID" on my class "product".
What is the good way for bind a combobox on OO classes ?