Jump to content


  • Content Count

  • Joined

  • Last visited

Everything posted by chkaufmann

  1. I have a generic method that requires an enumeration type and a value in this enumeration. Right now my function definition looks like this: procedure Foo(ASetType: PTypeInfo; ADefault: Integer); This works fine. With GetTypeData() I can extract what I need (number of elements in enumeration). However, in the usage of this function I have to add TypeInfo() and Ord() each time: type TSet1 = (a11, a12, a13); TSet2 = (a21, a22, a23) then I can call: Foo(TypeInfo(TSet1), Ord(a12)); Foo(TypeInfo(TSet2, Ord(a23)); My question is, how to define the two parameters in order to avoid the TypeInfo() and the Ord() each time? Christian
  2. chkaufmann

    Listener for TIdTcpClient

    My application has to connect to another application. It waits for messages in XML format, delimited with SOH and EOT. I wrote a listener thread. So far it works, but I wonder, if this is the way to do it. Here is my code: - Is it correct to call ReadLn and then the thread is blocked until any data is sent by the server? - I cannot test the error handling with SendData because I don't succeed to deactivate the server in the moment when the data is sent. Will it work that way? Or should I check another property of my TIdTcpClient to see if the server was deactivated? - Do I need more cleanup if I destroy my TTsClient object? Christian type TTsClientThread = class(TThread) strict private FClient : TIdTCPClientCustom; FMessages : IBSQueue<IBSXmlElement>; FPartMsg : String; strict protected procedure Execute; override; public constructor Create(AClient: TIdTCPClientCustom; const AMessages: IBSQueue<IBSXmlElement>); end; TTsClient = class(TIdTCPClientCustom) strict private FListener : TTsClientThread; FMessages : IBSQueue<IBSXmlElement>; public constructor Create(const AIpAddress: String; APort: Integer); destructor Destroy; override; function IsAlive: Boolean; function Pop(var AMessage: IBSXmlElement): Boolean; procedure SendData(const AMessage: IBSXmlElement); end; { TTsClientThread } constructor TTsClientThread.Create(AClient: TIdTCPClientCustom; const AMessages: IBSQueue<IBSXmlElement>); begin FClient := AClient; FMessages := AMessages; inherited Create; end; procedure TTsClientThread.Execute; var s : String; begin while not Terminated do begin try s := FClient.Socket.ReadLn(Char.EOT, IndyTextEncoding_UTF8).Substring(1); if not s.IsEmpty then FMessages.Append(NewBSXmlFromString(s)); except on E: EIdSocketError do begin Terminate; Exit; end; end; end; end; { TTsClient } constructor TTsClient.Create(const AIpAddress: String; APort: Integer); begin inherited Create(nil); FMessages := TBSGenerics.GenQueue<IBSXmlElement>; Host := AIpAddress; Port := APort; Connect; FListener := TTsClientThread.Create(Self, FMessages); end; destructor TTsClient.Destroy; begin FListener.Free; inherited; end; function TTsClient.IsAlive: Boolean; begin Result := not FListener.Terminated; end; function TTsClient.Pop(var AMessage: IBSXmlElement): Boolean; begin Result := FMessages.Pop(AMessage); end; procedure TTsClient.SendData(const AMessage: IBSXmlElement); begin try Socket.Write(IndyTextEncoding_UTF8.GetBytes(Char.SOH + AMessage.AsXmlDataString([]) + Char.EOT)); except on E: EIdSocketError do FListener.Terminate; end; end;
  3. chkaufmann

    Listener for TIdTcpClient

    Thanks for the reply. I have one final question. First I tried with this call: var tmp : TIdBytes; FClient.IOHandler.ReadBytes(tmp, -1, False); FPartMsg := FPartMsg + IndyTextEncoding_UTF8.GetString(tmp); .... Here I noticed, that if ReadBytes had nothing to read, tmp was not reset to length zero, but once some bytes could be read, tmp was set to the number of bytes. What is the reason for that? Christian
  4. For many enumeration type I have I define the same set of methods. Here is an example: TBSSwGender = (sgeNone, sgeMen, sgeWomen, sgeMixed); TBSSwGenderHelper = record helper for TBSSwGender public function AsInteger: Integer; function Code: String; function Name: String; end; function TBSSwGenderHelper.AsInteger: Integer; begin Result := Ord(Self); end; function TBSSwGenderHelper.Code: String; begin Result := '#Enumeration.BSSwGenderCode%d'.Fmt([AsInteger]).Translate; end; function TBSSwGenderHelper.Name: String; begin Result := '#Enumeration.BSSwGenderName%d'.Fmt([AsInteger]).Translate; end; Now my question is, instead of writing the same code for all types. So something like this: TBSEnumHelper<T> = record helper for T function AsInteger: Integer; ... end; TBSEnumHelper<TBSSwGender> = record helper for TBSSwGender Christian
  5. chkaufmann

    Enumeration Type as Parameter

    This is what I did. The builder class itself has no RTTI. But I created a generic record. IBSLookupElements is a simple collection of (Key, Caption, ParentKey) elements. Fmt() and Translate() are string helper methods for Format and language specific translations. Then TBSCatalogBuilderSimple is a general subclass of my TBSCatalogBuilder class. Here is my record definition: type TBSCatalogBuilder<T> = record strict private FCatalogId : Integer; FDefault : Integer; FName : String; FTextIdent : String; FTypeData : PTypeData; public constructor Create(ACatalogId: Integer; const AName: String); function Gen: TBSCatalogBuilder; function SetDefault(const AValue: T): TBSCatalogBuilder<T>; function SetTextIdent(const AValue: String): TBSCatalogBuilder<T>; end; { TBSCatalogBuilder<T> } constructor TBSCatalogBuilder<T>.Create(ACatalogId: Integer; const AName: String); var i : PTypeInfo; begin i := TypeInfo(T); Assert(i.Kind = tkEnumeration, 'Passed type is not an enumeration.'); FTypeData := GetTypeData(i); FCatalogId := ACatalogId; FDefault := 0; FName := AName; end; function TBSCatalogBuilder<T>.Gen: TBSCatalogBuilder; var tmp : IBSLookupElements; begin tmp := NewBSLookupElements; for var ix := (FTypeData.MinValue + 1) to FTypeData.MaxValue do tmp.AddOrSet(FTextIdent.Fmt([ix]).Translate, ix); Result := TBSCatalogBuilderSimple.Create(FCatalogId, FName, tmp, FDefault); end; function TBSCatalogBuilder<T>.SetDefault(const AValue: T): TBSCatalogBuilder<T>; begin FDefault := TValue.From<T>(AValue).AsOrdinal; Result := Self; end; function TBSCatalogBuilder<T>.SetTextIdent(const AValue: String): TBSCatalogBuilder<T>; begin FTextIdent := AValue + '%d'; Result := Self; end; Then the usage of the helper looks like this: type TElmeSchlafmodus = (esmNone, esmKeinSchlafmodus, esmTeilzeit, esmVollzeit); ADef.RegisterCatalog(TBSCatalogBuilder<TElmeSchlafmodus>.Create(CAT_ELME_BETRIEB_SCHLAFMODUS, 'Schlafmodus') .SetTextIdent('#ElmeBetrieb.Schlafmodus') .SetDefault(esmVollzeit).Gen); Thanks for all help. Christian
  6. chkaufmann

    Enumeration Type as Parameter

    Ok, here is the long story. In my system I define catalogs that can be used as lookup for values. There are complex catalogs with dynamic content that can be edited by the users. And there are simple catalogs that are hard coded in my application. For these hard coded catalogs I have builder class: TBSCatalogBuilder = class abstract strict protected function AddItem(AItemId: Integer; const AName: String; AIsDefault: Boolean = False; const AParentItem: IBSCatalogItem = nil): IBSCatalogItem; procedure DoBuildItems; virtual; abstract; public constructor Create(ACatalogId: Integer; const AName: String; AOptions: TBSCatalogOptions = []); end; So to define a catalog I create a subclass of TBSCatalogBuilder, overwrite the DoBuildItems method and call AddItem several times to define the catalog. Now some hard coded catalogs just represent the items of an enumaration type and instead of creating a subclass I would like one class with a constructor that takes the type of the enumeration and a second parameter with the default value. Right now the definition looks like this: TBSCatalogBuilderSetType = class sealed(TBSCatalogBuilder) strict protected procedure DoBuildItems; override; final; public constructor Create(ACatalogId: Integer; const AName: String; ASetType: PTypeInfo; ADefault: Integer = 0); end; and then I can use it like this: type TBSColumnType = (ctUnknown, ctBoolean, ctBytes, ctDateTime, ctFloat, ctInteger, ctMemo, ctString, ctGuid); begin myColumnTypeCatalog = TBSCatalogBuilderSetType.Create(1, 'Column Types', TypeInfo(TBSColumnType), Ord(ctString)); end; This works and it does what I want, but the TypeInfo() and the Ord() just don't look nice and I was wondering if there is not a way to avoid these. And I didn't want to define a generic class because like this the code is duplicated for every catalog. But maybe this is the only way to do it: TBSCatalogBuilderSet<T> = class(TBSCatalogBuilder) public constructor Create(ACatalogId: Integer; const AName: String; ADefault: T); end; Christian
  7. chkaufmann

    Enumeration Type as Parameter

    It's just a question of readability. My version works fine, but now I would like to avoid these TypeInfo() and Ord() when I call Foo(). And if I can validate the second parameter to be a valid member of the first (tkEnumeration) Parameter, this would be a nice additional check. Christian
  8. chkaufmann

    Enumeration Type as Parameter

    Yes "generic" is maybe the wrong word because I don't mean a generic method. Maybe I should have said untyped or typeless. I would just like to call it like this: Foo(TSet1, a13); Foo(TSet2, a21); Christian
  9. chkaufmann

    Find UDP Server

    I have two applications that communicate in a LAN using UDP. Both applications have TIdUDPClient and a TIdUDPServer. Until now the user must configure the port and the IP address. Now I would like a solution where client computers "find each other" automatically. What is the best way to do that? Regards Christian
  10. chkaufmann

    VirtualTreeview for FMX

    In my Windows applications I use VirtualTreeview for grids with inplace editors. Sometimes I load up to several 10'000 rows (nodes). For this I use rather light node classes. When I look at TTreeviewItem in FMX, then each node is a control and I don't think, this will work the same way. So are there alternatives. I need - tree check functionality (simple, tristate) - multiple columns - inplace editors - lazy load for child nodes Christian
  11. chkaufmann

    Database for Cloud and Offline

    Hi, I have an application that still uses an MS Access as database. With the library I have I can run it on other databases as well since all requests are pure SQL (no TTable or things like that). Now I'm looking for a replacement and I wonder, if there is a solution that could be used for cloud and offline. The databases are not that big (about 25 tables, maximum 25'000 records each). But each customer may have up to 50-100 databases. Ideal would be a solution where the database could be in the cloud, but there is an offline copy in case the internet connection is broken, so I can continue to work. When I'm back online, all changes would be synchronized to the server. Now I know there is probably no easy solutions, but I'm open for ideas on how to find a good solution. Christian
  12. chkaufmann

    Missing compiler warning

    Hi, I just fixed an error in the following function. I had to add the last line where I assign tmp to Result. Now I wonder, why I didn't get a compiler warning like "return value of function might be undefined.". I get such errors in the same project with other methods, so the options are set correctly. Christian class function TLnxResult.GenForEvent(AData: TLnxObject; AEventId, AAgegroupId: Integer): IBSEnumerable<IBSSwLenexResult>; var data : TLnxAbstractObject; clubs : TLnxCollection; tmp : IBSList<IBSSwLenexResult>; begin tmp := TBSGenerics.GenList<IBSSwLenexResult>; data := AData; while Assigned(data) and (data.Tag <> lxCOMPETITION) do data := data.Parent; clubs := TLnxCollection(TLnxObject(data).ChildObject[lxCLUBS]); if Assigned(clubs) then for var ixC := 0 to clubs.ChildCount - 1 do begin for var e in TLnxEntryResult.GenForClub(lxRESULTS, TLnxCollection(clubs.Objects[ixC].ChildObject[lxPERSONS]), AEventId, AAgegroupId) do tmp.Add(e as IBSSwLenexResult); for var e in TLnxEntryResult.GenForClub(lxRESULTS, TLnxCollection(clubs.Objects[ixC].ChildObject[lxRELAYS]), AEventId, AAgegroupId) do tmp.Add(e as IBSSwLenexResult); end; tmp.Sort(function(const AResult1, AResult2: IBSSwLenexResult): Integer begin Result := AResult1.Place(nil) - AResult2.Place(nil); if Result.IsZero and (AResult1.Place(nil) = BSSW_MAX_PLACE) then Result := AResult1.Status.AsInteger - AResult2.Status.AsInteger; end); Result := tmp; // <<<< this line was missing. end;
  13. chkaufmann

    Missing compiler warning

    So just that I get it right: Because my return value is an interface type, the compiler adds something like this as first statement of the method: Result := nil; Chrisitan
  14. In 10.3 (Rio), class constructors are implemented the same way as initialization sections of units. This means, I have no control about the order. So if I create an object of my class in an initialization section, I cannot be sure, that the class constructor for this class was already called. Just wondering: is this problem fixed in 10.4? Or is it still done the same way? Christian
  15. chkaufmann

    Class Constructor in Delphi 10.4

    The problem is to create a reproducable case. The order of initialization sections and class constructor is at least partial random. So things work fine but if you add a unit or change one of the uses in a project, the order is different. So I don't use class constructor anymore.
  16. chkaufmann

    Modern TColorDialog

    Hi, The color select dialog in Delphi (TColorDialog, TColorBox) looks quite old and keeping multiple custom colors available doesn't really work. Does somebody have a recommendation to replace it? Christian
  17. chkaufmann

    WM_MOUSEMOVE message

    In my application I have a TApplicationEvents object and there I use the OnMessage event. I was wondering why this event is called all the time even then when I don't touch the mouse and/or the keyboard. Now I noticed that WM_MOUSEMOVE are sent all the time. Is this normal - probably not - but how can I find out, where these messages come from. As soon as I move the mouse cursor outside of my applications window the messages stop. Christian
  18. We built our own framework where we can define different entity types. Each Entity Type has 1-n values. Then we allow different relation between entity types: - Parent / Child - Reference - Link (m:n relation) At first glance a parent/child relation is the same as a mandatory reference. But somehow it feels, it's not and I try to find the rules when to choose parent/child and when to choose reference. Can someone point me to appropriate reading? Thanks Christian
  19. No. In my approach is I have only two tables in the SQL database: ENTITY and ENTITYVALUE. Basically each object has one record in ENTITY with general values (id, guid, ownerid, created, modified) and then it has one record for each value in ENTITYVALUE. Like this, the database is very generic and the meta information is in the code only. Christian
  20. chkaufmann

    AV in GExperts with Themes

    Hi, from time to time I run into an endless AV when running grep from GExperts. Here is the stack trace. I'm not sure if this is a GExperts or a DevExpress problem: [5005FD90]{rtl260.bpl } System.TObject.InheritsFrom (Line 18239, "System.pas" + 6) + $0 [5005FBE2]{rtl260.bpl } System.@IsClass (Line 17942, "System.pas" + 1) + $8 [0B4ED18C]{themeloader260.bpl} System. + $0 [0B4ED959]{themeloader260.bpl} Idetheme.Vclstyledialogshooks.TTabControlIDEDialogStyleHook.Paint + $2D [50E05577]{vcl260.bpl } Vcl.Themes.TCustomStyleServices.DrawParentBackground (Line 2592, "Vcl.Themes.pas" + 1) + $F [50E05591]{vcl260.bpl } Vcl.Themes.TCustomStyleServices.DrawParentBackground (Line 2598, "Vcl.Themes.pas" + 1) + $D [0B4E61F2]{themeloader260.bpl} Idetheme.Vclstylehooks.TTabControlIDEStyleHook.PaintBackground + $82 [0B4D8D17]{themeloader260.bpl} Idetheme.Vclstylehooks.TIDEStyleHook.WMPaint + $EF [0B4D8E82]{themeloader260.bpl} Idetheme.Vclstylehooks.TIDEStyleHook.WndProc + $7A [0B4D97B0]{themeloader260.bpl} Idetheme.Vclstylehooks.TMouseTrackControlIDEStyleHook.WndProc + $0 [0B4E6AB2]{themeloader260.bpl} Idetheme.Vclstylehooks.TTabControlIDEStyleHook.WndProc + $A [0B4D8924]{themeloader260.bpl} Idetheme.Vclstylehooks.TIDEStyleHook.HandleMessage + $A0 [0B511493]{themeloader260.bpl} Idetheme.Stylemanager.TIDEThemeStyleEngine.HandleMessage + $97 [0B511C96]{themeloader260.bpl} Idetheme.Stylemanager.TIDEThemeStyleEngine.UnRegisterSysStyleHook + $DA [50D197BC]{vcl260.bpl } Vcl.Controls.TWinControl.WndProc (Line 10122, "Vcl.Controls.pas" + 10) + $48 [50CF65D7]{vcl260.bpl } Vcl.Graphics.FreeMemoryContexts (Line 7138, "Vcl.Graphics.pas" + 12) + $8 [50D19314]{vcl260.bpl } Vcl.Controls.TWinControl.MainWndProc (Line 9977, "Vcl.Controls.pas" + 3) + $6 [501796A4]{rtl260.bpl } System.Classes.StdWndProc (Line 17932, "System.Classes.pas" + 11) + $2 [1D14BADA]{DevEx_Main.bpl} Cxcontainer.TcxCustomInnerListBox.DoGetGestureOptions + $AFE [1D08A9E1]{DevEx_Main.bpl} Dxhooks. + $0 [50E6EC33]{vcl260.bpl } Vcl.Forms.TApplication.ProcessMessage (Line 10747, "Vcl.Forms.pas" + 23) + $1 [50E6EC5E]{vcl260.bpl } Vcl.Forms.TApplication.ProcessMessages (Line 10769, "Vcl.Forms.pas" + 1) + $4 I'm using Delphi 10.3.2 and GExperts Christian
  21. chkaufmann

    TEdit - Center text vertical

    I have a TEdit that is a bit higher than normal in order to align it with other controls. Then I wanted to center the text vertically but there is no such property and the only solution I found is to overwrite the Paint method in a subclass. Is this really the case? Or did I miss something? Christian
  22. Hi, I try to create this helper: TComponentHelper = class helper for TComponent public function FindComponents<I:IInterface>: IBSEnumerable<I>; overload; function FindComponents<T:class>: IBSEnumerable<T>; overload; end; but I get an error "methods with identical parameters". In my generic code I filter components either by using "InheritsFrom" or "Supports()". Therefore I need IInterface and class in the declaration or is there another way to do that? Christian
  23. chkaufmann

    Overloaded generic methods

    Thanks Remy, perfect solution. Christian
  24. chkaufmann

    Overloaded generic methods

    But if I don't declare T:class I cannot use it in the InheritsFrom() method. Same for an IInterface with the Support method: function TComponentHelper.FindComponents<I:IInterface>: IBSEnumerable<I>; var ix : Integer; lst : IBSList<I>; tmp : I; begin lst := TBSGenerics.GenList<I>; for ix := 0 to ComponentCount - 1 do if Supports(Components[ix], I, tmp) then lst.Add(tmp); Result := tmp; end; function TComponentHelper.FindComponents<T:class>: IBSEnumerable<T>; var ix : Integer; lst : IBSList<T>; tmp : T; begin Assert(T.InheritsFrom(TComponent), 'Nur für Subklassen von TComponent'); lst := TBSGenerics.GenList<T>; for ix := 0 to ComponentCount - 1 do if Components[ix].InheritsFrom(T) then begin TComponent(tmp) := Components[ix]; lst.Add(tmp); end; Result := lst; end; Christian
  25. I have to solve some "out of memory" problems during an import in my application. My application is still 32bit and when I check the memory in the process explorer, it's not 100% clear for me, which values are critical. Here are my questions: - What is an easy way to see the number of objects of each class during debugging? Do I have to build something on my own? Can I get this info for records as well? - Is it worth to switch the application to 64bit? - For 32bit, which values in the Process Explorer are critical and what are the maximum values during runtime that will be save? Thanks for any hints and tips here. Christian