-
Content Count
3056 -
Joined
-
Last visited
-
Days Won
139
Everything posted by Remy Lebeau
-
how to respond on user drag of a column border in a listview
Remy Lebeau replied to vshvetsov's topic in VCL
Off the top of my head, something like this: private OldListViewWndProc: TWndMethod; procedure MyListViewWndProc(var Message: TMessage); ... uses ..., Winapi.CommCtrl, Winapi.Messages; procedure TMyForm.FormCreate(Sender: TObject); begin OldListViewWndProc := ListView1.WindowProc; ListView1.WindowProc := MyListViewWndProc; end; procedure TMyForm.MyListViewWndProc(var Message: TMessage); var pnmhdr: PHDNotifyW; begin if Message.Msg = WM_NOTIFY then begin case TWMNotify(Message).NMHdr^.code of HDN_BEGINTRACKW, HDN_ENDTRACKW, HDN_TRACKW: pnmhdr := PHDNotifyW(TWMNotify(Message).NMHdr); // use pnmhdr^ fields as needed: // - pnmhdr^.Item is the index of the column being tracked // - pnmhdr^.Button is the mouse button generating the message: 0=left, 1=right, 2=middle // - pnmhdr^.PItem is a pointer to a THDItemW describing the changes to the tracked column // // to block changes, set Message.Result := 1 and Exit here... end; end; OldListViewWndProc(Message); end; -
Using TJSONTextReader, you can parse the document like this: uses ..., System.Generics.Collections, System.JSON.Types, System.JSON.Readers; type TColumnNode = record Column: string; Caption: string; Width: string; end; TSearchNode = record Column: string; Rules: string; end; TColorNode = record Column: string; Rules: string; end; TReadState = ( None, InTopObject, InColumnsProp, InColumnsArray, InColumnObject, InSearchProp, InSearchArray, InSearchObject, InColorsProp, InColorsArray, InColorObject ); var JSON: string; Columns: TList<TColumnNode>; Search: TList<TSearchNode>; Colors: TList<TColorNode>; ColumnNode: TColumnNode; SearchNode: TSearchNode; ColorNode: TColorNode; TextReader: TTextReader; JSONReader: TJSONTextReader; State: TReadState; PropertyName: string; begin Columns := nil; Search := nil; Colors := nil; JSON := '...'; try Columns := TList<TColumnNode>.Create; Search := TList<TSearchNode>.Create; Colors := TList<TColorNode>.Create; TextReader := TStringReader.Create(JSON); try JSONReader := TJSONTextReader.Create(TextReader); try State := TReadState.None; while JSONReader.Read do begin case JSONReader.TokenType of TJsonToken.StartObject: begin case State of TReadState.None: State := TReadState.InTopObject; TReadState.InColumnsArray: begin ColumnNode := Default(TColumnNode); State := TReadState.InColumnObject; end; TReadState.InSearchArray: begin SearchNode := Default(TSearchNode); State := TReadState.InSearchObject; end; TReadState.InColorsArray: begin ColorNode := Default(TColorNode); State := TReadState.InColorObject; end; end; end; TJsonToken.EndObject: begin case State of TReadState.InTopObject: State := TReadState.None; TReadState.InColumnObject: begin Columns.Add(ColumnNode); State := TReadState.InColumnsArray; end; TReadState.InSearchObject: begin Search.Add(SearchNode); State := TReadState.InSearchArray; end; TReadState.InColorObject: begin Colors.Add(ColorNode); State := TReadState.InColorsArray; end; end; end; TJsonToken.StartArray: begin case State of TReadState.InColumnsProp: State := TReadState.InColumnsArray; TReadState.InSearchProp: State := TReadState.InSearchArray; TReadState.InSearchProp: State := TReadState.InColorsArray; end; end; TJsonToken.EndArray: begin case State of TReadState.InColumnsArray, TReadState.InSearchArray, TReadState.InColorsArray: State := TReadState.InTopObject; end; end; TJsonToken.PropertyName: begin PropertyName := JSONReader.Value.AsString; case State of TReadState.InTopObject: begin if PropertyName = 'columns' then State := TReadState.InColumnsProp end else if PropertyName = 'search' then State := TReadState.InSearchProp end else if PropertyName = 'colors' then State := TReadState.InColorsProp else JSONReader.Skip; end; TReadState.InColumnObject: begin if PropertyName = 'column' then CurrColumn.Column := JSONReader.ReadAsString else if PropertyName = 'caption' then CurrColumn.Caption := JSONReader.ReadAsString else if PropertyName = 'width' then CurrColumn.Width := JSONReader.ReadAsString else JSONReader.Skip; end; TReadState.InSearchObject: begin if PropertyName = 'column' then CurrSearch.Column := JSONReader.ReadAsString else if PropertyName = 'rules' then CurrSearch.Rules := JSONReader.ReadAsString else JSONReader.Skip; end; TReadState.InColorObject: begin if PropertyName = 'column' then CurrColor.Column := JSONReader.ReadAsString else if PropertyName = 'rules' then CurrColor.Rules := JSONReader.ReadAsString else JSONReader.Skip; end; else JSONReader.Skip; end; end; end; end; finally JSONReader.Free; end; finally TextReader.Free; end; // use Columns, Search, and Colors as needed... finally Columns.Free; Search.Free; Colors.Free; end; end; Obviously, take out whatever pieces you don't need. But needless to say, using a Reader can keep memory usage to a minimum, but at the cost of complicating the parsing logic. If your document is not very large, you might consider using a little extra memory in order to utilize the DOM-based framework instead, eg: uses ..., System.Generics.Collections, System.JSON; type TColumnNode = record Column: string; Caption: string; Width: string; end; TSearchNode = record Column: string; Rules: string; end; TColorNode = record Column: string; Rules: string; end; var JSON: string; Columns: TList<TColumnNode>; Search: TList<TSearchNode>; Colors: TList<TColorNode>; ColumnNode: TColumnNode; SearchNode: TSearchNode; ColorNode: TColorNode; JSONValue: TJSONValue; JSONObject: TJSONObject; JSONArray: TJSONArray; ArrElement: TJSONValue; begin Columns := nil; Search := nil; Colors := nil; JSON := '...'; try Columns := TList<TColumnNode>.Create; Search := TList<TSearchNode>.Create; Colors := TList<TColorNode>.Create; JSONValue := TJSONObject.ParseJSONValue(JSON); if JSONValue <> nil then try JSONObject := JSONValue as TJSONObject; JSONArray := JSONObject.GetValue('columns') as TJSONArray; for ArrElement in JSONArray do begin with ArrElement as JSONObject do begin ColumnNode.Column := GetValue('column').Value; ColumnNode.Caption := GetValue('caption').Value; ColumnNode.Width := GetValue('width').Value; Columns.Add(ColumnNode); end; end; JSONArray := JSONObject.GetValue('search') as TJSONArray; for ArrElement in JSONArray do begin with ArrElement as JSONObject do begin SearchNode.Column := GetValue('column').Value; SearchNode.Rules := GetValue('rules').Value; Search.Add(SearchNode); end; end; JSONArray := JSONObject.GetValue('colors') as TJSONArray; for ArrElement in JSONArray do begin with ArrElement as JSONObject do begin ColorNode.Column := GetValue('column').Value; ColorNode.Rules := GetValue('rules').Value; Colors.Add(ColorNode); end; end; finally JSONValue.Free; end; // use Columns, Search, and Colors as needed... finally Columns.Free; Search.Free; Colors.Free; end; end;
-
Not really sure I understand the question. The only GetPrinter() I see in the FMX.Printer unit is the getter method for the TPrinter.Printers[] property. Are you looking for an FMX equivalent of the VCL TPrinter.GetPrinter() method, which outputs device info and a Handle to the currently active Printer? On Windows, the FMX.Printer.Win.TPrinterWin class has such a method. What status are you looking for exactly? Please clarify.
-
Reading a binary file hangup without error
Remy Lebeau replied to William23668's topic in RTL and Delphi Object Pascal
I see several issues with that code: 1. your SetLength() is useless, because the subsequent ReadBytes() is completely replacing your allocated array with a different array allocates by ReadBytes(). 2. your SetString() is wrong, for two reasons: a. String is UnicodeString in D2009+, which you are clearly using due to TEncoding, which was also introduced in D2009. You are trying to populate the UnicodeString with AnsiChar data, but there is no overload of SetString() for that. You can only populate a UnicodeString with WideChar data. For AnsiChar data, you need to use AnsiString instead. b. You are advancing a PAnsiChar pointer through the byte array, but you are giving the array's whole length to SetString(), which means SetString() is going to read beyond the end of the array into surrounding memory after the 1st loop iteration. Since you are trying to convert each Byte into an (Ansi)String, you would need to use 1 for the SetString() length instead. 3. appending strings to the RichEdit1.Text property in a loop is VERY inefficient. It requires reading the RichEdit's entire content into a String in memory, then expanding that String in memory with appended data, then replacing the RichEdit's entire content with the new string. A much more efficient way to append strings to a RichEdit's content is to use its SelText property instead, eg: RichEdit1.SelStart := RichEdit1.GetTextLen; RichEdit1.SelLength := 0; RichEdit1.SelText := ...; But even that is not the best approach in this situation. Since you are trying to display a byte array as-is in the RichEdit as text, you may as well just assign the RichEdit.Text property 1 time with the entire array as 1 string, no loop needed at all, eg: var AFile: TFileStream; BR: TBinaryReader; MyByteArray: TBytes; mstr: AnsiString; begin AFile := TFileStream.Create(filename, fmOpenRead); BR := TBinaryReader.Create(AFile, TEncoding.Unicode, false); try // Get the whole file contents into the byte array MyByteArray := BR.ReadBytes(AFile.Size); SetString(mstr, PAnsiChar(@MyByteArray[0]), Length(MyByteArray)); RichEdit1.Text := mstr; // or: RichEdit1.Text := TEncoding.ANSI.GetString(MyByteArray); BR.Close; finally BR.Free; AFile.Free; end; end; In which case, you may as well just get rid of the TBinaryReader, as you can read bytes from the TFileStream directly: var AFile: TFileStream; MyByteArray: TBytes; mstr: AnsiString; begin AFile := TFileStream.Create(filename, fmOpenRead); try SetLength(MyByteArray, AFile.Size); // Get the whole file contents into the byte array AFile.ReadBuffer(MyByteArray[0], SetLength(MyByteArray)); SetString(mstr, PAnsiChar(@MyByteArray[0]), Length(MyByteArray)); RichEdit1.Text := mstr; // or: RichEdit1.Text := TEncoding.ANSI.GetString(MyByteArray); finally AFile.Free; end; end; In which case, you can just get rid of the TFileStream altogether, too: RichEdit1.Text := TFile.ReadAllText(filename, TEncoding.ANSI); Or RichEdit1.Lines.LoadFromFile(filename, TEncoding.ANSI); -
For what, exactly? There is no request_id in the HTTP protocol. Perhaps you are working with a particular REST protocol instead that defines a request_id in its data, outside of HTTP?
-
How to load a PNG file into TImage, then copy another PNG file into it
Remy Lebeau replied to alank2's topic in VCL
What kind of error? I've concatenated PNGs together before, but that was a long time ago, and IIRC I had to resort to a 3rd party PNG library to accomplish it (TPNGImage wasn't yet available in the VCL at the time). Copying the source PNGs to a temp bitmap, and then converting that bitmap back to a PNG, just didn't have the correct result for me (maybe it screwed up the alpha channels, I don't remember). So I had to drop down a layer and work with the raw PNG pixel data directly. The resulting PNGs were clean, but the effort was not. -
How to load a PNG file into TImage, then copy another PNG file into it
Remy Lebeau replied to alank2's topic in VCL
That is FMX code, but this question was posted in a VCL forum. -
TGIFImage is a VCL class, AFAIK it is not available in FMX. Also, as I don't use FMX myself, my claim about its lack of support for animated GIFs is mainly based on the below post, which presents some 3rd party solutions for displaying animated GIFs in an FMX UI: https://stackoverflow.com/questions/45285599/how-to-use-animated-gif-in-firemonkey For displaying a series of bitmaps in a UI, one can use TBitmapListAnimation, but I don't know if that can also be used to create a GIF. Embarcadero's documentation only mentions that FMX's TBitmap supports GIF on all platforms except iOS, but doesn't mention what kinds of GIFs are supported.
-
AFAIK FireMonkey doesn't support animated GIFs. Use an 3rd party image library that does.
-
Honestly, I have yet to try any of the Clang compilers at all, and while I would love to be able to use newer C++ language features in my code, this issue just reaffirms why I have no desire to EVER use the Clang compilers. They are just not supported properly in the IDE or the libraries. Too many problems that are "solved" by using the classic compiler instead.
-
Opinions about Pascal vs C/C++ IDE
Remy Lebeau replied to TimCruise's topic in Tips / Blogs / Tutorials / Videos
There is no RAD Studio Community edition. There is a Delphi Community edition, and a C++Builder Community edition. They are separate products, and cannot be installed together. RAD Studio is the commercial bundle that includes both Delphi and C++Builder as a single product.- 39 replies
-
- programming hardware
- rad studio
-
(and 1 more)
Tagged with:
-
Where I can find all implemented services documentation
Remy Lebeau replied to William23668's topic in FMX
Perhaps you are looking for this? https://docwiki.embarcadero.com/RADStudio/en/FireMonkey_Platform_Services -
Ah. I see it now. I wonder why that message didn't show up on my feed earlier? Oh well.
-
Embed Signature Resource in email
Remy Lebeau replied to bazzer747's topic in RTL and Delphi Object Pascal
Addressed in this discussion thread. -
Sending Email via GMail Using OAuth 2.0 via Indy
Remy Lebeau replied to Ugochukwu Mmaduekwe's topic in Indy
If you use the TIdMessageBuilder... classes, such as TIdMessageBuilderPlain or TIdMessageBuilderHtml (https://www.indyproject.org/2008/01/16/new-html-message-builder-class/), they will populate the TIdMessage for you. If you populate the TIdMessage manually, then yes, you are responsible for populating it correctly (ie https://www.indyproject.org/2005/08/17/html-messages/). -
All string classes in C++Builder - the RTL's (Ansi|Unicode|Wide)String classes, the Standard Library's std::(w|u8|u16|u32)string classes, etc - have concatenation methods built-in. The main reason for using a StringBuilder is memory management (being able to preallocate a buffer and then performing multiple concatenations into it), and that really only applies to RTL strings, as Standard Library strings have a reserve() method for that same purpose. Also, the standard C++ alternative to a StringBuilder is the std::(w)istringstream classes.
-
Don't feel bad. Until about a year ago, I was still regularly using C+++Builder 6 at my day job. Why are you concatenating a WIDE string literal to an ANSI string? You should drop the 'L' prefix, it doesn't really belong there. AnsiString doesn't have an operator+= that takes a Unicode string as input, only another AnsiString. So you are actually invoking the AnsiString constructor that accepts a Unicode string, and that is forcing a Unicode->ANSI data conversion at runtime, which you don't need. The code looks fine, provided that the Form1 pointer is valid to begin with, otherwise the code would have undefined behavior. Yes, you should, provided everything is valid. Have you tried displaying the AnsiString anywhere other than in a debug inspector? Have you tried showing it in your app's UI? Or in a popup MessageBox? Or save it to a file? Anything? That is what the debugger is meant for. Step through the code at runtime line by line, and actually step into the internal logic of the assignment and concatenation operations to make sure they are doing what you are expecting. The only way I could imagine that happening is if either the Form1 pointer is invalid during the assignment, or if the debugger is broken. Which do you think is more likely?
-
Sample needed for Net.TSocket UDP cliente and server
Remy Lebeau replied to Donald Shimoda's topic in Network, Cloud and Web
Just because it runs is in a thread doesn't guarantee it is fast/efficient. And actually, in your scenario, there is actually an interrupt, see below... I just now gave it a quick look, and one major issue I see is that you are not using TIdUDPServer the way it is meant to be used, You are creating it in your own thread that does its own reading of the UDP socket, rather than using TIdUDPServer's OnUDPRead event. It seems you are not aware that TIdUDPServer is a multi-threaded component, its OnUDPRead event is fired in the context of internal reading threads, one per Binding. The Synapse/Net.TSocket UDP sockets you are using don't work that same way. So, it actually makes sense now why you are losing packets when using Indy - your code has multiple threads reading from the same UDP socket at the same time, so there are going to be times that you will receive packets in your own thread and be able to process them, and there are going to be times when TIdUDPServer will receive packets that you are ignoring. If you want to do your own reading of an Indy UDP socket, don't use TIdUDPServer, use TIdUDPClient instead. Despite their names, they are not truly client/server components, like with how TCP components work. Since UDP is connection-less, you can actually mix TIdUDPClient and TIdUDPServer in various ways. You can use TIdUDPServer as a client and TIdUDPClient as a server. Or you can have two TIdUDPClients, or two TIdUDPServers, communicate with each other. The difference between them is only in how they manage their respective sockets internally. With TIdUDPClient, it just creates the socket, but you are responsible for all of the reading/writing. With TIdUDPServer, it does all of the reading for you, and then you are responsible for writing. That being said, I see a secondary issue in your code, not related to any particular socket library, that could also cause packet loss. In your reading thread, when you do receive a packet, you lock a TCriticalSection before processing the packet. But, you are using TCriticalSection.TryLock() for that lock, which is not a blocking function. If, for some reason, the CS is already locked by another thread, TryLock() will exit immediate with a False return value, and you will then skip processing the packet, you are not caching/retrying the packet, you just throw it away. There is actually 2 buffers involved - the kernel buffer inside the socket itself, and the application buffer that TIdUDPServer reads into before giving you the data in the OnUDPRead event. To set the size of the socket's buffer, you can use Indy's TIdSocketHandle.SetSockOpt() method to assign a value to the socket's SO_RCVBUF option. To set the size of the OnUDPRead buffer, TIdUDPServer has a BufferSize property, which defaults to 8K. -
According to the timestamps of the posts, you posted this topic here at 2021-12-17 14:17:34Z, and posted on StackOverflow at 2021-12-17 14:21:24Z. Same day, 4 minutes apart.
-
Opinions about Pascal vs C/C++ IDE
Remy Lebeau replied to TimCruise's topic in Tips / Blogs / Tutorials / Videos
Yes. There are MANY solutions available for cross-platform development. There is FreePascal, for instance. Or Java. Or many other choices. Not really. It is up to you to decide. You know what you need. Use what suits your needs. Every language and IDE has advantages and disadvantages. Do some research on each one then decide for yourself. It depends on the platform, and what degree the OS allows you to directly access hardware. Most direct hardware access on modern systems is restricted to just the OS, so you would have to use higher level APIs provided by the OS or the hardware vendor. No. Pascal can get just as low level as C/C++ can get.- 39 replies
-
- programming hardware
- rad studio
-
(and 1 more)
Tagged with:
-
You posted your question on this forum at 14:17:35Z, then you posted on StackOverflow at 14:21:24Z. That is a difference of only 4 MINUTES. These are NOT real-time forums. You have to give people TIME to answer. Personally, I didn't see your questions on either forum until 3 HOURS after you had posted them. Sometimes it can take people DAYS to answer. So please be patient. Also, it is generally considered rude to ask the same question on multiple forums at the same time. It means you consider other people's time less important than your own ("I posted here, but I'm not going to wait around for a response, I'm going to go over there instead").
-
You asked this exact same question on StackOverflow, and received answers there: https://stackoverflow.com/questions/70394569/delphi-using-resource-in-email-rather-than-image
-
Sending Email via GMail Using OAuth 2.0 via Indy
Remy Lebeau replied to Ugochukwu Mmaduekwe's topic in Indy
TIdMessage creates RFC822-style emails. Do note, however, that the TIdMessage.SaveTo...() methods are designed for SMTP usage, and so will escape emails per SMTP dot-transparency guidelines. So, to use TIdMessage emails in other protocols, be sure to use the overloaded SaveTo...() methods provided by the TIdMessageHelper unit, which adds an AUseDotTransparency parameter that you can set to False to disable that escaping. https://www.indyproject.org/2015/07/30/new-tidmessage-helper/ -
Sending Email via GMail Using OAuth 2.0 via Indy
Remy Lebeau replied to Ugochukwu Mmaduekwe's topic in Indy
No, unless you do it yourself manually. You are simply using the wrong tool for the job to begin with. TIdMessage is designed for email only, not for HTTP posts. There is nothing in Indy that specifically handles the Gmail REST API you are referring to, so you are going to have to implement it yourself. -
When was the original GetFieldData deprecated?
Remy Lebeau replied to Ian Branch's topic in RTL and Delphi Object Pascal
The new overloads were added, and the original overloads were deprecated, in XE3.