-
Content Count
2684 -
Joined
-
Last visited
-
Days Won
113
Everything posted by Remy Lebeau
-
Are you referring to this ticket? [RSP-41434] StrToDateTime uses Decimalseparator for microseconds There is not much useful information provided in that ticket, and certainly not a reproducible example. I can imagine Embarcadero closing the ticket as "not reproducible" or "works as designed". Also, you filed the ticket as a bug (which it is not, IMHO, at least not the way you described it), but you are proposing a new feature at the end, so you should have filed the ticket as a Feature Request instead of as a Bug. In any case, I don't understand what the actual problem is. You say in the ticket: ScanTime() uses TFormatSettings.DecimalSeparator to parse milliseconds, so what is stopping you from specifying a custom separator when parsing a string? var Fmt: TFormatSettings; Fmt := TFormatSettings.Create; Fmt.TimeSeparator := ':'; Fmt.DecimalSeparator := '!'; StrToDateTime('12:34:56!789', Fmt); I don't see that being necessary at all. Especially since most platforms don't define such a separator in their respective environments anyway, AFAIK.
-
Some UI controls do, and some do not. A TLabel control does not have a Clear() method, in either VCL or FMX. Also, VCL's TLabel does not have a Text property, it has a Caption property instead: labelAnswer->Caption = ""; On the other hand, FMX's TLabel does have a Text property. VCL's TEdit does have a Clear() method: Edit1->Clear(); FMX's TEdit, on the other hand, does not. However, it does have SelectAll() and DeleteSelection() methods instead: Edit1->SelectAll(); Edit1->DeleteSelection();
-
Maybe not in your code, but who knows what the VCL does internally. MainFormOnTaskbar=true does some really funny things. In any case, have you tried verifying with GetWindowLongPtr(GWL_EXSTYLE) or Spy++ or equivalent that your affected windows don't actually have that style when you are not expecting it? Just a thought. Can you produce a bare-bones test project with minimal code that reproduces the behavior?
-
Not even if you edit the Registry to add that command-line parameter to the file association that launches the IDE?
-
The expression: ch + " is " + chValue Does not do what you think it does. It does not perform string concatenation, it actually performs pointer arithmetic instead. The string literal " is " is a 'const char[5]' array, which in this context will decay into a 'const char*' pointer to its 1st character. You are adding a 'char' and an 'int' (both integer types) to that pointer, moving around where it points in memory. So, for example, if 'A' is entered, then you are advancing the pointer forward in memory by 65+65=130 characters. And then, you are assigning that final pointer to the Label->Caption, which is why you end up with garbage output. If you are going to append integer values to strings, make sure you append them to String objects, not to char* pointers. AnsiString and UnicodeString have overloaded constructors for converting various data types, including integers, eg: Char ch = EditCharacterEntry->Text[1]; int chValue = static_cast<int>(ch); LabelAnswer->Caption = ch + String(" is ") + chValue; Alternatively, you can convert the integers to String object before concatenating them, eg: Char ch = EditCharacterEntry->Text[1]; int chValue = static_cast<int>(ch); LabelAnswer->Caption = String(ch) + " is " + String(chValue); // or IntToStr(chValue) Alternatively, use a function that to designed for formatting strings from different types of inputs, such as Sysutils::Format() or String::sprintf(), eg: Char ch = EditCharacterEntry->Text[1]; int chValue = static_cast<int>(ch); LabelAnswer->Caption = Format("%s is %d", ARRAYOFCONST((ch, chValue))); or LabelAnswer->Caption = String().sprintf(_D("%c is %d"), ch, chValue);
-
It is equally important to make sure that only the remote ends of the pipe are actually inherited, not the local ends, or else you won't be able to handle the pipes being closed correctly when the child process exits. And also, be careful if you call CreateProcess() multiple times with bInheritHandles=TRUE, as each child process may end up inheriting handles you didn't intend for it to inherit. Vista addressed that issue with the PROC_THREAD_ATTRIBUTE_LIST option of STARTUPINFOEX: Programmatically controlling which handles are inherited by new processes in Win32 Another way to create a process with attributes, maybe worse maybe better
-
Get Minimize, Maximize and Close button width of a Tform
Remy Lebeau replied to FabDev's topic in VCL
Your code snippet is declaring a variable named TitleInfo, but you are sending WM_GETTITLEBARINFOEX with a pointer to a variable named TitleBarInfoEx instead. Also, make sure you initialize the TTitleBarInfo/Ex.cbSize member before sending WM_GETTITLEBARINFOEX. https://learn.microsoft.com/en-us/windows/win32/menurc/wm-gettitlebarinfoex https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-gettitlebarinfo So, try this instead: var TitleInfo : TTitleBarInfoEx; ... begin ... TitleInfo.cbSize := SizeOf(TitleInfo); // <-- ADD THIS! SendMessage(Handle, WM_GETTITLEBARINFOEX, 0, LPARAM(@TitleInfo)); ... end; -
Are you switching between actual Win32 desktop, or virtual desktops? They are very different things. A window cannot be moved between Win32 desktops, it exists only in the desktop in which it was created. A window can be freely moved between virtual desktops, either by user action, or by code calling the IVirtualDesktopManager.MoveWindowToDesktop() method. Note, a window can appear in multiple desktops: https://learn.microsoft.com/en-us/answers/questions/611694/windows-virtual-desktops Another interesting tidbit: https://superuser.com/questions/950960/pin-applications-to-multiple-desktops-in-windows-10 Do your Forms in question have their BorderStyle set to bsToolWindow or bsSizeToolWin, by chance?
-
Using UnicodeString::operator[] to index outside of the bounds of the string (< 1, or > Length()) will throw an ERangeError exception.
-
Like any other string type, UnicodeString is an array of characters. You can index into individual characters, eg: // UnicodeString is 1-based, not 0-based! System::Char ch = EditCharacterEntry->Text[1]; int chValue = static_cast<int>(ch); Note that UnicodeString is a UTF-16 encoded string, where System::Char is a UTF-16 codeunit. It is wchar_t on Windows and char16_t on other platforms. If you need the 8-bit 'char' type specifically, you will have to perform a Unicode->ANSI data conversion at runtime, eg: // AnsiString is also 1-based! char ch = AnsiString(EditCharacterEntry->Text)[1]; int chValue = static_cast<int>(ch); Note that such a conversion is potentially lossy for non-ASCII characters > 127. AnsiString uses the caller's default codepage, so you will lose data if the UnicodeString holds Unicode characters that are not present in that codepage. You can use AnsiStringT<T> instead if you need to specify a specific codepage, eg: // AnsiStringT is also 1-based! // 28591 = ISO-8859-1, see https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers char ch = AnsiStringT<28591>(EditCharacterEntry->Text)[1]; int chValue = static_cast<int>(ch); Note that UTF8String is an alias for AnsiStringT<65001>.
-
The main project file should be a .cpp file without an associated .h[pp] file, and then each Unit in the project is a .cpp+.h[pp] pair (or a .cpp/.h[pp]/.dfm triplet if the Unit is a TForm/TFrame/TDataModule).
-
std::lock_guard<std::mutex> throwing exceptions under windows
Remy Lebeau replied to Roger Cigol's topic in General Help
You did not show your code that is using TThreadDataInterface_TS, but I would assume a bug in your code before I would assume a bug in the standard library. This kind of random behavior is a good indicator that your code is experiencing undefined behavior. Perhaps you are calling Write_TS() on an invalid TThreadDataInterface_TS object? Maybe some piece of code has previously corrupted memory and TThreadDataInterface_TS is the unwitting victim? There are many possibilities. But they all stem from one fact - you have a bug SOMEWHERE in your code, and this behavior is the RESULT of that bug. -
The IDE does not create header files with a .cpp extension, so you must have done that manually at some point.
-
SAVE - save the current unit file using its current filename. SAVE AS - save the current unit file using a new filename. SAVE PROJECT AS - save the current project using a new filename. SAVE ALL - save the current project and all unit files using their current filenames. Correct to both. Save one file/unit, vs save all files/units. Save whenever you want. But, consider also enabling the IDE's auto-save options, too: https://docwiki.embarcadero.com/RADStudio/en/Saving_and_Desktop
-
There is usually no need to ever use ReuseSocket on a client, unless you use its BoundIP and BoundPort properties to make it connect from a specific local IP/Port pair, AND you are connecting to the same remote IP/Port pair as a previous connection from the same local IP/Port. By default, a client socket connects from a random local port. ReuseSocket is more commonly used only on servers instead. When a server gets shutdown and restarted quickly, ReuseSocket can can allow it to listen on the same local IP/Port as the previous run, in case the OS hasn't released the local IP/Port yet. You are using the InputBuffer the wrong way. After you Write() out your wBuffer, you are waiting in an endless loop until a reply arrives. Why are you using a loop at all? That is not necessary. Indy's reading behavior is designed to block the calling thread waiting for requested data to arrive. Most of the IOHandler's reading methods get their data from the InputBuffer only, not from the socket directly. CheckForDataOnSource() reads directly from the socket and saves whatever it receives into the InputBuffer. So, if you ask a reading method (ie, in this case, ReadBytes()) to read something (ie, in this case, 65 bytes), the method does not exit until all of the bytes for that something are available in the InputBuffer, or until the ReadTimeout elapses (which is infinite by default). In your case, when you do eventually get a reply, you are reading it from the InputBuffer into your rBuffer, but then you are ignoring rBuffer that you just read into and instead you are checking the InputBuffer directly to see if it has any buffered bytes that you have NOT read yet, and only if it DOES then you are flagging yourself to make the next Write() call. But if the InputBuffer is EMPTY (because you have already read everything that was in it) then you are NEVER calling Write() again, and you end up stuck in your reading loop waiting for CheckForDataOnSource() to receive new bytes from the socket which you are NEVER requesting the server to send. You have over-complicated your thread logic. You don't need all of that InputBuffer handling at all. Just call Write(), then ReadBytes() (letting it block), and repeat. That being said, there are some other issues with your code, too. You are not calling Disconnect() after Connect() is successful. You are accessing the InputBuffer in the main threadd while your worker thread may also be accessing it at the same time. And you are not capturing the Exception when calling TThread.Queue(), so the Exception will be destroyed long before the main thread has a chance to access its Message. With all of that said, try this instead: inherited Create(True); TCPClient := TIdTCPClient.Create; TCPClient.Host := AHost; TCPClient.Port := APort; TCPClient.ConnectTimeout := 5000; TCPClient.ReadTimeout := ...; // infinite by default ... // a separate procedure is needed so that TThread.Queue() can // capture and extend the lifetime of the String. See the // documentation for more details: // https://docwiki.embarcadero.com/RADStudio/Sydney/en/Anonymous_Methods_in_Delphi // procedure DisplayMessageInUI(const AMsg: string); begin TThread.Queue(nil, procedure begin Form2.mmo1.Lines.Add(AMsg); end); end; ... SetLength(wBuffer, 6); //write some bytes into wBuffer SetLength(rBuffer, 65); while not Terminated do begin try TCPClient.Connect; except on E: Exception do begin DisplayMessageInUI('Exception: ' + e.Message); for i := 1 to 5 do begin if Terminated then Exit; Sleep(1000); end; Continue; end; end; try try i := 1; while not Terminated do begin TCPClient.IOHandler.Write(wBuffer); TCPClient.IOHandler.ReadBytes(rBuffer, 65, False); // waits for reply // { Alternatively, if you want to keep checking Terminated while waiting: while TCPClient.IOHandler.InputBufferIsEmpty do begin TCPClient.IOHandler.CheckForDataOnSource(100); TCPClient.IOHandler.CheckForDisconnect; if Terminated then Exit; end; TCPClient.IOHandler.ReadBytes(rBuffer, 65, False); } DisplayMessageInUI('Reply received'); //do some stuff with rBuffer Inc(i); Sleep(1000); end; finally TCPClient.Disconnect; end; except on E: Exception do begin DisplayMessageInUI('Exception: ' + e.Message); if Terminated then Exit; Sleep(1000); end; end; end;
-
You have to use the new TCustomGrid_ActualGridLineWidth() function to account for that.
-
New Behaviour Weird: My new VCL Forms (ALL) (in new projects) using "SHOW" procedure always in TOPMOST on ZOrder after SetWindowPos usage
Remy Lebeau replied to programmerdelphi2k's topic in VCL
The behavior you describe happens if Form1's window is set as the owner window (in Win32 API terms, not VCL terms) of Form2's window. A window cannot go behind its owner. You can use GetParent() or GetWindow() to determine a window's owner. In the first example, you can freely switch between Form1 and Form2 only if Form1 is not the owner of Form2. SetWindowPos() DOES NOT change window ownership! That can only be done with CreateWindow/Ex() when creating a new window, or with SetParent() or SetWindowLongPtr() on an existing window. How a TForm determines which owner to use when creating its window is a bit complex (there are other conditions that can affect the following, but this is the basics - see the source code for TCustomForrm.CreateParams() if you want to see the full logic) : If a TForm's PopupMode is pmNone (the default), then: if Application.MainFormOnTaskBar is true, the Application.MainForm window will be the owner. unless there is no MainForm, or Application.MainFormOnTaskBar is false, then the Application window will be the owner. If a TForm's PopupMode is pmAuto, then: the currently active TForm window will be the owner, unless there is no active TForm, or its window is currently minimized or hidden, then the Application.MainForm window will be the owner, unless there is no MainForm, or Application.MainFormOnTaskBar is false, then the Application window will be the owner. If a TForm's PopupMode is pmExplicit, then: the PopupParent window will be the owner, unless there is no PopupParent, then the Application.MainForm window will be the owner, unless there is no MainForm, then the Application window will be the owner. -
Access Violation when Enabling TTimer in TComponent
Remy Lebeau replied to egnew's topic in RTL and Delphi Object Pascal
It is a shame that the IDE doesn't have an option to validate that issue automatically when a component is first dropped on a Form Designer. It knows the type being created, it could quickly check the component's RTTI to see if its most-derived constructor is properly overriding the virtual TComponent constructor, and if not then warn the user of that potential bug so they can fix it. Oh well... -
I have no idea what you are referring to. Please provide a screenshot.
-
Here is a dirtier hack that shouldn't require overriding WndProc at all (untested!): procedure TForm12.cmniOpenRecentAdvancedDrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect; State: TOwnerDrawState); begin // pop the HDC state that TForm saved... RestoreDC(ACanvas.Handle, -1); // Prevent the OS from drawing the arrow... ExcludeClipRect(ACanvas.Handle, ARect.Left, ARect.Top, ARect.Right, ARect.Bottom); // save the new state so TForm will restore it... SaveDC(ACanvas.Handle); end; procedure TForm12.cmniOpenRecentDrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect; Selected: Boolean); begin ... // OnAdvancedDrawItem is fired after OnDrawItem, so don't exclude here... // ExcludeClipRect(ACanvas.Handle, ARect.Left, ARect.Top, ARect.Right, ARect.Bottom); end;
-
Not true. Something DOES happen. It is just not what you are expecting to happen. Your code has two logic flaws: 1) You are inadvertently invoking pointer arithmetic rather than string concatenation. " " is a string literal of type 'const char[2]', where its 1st element is a space character, and its 2nd element is a null terminator, eg: const char literal[2] = {' ', '\0'}; So, in the expression: numbers[t] + " " You are adding a char[] array to an integer, so the compiler will implicitly decay the char[] array into a char* pointer to its first element, and then the integer will advance that pointer by the number of characters which the integer specifies. So, on the 1st loop iteration, you are effectively assigning the TEdit::Text property a char* pointer to the literal's character at index 0, ie the space character, as-if you had written the code like this: const char literal[2] = {' ', '\0'}; const char *temp = &literal[0]; temp += numbers[t]; // effectively: temp += 0, aka &literal[0] Edit1->Text = temp; Which works fine, as the resulting char* pointer being assigned to the TEdit::Text is a valid null-terminated C-string of length 1 character, so you end up with just the space character in the TEdit::Text, as expected. Then, on the 2nd loop iteration, you are effectively assigning the TEdit::Text property a char* pointer to the literal's character at index 1, ie the null terminator, eg: const char literal[2] = {' ', '\0'}; const char *temp = &literal[0]; temp += numbers[t]; // effectively: temp += 1, aka &literal[1] Edit1->Text = temp; Which also works fine, as the resulting char* pointer being assigned to the TEdit::Text is a valid null-terminated C-string of length 0 characters, so you end up clearing the TEdit::Text. However, on the 3rd loop iteration, you are effectively assigning the TEdit::Text property a char* pointer to the literal's character at index 2, which doesn't exist, so you end up invoking undefined behavior by reading into invalid memory. const char literal[2] = {' ', '\0'}; const char *temp = &literal[0]; temp += numbers[t]; // effectively: temp += 2, aka &literal[2] Edit1->Text = temp; // UB, literal[2] doesn't exist !!! To fix that, you need to explicitly convert each number into a String object yourself before adding " " to it. The compiler will NOT do that conversion automatically for you. You can use the RTL's IntToStr() function, or the String's class constructor that takes an integer as a parameter. 2) your final loop is attempting to display only one element from the numbers array on each iteration. You are completely replacing the content of TEdit::Text with a new string value. As such, by the time that loop is finished, only the last element in the numbers array would be visible in the TEdit. To fix that, you would need to change this: Edit1 -> Text = ...; To this instead: Edit1 -> Text = Edit1 -> Text + ...; So that each loop iteration is appending to the current TEdit::Text value, rather than replacing it. With all of that said, try this instead: void __fastcall TForm1::Button1Click(TObject *Sender) { int numbers[3]; int t; for(t = 0; t < 3; ++t) { numbers[t] = t; } Edit1->Clear(); for(t = 0; t < 3; ++t) { Edit1->Text = Edit1->Text + IntToStr(numbers[t]) + " "; // or: // Edit1->Text = Edit1->Text + String(numbers[t]) + " "; } } That being said, I would suggest another change - don't assign the TEdit::Text on each loop iteration. Use a local String variable first, and then assign that to the TEdit::Text after the loop is finished. Don't make the UI do more work than it actually needs to, eg: void __fastcall TForm1::Button1Click(TObject *Sender) { int numbers[3]; int t; for(t = 0; t < 3; ++t) { numbers[t] = t; } String str; for(t = 0; t < 3; ++t) { str += (IntToStr(numbers[t]) + " "); // or: // str += (String(numbers[t]) + " "); // alternatively: // str.cat_sprintf(_D("%d "), numbers[t]); } Edit1->Text = str; }
-
All of that code is irrelevant. You asked what the Destroying property is meant for. But none of that code is setting or reading that property. So, where is it ACTUALLY being used? If you can't find that, then the property is likely no longer being used at all and could be removed so you don't have to worry about it anymore. I question why it was ever needed in the first place, since the Form's ComponentState property has a csDestroying flag available. Unless it is not the Form's destruction that you were keeping track of...
-
Yes, because objects are reference types. You are passing in a pointer to a TStringList object, so it doesn't matter whether the pointer itself is passed by value or by reference, you still have to dereference the pointer either way to access the object.
-
Offline Help updates available from Embarcadero
Remy Lebeau replied to DelphiUdIT's topic in Delphi IDE and APIs
Agreed. I HATE that they did that. I used to refer to the old docs pretty often, and now its all gone. I have the source code for D5-XE3 and 10.3, but not for XE4-10.2 or 11.x. -
How to register a shell property sheet for a single file type?
Remy Lebeau replied to FPiette's topic in Windows API
Just FYI, I have just now looked back at an old project of mine, which was first written back in the XP days before SystemFileAssociations was introduced. It still registers a custom PropertySheetHandler for a specific file extension under HKCR\<FileExtProgID>\ShellEx\PropertySheetHandlers and HKLM\SOFTWARE\Windows\CurrentVersion\Shell Extensions\Approved, and it still works fine on Win7 and Win10 (I didn't test on Win11). If SystemFileAssociations also works, good for it.