convert TDataSet results to JSON format - delphi

Need to achieve a common conversion between TDataset and JSON in C++ Builder, for the realization of data communication and conversion. However, this is difficult for an amateur developer.
I've found this already done in Delphi, but I don't know Delphi, however it seems to be good example. Maybe somebody can convert it to C++ Builder:
unit uDBJson;
interface
uses
SysUtils,Classes,Variants,DB,DBClient,SuperObject;
type
TTableJSon = class
private
const cstFieldType = 'FieldType';
const cstFieldName = 'FieldName';
const cstFieldSize = 'FieldSize';
const cstJsonType = 'JsonType';
const cstRequired = 'Required';
const cstFieldIndex = 'FieldIndex';
const cstCols= 'Cols';
const cstData= 'Data';
public
class function JSonFromDataSet(DataSet:TDataSet):string;
class function CreateFieldByJson(Fields:TFieldDefs;ColsJson:ISuperObject):Boolean;
class function ImportDataFromJSon(DataSet:TDataSet;DataJson:ISuperObject):Integer;
class function CDSFromJSon(CDS:TClientDataSet;Json:ISuperObject):Boolean;
class function GetValue(Json:ISuperObject;const Name:string):Variant;
class function CreateJsonValue(Json:ISuperObject;const Name:string;const Value:Variant):Boolean;
class function CreateJsonValueByField(Json:ISuperObject;Field:TField):Boolean;
class function GetValue2Field(Field:TField;JsonValue:ISuperObject):Variant;
end;
implementation
uses TypInfo,encddecd;
{ TTableJSon }
class function TTableJSon.CDSFromJSon(CDS: TClientDataSet;
Json: ISuperObject): Boolean;
var
ColsJson:ISuperObject;
begin
Result := False;
if Json = nil then
Exit;
CDS.Close;
CDS.Data := Null;
ColsJson := Json.O[cstCols];
CreateFieldByJson(CDS.FieldDefs,ColsJson);
if CDS.FieldDefs.Count >0 then
CDS.CreateDataSet;
ImportDataFromJSon(CDS,Json.O[cstData]);
Result := True;
end;
class function TTableJSon.CreateFieldByJson(Fields: TFieldDefs;
ColsJson: ISuperObject): Boolean;
var
SubJson:ISuperObject;
ft:TFieldType;
begin
Result := False;
Fields.DataSet.Close;
Fields.Clear;
for SubJson in ColsJson do
begin
ft := TFieldType(GetEnumValue(TypeInfo(TFieldType),'ft'+SubJson.S[cstFieldType]));
if ft= ftAutoInc then
ft := ftInteger;
Fields.Add(SubJson.S[cstFieldName],ft,SubJson.I[cstFieldSize],SubJson.B[cstRequired]);
end;
Result := True;
end;
class function TTableJSon.CreateJsonValue(Json: ISuperObject;
const Name: string; const Value: Variant): Boolean;
begin
Result := False;
Json.O[Name] := SO(Value);
Result := True;
end;
class function TTableJSon.CreateJsonValueByField(Json: ISuperObject;
Field: TField): Boolean;
begin
Result := False;
if Field Is TDateTimeField then
Json.O[Field.FieldName] := SO(Field.AsDateTime)
else if Field is TBlobField then
Json.S[Field.FieldName] := EncodeString(Field.AsString)
else
Json.O[Field.FieldName] := SO(Field.Value);
Result := True;
end;
class function TTableJSon.GetValue(
Json: ISuperObject;const Name: string): Variant;
begin
case Json.DataType of
stNull: Result := Null;
stBoolean: Result := Json.B[Name];
stDouble: Result := Json.D[Name];
stCurrency: Result := Json.C[Name];
stInt: Result := Json.I[Name];
stString: Result := Json.S[Name];
end;
end;
class function TTableJSon.GetValue2Field(Field: TField; JsonValue:ISuperObject): Variant;
begin
if JsonValue.DataType = stNull then
Result := Null
else if Field is TDateTimeField then
Result := JavaToDelphiDateTime(JsonValue.AsInteger)
else if (Field is TIntegerField) or (Field is TLargeintField) then
Result := JsonValue.AsInteger
else if Field is TNumericField then
Result := JsonValue.AsDouble
else if Field is TBooleanField then
Result := JsonValue.AsBoolean
else if Field is TStringField then
Result := JsonValue.AsString
else if Field is TBlobField then
Result := DecodeString(JsonValue.AsString)
end;
class function TTableJSon.ImportDataFromJSon(DataSet: TDataSet;
DataJson: ISuperObject): Integer;
var
SubJson:ISuperObject;
i:Integer;
iter: TSuperObjectIter;
begin
if not DataSet.Active then
DataSet.Open;
DataSet.DisableControls;
try
for SubJson in DataJson do
begin
DataSet.Append;
if ObjectFindFirst(SubJson,iter) then
begin
repeat
if DataSet.FindField(iter.Ite.Current.Name)<>nil then
DataSet.FindField(iter.Ite.Current.Name).Value :=
GetValue2Field(
DataSet.FindField(iter.Ite.Current.Name),
iter.Ite.Current.Value);
until not ObjectFindNext(iter) ;
end;
DataSet.Post;
end;
finally
DataSet.EnableControls;
end;
end;
class function TTableJSon.JSonFromDataSet(DataSet:TDataSet):string;
procedure GetFieldTypeInfo(Field:TField;var Fieldtyp,JsonTyp:string);
begin
Fieldtyp := GetEnumName(TypeInfo(tfieldtype),ord(Field.DataType));
Delete(Fieldtyp,1,2);
if Field is TStringField then
JsonTyp := 'string'
else if Field is TDateTimeField then
JsonTyp := 'integer'
else if (Field is TIntegerField) or (Field is TLargeintField) then
JsonTyp := 'integer'
else if Field is TCurrencyField then
JsonTyp := 'currency'
else if Field is TNumericField then
JsonTyp := 'double'
else if Field is TBooleanField then
JsonTyp := 'boolean'
else
JsonTyp := 'variant';
end;
var
sj,aj,sj2:ISuperObject;
i:Integer;
Fieldtyp,JsonTyp:string;
List:TStringList;
begin
sj := SO();
aj := SA([]);
List := TStringList.Create;
try
List.Sorted := True;
for i := 0 to DataSet.FieldCount - 1 do
begin
sj2 := SO();
GetFieldTypeInfo(DataSet.Fields[i],Fieldtyp,JsonTyp);
sj2.S[cstFieldName] := DataSet.Fields[i].FieldName;
sj2.S[cstFieldType] := Fieldtyp;
sj2.S[cstJsonType] := JsonTyp;
sj2.I[cstFieldSize] := DataSet.Fields[i].Size;
sj2.B[cstRequired] := DataSet.Fields[i].Required;
sj2.I[cstFieldIndex] := DataSet.Fields[i].Index;
aj.AsArray.Add(sj2);
List.Add(DataSet.Fields[i].FieldName+'='+JsonTyp);
end;
sj.O['Cols'] := aj;
DataSet.DisableControls;
DataSet.First;
aj := SA([]);
while not DataSet.Eof do
begin
sj2 := SO();
for i := 0 to DataSet.FieldCount - 1 do
begin
//sj2.S[IntToStr(DataSet.Fields[i].Index)] := VarToStrDef(DataSet.Fields[i].Value,'');
if VarIsNull(DataSet.Fields[i].Value) then
sj2.O[DataSet.Fields[i].FieldName] := SO(Null)
else
begin
CreateJsonValueByField(sj2,DataSet.Fields[i]);
end;
end;
aj.AsArray.Add(sj2);
DataSet.Next;
end;
sj.O['Data'] := aj;
Result := sj.AsString;
finally
List.Free;
DataSet.EnableControls;
end;
end;
end.
var
json:TTableJSon;
s:string;
begin
S := json.JSonFromDataSet(ADODataSet1);
end;
var
json:ISuperObject;
begin
json := TSuperObject.ParseFile('json.txt',False);
TTableJSon.CDSFromJSon(cdsJSON,json);
end;
Here is what I did/got using C++ Builder compiler which translated from pascal to c++, this code:
// CodeGear C++Builder
// Copyright (c) 1995, 2016 by Embarcadero Technologies, Inc.
// All rights reserved
// (DO NOT EDIT: machine generated header) 'uDBJson.pas' rev: 31.00 (Windows)
#ifndef UdbjsonHPP
#define UdbjsonHPP
#pragma delphiheader begin
#pragma option push
#pragma option -w- // All warnings off
#pragma option -Vx // Zero-length empty class member
#pragma pack(push,8)
#include <System.hpp>
#include <SysInit.hpp>
#include <System.SysUtils.hpp>
#include <System.Classes.hpp>
#include <System.Variants.hpp>
#include <Data.DB.hpp>
#include <Datasnap.DBClient.hpp>
#include "superdate.hpp"
#include "superobject.hpp"
//-- user supplied -----------------------------------------------------------
namespace Udbjson
{
//-- forward type declarations -----------------------------------------------
class DELPHICLASS TTableJSon;
//-- type declarations -------------------------------------------------------
#pragma pack(push,4)
class PASCALIMPLEMENTATION TTableJSon : public System::TObject
{
typedef System::TObject inherited;
private:
#define cstFieldType L"FieldType"
#define cstFieldName L"FieldName"
#define cstFieldSize L"FieldSize"
#define cstJsonType L"JsonType"
#define cstRequired L"Required"
#define cstFieldIndex L"FieldIndex"
#define cstCols L"Cols"
#define cstData L"Data"
public:
__classmethod System::UnicodeString __fastcall JSonFromDataSet(Data::Db::TDataSet* DataSet);
__classmethod bool __fastcall CreateFieldByJson(Data::Db::TFieldDefs* Fields, Superobject::_di_ISuperObject ColsJson);
__classmethod int __fastcall ImportDataFromJSon(Data::Db::TDataSet* DataSet, Superobject::_di_ISuperObject DataJson);
__classmethod bool __fastcall CDSFromJSon(Datasnap::Dbclient::TClientDataSet* CDS, Superobject::_di_ISuperObject Json);
__classmethod System::Variant __fastcall GetValue(Superobject::_di_ISuperObject Json, const System::UnicodeString Name);
__classmethod bool __fastcall CreateJsonValue(Superobject::_di_ISuperObject Json, const System::UnicodeString Name, const System::Variant &Value);
__classmethod bool __fastcall CreateJsonValueByField(Superobject::_di_ISuperObject Json, Data::Db::TField* Field);
__classmethod System::Variant __fastcall GetValue2Field(Data::Db::TField* Field, Superobject::_di_ISuperObject JsonValue);
public:
/* TObject.Create */ inline __fastcall TTableJSon(void) : System::TObject() { }
/* TObject.Destroy */ inline __fastcall virtual ~TTableJSon(void) { }
};
bool __fastcall TTableJSon::CDSFromJSon(Datasnap::Dbclient::TClientDataSet* CDS, Superobject::_di_ISuperObject Json)
{
ISuperObject *ColsJson;
bool Result = false;
if(Json == NULL)return Result;
CDS->Close();
CDS->Data = NULL;
ColsJson = Json->O[cstCols];
CreateFieldByJson(CDS->FieldDefs,ColsJson);
if(CDS->FieldDefs->Count >0)CDS->CreateDataSet();
ImportDataFromJSon(CDS,Json->O[cstData]);
return true;
}
bool __fastcall TTableJSon::CreateFieldByJson(Data::Db::TFieldDefs* Fields, Superobject::_di_ISuperObject ColsJson)
{
ISuperObject *SubJson;
TFieldType *ft;
bool Result = false;
Fields->DataSet->Close();
Fields->Clear();
// Delphi Pascal code, which I don't know how to convert
for SubJson in ColsJson do
begin
ft := TFieldType(GetEnumValue(TypeInfo(TFieldType),'ft'+SubJson.S[cstFieldType]));
if ft= ftAutoInc then
ft := ftInteger;
Fields.Add(SubJson.S[cstFieldName],ft,SubJson.I[cstFieldSize],SubJson.B[cstRequired]);
end;
return true;
}
#pragma pack(pop)
//-- var, const, procedure ---------------------------------------------------
} /* namespace Udbjson */
#if !defined(DELPHIHEADER_NO_IMPLICIT_NAMESPACE_USE) && !defined(NO_USING_NAMESPACE_UDBJSON)
using namespace Udbjson;
#endif
#pragma pack(pop)
#pragma option pop
#pragma delphiheader end.
//-- end unit ----------------------------------------------------------------
#endif // UdbjsonHPP
Please help to translate this code to C++ Builder.
right now I don't know how to translate this piece of code:
for SubJson in ColsJson do
begin
ft := TFieldType(GetEnumValue(TypeInfo(TFieldType),'ft'+SubJson.S[cstFieldType]));
if ft= ftAutoInc then
ft := ftInteger;
Fields.Add(SubJson.S[cstFieldName],ft,SubJson.I[cstFieldSize],SubJson.B[cstRequired]);
end;
All other the missing code of superobject can be found here:
https://github.com/hgourvest/superobject
UPDATE ________________________________________
Below is what I tried to translated from Delphi to C++ Builder, and the only errors I got when tried to compile I have pasted below. Please check this and help to make it correctly translated.
// CodeGear C++Builder
// Copyright (c) 1995, 2016 by Embarcadero Technologies, Inc.
// All rights reserved
// (DO NOT EDIT: machine generated header) 'uDBJson.pas' rev: 31.00 (Windows)
#ifndef UdbjsonHPP
#define UdbjsonHPP
#pragma delphiheader begin
#pragma option push
#pragma option -w- // All warnings off
#pragma option -Vx // Zero-length empty class member
#pragma pack(push,8)
#include <System.hpp>
#include <SysInit.hpp>
#include <System.SysUtils.hpp>
#include <System.Classes.hpp>
#include <System.Variants.hpp>
#include <Data.DB.hpp>
#include <Datasnap.DBClient.hpp>
#include "superdate.hpp"
#include "superobject.hpp"
//-- user supplied -----------------------------------------------------------
namespace Udbjson
{
//-- forward type declarations -----------------------------------------------
class DELPHICLASS TTableJSon;
//-- type declarations -------------------------------------------------------
#pragma pack(push,4)
class PASCALIMPLEMENTATION TTableJSon : public System::TObject
{
typedef System::TObject inherited;
private:
#define cstFieldType L"FieldType"
#define cstFieldName L"FieldName"
#define cstFieldSize L"FieldSize"
#define cstJsonType L"JsonType"
#define cstRequired L"Required"
#define cstFieldIndex L"FieldIndex"
#define cstCols L"Cols"
#define cstData L"Data"
public:
__classmethod System::UnicodeString __fastcall JSonFromDataSet(Data::Db::TDataSet* DataSet);
__classmethod bool __fastcall CreateFieldByJson(Data::Db::TFieldDefs* Fields, Superobject::_di_ISuperObject ColsJson);
__classmethod int __fastcall ImportDataFromJSon(Data::Db::TDataSet* DataSet, Superobject::_di_ISuperObject DataJson);
__classmethod bool __fastcall CDSFromJSon(Datasnap::Dbclient::TClientDataSet* CDS, Superobject::_di_ISuperObject Json);
__classmethod System::Variant __fastcall GetValue(Superobject::_di_ISuperObject Json, const System::UnicodeString Name);
__classmethod bool __fastcall CreateJsonValue(Superobject::_di_ISuperObject Json, const System::UnicodeString Name, const System::Variant &Value);
__classmethod bool __fastcall CreateJsonValueByField(Superobject::_di_ISuperObject Json, Data::Db::TField* Field);
__classmethod System::Variant __fastcall GetValue2Field(Data::Db::TField* Field, Superobject::_di_ISuperObject JsonValue);
public:
/* TObject.Create */ inline __fastcall TTableJSon(void) : System::TObject() { }
/* TObject.Destroy */ inline __fastcall virtual ~TTableJSon(void) { }
};
#pragma pack(pop)
bool __fastcall TTableJSon::CDSFromJSon(Datasnap::Dbclient::TClientDataSet* CDS, Superobject::_di_ISuperObject Json)
{
ISuperObject *ColsJson;
bool Result = false;
if(Json == NULL)return Result;
CDS->Close();
CDS->Data = NULL;
ColsJson = Json->O[cstCols];
CreateFieldByJson(CDS->FieldDefs,ColsJson);
if(CDS->FieldDefs->Count >0)CDS->CreateDataSet();
ImportDataFromJSon(CDS,Json->O[cstData]);
return true;
}
bool __fastcall TTableJSon::CreateFieldByJson(Data::Db::TFieldDefs* Fields, Superobject::_di_ISuperObject ColsJson)
{
ISuperObject *SubJson;
TFieldType ft;
bool Result = false;
Fields->DataSet->Close();
Fields->Clear();
for(int i = 0; i < ColsJson->AsArray()->Length; ++i)
{
SubJson = ColsJson->AsArray()->O[i]; //>GetO(i);
ft = TFieldType(GetEnumValue(__delphirtti(TFieldType), "ft" + SubJson->S[cstFieldType]));
if(ft == ftAutoInc)
ft = ftInteger;
Fields->Add(SubJson->S[cstFieldName], ft, SubJson->I[cstFieldSize], SubJson->B[cstRequired]);
}
return true;
}
bool __fastcall TTableJSon::CreateJsonValue(Superobject::_di_ISuperObject Json, const System::UnicodeString Name, const System::Variant &Value)
{
bool Result = false;
Json->O[Name] = SO(Value);
return true;
}
bool __fastcall TTableJSon::CreateJsonValueByField(Superobject::_di_ISuperObject Json, Data::Db::TField* Field)
{
bool Result = false;
if(dynamic_cast<TDateTimeField*>(Field) != 0)
Json->O[Field->FieldName] = SO(Field->AsDateTime);
else if(dynamic_cast<TBlobField*>(Field) != 0)
Json->S[Field->FieldName] = Field->AsString; //EncodeString(..) Field->AsVariant; TIdEncoderMIME.EncodeString(m1.Text, IndyTextEncoding_UTF8); TNetEncoding.Base64.Encode TNetEncoding.Base64.Encode(myString);
else
Json->O[Field->FieldName] = SO(Field->Value);
return true;
}
System::Variant __fastcall TTableJSon::GetValue(Superobject::_di_ISuperObject Json, const System::UnicodeString Name)
{
switch(Json->DataType)
{
case stNull: return NULL; break;
case stBoolean: return Json->B[Name]; break;
case stDouble: return Json->D[Name]; break;
case stCurrency: return Json->C[Name]; break;
case stInt: return Json->I[Name]; break;
case stString: return Json->S[Name]; break;
}
return NULL;
}
System::Variant __fastcall TTableJSon::GetValue2Field(Data::Db::TField* Field, Superobject::_di_ISuperObject JsonValue)
{
if(JsonValue->DataType == stNull)
return NULL;
else if(dynamic_cast<TDateTimeField*>(Field) != 0)
return JavaToDelphiDateTime(JsonValue->AsInteger());
else if (dynamic_cast<TIntegerField*>(Field) != 0 || dynamic_cast<TLargeintField*>(Field) != 0)
return JsonValue->AsInteger();
else if(dynamic_cast<TNumericField*>(Field) != 0)
return JsonValue->AsDouble();
else if(dynamic_cast<TBooleanField*>(Field) != 0)
return JsonValue->AsBoolean();
else if(dynamic_cast<TStringField*>(Field) != 0)
return JsonValue->AsString();
else if(dynamic_cast<TBlobField*>(Field) != 0)
return JsonValue->AsString(); //DecodeString(JsonValue.AsString) //Field->AsVariant; TIdEncoderMIME.EncodeString(m1.Text, IndyTextEncoding_UTF8); TNetEncoding.Base64.Encode TNetEncoding.Base64.Encode(myString);
}
int __fastcall TTableJSon::ImportDataFromJSon(Data::Db::TDataSet* DataSet, Superobject::_di_ISuperObject DataJson)
{
ISuperObject *SubJson;
int i;
TSuperObjectIter iter;
if(! DataSet->Active)
DataSet->Open();
DataSet->DisableControls();
try
{
for(int i = 0; i < DataJson->AsArray()->Length; ++i)
{
SubJson = DataJson->AsArray()->O[i]; //>GetO(i);
DataSet->Append();
if(ObjectFindFirst(SubJson,iter))
{
do
{ if(DataSet->FindField(iter.Ite->Current->Name) != NULL)
DataSet->FindField(iter.Ite->Current->Name)->Value =
GetValue2Field(
DataSet->FindField(iter.Ite->Current->Name),
iter.Ite->Current->Value);
}
while( ! ObjectFindNext(iter));
}
DataSet->Post();
}
}
__finally
{
DataSet->EnableControls();
}
}
void GetFieldTypeInfo(Data::Db::TField *Field, String &Fieldtyp, String &JsonTyp)
{
Fieldtyp = GetEnumName(__delphirtti(TFieldType),(int)(Field->DataType));
Fieldtyp = Fieldtyp.Delete(1,2);
if(dynamic_cast<TStringField*>(Field) != 0)
JsonTyp = "string";
else if(dynamic_cast<TDateTimeField*>(Field) != 0)
JsonTyp = "integer";
else if(dynamic_cast<TIntegerField*>(Field) != 0 || dynamic_cast<TLargeintField*>(Field) != 0)
JsonTyp = "integer";
else if(dynamic_cast<TCurrencyField*>(Field) != 0)
JsonTyp = "currency";
else if(dynamic_cast<TNumericField*>(Field) != 0)
JsonTyp = "double";
else if(dynamic_cast<TBooleanField*>(Field) != 0)
JsonTyp = "boolean";
else
JsonTyp = "variant";
}
System::UnicodeString __fastcall TTableJSon::JSonFromDataSet(Data::Db::TDataSet* DataSet)
{
ISuperObject *sj, *aj, *sj2;
int i;
String Fieldtyp,JsonTyp;
TStringList *List;
sj = SO();
aj = SA(new TVarRec(),0);
List = new TStringList;
try
{
List->Sorted = true;
for(int i = 0; i< DataSet->FieldCount - 1; i++)
{
sj2 = SO();
GetFieldTypeInfo(DataSet->Fields[i].Fields[0],Fieldtyp,JsonTyp);
sj2->S[cstFieldName] = DataSet->Fields[i].Fields[0]->FieldName;
sj2->S[cstFieldType] = Fieldtyp;
sj2->S[cstJsonType] = JsonTyp;
sj2->I[cstFieldSize] = DataSet->Fields[i].Fields[0]->Size;
sj2->B[cstRequired] = DataSet->Fields[i].Fields[0]->Required;
sj2->I[cstFieldIndex] = DataSet->Fields[i].Fields[0]->Index;
aj->AsArray()->Add(sj2);
List->Add(DataSet->Fields[i].Fields[0]->FieldName+"="+JsonTyp);
}
sj->O["Cols"] = aj;
DataSet->DisableControls();
DataSet->First();
aj = SA(new TVarRec(),0);
while(! DataSet->Eof)
{
sj2 = SO();
for(int i = 0; i< DataSet->FieldCount - 1; i++)
{
//sj2.S[IntToStr(DataSet.Fields[i].Index)] := VarToStrDef(DataSet.Fields[i].Value,'');
if(VarIsNull(DataSet->Fields[i].Fields[0]->Value))
sj2->O[DataSet->Fields[i].Fields[0]->FieldName] = SO(NULL);
else
CreateJsonValueByField(sj2,DataSet->Fields[i].Fields[0]);
}
aj->AsArray()->Add(sj2);
DataSet->Next();
}
sj->O["Data"] = aj;
return sj-> AsString();
}
__finally
{
List->Free();
DataSet->EnableControls();
}
}
//-- var, const, procedure ---------------------------------------------------
} /* namespace Udbjson */
#if !defined(DELPHIHEADER_NO_IMPLICIT_NAMESPACE_USE) && !defined(NO_USING_NAMESPACE_UDBJSON)
using namespace Udbjson;
#endif
#pragma pack(pop)
#pragma option pop
#pragma delphiheader end.
//-- end unit ----------------------------------------------------------------
#endif // UdbjsonHPP
[ilink32 Error] Error: Unresolved external 'Udbjson::TTableJSon::' referenced from C:\USERS\USER\DESKTOP\WIN32\DEBUG\UNIT1.OBJ
[ilink32 Error] Error: Unresolved external '__fastcall Superobject::SA(System::TVarRec *, const int)' referenced from C:\USERS\USER\DESKTOP\WIN32\DEBUG\UNIT1.OBJ
[ilink32 Error] Error: Unresolved external '__fastcall Superobject::SO(System::UnicodeString)' referenced from C:\USERS\USER\DESKTOP\WIN32\DEBUG\UNIT1.OBJ
[ilink32 Error] Error: Unresolved external '__fastcall Superobject::SO(System::Variant&)' referenced from C:\USERS\USER\DESKTOP\WIN32\DEBUG\UNIT1.OBJ
[ilink32 Error] Error: Unresolved external '__fastcall Superobject::TSuperArray::Add(bool)' referenced from C:\USERS\USER\DESKTOP\WIN32\DEBUG\UNIT1.OBJ
[ilink32 Error] Error: Unable to perform link

