Jump to content

Remy Lebeau

Members
  • Content Count

    2684
  • Joined

  • Last visited

  • Days Won

    113

Everything posted by Remy Lebeau

  1. Remy Lebeau

    looking for a "special" checkbox component

    What, 1 click to open the list, and 1 click to select? I really don't think that is much of an issue. But if it is, then just use TRadioGroup/3xTRadioButton instead.
  2. Remy Lebeau

    looking for a "special" checkbox component

    If UI space is an issue, you could use a TComboBox with Style=csDropDownList instead, and just have 3 items to choose from.
  3. Remy Lebeau

    Call for Delphi 12 Support in OpenSource projects.

    I also use {$LEGACYIFEND ON} in Indy for XE4+ versions. Helps the code remain backwards compatible with earlier versions by forcing the compiler to not accept {$ENDIF} to close {$IF}. The compiler can run into issues when you have nested {$IF} and {$IFDEF} blocks but don't use {$IFEND} consistently, which can trigger error E2029, as mentioned in the {$LEGACYIFEND} documentation (and I've run into it before).
  4. Remy Lebeau

    Call for Delphi 12 Support in OpenSource projects.

    I don't remember the specific version, but I know there was a time when the compiler had problems with the evaluation order of {$IF} and {$IFDEF}, I think that was around the early Kylix days, so would have probably been around Delphi 6-8. But that is just a guess...
  5. Remy Lebeau

    IdTCPServer IdTCPClient knowledge.

    Again, the PeerPort alone is typically not unique enough to identify clients. You are better off assigning your own custom IDs to the TIdContext objects when clients connect/login, eg: type TMyContext = class(TIdServerContext) UserID: string; end; var Clients: TIdThreadSafeStringList; procedure TMyForm.FormCreate(Sender: TObject); begin Clients := TIdThreadSafeStringList.Create; IdTCPServer1.ContextClass := TMyContext; IdTCPServer1.Active := True; end; procedure TMyForm.FormDestroy(Sender: TObject); begin IdTCPServer1.Active := False; Clients.Free; end; procedure TMyForm.IdTCPServer1Connect(AContext: TIdContext); var UserID: string; List: TStringList; begin UserID := ...; // read user ID from client List := Clients.Lock; try if List.IndexOf(UserID) <> -1 then begin AContext.Connection.Disconnect; Exit; end; List.AddObject(UserID, AContext); TMyContext(AContext).UserID := UserID; finally Clients.Unlock; end; end; procedure TMyForm.IdTCPServer1Disconnect(AContext: TIdContext); begin if TMyContext(AContext).UserID <> '' then Clients.Remove(TMyContext(AContext).UserID); end; And then you can search your Client's list for your user IDs whenever you need, and then you'll have access to the TIdContext object. Just be careful accessing it if it happens at the same time that the client disconnects. That's why if you intend to send data from outside of the server's own threads, it is best to add a queue to the TIdContext and have the OnExecute event send the queue when safe to do so.
  6. Standard strings are compiler-managed types. They are created dynamically and are also reference-counted, so the compiler needs to be able to manage and release them correctly. Don't just let them leak. Otherwise, use fixed-length character arrays if you really don't want to allocate their memory dynamically. Instances of a Class are created dynamically and must be Destroy'ed/Free'd to avoid memory leaks. Otherwise, use a Record instead of a Class.
  7. Remy Lebeau

    IdTCPServer IdTCPClient knowledge.

    It is not impossible. You simply send the desired data using the IOHandler of the TIdContext object that represents the client on the server. But, you do have to be VERY careful when sending unsolicited (ie, non-requested) data from the server to a client. If you are not careful and send the unsolicited data incorrectly, you can easily corrupt your TCP communication. You need to make sure the client is not trying to request data at the same time that you are sending unsolicited data, or the client might misinterpret the data. Most protocols tat support unsolicited data design their messages in such a way that clients can identify what messages are unsolicited and what messages are responses to requests. Is this particular client EVER requesting data from the server? Or does the server ALWAYS initiate the sending? It makes a BIG difference in how you design your protocol and manage it in your code. So it is kind of difficult to answer your question definitively without knowing more about your communication needs. TCP is a 1:1 connection between client and server. There is no broadcasting. The data will be sent only to the specific client that you send it to. Are there ANY requests being sent from this client to the server? If yes, then you need to synchronize your sends so that unsolicited data does not overlap response data. This is typically done by creating a per-client queue of outgoing data, and then having the server's OnExecute event send the calling client's queue when it is safe to do so, ie when it is not servicing any requests. If no, then you can simply send data directly to the client whenever you want. There is no "peer number" in TCP. But, the server does have a unique socket handle (ie, TIdContext object) for each connected client. And you can always assign your own ID to each client, as well. You would simply send to whichever socket/context that you are interested in. This is typically handled inside the server's OnExecute event, using the TIdContext that is passed in as a parameter. But, depending on your needs, you can also search the server's Contexts list for the desired client and then send to it (if/when safe to do so). The PeerPort by itself is not adequate enough to uniquely identify clients. If clients connects to the server from different networks, they could use the same PeerPort. You would need the combination of PeerIP+PeerPort to uniquely identify each client. Better to just use the TIdContext objects instead, since they are already unique. Yes, it is.
  8. Remy Lebeau

    Current alternatives for SMTP with TLS 1.3

    Indy's support for TLS 1.3 has already been implemented here, it just hasn't been merged into the main codebase yet. There is a difference. You are free to try the code for yourself and see if it meets your needs. Eventually, it will get merged in, I just couldn't tell you when. That doesn't mean you can't use it today. Other people are.
  9. Remy Lebeau

    Returning a string from a bpl

    UniqueString() would have done the same thing with less code, eg: s := GetPluginName; UniqueString(s); To make things easier on the caller, that should be done inside of the package function instead, eg: function GetPluginName: string; begin Result := 'test'; UniqueString(result); end; That makes sense if you are letting the compiler clear "tmp" AFTER the package has been unloaded, since "tmp" will be pointing at unloaded memory when the RTL tries to access it for cleanup, Calling UniqueString()/Copy() BEFORE the package is unloaded addresses that problem. Of course, you could also solve the problem by simply not returning a string literal from the function in the first place, or by ensuring the caller is completely finished using any data from the package before unloading it from memory.
  10. Remy Lebeau

    Using bold text in Listview

    Rather than using a separate class in the TListItem.Data property, it would be safer and more efficient to derive a new class from TListItem and add whatever members you want to it, and then return that class type from the ListView's OnCreateItemClass event. You can then type-cast any TListItem in the ListView to your custom class type when needed, and you don't have to worry about freeing them manually. For example: type TMyListItem = class(TListItem) public BoldText: Boolean; end; procedure TForm8.ListView1CreateItemClass(Sender: TCustomListView; var ItemClass: TListItemClass); begin ItemClass := TMyListItem; end; procedure TForm8.ListView1AdvancedCustomDrawSubItem(Sender: TCustomListView; Item: TListItem; SubItem: Integer; State: TCustomDrawState; Stage: TCustomDrawStage; var DefaultDraw: Boolean); begin if TMyListItem(Item).BoldText then Sender.Canvas.Font.Style := [fsBold]; end; ... var li := ListView1.Items.Add; TMyListItem(li).BoldText := True; Of course, in this particular example, there is a much simpler solution - just look at the data being drawn and act accordingly, eg: procedure TForm8.ListView1AdvancedCustomDrawSubItem(Sender: TCustomListView; Item: TListItem; SubItem: Integer; State: TCustomDrawState; Stage: TCustomDrawStage; var DefaultDraw: Boolean); begin if (SubItem > 0) and (StrToInt(Item.SubItems[SubItem-1]) < 0) then Sender.Canvas.Font.Style := [fsBold]; end;
  11. Have you tried making the FMX form override its CreateHandle() method to apply the WS_EX_TOPMOST style?
  12. Remy Lebeau

    Problem with UDPClient

    Which UDP components, exactly? Indy's? Someone else's? Please be more specific. Please show your actual code on both sides. The only way that makes sense is if the server is actually sending a $00 byte, OR, if you have pre-sized your Buf beforehand and ReceiveBuffer() is not filling it in (ie, is ReceiveBuffer() returning 0 bytes read as its return value?). Not without seeing your actual code. UDP is connection-less. There is no connection to reset.
  13. According to this documentation, the request requires a JSON body containing a recaptchaToken, iosSecret, iosReceipt, and phoneNumber, are you sending those values? That being said, I think you are using an older URL, you should be using the latest Identity Toolkit API located at https://identitytoolkit.googleapis.com instead: https://cloud.google.com/identity-platform/docs/reference/rest https://cloud.google.com/blog/products/identity-security/getting-started-with-identity-platform https://developers.google.com/identity/toolkit/migrate-identityplatform
  14. There are plenty of C macros that are translated into Delphi's Window SDK units. More likely, they were not translated simply because fibers are just not used by most developers, not even Microsoft: Fibers aren’t useful for much any more; there’s just one corner of it that remains useful for a reason unrelated to fibers
  15. Remy Lebeau

    EXE Speeds: Delphi vs. FPC?

    I'm sure either one will be fast enough for your needs. Chess isn't very computationally heavy. The only way to know for sure is to benchmark them yourself and see which one suits your needs better.
  16. Remy Lebeau

    What makes code not to compile

    https://docwiki.embarcadero.com/RADStudio/en/E2217_Published_field_'%s'_not_a_class_or_interface_type_(Delphi)
  17. Remy Lebeau

    What am I missing here??

    The signature of the event is documented: https://docwiki.embarcadero.com/Libraries/en/Data.DB.TDataSet.AfterEdit property AfterEdit: TDataSetNotifyEvent read FAfterEdit write FAfterEdit; https://docwiki.embarcadero.com/Libraries/en/Data.DB.TDataSetNotifyEvent TDataSetNotifyEvent = procedure(DataSet: TDataSet) of object;
  18. Wouldn't TParallel.For() be more appropriate in this situation? procedure TForm2.RunButtonClick(Sender: TObject); const NumberOfParts = 1; begin TParallel.For(0, NumberOfParts - 1, procedure(counter: Integer) begin RunWork(counter); end ); // or simply: // TParallel.For(0, NumberOfParts - 1, RunWork); end;
  19. Remy Lebeau

    Can I use managed C# DLL in unmanaged Delphi application ?

    Or Atozed CrossTalk, too.
  20. Not related to the 127.0.0.1 issue - note that the code above will leak the returned TStringList on mobile platforms in Delphi 10.4 and later, as ARC is no longer being used for object lifetime management, so you need to Free() the list when you are done using it on all platforms, eg: procedure TForm1.btnGetRemyClick(Sender: TObject); var IPs: TStringList; begin IPs := GetLocalIpList2(''); try m1.Lines := IPs; finally IPs.Free; end; end; A better option is to pass in the target TStrings as a parameter and let the function add to it as needed, eg: procedure GetLocalIpList2(Name: string; IPList: TStrings); var ... begin ... IPList.BeginUpdate; try ... IPList.Add(...); ... finally IPList.EndUpdate; end; ... end; procedure TForm1.btnGetRemyClick(Sender: TObject); begin GetLocalIpList2('', m1.Lines); end;
  21. You didn't say which code you used for which scenario. Indy's GetLocalAddressList() has a known issue where it reports only 127.0.0.1 on Android.
  22. Remy Lebeau

    while TStream_TryRead() do

    Sadly, not only is -1 not documented, but it's also not always implemented. For example, THandleStream.Read() returns 0 on failure and end-of-file. Which makes error handling practically impossible for some stream types. That is what the ReadXXX() methods are meant for. Read() is the lower level method that they all use internally.
  23. Remy Lebeau

    while TStream_TryRead() do

    Read() is not guaranteed to return as many bytes as requested. The documentation even says so. Use other ReadXXX() methods for that purpose instead. Since Read() can return fewer bytes without actually failing, breaking the loop on a lesser return is wrong if the intent is to read the whole stream. Only a return of -1 (error) or 0 (end of stream) should be the actual breaking condition. I prefer using 'repeat ... until False' because it avoids having to test the return value twice, eg: repeat BytesRead := Stream.Read(Buffer, BufSize); if BytesRead > 0 then ... until BytesRead <= 0;
  24. Remy Lebeau

    while TStream_TryRead() do

    repeat BytesRead := Stream.Read(Buffer, BufSize); if BytesRead <= 0 then Break; // .. until False;
  25. What you describe gets the local IPs of the device that your code is running it. It does not get the IPs of other devices on the local network. You don't need to use the IdStackXXX units directly in this example, just the base IdStack unit by itself will suffice. The correct way to use TIdStack methods is to call them on the global GStack object. Indy can instantiate the correct derived class for you based on the local OS platform, eg: uses IdGlobal, IdStack; function GetLocalIpList(Name: string): TStringList; var Lista: TIdStackLocalAddressList; temp: TIdStackLocalAddress; I: Integer; begin Result := TStringList.Create; try TIdStack.IncUsage; // instantiates GStack if needed... try Lista := TIdStackLocalAddressList.Create; try GStack.GetLocalAddressList(Lista); for I := 0 to Lista.Count-1 do begin temp := Lista[I]; if temp.IPVersion = Id_IPv4 then Result.add(temp.IPAddress); end; finally Lista.Free; end; finally TIdStack.DecUsage; // frees GStack if needed... end; except Result.Free; raise; end; end;
×