Actually you will not need to translate pascal code to C++, if you are using RAD Studio, as you can simply just #include "uDBJson.hpp" after you add the uDBJson.pas file directly to your project. Your RAD Studio will create the hpp file for you and you can call the methods in the class

Related

Screenshot not show mouse cursor when is moved to second monitor

I have been doing a lot of work lately with taking screenshots (for a remote desktop system) and just stumbled across a problem while I'm trying to implement support for multiple monitors. While taking the screenshot is OK, the method I'm using to draw the cursor only presumes 1 screen. If I position the pointer on an additional screen (when taking a screenshot of that additional screen), the cursor does NOT show. I move the pointer to the main screen and it shows (of course in the wrong spot because it's the wrong screen).
My code is entirely below.
program Test;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
vcl.Graphics,
SysUtils;
function GetCursorInfo2: TCursorInfo;
var
hWindow: HWND;
pt: TPoint;
dwThreadID, dwCurrentThreadID: DWORD;
begin
Result.hCursor := 0;
ZeroMemory(#Result, SizeOf(Result));
if GetCursorPos(pt) then
begin
Result.ptScreenPos := pt;
hWindow := WindowFromPoint(pt);
if IsWindow(hWindow) then
begin
dwThreadID := GetWindowThreadProcessId(hWindow, nil);
dwCurrentThreadID := GetCurrentThreadId;
if (dwCurrentThreadID <> dwThreadID) then
begin
if AttachThreadInput(dwCurrentThreadID, dwThreadID, True) then
begin
Result.hCursor := GetCursor;
AttachThreadInput(dwCurrentThreadID, dwThreadID, False);
end;
end
else
Result.hCursor := GetCursor;
end;
end;
end;
procedure TakeScreenshot(var Bmp: TBitmap; WndHdc: HDC; Width, Height, Left, Top: Integer);
const
CAPTUREBLT = $40000000;
var
DesktopCanvas: TCanvas;
MyCursor: TIcon;
CursorInfo: TCursorInfo;
IconInfo: TIconInfo;
DC: HDC;
begin
DC := GetDC(WndHdc);
try
if (DC = 0) then
Exit;
Bmp.Width := Width;
Bmp.Height := Height;
DesktopCanvas := TCanvas.Create;
try
DesktopCanvas.Handle := DC;
BitBlt(Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, DesktopCanvas.Handle, Left, Top, SRCCOPY or CAPTUREBLT);
MyCursor := TIcon.Create;
try
CursorInfo := GetCursorInfo2;
if CursorInfo.hCursor <> 0 then
begin
MyCursor.Handle := CursorInfo.hCursor;
GetIconInfo(CursorInfo.hCursor, IconInfo);
Bmp.Canvas.Draw(CursorInfo.ptScreenPos.X - IconInfo.xHotspot, CursorInfo.ptScreenPos.Y - IconInfo.yHotspot, MyCursor);
end;
finally
MyCursor.ReleaseHandle;
MyCursor.Free;
end;
finally
DesktopCanvas.Free;
end;
finally
if (DC <> 0) then
ReleaseDC(0, DC);
end;
end;
function EnumDisplayMonitors(dc: HDC; rect: PRect; EnumProc: pointer; lData: Integer): Boolean; stdcall; external user32 name 'EnumDisplayMonitors';
type
TMonInfo = record
h: THandle;
DC: HDC;
R: TRect;
end;
var
MonList: array of TMonInfo;
function MonitorEnumProc(hMonitor: THandle; hdcMonitor: HDC; lprcMonitor: DWORD; dwData: Integer): Boolean; stdcall;
var
I, Width, Height, Left, Top: Integer;
Bmp: TBitmap;
begin
I := High(MonList) + 1;
SetLength(MonList, I + 1);
MonList[I].h := hMonitor;
MonList[I].DC := hdcMonitor;
MonList[I].R := PRect(lprcMonitor)^;
Left := PRect(lprcMonitor)^.Left;
Top := PRect(lprcMonitor)^.Top;
Width := PRect(lprcMonitor)^.Width;
Height := PRect(lprcMonitor)^.Height;
Bmp := TBitmap.Create;
try
TakeScreenshot(Bmp, hdcMonitor, Width, Height, Left, Top);
Bmp.SaveToFile('C:\Screen' + IntToStr(I + 1) + '.bmp');
finally
Bmp.Free;
end;
Result := True;
end;
procedure Main;
var
S: string;
I: Integer;
begin
Writeln('Number of monitors: ' + IntToStr(High(MonList) + 1) + #13#10);
Writeln('-----------------');
for I := 0 to High(MonList) do
with MonList[I] do
begin
S := #13#10 + 'Handle: ' + IntToStr(h) + #13#10 + 'Dc: ' + IntToStr(DC) + #13#10 + 'Size: ' + IntToStr(R.Right) + 'x' + IntToStr(R.Bottom) + #13#10;
Writeln(S);
Writeln('-----------------');
end;
end;
begin
try
EnumDisplayMonitors(0, nil, Addr(MonitorEnumProc), 0);
Main;
Writeln(#13#10 + 'Connected: ' + IntToStr(GetSystemMetrics(SM_CMONITORS)) + #13#10);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
The issue was that the cursor coordinates you get from GetCursorInfo2 are not the correct coordinates relative to your Bitmap.
First, determine whether the cursor point is in the lprcMonitor, you ccould use PtInRect, and then use DrawIcon to draw the hcursor into the bitmap, if it returns true.
Here is an C++ sample convert from your code(since I am not familiar with delphi):
#include <windows.h>
#include <iostream>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <gdiplus.h>
#include <stdio.h>
using namespace Gdiplus;
using namespace std;
#pragma comment(lib, "Gdiplus.lib")
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if (size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL)
return -1; // Failure
GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j)
{
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
free(pImageCodecInfo);
return -1; // Failure
}
//HCURSOR GetCursorInfo2(POINT * pt)
//{
// POINT p = { 0 };
// HWND hWindow = NULL;
// HCURSOR hCursor = NULL;
// if (GetCursorPos(&p))
// {
// pt->x = p.x;
// pt->y = p.y;
// hWindow = WindowFromPoint(*pt);
// if (IsWindow(hWindow))
// {
// DWORD dwThreadID = GetWindowThreadProcessId(hWindow, NULL);
// DWORD dwCurrentThreadID = GetCurrentThreadId();
// if (dwCurrentThreadID != dwThreadID)
// {
// if (AttachThreadInput(dwCurrentThreadID, dwThreadID, TRUE))
// {
// hCursor = GetCursor();
// AttachThreadInput(dwCurrentThreadID, dwThreadID, FALSE);
// }
// }
// }
// }
// return hCursor;
//}
void TakeScreenshot(HDC hdcbmp, HDC WndHdc, int Width, int Height, int Left, int Top)
{
HDC hdc = GetDC(NULL);
if (hdc == 0) exit(-1);
BitBlt(hdcbmp, 0, 0, Width, Height, hdc, Left, Top, SRCCOPY | CAPTUREBLT);
CURSORINFO cursorinfo = { 0 };
cursorinfo.cbSize = sizeof(CURSORINFO);
if (GetCursorInfo(&cursorinfo))
{
RECT rc = { Left ,Top,Left + Width ,Top + Height };
if (PtInRect(&rc, cursorinfo.ptScreenPos))
{
DrawIcon(hdcbmp, cursorinfo.ptScreenPos.x - Left, cursorinfo.ptScreenPos.y - Top, cursorinfo.hCursor);
}
}
/*ICONINFO IconInfo = { 0 };
GetIconInfo(hCursor, &IconInfo);*/
}
BOOL CALLBACK Monitorenumproc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
static int count = 0;
int Left = lprcMonitor->left;
int Top = lprcMonitor->top;
int Width = lprcMonitor->right - lprcMonitor->left;
int Height = lprcMonitor->bottom - lprcMonitor->top;
HDC dev = GetDC(NULL);
HDC CaptureDC = CreateCompatibleDC(dev);
HBITMAP CaptureBitmap = CreateCompatibleBitmap(dev, Width, Height);
HGDIOBJ old_obj = SelectObject(CaptureDC, CaptureBitmap);
TakeScreenshot(CaptureDC, dev, Width, Height, Left, Top);
Gdiplus::Bitmap bitmap(CaptureBitmap, NULL);
CLSID pngClsid;
GetEncoderClsid(L"image/bmp", &pngClsid);
wstring BmpNameString = L"C:\\screen";
BmpNameString = BmpNameString + std::to_wstring(count) + L".bmp";
count++;
bitmap.Save(BmpNameString.c_str(), &pngClsid, NULL);
SelectObject(CaptureDC, old_obj);
DeleteDC(CaptureDC);
ReleaseDC(NULL, dev);
DeleteObject(CaptureBitmap);
return TRUE;
}
int main(void)
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
EnumDisplayMonitors(0, NULL, Monitorenumproc, 0);
GdiplusShutdown(gdiplusToken);
return 0;
}
And attention to these lines in function TakeScreenshot:
CURSORINFO cursorinfo = { 0 };
cursorinfo.cbSize = sizeof(CURSORINFO);
if (GetCursorInfo(&cursorinfo))
{
RECT rc = { Left ,Top,Left + Width ,Top + Height };
if (PtInRect(&rc, cursorinfo.ptScreenPos))
{
DrawIcon(hdcbmp, cursorinfo.ptScreenPos.x - Left, cursorinfo.ptScreenPos.y - Top, cursorinfo.hCursor);
}
}

TIdTCPServer hangs when setting Active = false

I was looking at this example for using TIdTCPServer/client components and I found that if there are any clients then the server component will hang when you change active to false. Specifically, it hangs on the call to the Windows "ExitThread" function call for the context thread.
To reproduce the behavior:
run the server,
click the "Start Server" button,
run a client,
click the connect button
click the "Stop Server" button
I want a simple TCP server to monitor a process over the LAN but I can't figure out how to prevent this lock up. I have found a lot of information that skirts around this but nothing has made sense to me yet. I'm using Delphi 10.2 on Win 8.1 with Indy 10.6.2.5366.
ExitThread() can't hang, unless a DLL is misbehaving in its DllMain/DllEntryPoint() handler, causing a deadlock in the DLL loader. But, the server's Active property setter can certainly hang, such as if any of the client threads are deadlocked.
The example you linked to is NOT a good example to follow. The threaded event handlers are doing things that are not thread-safe. They are accessing UI controls without syncing with the main UI thread, which can cause many problems including deadlocks and dead UI controls. And the server's broadcast method is implemented all wrong, making it prone to deadlocks, crashes, and data corruption.
Whoever wrote that example (not me) clearly didn't know what they were doing. It needs to be rewritten to take thread safety into account properly. Try something more like this instead:
unit UServer;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdContext, IdComponent, Vcl.StdCtrls,
IdBaseComponent, IdCustomTCPServer, IdTCPServer, Vcl.ExtCtrls;
type
TFServer = class(TForm)
Title : TLabel;
btn_start : TButton;
btn_stop : TButton;
btn_clear : TButton;
clients_connected : TLabel;
IdTCPServer : TIdTCPServer;
Label1 : TLabel;
Panel1 : TPanel;
messagesLog : TMemo;
procedure FormShow(Sender: TObject);
procedure btn_startClick(Sender: TObject);
procedure btn_stopClick(Sender: TObject);
procedure btn_clearClick(Sender: TObject);
procedure IdTCPServerConnect(AContext: TIdContext);
procedure IdTCPServerDisconnect(AContext: TIdContext);
procedure IdTCPServerExecute(AContext: TIdContext);
procedure IdTCPServerStatus(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: string);
private
{ Private declarations }
procedure broadcastMessage(p_message : string);
procedure Log(p_who, p_message: string);
procedure UpdateClientsConnected(ignoreOne: boolean);
public
{ Public declarations }
end;
// ...
var
FServer : TFServer;
implementation
uses
IdGlobal, IdYarn, IdThreadSafe;
{$R *.dfm}
// ... listening port
const
GUEST_CLIENT_PORT = 20010;
// *****************************************************************************
// CLASS : TMyContext
// HELPER CLASS FOR QUEUING OUTBOUND MESSAGES TO A CLIENT
// *****************************************************************************
type
TMyContext = class(TIdServerContext)
private
FQueue: TIdThreadSafeStringList;
FAnyInQueue: Boolean;
public
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
destructor Destroy; override;
procedure AddToQueue(p_message: string);
procedure CheckQueue;
end;
constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
inherited Create(AConnection, AYarn, AList);
FQueue := TIdThreadSafeStringList.Create;
FAnyQueued := false;
end;
destructor TMyContext.Destroy;
begin
FQueue.Free;
inherited;
end;
procedure TMyContext.AddToQueue(p_message: string);
begin
with FQueue.Lock do
try
Add(p_message);
FAnyInQueue := true;
finally
FQueue.Unlock;
end;
end;
procedure TMyContext.CheckQueue;
var
queue, tmpList : TStringList;
i : integer;
begin
if not FAnyInQueue then Exit;
tmpList := TStringList.Create;
try
queue := FQueue.Lock;
try
tmpList.Assign(queue);
queue.Clear;
FAnyInQueue := false;
finally
FQueue.Unlock;
end;
for i := 0 to tmpList.Count - 1 do begin
Connection.IOHandler.WriteLn(tmpList[i]);
end;
finally
tmpList.Free;
end;
end;
// .............................................................................
// *****************************************************************************
// EVENT : onShow()
// ON FORM SHOW
// *****************************************************************************
procedure TFServer.FormShow(Sender: TObject);
begin
// ... INITIALIZE:
// ... clear message log
messagesLog.Lines.Clear;
// ... zero to clients connected
clients_connected.Caption := IntToStr(0);
// ... set buttons
btn_start.Visible := true;
btn_start.Enabled := true;
btn_stop.Visible := false;
// ... set context class
IdTCPServer.ContextClass := TMyContext;
end;
// .............................................................................
// *****************************************************************************
// EVENT : btn_startClick()
// CLICK ON START BUTTON
// *****************************************************************************
procedure TFServer.btn_startClick(Sender: TObject);
begin
btn_start.Enabled := false;
// ... START SERVER:
// ... clear the Bindings property ( ... Socket Handles )
IdTCPServer.Bindings.Clear;
// ... Bindings is a property of class: TIdSocketHandles;
// ... add listening ports:
// ... add a port for connections from guest clients.
IdTCPServer.Bindings.Add.Port := GUEST_CLIENT_PORT;
// ... etc..
// ... ok, Active the Server!
IdTCPServer.Active := true;
// ... hide start button
btn_start.Visible := false;
// ... show stop button
btn_stop.Visible := true;
btn_stop.Enabled := true;
// ... message log
Log('SERVER', 'STARTED!');
end;
// .............................................................................
// *****************************************************************************
// EVENT : btn_stopClick()
// CLICK ON STOP BUTTON
// *****************************************************************************
procedure TFServer.btn_stopClick(Sender: TObject);
begin
btn_stop.Enabled := false;
// ... before stopping the server ... send 'good bye' to all clients connected
broadcastMessage( 'Goodbye my Clients :)');
// ... stop server!
IdTCPServer.Active := false;
// ... hide stop button
btn_stop.Visible := false;
// ... show start button
btn_start.Visible := true;
btn_start.Enabled := true;
// ... message log
Log('SERVER', 'STOPPED!');
end;
// .............................................................................
// *****************************************************************************
// EVENT : btn_clearClick()
// CLICK ON CLEAR BUTTON
// *****************************************************************************
procedure TFServer.btn_clearClick(Sender: TObject);
begin
//... clear messages log
MessagesLog.Lines.Clear;
end;
// .............................................................................
// *****************************************************************************
// EVENT : onConnect()
// OCCURS ANY TIME A CLIENT IS CONNECTED
// *****************************************************************************
procedure TFServer.IdTCPServerConnect(AContext: TIdContext);
var
PeerIP : string;
PeerPort : TIdPort;
begin
// ... OnConnect is a TIdServerThreadEvent property that represents the event
// handler signalled when a new client connection is connected to the server.
// ... Use OnConnect to perform actions for the client after it is connected
// and prior to execution in the OnExecute event handler.
// ... see indy doc:
// http://www.indyproject.org/sockets/docs/index.en.aspx
// ... getting IP address and Port of Client that connected
PeerIP := AContext.Binding.PeerIP;
PeerPort := AContext.Binding.PeerPort;
// ... message log ...........................................................
Log('SERVER', 'Client Connected! Peer=' + PeerIP + ':' + IntToStr(PeerPort));
// ...
// ... update number of clients connected
UpdateClientsConnected(false);
// ...
// ... send the Welcome message to Client connected
AContext.Connection.IOHandler.WriteLn('Welcome GUEST Client :)');
end;
// .............................................................................
// *****************************************************************************
// EVENT : onDisconnect()
// OCCURS ANY TIME A CLIENT IS DISCONNECTED
// *****************************************************************************
procedure TFServer.IdTCPServerDisconnect(AContext: TIdContext);
var
PeerIP : string;
PeerPort : TIdPort;
begin
// ... getting IP address and Port of Client that connected
PeerIP := AContext.Binding.PeerIP;
PeerPort := AContext.Binding.PeerPort;
// ... message log ...........................................................
Log('SERVER', 'Client Disconnected! Peer=' + PeerIP + ':' + IntToStr(PeerPort));
// ...
// ... update number of clients connected
UpdateClientsConnected(true);
// ...
end;
// .............................................................................
// *****************************************************************************
// EVENT : onExecute()
// ON EXECUTE THREAD CLIENT
// *****************************************************************************
procedure TFServer.IdTCPServerExecute(AContext: TIdContext);
var
PeerIP : string;
PeerPort : TIdPort;
msgFromClient : string;
begin
// ... OnExecute is a TIdServerThreadEvents event handler used to execute
// the task for a client connection to the server.
// ... check for pending broadcast messages to the client
TMyContext(AContext).CheckQueue;
// ...
// check for inbound messages from client
if AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
AContext.Connection.IOHandler.CheckForDataOnSource(100);
AContext.Connection.IOHandler.CheckForDisconnect;
if AContext.Connection.IOHandler.InputBufferIsEmpty then Exit;
end;
// ... received a message from the client
// ... get message from client
msgFromClient := AContext.Connection.IOHandler.ReadLn;
// ... getting IP address, Port and PeerPort from Client that connected
PeerIP := AContext.Binding.PeerIP;
PeerPort := AContext.Binding.PeerPort;
// ... message log ...........................................................
Log('CLIENT', '(Peer=' + PeerIP + ':' + IntToStr(PeerPort) + ') ' + msgFromClient);
// ...
// ... process message (request) from Client
// ...
// ... send response to Client
AContext.Connection.IOHandler.WriteLn('... response from server :)');
end;
// .............................................................................
// *****************************************************************************
// EVENT : onStatus()
// ON STATUS CONNECTION
// *****************************************************************************
procedure TFServer.IdTCPServerStatus(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: string);
begin
// ... OnStatus is a TIdStatusEvent property that represents the event handler
// triggered when the current connection state is changed...
// ... message log
Log('SERVER', AStatusText);
end;
// .............................................................................
// *****************************************************************************
// PROCEDURE : broadcastMessage()
// BROADCAST A MESSAGE TO ALL CLIENTS CONNECTED
// *****************************************************************************
procedure TFServer.broadcastMessage( p_message : string );
var
tmpList : TIdContextList;
contexClient : TIdContext;
i : integer;
begin
// ... send a message to all clients connected
// ... get context Locklist
tmpList := IdTCPServer.Contexts.LockList;
try
for i := 0 to tmpList.Count-1 do begin
// ... get context ( thread of i-client )
contexClient := tmpList[i];
// ... queue message to client
TMyContext(contexClient).AddToQueue(p_message);
end;
finally
// ... unlock list of clients!
IdTCPServer.Contexts.UnlockList;
end;
end;
// .............................................................................
// *****************************************************************************
// PROCEDURE : Log()
// LOG A MESSAGE TO THE UI
// *****************************************************************************
procedure TFServer.Log(p_who, p_message : string);
begin
TThread.Queue(nil,
procedure
begin
MessagesLog.Lines.Add('[' + p_who + '] - ' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + ': ' + p_message);
end
);
end;
// .............................................................................
// *****************************************************************************
// PROCEDURE : UpdateClientsConnected()
// DISPLAY THE NUMBER OF CLIENTS CONNECTED
// *****************************************************************************
procedure TFServer.UpdateClientsConnected(ignoreOne: Boolean);
var
NumClients: integer;
begin
with IdTCPServer.Contexts.LockList do
try
NumClients := Count;
finally
IdTCPServer.Contexts.UnlockList;
end;
if ignoreOne then Dec(NumClients);
TThread.Queue(nil,
procedure
begin
clients_connected.Caption := IntToStr(NumClients);
end
);
end;
// .............................................................................
end.
unit UClient;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent,
IdComponent, IdTCPConnection, IdTCPClient, IdThreadComponent;
type
TFClient = class(TForm)
Label1 : TLabel;
Label2 : TLabel;
messageToSend : TMemo;
messagesLog : TMemo;
btn_connect : TButton;
btn_disconnect: TButton;
btn_send : TButton;
// ... TIdTCPClient
IdTCPClient : TIdTCPClient;
// ... TIdThreadComponent
IdThreadComponent : TIdThreadComponent;
procedure FormShow(Sender: TObject);
procedure btn_connectClick(Sender: TObject);
procedure btn_disconnectClick(Sender: TObject);
procedure btn_sendClick(Sender: TObject);
procedure IdTCPClientConnected(Sender: TObject);
procedure IdTCPClientDisconnected(Sender: TObject);
procedure IdThreadComponentRun(Sender: TIdThreadComponent);
private
{ Private declarations }
procedure Log(p_who, p_message: string);
public
{ Public declarations }
end;
var
FClient : TFClient;
implementation
{$R *.dfm}
// ... listening port: GUEST CLIENT
const
GUEST_PORT = 20010;
// *****************************************************************************
// EVENT : onShow()
// ON SHOW FORM
// *****************************************************************************
procedure TFClient.FormShow(Sender: TObject);
begin
// ... INITAILIZE
// ... message to send
messageToSend.Clear;
messageToSend.Enabled := false;
// ... log
messagesLog.Clear;
// ... buttons
btn_connect.Enabled := true;
btn_disconnect.Enabled := false;
btn_send.Enabled := false;
end;
// .............................................................................
// *****************************************************************************
// EVENT : btn_connectClick()
// CLICK ON CONNECT BUTTON
// *****************************************************************************
procedure TFClient.btn_connectClick(Sender: TObject);
begin
btn_connect.Enabled := false;
// ... try to connect to Server
try
IdTCPClient.Connect;
except
on E: Exception do begin
Log('CLIENT', 'CONNECTION ERROR! ' + E.Message);
btn_connect.Enabled := true;
end;
end;
end;
// .............................................................................
// *****************************************************************************
// EVENT : btn_disconnectClick()
// CLICK ON DISCONNECT BUTTON
// *****************************************************************************
procedure TFClient.btn_disconnectClick(Sender: TObject);
begin
btn_disconnect.Enabled := false;
// ... disconnect from Server
IdTCPClient.Disconnect;
// ... set buttons
btn_connect.Enabled := true;
btn_send.Enabled := false;
// ... message to send
messageToSend.Enabled := false;
end;
// .............................................................................
// *****************************************************************************
// EVENT : onConnected()
// OCCURS WHEN CLIENT IS CONNECTED
// *****************************************************************************
procedure TFClient.IdTCPClientConnected(Sender: TObject);
begin
// ... messages log
Log('CLIENT', 'CONNECTED!');
// ... after connection is ok, run the Thread ... waiting messages
// from server
IdThreadComponent.Active := true;
// ... set buttons
btn_disconnect.Enabled := true;
btn_send.Enabled := true;
// ... enable message to send
messageToSend.Enabled := true;
end;
// .............................................................................
// *****************************************************************************
// EVENT : onDisconnected()
// OCCURS WHEN CLIENT IS DISCONNECTED
// *****************************************************************************
procedure TFClient.IdTCPClientDisconnected(Sender: TObject);
begin
// ... message log
Log('CLIENT', 'DISCONNECTED!');
end;
// .............................................................................
// *****************************************************************************
// EVENT : btn_sendClick()
// CLICK ON SEND BUTTON
// *****************************************************************************
procedure TFClient.btn_sendClick(Sender: TObject);
begin
// ... send message to Server
IdTCPClient.IOHandler.WriteLn(messageToSend.Text);
end;
// .............................................................................
// *****************************************************************************
// EVENT : onRun()
// OCCURS WHEN THE SERVER SEND A MESSAGE TO CLIENT
// *****************************************************************************
procedure TFClient.IdThreadComponentRun(Sender: TIdThreadComponent);
var
msgFromServer : string;
begin
// ... read message from server
msgFromServer := IdTCPClient.IOHandler.ReadLn();
// ... messages log
Log('SERVER', msgFromServer);
end;
// .............................................................................
// *****************************************************************************
// FUNCTION : Log()
// LOGS A MESSAGE TO THE UI
// *****************************************************************************
procedure TFClient.Log(p_who, p_message: string);
begin
TThread.Queue(nil,
procedure
begin
MessagesLog.Lines.Add('[' + p_who + '] - ' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + ': ' + p_message);
end
);
end;
// .............................................................................
end.
I have had the same issue with the program freezing on clearing the Active flag with connected clients. It appears to be a fault in IdScheduler.
My Code
`
//---------------------------------------------------------------------------
#include <vcl.h>
#include <IdSync.hpp>
#pragma hdrstop
//---------------------------------------------------------------------------
/*
This is a general framework for TIdTCSServer and TIdTCPClient
It uses a thread to read from the client.
All threads are named.
Bugs:
4/11/19 Resetting the 'Active' property while there are still active
connections (either local or from another program) locks up on
that line. Both client and server threads remain active.
Closing the program however works, so its processes must
operate in a different manner.
Closing a different process that is running a connected client
works.
Resetting the 'Active' property with a differnt process and a
connected client locks on that line, and does not release
when the other process is closed ();
Maybe not an actual bug
Server::OnStatus doesnt fire. Why ?
Notes -
It appears that setting 'Bindings' on the server has no effect.
Default Ip (0's) will accept on any network (I run several at once,
even if just ethernet & VirtualBox).
I had thought that setting the bindings would allow certain network
cards to be excluded from server access. In a production environment,
I often find seperated networks are required by my customers.
(I am aware I can easily refuse non-authorized connections)
Two string altering functions 'IsMainThread' & 'IsNotMainThread' are
provided to ensure that the proper mechanisms are used to write
to the respective TListBox objects (VCL not being thread-safe).
*/
//---------------------------------------------------------------------------
#include "TIdTCPClientServerWin.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
//---------------------------------------------------------------------------
// A TIdSync is required for reading from the Server
//---------------------------------------------------------------------------
class TMyNotify : public TIdSync {
private:
TListBox * lb;
public:
String str;
__fastcall TMyNotify ( TListBox * l ) {
lb = l;
}
void __fastcall DoSynchronize (void) {
Form2->IsNotMainThread ( str );
lb->Items->Add ( str );
}
};
//---------------------------------------------------------------------------
TForm2 *Form2;
//---------------------------------------------------------------------------
// Form
//---------------------------------------------------------------------------
__fastcall TForm2::TForm2 ( TComponent * Owner )
: TForm ( Owner ) {
String str;
mn = new TMyNotify ( lbServer );
str = "Main Thread";
uiMainThread = GetCurrentThreadId ();
TThread::NameThreadForDebugging ( str );
}
//---------------------------------------------------------------------------
// Thread checks to ensure msgs that require syncing get it, and vice versa.
//---------------------------------------------------------------------------
void __fastcall TForm2::IsNotMainThread ( String& str ) {
unsigned int uiCurrentThread;
uiCurrentThread = GetCurrentThreadId ();
if ( uiCurrentThread != uiMainThread ) {
str += " Not Main";
} /* endif */
}
//---------------------------------------------------------------------------
void __fastcall TForm2::IsMainThread ( String& str ) {
unsigned int uiCurrentThread;
uiCurrentThread = GetCurrentThreadId ();
if ( uiCurrentThread != uiMainThread ) return;
str += " IsMain";
}
//---------------------------------------------------------------------------
// Server
//---------------------------------------------------------------------------
// Locks up when disabling - in vcl.forms
void __fastcall TForm2::cbServerActiveClick ( TObject * Sender ) {
bool bFlag;
bFlag = cbServerActive->Checked;
IdTCPServer1->Active = bFlag;
}
//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPServer1Execute ( TIdContext * AContext ) {
try {
mn->str = String ( "read " )
+ AContext->Connection->IOHandler->ReadLn ();
IsMainThread ( mn->str );
mn->Synchronize ();
AContext->Connection->IOHandler->WriteLn ( mn->str );
IsMainThread ( mn->str );
mn->str = String ( "write" );
mn->Synchronize ();
} catch (...) {
AContext->Connection->Disconnect ();
IsMainThread ( mn->str );
mn->str = String ( "Exception caused by disconnection caught" );
mn->Synchronize ();
} /* end try/catch */
}
//---------------------------------------------------------------------------
// Thread Naming
//---------------------------------------------------------------------------
// names listener threads
void __fastcall TForm2::IdTCPServer1BeforeListenerRun ( TIdThread * AThread ) {
String str;
TIdIPVersion ver;
TIdListenerThread * listen;
listen = (TIdListenerThread *) AThread;
str = IdTCPServer1->Name
+ String ( ":Listening for " );
ver = listen->Binding->IPVersion;
switch ( ver ) {
case Id_IPv4:
str += String ( "IPv4" );
break;
case Id_IPv6:
str += String ( "IPv6" );
break;
default:
str += String ( "Undefined" ) + String ( (int) ver );
break;
}
str += String ( " connections on " );
str += listen->Binding->IP;
AThread->NameThreadForDebugging ( str );
}
//---------------------------------------------------------------------------
// Messaging ( some require syncing )
//---------------------------------------------------------------------------
// Overrides thread's 'OnBeforeRun' event
void __fastcall TForm2::IdTCPServer1Connect ( TIdContext * AContext ) {
String str;
String strPrologue;
strPrologue = IdTCPServer1->Name
+ String ( ":" );
str = String ( "Connection from " )
+ AContext->Binding->PeerIP
+ String ( ":" )
+ AContext->Binding->PeerPort
+ String ( " accepted" );
TThread::NameThreadForDebugging ( strPrologue + str );
mn->str = str;
IsMainThread ( mn->str );
mn->Synchronize ();
}
//---------------------------------------------------------------------------
// Overrides thread's 'OnAfterRun' event
void __fastcall TForm2::IdTCPServer1Disconnect ( TIdContext * AContext ) {
mn->str = String ( "Disconnected from " )
+ AContext->Connection->Socket->Binding->PeerIP
+ String ( ":" )
+ AContext->Connection->Socket->Binding->PeerPort;
IsMainThread ( mn->str );
mn->Synchronize ();
}
//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPServer1Status ( TObject * ASender,
const TIdStatus AStatus,
const UnicodeString AStatusText ) {
String str;
str = String ( "Status:" )
+ AStatusText;
IsNotMainThread ( str );
lbServer->Items->Add ( AStatusText );
}
//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPServer1Exception ( TIdContext * AContext,
Exception * AException ) {
IsMainThread ( mn->str );
mn->str = String ( "Exception:" )
+ AException->Message;
mn->Synchronize ();
}
//---------------------------------------------------------------------------
// Client
//---------------------------------------------------------------------------
// A thread is required for reading from the Client
class TMyThread : public TIdThread {
private:
String str;
TIdTCPClient * cli;
TListBox * lb;
public:
String __fastcall ThreadName ( TIdTCPClient * c ) {
str = c->Name
+ String ( ":Host " )
+ c->Socket->Host
+ String ( " connected using local port " )
+ c->Socket->Binding->Port;
return str;
}
__fastcall TMyThread ( TIdTCPClient * c, TListBox * l )
: TIdThread ( true,
true,
ThreadName ( c ) ) {
cli = c;
lb = l;
FreeOnTerminate = false;
}
void __fastcall MyRead ( void ) {
String strMsg;
strMsg = String ( "recvd " ) + str;
Form2->IsNotMainThread ( str );
lb->Items->Add ( strMsg );
}
void __fastcall MyTerm ( void ) {
String strMsg;
strMsg = String ( "Terminated" );
Form2->IsNotMainThread ( str );
lb->Items->Add ( str );
}
void __fastcall Run ( void ) {
try {
str = cli->IOHandler->ReadLn ();
cli->IOHandler->CheckForDisconnect ( true, true );
Synchronize ( MyRead );
} catch (...) {
Synchronize ( MyTerm );
Terminate ();
} /* end try/catch */
}
};
//---------------------------------------------------------------------------
void __fastcall TForm2::btnSendClick ( TObject * Sender ) {
String str;
TDateTime dt;
dt = Now ();
str = dt.FormatString ( "HH:NN:SS" );
try {
IdTCPClient1->IOHandler->WriteLn ( str );
IsNotMainThread ( str );
lbClient->Items->Add ( str );
} catch (...) {
str = "Exception in Write";
IsNotMainThread ( str );
lbClient->Items->Add ( str );
IdTCPClient1->Disconnect ();
} /* end try/catch */
}
//---------------------------------------------------------------------------
void __fastcall TForm2::cbClientEnabledClick ( TObject * Sender ) {
if ( cbClientEnabled->Checked ) {
IdTCPClient1->Connect ();
return;
} /* endif */
IdTCPClient1->Disconnect ();
}
//---------------------------------------------------------------------------
// Messaging
//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPClient1Connected ( TObject * Sender ) {
mt = new TMyThread ( IdTCPClient1, lbClient );
mt->Start ();
}
//---------------------------------------------------------------------------
// Connection not yet established at this point
void __fastcall TForm2::IdTCPClient1SocketAllocated ( TObject * Sender ) {
String str;
str = "New Socket";
IsNotMainThread ( str );
lbClient->Items->Add ( str );
}
//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPClient1Status ( TObject * ASender,
const TIdStatus AStatus,
const UnicodeString AStatusText ) {
String str;
int iLen;
str = String ( "Status:" )
+ AStatusText;
str.Delete ( str.Length (), 1 );
switch ( AStatus ) {
case hsConnected:
str += String ( " using local port " )
+ String ( IdTCPClient1->Socket->Binding->Port );
break;
};
IsNotMainThread ( str );
lbClient->Items->Add ( str );
}
//---------------------------------------------------------------------------
void __fastcall TForm2::lbClearDblClick ( TObject * Sender ) {
TListBox * lb;
lb = (TListBox *) Sender;
lb->Items->Clear ();
}
//---------------------------------------------------------------------------
// End of File
Header File :
//---------------------------------------------------------------------------
#ifndef TIdTCPClientServerWinH
#define TIdTCPClientServerWinH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <IdBaseComponent.hpp>
#include <IdComponent.hpp>
#include <IdContext.hpp>
#include <IdCustomTCPServer.hpp>
#include <IdTCPClient.hpp>
#include <IdTCPConnection.hpp>
#include <IdTCPServer.hpp>
#include <Vcl.ComCtrls.hpp>
#include <IdThread.hpp>
#include <System.SysUtils.hpp>
#include <IdAntiFreezeBase.hpp>
#include <Vcl.IdAntiFreeze.hpp>
//---------------------------------------------------------------------------
class TMyNotify;
class TMyThread;
//---------------------------------------------------------------------------
class TForm2 : public TForm
{
__published: // IDE-managed Components
TIdTCPServer *IdTCPServer1;
TIdTCPClient *IdTCPClient1;
TListBox *lbServer;
TButton *btnSend;
TGroupBox *GroupBox1;
TCheckBox *cbServerActive;
TGroupBox *GroupBox2;
TListBox *lbClient;
TCheckBox *cbClientEnabled;
TStatusBar *StatusBar1;
TIdAntiFreeze *IdAntiFreeze1;
void __fastcall btnSendClick(TObject *Sender);
void __fastcall IdTCPServer1Connect(TIdContext *AContext);
void __fastcall IdTCPServer1Disconnect(TIdContext *AContext);
void __fastcall IdTCPServer1Status(TObject *ASender, const TIdStatus AStatus,
const UnicodeString AStatusText);
void __fastcall IdTCPServer1Execute(TIdContext *AContext);
void __fastcall cbClientEnabledClick(TObject *Sender);
void __fastcall cbServerActiveClick(TObject *Sender);
void __fastcall IdTCPClient1Connected(TObject *Sender);
void __fastcall IdTCPClient1SocketAllocated(TObject *Sender);
void __fastcall IdTCPClient1Status(TObject *ASender, const TIdStatus AStatus,
const UnicodeString AStatusText);
void __fastcall IdTCPServer1BeforeListenerRun(TIdThread *AThread);
void __fastcall IdTCPServer1Exception(TIdContext *AContext, Exception
*AException);
void __fastcall lbClearDblClick(TObject *Sender);
private: // User declarations
TMyNotify * mn;
TMyThread * mt;
unsigned int uiMainThread;
void __fastcall RdSync ( void );
void __fastcall WrSync ( void );
void __fastcall ExSync ( void );
void __fastcall BeforeContextRun ( TIdContext * AContext );
void __fastcall AfterContextRun ( TIdContext * AContext );
public: // User declarations
__fastcall TForm2(TComponent* Owner);
void __fastcall IsMainThread ( String& str );
void __fastcall IsNotMainThread ( String& str );
};
//---------------------------------------------------------------------------
extern PACKAGE TForm2 *Form2;
//---------------------------------------------------------------------------
#endif
DFM file:
object Form2: TForm2
Left = 0
Top = 0
Caption = 'TIdTCP Client Sever Test'
ClientHeight = 314
ClientWidth = 554
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
DesignSize = (
554
314)
PixelsPerInch = 96
TextHeight = 13
object GroupBox1: TGroupBox
Left = 8
Top = 8
Width = 265
Height = 273
Anchors = [akLeft, akTop, akBottom]
Caption = 'Server'
TabOrder = 0
DesignSize = (
265
273)
object lbServer: TListBox
Left = 16
Top = 40
Width = 233
Height = 217
Anchors = [akLeft, akTop, akRight, akBottom]
ItemHeight = 13
TabOrder = 0
OnDblClick = lbClearDblClick
end
object cbServerActive: TCheckBox
Left = 16
Top = 16
Width = 97
Height = 17
Caption = 'cbServerActive'
TabOrder = 1
OnClick = cbServerActiveClick
end
end
object GroupBox2: TGroupBox
Left = 288
Top = 8
Width = 258
Height = 273
Anchors = [akTop, akRight, akBottom]
Caption = 'Client'
TabOrder = 1
DesignSize = (
258
273)
object lbClient: TListBox
Left = 16
Top = 51
Width = 226
Height = 206
Anchors = [akLeft, akTop, akRight, akBottom]
ItemHeight = 13
TabOrder = 0
OnDblClick = lbClearDblClick
ExplicitWidth = 193
end
object btnSend: TButton
Left = 134
Top = 20
Width = 75
Height = 25
Caption = 'Send'
TabOrder = 1
OnClick = btnSendClick
end
object cbClientEnabled: TCheckBox
Left = 16
Top = 20
Width = 97
Height = 25
Caption = 'cbClientEnabled'
TabOrder = 2
OnClick = cbClientEnabledClick
end
end
object StatusBar1: TStatusBar
Left = 0
Top = 295
Width = 554
Height = 19
Panels = <>
SimplePanel = True
end
object IdTCPServer1: TIdTCPServer
OnStatus = IdTCPServer1Status
Bindings = <>
DefaultPort = 474
OnBeforeListenerRun = IdTCPServer1BeforeListenerRun
OnConnect = IdTCPServer1Connect
OnDisconnect = IdTCPServer1Disconnect
OnException = IdTCPServer1Exception
UseNagle = False
OnExecute = IdTCPServer1Execute
Left = 128
Top = 24
end
object IdTCPClient1: TIdTCPClient
OnStatus = IdTCPClient1Status
OnConnected = IdTCPClient1Connected
ConnectTimeout = 0
Host = '127.0.0.1'
IPVersion = Id_IPv4
Port = 474
ReadTimeout = -1
UseNagle = False
OnSocketAllocated = IdTCPClient1SocketAllocated
Left = 320
Top = 24
end
object IdAntiFreeze1: TIdAntiFreeze
Left = 272
Top = 56
end
end
`
I followed the execution path using the debugger and found that it gets caught in a loop in procedure TIdScheduler.TerminateAllYarns.
Summary
In IdSceduler:168 [procedure TIdScheduler.TerminateAllYarns],
we try to terminate all threads. The thread is reported as stopped [by procedure TIdThread.GetStopped], but this is never reflected in FActiveYarns, as specified via LList.Count (IdScheduler:182).
I am using Indy 10.1.5, with CBuilder 10.0 (Seattle) Version 23.0.20618.2753
Regards
`
I had the same problem.
The previous answers never helped me.
I finally found it myself.
Although I read this article late, I hope it helps you and others
you have something to do before
tcpServer.Active := False;
First, you need to make the onDisconnect event hander not working.
tcpServer.OnDisconnect:= nil;
And you have to disconnect all clients
aContexClient.Connection.Disconnect(); //aContect -> all Context
See coding below
procedure disconnectAllclient();
var
tmpList : TList;
contexClient : TidContext;
begin
tmpList := tcpServer.Contexts.LockList;
try
while (tmpList.Count > 0) do begin
contexClient := tmpList[0];
contexClient.Connection.Disconnect();
tmpList.Delete(0);
end;
finally
tcpServer.Contexts.UnlockList;
end;
end;
use :
tcpServer.OnDisconnect := nil;
disconnectAllclient();
tcpServer.Active := False;

Inno.TLabel not showing on using GDI+

I am trying to use GDI+ with Inno through DLL for antialiasing and other benefits.
But I can't able to use Inno's own Tlabel with DLL. When creating any object through GDI+. The TLabel will not show up. Though I am able to draw TPanel but TLabel doesn't seems to work at all(show up).
Host ISS:
[Defines]
#define AppName "AppName"
#define AppVersion "0.1"
#define Color "$d03a1d"
[Setup]
AppName={#AppName}
AppVersion={#AppVersion}
DefaultDirName=./
Compression=none
[Code]
#define GDIDLLPATH "E:\Cpp\Projects\Build\build-GDI\build-GDI-msvc_x32-Release\MinimalGID.dll"
type
ARGB = DWORD;
var
l :TLabel;
function DrawRectangle(h : HWND; LineColor: ARGB;startX: integer;startY: integer; width,
height: integer): integer;
external 'DrawRectangle#{#GDIDLLPATH} stdcall delayload';
procedure gdishutdown();
external 'gdishutdown#{#GDIDLLPATH} stdcall delayload';
function Createlabel(hParent:TWInControl; hAutoSize,hWordwrap:Boolean;l,t,w,h:Integer; FSize,FColor:TColor;hCaption,hFontName:String;hAlignment: TAlignment):TLabel;
begin
Result := TLAbel.Create(hParent);
with Result do
begin
Parent:=hParent;
AutoSize:=hAutoSize;
SetBounds(l,t,w,h);
WordWrap := hWordWrap;
with Font do
begin
Name:= hFontName;
Size:=Fsize;
Color:=FColor;
end;
Alignment:=hAlignment;
Caption:= hCaption;
BringToFront;
end;
end;
function CreateDefaultTxt(hParent :TWinControl; hLeft, hTop,hFontSize : Integer;hColor: TColor; hTxt : String): TLabel;
begin
Result := Createlabel(hParent,true,false,hLeft,hTop,0,0,hFontSize,hColor,hTxt,'Segoe UI',taLeftJustify);
end;
procedure InitializeWizard();
begin
with WizardForm do
begin
BorderStyle := bsNone;
ClientWidth:=800;
ClientHeight:=480;
Center;
OuterNotebook.Hide;
InnerNotebook.Hide;
Bevel.Hide;
PageNameLabel.Hide;
PageDescriptionLabel.Hide;
MainPanel.Hide;
BackButton.SetBounds(0,0,0,0);
NextButton.SetBounds(0,0,0,0);
CancelButton.SetBounds(0,0,0,0);
DirBrowseButton.SetBounds(0,0,0,0);
GroupBrowseButton.SetBounds(0,0,0,0);
l := CreateDefaultTxt(WizardForm,500,10,98,clRed,'Txt');
DrawRectangle(Handle,$23000000,0,-6,Width,40);
end;
end;
procedure DeinitializeSetup();
begin
gdishutdown;
end;
TLabel will show up correctly if DrawRectangle() is removed.
My DLL:
#include <Windows.h>
#include <gdiplus.h>
using namespace Gdiplus;
#include <objidl.h>
#pragma comment(lib, "Gdiplus.lib")
#include <functional>
#include <map>
#include <memory>
#include <vector>
#define DLL_EXPORT(RETURN_TYPE) \
extern "C" __declspec(dllexport) RETURN_TYPE __stdcall
class _GdiManager {
public:
_GdiManager() {
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
}
void shutdown() { GdiplusShutdown(gdiplusToken); }
private:
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
} GdiManager;
class DrawableObject {
public:
virtual void draw(Gdiplus::Graphics &Graphics) = 0;
virtual ~DrawableObject() = default;
};
namespace DrawableObjects {
class Rectangle : public DrawableObject {
public:
Rectangle(ARGB Color, int X, int Y, int Width, int Height)
: m_X{X}, m_Y{Y}, m_Width{Width}, m_Height{Height}, m_Brush{Color} {}
void draw(Gdiplus::Graphics &graphics) override {
graphics.FillRectangle(&m_Brush, m_X, m_Y, m_Width, m_Height);
}
private:
int m_X, m_Y, m_Width, m_Height;
Gdiplus::SolidBrush m_Brush;
};
} // namespace DrawableObjects
LRESULT MasterWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
class Painter {
public:
Painter(HWND hWnd) : m_WindowHandle{hWnd}, m_Graphics{hWnd} {
m_OriginalWindowProc = (WNDPROC)GetWindowLongW(m_WindowHandle, GWL_WNDPROC);
SetWindowLongW(m_WindowHandle, GWL_WNDPROC, (LONG)MasterWindowProc);
}
~Painter() {
SetWindowLongW(m_WindowHandle, GWL_WNDPROC, (LONG)m_OriginalWindowProc);
}
LRESULT CallOriginalWndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
LPARAM lParam) {
return CallWindowProcW(m_OriginalWindowProc, hwnd, uMsg, wParam, lParam);
}
void Paint(LPPAINTSTRUCT ps) {
for (auto &o : m_Objects)
o->draw(m_Graphics);
}
std::vector<std::unique_ptr<DrawableObject>> &Objects() { return m_Objects; }
private:
HWND m_WindowHandle;
Gdiplus::Graphics m_Graphics;
WNDPROC m_OriginalWindowProc;
std::vector<std::unique_ptr<DrawableObject>> m_Objects;
};
std::map<HWND, std::unique_ptr<Painter>> windowPaint;
LRESULT MasterWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
auto &p = windowPaint[hwnd];
if (uMsg == WM_PAINT) {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
p->Paint(&ps);
EndPaint(hwnd, &ps);
} else if (uMsg == WM_DESTROY)
PostQuitMessage(0);
return p->CallOriginalWndProc(hwnd, uMsg, wParam, lParam);
}
auto &insertPainter(HWND hwnd) {
auto &my_painter = windowPaint[hwnd];
if (!my_painter)
my_painter = std::make_unique<Painter>(hwnd);
return my_painter;
}
DLL_EXPORT(int)
DrawRectangle(HWND hwnd, ARGB LineColor, int startX, int startY, int width,
int height) {
auto &my_painter = insertPainter(hwnd);
my_painter->Objects().push_back(std::make_unique<DrawableObjects::Rectangle>(
LineColor, startX, startY, width, height));
return 0;
}
DLL_EXPORT(void) gdishutdown() {
windowPaint.clear();
GdiManager.shutdown();
}
in DLL, for every Object to draw, I capture the Parent's WndProc for Drawing on WM_PAINT and call its original WndProc after Drawing on WM_PAINT. This way I don't need host to Manually Capture Parent's WndProc for Drawing

Define and build lapsed timer

In Delphi, I understand how to build lapsing timer. But I am not sure about how to write code for C++Builder. I could not find any example.
In Delphi I wrote this code below, a copy from the source somewhere:-
....
type
TFrame2 = class(TFrame)
StatusBar1: TStatusBar;
Timer1: TTimer;
constructor TFrame2.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
StartTime := Now;
Timer1.Enabled := True;
end;
destructor TFrame2.Destroy;
begin
inherited Destroy
end;
procedure TFrame2.Timer1Timer(Sender: TObject);//This event occurs every second.
Var
Hour, Min, Sec, MSec : Word;
Diff : TTime;
begin
Timer1.Enabled := False;
Diff := Now - StartTime;
DecodeTime(Diff, Hour, Min, Sec, MSec);
StatusBar1.Panels.Items[1].Text := IntToStr(Min)+' Minutes, '+IntToStr(Sec)+' Seconds.';
Timer1.Enabled := True;
end;
...
Please kindly how to do same in C++.
Thanks
Try this:
....
class TFrame2 : public TFrame
{
__published:
TStatusBar *StatusBar1;
TTimer *Timer1;
...
void __fastcall Timer1Timer(TObject *Sender);
...
private:
TDateTime StartTime;
...
public:
__fastcall TFrame2(TComponent *TheOwner);
};
__fastcall TFrame2::TFrame2(TComponent *TheOwner)
: TFrame(TheOwner)
{
StartTime = Now();
Timer1->Enabled = true;
}
void __fastcall TFrame2::Timer1Timer(TObject *Sender) //This event occurs every second.
{
Timer1->Enabled = false;
TDateTime Diff = Now() - StartTime;
Word Hour, Min, Sec, MSec;
DecodeTime(Diff, Hour, Min, Sec, MSec);
StatusBar1->Panels->Items[1]->Text = String(Min)+" Minutes, "+String(Sec)+" Seconds.";
Timer1->Enabled = true;
}
...
Alternatively, you can simplify Timer1Timer() to this:
void __fastcall TFrame2::Timer1Timer(TObject *Sender) //This event occurs every second.
{
// this is not overhead-intense code, so
// stopping and re-starting the timer
// is wasting unnecessary processing time...
//Timer1->Enabled = true;
TDateTime Diff = Now() - StartTime;
StatusBar1->Panels->Items[1]->Text = Diff.FormatString("n' Minutes, 's' Seconds.'");
//Timer1->Enabled = true;
}
Personally, I would not use the system clock at all, in case the user changes the clock, or it auto-rolls for DST, while your timer is running. I would use CPU ticks instead, either manually:
....
class TFrame2 : public TFrame
{
__published:
TStatusBar *StatusBar1;
TTimer *Timer1;
...
void __fastcall Timer1Timer(TObject *Sender);
...
private:
DWORD StartTime;
...
public:
__fastcall TFrame2(TComponent *TheOwner);
};
__fastcall TFrame2::TFrame2(TComponent *TheOwner)
: TFrame(TheOwner)
{
StartTime = GetTickCount();
Timer1->Enabled = true;
}
void __fastcall TFrame2::Timer1Timer(TObject *Sender) //This event occurs every second.
{
//Timer1->Enabled = false;
DWORD Diff = GetTickCount() - StartTime;
DWORD Mins = Diff / 60000; Diff %= 60000;
DWORD Secs = Diff / 1000;
StatusBar1->Panels->Items[1]->Text = String(Mins)+" Minutes, "+String(Secs)+" Seconds.";
//Timer1->Enabled = true;
}
...
Or via TStopWatch:
#include <System.Diagnostics.hpp>
....
class TFrame2 : public TFrame
{
__published:
TStatusBar *StatusBar1;
TTimer *Timer1;
...
void __fastcall Timer1Timer(TObject *Sender);
...
private:
TStopwatch SW;
...
public:
__fastcall TFrame2(TComponent *TheOwner);
};
__fastcall TFrame2::TFrame2(TComponent *TheOwner)
: TFrame(TheOwner)
{
SW = TStopwatch::StartNew();
Timer1->Enabled = true;
}
void __fastcall TFrame2::Timer1Timer(TObject *Sender) //This event occurs every second.
{
//Timer1->Enabled = false;
SW.Stop();
TTimeSpan TS = SW.Elapsed;
StatusBar1->Panels->Items[1]->Text = String(TS.Minutes)+" Minutes, "+String(TS.Seconds)+" Seconds.";
SW.Start();
//Timer1->Enabled = true;
}

Firemonkey: TGrid usage on Embarcadero C++ Builder XE3

I'm try to build a tool that reads data from a database and displays it as a table using a TGrid in Firemonkey. I need to use different types of columns like TCheckColumn and TPopupColumn but can't find any good guide or example on how to use them in C++ Builder.
Any way, I managed to understand the usage of the TStringColumn,TProgressColumn setting the Value of the cell in the TGrid's event onGetValue.
Does any one of you know how to set the Value for columns of type TCheckColumn, TImageColumn and TPopupColumn?
thanks
Daniele
---UPDATE---
I managed to use the TProgressColumn. This is what I do in the Form's constructor:
// TStringColumn
Grid1->AddObject(new TStringColumn(this));
// TCheckColumn
TCheckColumn* c = new TCheckColumn(this);
Grid1->AddObject(c);
// TPopupColumn
// list of values
TStringList * l = new TStringList(NULL);
l->Add(L"First");
l->Add(L"Second");
l->Add(L"Third");
TPopupColumn* p = new TPopupColumn(this);
// adding the list to the PopupColumn
p->Items = l;
Grid1->AddObject(p);
// TProgressColumn
Grid1->AddObject(new TProgressColumn (this));
Grid1->RowCount = 3 ;
and this is the Grid1GetValue method:
// TStringColumn
if(Col == 0) Value = TValue::From<String>(Row);
// TCheckColumn !! Can't make it work
if(Col == 1) Value = TValue::From<Boolean>(true);
// TPopupColumn
if(Col == 2) Value = TValue::From<int>(2);
// TProgressColumn
if(Col == 3) Value = TValue::From<double>(50.0);
---UPDATE---
if I save the value of the column using the method OnSetValue
void __fastcall TForm1::Grid1SetValue(...)
{
if(Col == 1) check = Value;
}
and then set it with the method OnGetValue:
void __fastcall TForm1::Grid1GetValue(...)
{
// TCheckColumn !! Can't make it work
if(Col == 1) Value = check;// TValue::From<Boolean>(true);
}
After I click on one checkbox all the other checkboxes change state. So the component works correctly... now the point is how to set the Value to true or false in the right way.
TGris does not store any data, you should create your own datastorage.
Example: TGrid with TCheckColumn, TStringColumn and TPopupColumn
type
TField = record
Checked: Boolean;
Name: string;
Column: Byte;
end;
var
Fields: TList<TField>;
function SetField(const AChecked: Boolean; const AName: string; const AColumn: Byte): TField;
begin
with Result do begin
Checked := AChecked;
Name := AName;
Column := AColumn;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
begin
Fields := TList<TField>.Create;
Fields.Add(SetField(True, 'Name', 1));
Fields.Add(SetField(True, 'Login', 2));
Fields.Add(SetField(True, 'Password', 3));
for I := 1 to Fields.Count do
PopupColumn1.Items.Add('Column ' + IntToStr(I));
gdFields.RowCount := Fields.Count;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Fields.Free;
end;
procedure TFormExport.gdFieldsGetValue(Sender: TObject; const Col, Row: Integer; var Value: TValue);
begin
case gdFields.Columns[Col].TabOrder of
0: Value := Fields[Row].Checked;
1: Value := Fields[Row].Name;
2: Value := Fields[Row].Column - 1;
end;
end;
procedure TFormExport.gdFieldsSetValue(Sender: TObject; const Col, Row: Integer; const Value: TValue);
var
FRec: TField;
begin
FRec := Fields[Row];
case gdFields.Columns[Col].TabOrder of
0: FRec.Checked := Value.AsBoolean;
1: FRec.Name := Value.AsString;
2: FRec.Column := Value.AsInteger + 1;
end;
Fields[Row] := FRec;
end;
Now all data from your datastorage will be changed after editing your TGrid, but possible bug in TGrid - never received OnSetValue after changing PopupColumn
I can't give C++ code but a Delphi example should be easy enough to translate.
You get and set all cell values the same way, by listening for the OnGetData and OnSetData events, get take/give values of type TValue (XE3 and later). It's just a case of returning the appropriate type in the TValue:
uses System.RTTI;
procedure Form1.Grid1GetValue(Sender: TObject;const Col, Row: Integer;var Value: TValue);
begin
if Col = 1 then
Value := TValue.From<Integer>(1)
else if Col = 2 then
Value := TValue.From<String>('Hello')
else if Col = 3 then
Value := Tvalue.From<Single>(1.0);
end;
procedure Form1.Grid1SetValue(Sender: TObject;const Col, Row: Integer;const Value: TValue);
begin
if Col = 1 then
I := Value.As<Integer>
else if Col = 2 then
St := Value.As<String>
else if Col = 3 then
Si := Value.As<Single>;
end;
As far as I can tell a popup menu can't accept or give data.
In order to solve your problem, redefine the TCheckCell class in the following way:
#include <FMX.Grid.hpp>
#include <boost/dynamic_bitset.hpp>
class CheckCellClass:public TCheckCell
{
public:
__fastcall virtual CheckCellClass(System::Classes::TComponent*AOwner):TCheckCell(AOwner)
{
};
virtual System::Rtti::TValue __fastcall GetData(void)
{
return TValue::From<bool>(this->IsChecked);
};
virtual void __fastcall SetData(const TValue&Value)
{
TValue V(Value);
this->IsChecked=V.AsBoolean();
};
};
//Redifine TCheckColumn class
class CheckColumnClass:public TCheckColumn
{
private:
virtual Fmx::Controls::TStyledControl*__fastcall CreateCellControl(void)
{
CheckCellClass*Cell=new CheckCellClass(this);
Cell->OnChange =&(this->DoCheckChanged);
return Cell;
};
public:
__fastcall CheckColumnClass(System::Classes::TComponent*AOwner):TCheckColumn(AOwner)
{
};
};
//global Data for Save curent State Cell
boost::dynamic_bitset<unsigned char>FullDiscreteInputs;
Add To Grid In Constuctor
FullDiscreteInputs.resize(100);
DiscreteInputsGrid->RowCount=FullDiscreteInputs.size();
CheckColumnClass* DiscreteInPutsCheckColumn =new CheckColumnClass(DiscreteInputsGrid);
DiscreteInputsGrid->AddObject(CoilsCheckColumn);
void __fastcall TForm1::DiscreteInputsGridGetValue(TObject*Sender, const int Col, const int Row,TValue&Value)
{
//...
if(DiscreteInputsGrid->ColumnByIndex(Col)==DiscreteInPutsCheckColumn)
{
Value=TValue::From<bool>(FullDiscreteInputs[Row]);
}
//...
}
//---------------------------------------------------------------------------
void __fastcall TForm1::DiscreteInputsGridSetValue(TObject*Sender, const int Col, const int Row, const TValue&Value)
{
TValue V(Value);
if(DiscreteInputsGrid->ColumnByIndex(Col)==DiscreteInPutsCheckColumn)
{
FullDiscreteInputs[Row]=V.AsBoolean();
}
}
//---------------------------------------------------------------------------

Resources