Jump to content

Remy Lebeau

Members
  • Content Count

    2343
  • Joined

  • Last visited

  • Days Won

    95

Everything posted by Remy Lebeau

  1. Remy Lebeau

    Efficient list box items with Frames

    Using a TFrame is fine, but you can't combine that with a VCL TListBox. Putting the TFrame objects onto a TPanel or even a TScrollBox would make more sense.
  2. Remy Lebeau

    Any advice when to use FileExists?

    The FileExists() call becomes redundant if you make _IsFileValidForParsing() return False for a non-existing file. But really, I would suggest completely re-writing this loop to actually open the File, decide whether to call _ParseAsA() or _ParseAsB() based on the content of the opened file rather than just its filename, and wrap the loop code in a try/except to handle IO errors. For instance: procedure MainMethod; var File: string; begin for File in GetMainPath do begin if _IsFileValidForParsing(File) then // <-- OK to leave in only if it checks just the filename and not the content ... begin try FS := TFileStream.Create(File, fmOpenRead or fmShareDenyWrite); try if _FileIsA(FS) then _ParseAsA(FS) else if _FileIsB(FS) then _ParseAsB(FS); finally FS.Free; end; except // handle error as needed ... end; end; end; end;
  3. Can you lock this thread from further posts?
  4. Note that C++11 addes the 'override' keyword to the language, and its use is highly recommended to discover typos at compile-time. If a derived class method wants to override a base class virtual method, an explicit 'override' allows the compiler to validate that the overriding method actually matches the signature of an available base virtual method, if not the compile fails. Without the explicit 'override', a mistyped overriding method would be an overload instead, which can lead to logic errors not detectable until run-time.
  5. If you want to use the same name with different parameters, you have to declare both methods as 'overload', or else declare the TBar method as 'reintroduce', which will still hide the TFoo method. Otherwise, I would just rename the TBar method to something more meaningful: TFoo = class procedure Terminate(aNow: Boolean); virtual; end; TBar = class(TFoo) procedure TerminateNow(); end; procedure TBar.TerminateNow(); begin inherited Terminate(True); end;
  6. Remy Lebeau

    TOpenDialog.FileName encoding problems on Mac OS

    As they should be. Makes sense, since you are saving to an ANSI database field, so you are invoking a lossy Unicode->ANSI conversion. Because you are still invoking a conversion to ANSI. Have you tried switching the database column to nvarchar, and using TField.AsWideString?
  7. Borland tried that once before. The CLX framework in Delphi 6 was a cross-platform version of the VCL. Most of the interfaces remained the same, just the internal implementations changed. That didn't work out so well for them, CLX had to be scrapped.
  8. Multi-threading issues for TBitmap were addressed in 10.2 Tokyo: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/What's_New#Multi-Threading_Support_for_TBitmap.2C_TCanvas_and_TContext3D
  9. TBitmap works in a thread, so you are probably just not using it correctly. But, if threading is not an option, then I suggest to break up your calculations into small chunks that you can execute asynchronously without blocking the UI thread, such as with TThread.ForceQueue(). Execute a chunk and queue the next chunk, then return to the main message loop. When ready, the next chunk will execute, queue the next chunk, return to the main message loop, and so on, until the calculations are finished.
  10. Remy Lebeau

    [iOS ANDROID] MessageDialog Modal

    Android does not support modal dialogs, you MUST use asynchronous dialogs instead, such as via TDialogServiceAsync.MessageDialog().
  11. The answer you were given on StackOverflow suggested you use a background thread, which is what you should be using. Start a background thread on app startup to perform your time-consuming work. Have the thread use TThread.Synchronize() or TThread.Queue(), or simply assign a handler to the TThread.OnTerminate event, to communicate back to the main thread when the background thread is finished. Have your UI display the trackbar by default, and then hide the trackbar when the background thread is finished. You don't need to in this situation. The OnIdle event is triggered whenever the main message loop receives a new message and then finishes processing all pending messages. Don't rely on the timing of that event to drive your app's logic. Application.ProcessMessages() is broken in 10.3. You shouldn't be using it at all anyway. Do the pre-calculation in a background thread, and have the background thread post status updates to the main thread as needed. Let the main thread update the UI on its own schedule.
  12. Remy Lebeau

    My android app restarts on permission request

    Which circumstances exactly? ForceDirectories() SHOULD succeed without error in that case. Its job is to ensure a path exists, not to report an error if it does exist. The only time it should ever raise an exception is if the input path is blank. Anything else should cause it to simply return True if the path already exists or was successfully created, or False otherwise. If it is not working that way, that is a bug for Embarcadero to fix.
  13. Doubtful, since transitions are meant to be used asynchronously. TIntAnimation has an OnFinish event, you are supposed to use that instead of using a blocking wait loop on the Running property. Good thing SetActiveTabWithTransitionASync() has an option to skip the actual transition.
  14. Remy Lebeau

    My android app restarts on permission request

    and a look at it in the source code verifies it. Last time I checked, ForceDirectories() DOES NOT raise an exception if the specified directory already exists. Are you sure the documentation you quoted is referring to ForeceDirectories() and not TDirListBox? Which source code did you "verify" exactly? The ForceDirectories() documentation does warn against calling it with a blank string, which I accidentally did in my example above if TPath.GetSharedPicturePath() returns a blank string.
  15. Only if you use the syntax you showed, yes. An alternative would be to use the TList<T>.List property instead, which gives you somewhat more direct access to the internal array: procedure TForm1.FormCreate(Sender: TObject); var ... vList: TList<TRecLine>; vNode: PVirtualNode; ... begin ... vList.List[0].VirtualNode := vNode; ... end; Or, use the TList<T>.PList property instead, which is as bare-bones as you can get to the internal array: type TRecLine = record ... end; PRecLine = ^TRecLine; procedure TForm1.FormCreate(Sender: TObject); var ... vList: TList<TRecLine>; vNode: PVirtualNode; ... begin ... (PRecLine(vList.PList)+0{Index}).VirtualNode := vNode; ... end; Do note that System.TArray<T> (not to be confused with System.Generics.Collections.TArray<T>) has supported insert and concatenation operations since XE7. See String-Like Operations Supported on Dynamic Arrays and Dynamic Arrays in Delphi XE7. VST doesn't care how you store your data, only that you should store a user-defined value inside each VST node so you can get back to your data given a node when needed. The best way to do that would be to store an actual TRecLine pointer in the node, where that pointer points to an item in your TList/TArray. But, if you are going to be adding/removing items from your TList/TArray, then that gets dangerous since you will be invalidating stored pointers. The next best option would be to store a TList/TArray index in each node instead, but then you have to worry about invalidating stored indexes. The next best option might be to store a unique ID number in each TRecLine, and then store those IDs in each node, and look for them in your TList/TArray when needed, but then you get into performance issues having to re-iterate the TList/TArray all the time. Certainly other options may exist, but what they are really depends on the design of your data and how you intend to use/manipulate it.
  16. Even though TList<T> uses a dynamic array internally, accessing its elements is very different than accessing the elements of TArray<T>. In your case, accessing vArray[n] is direct access to your record items, but vList[n] uses the TList<T>.Items property, which returns a copy of your record items, and you can't modify a copy inline the way you are trying to. You would have to manually save that copy to a variable and reassign it back to the TList<T> after modifying it, eg: procedure TForm1.FormCreate(Sender: TObject); var vArray: TArray<TRecLine>; vList: TList<TRecLine>; vNode: PVirtualNode; vRec: TRecLine; begin ... vArray[0].VirtualNode := vNode; // OK //vList[0].VirtualNode := vNode; // NOT OK! vRec := vList[0]; vRec.VirtualNode := vNode; vList[0] := vRec; ... end;
  17. Remy Lebeau

    My android app restarts on permission request

    It shouldn't. It has a Boolean return value, it should just return False on failure. @Yaron the code you have showed can be simplified a little bit: // Request permission PermissionsService.RequestPermissions( [FPermission_WRITE_EXTERNAL_STORAGE, FPermission_READ_EXTERNAL_STORAGE], procedure(const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>) var fmt: TFormatSettings; begin if (Length(AGrantResults) = 2) and (AGrantResults[0] = TPermissionStatus.Granted) and (AGrantResults[1] = TPermissionStatus.Granted) then begin { Save file } PIKAFolder := System.IOUtils.TPath.GetSharedPicturesPath; if PIKAFolder <> '' then PIKAFolder := TPath.Combine(PIKAFolder, clientGameName); if ForceDirectories(PIKAFolder) then begin fmt := TFormatSettings.Create; fmt.DateSeparator := '.'; fmt.TimeSeparator := '.'; PIKAFile := TPath.Combine(PIKAFolder, clientGameName + '_' + DateTimeToStr(Now, fmt) + saveImageFileExt); try MyImage.SaveToFile(PIKAFile); clientFadingMessage := strPictureSaved; except clientFadingMessage := strErrorSavingPicture; end; end else clientFadingMessage := strPictureFolderNotFound; end else clientFadingMessage := strErrorSavingPicture; end );
  18. Since you are developing for mobile. DO NOT run blocking tasks in the main UI thread, it must service only the UI. Long-running tasks must be done in the background instead. In your case, you have a LONG-running task (5.5 seconds) that blocks the UI until the task is finished. Tokyo let you get away with it using a hack. Rio doesn't. So just don't do it anymore. You need to change the code (even for Tokyo) to work with the main UI thread correctly, stay away from ProcessMessages() at all costs (especially now that Embarcadero has broken it and doesn't want to fix it). Do things asynchronously so flow returns to the main UI message loop in a timely manner (otherwise Android is likely to kill your app!). For example: procedure TForm2.Button1Click(Sender: TObject); var StartTimer: TProc; begin ProgressBar1.Max := 100; ProgressBar1.Value := 0; StartTimer := procedure begin Timer1.Interval := 500; // <-- can be set at design-time Timer1.Enabled := True; end; {$IF CompilerVersion < 33} // 10.2 Tokyo or earlier TabControl1.TabIndex := 1; StartTimer; {$ELSE} // 10.3 Rio or later TabControl1.SetActiveTabWithTransitionAsync(TabControl1.Tabs[1], TTabTransition.None, TTabTransitionDirection.Normal, StartTimer); {$IFEND} end; procedure TForm2.Timer1Timer(Sender: TObject); begin ProgressBar1.Value := ProgressBar1.Value + 10; if ProgressBar1.Value >= ProgressBar1.Max then Timer1.Enabled := False; end; Yes, sorry about that. Fixed above. What is there not to understand? The TTabControl is transitioned to the desired Tab and a timer is started, then flow is returned to the main UI message loop. When the timer fires after 500ms, the ProgressBar is incremented and flow is returned to the main UI message loop. The timer fires again after another 500ms, and again, each time returning to the main UI message loop. Eventually, the ProgressBar reaches its Max and the timer is stopped.
  19. Remy Lebeau

    Initialize local variables at declaration

    There is no technical reason that I can think of, Borland/CodeGear/Embarcadero could have expanded on the Pascal syntax, like FreePascal did, but they simply never got around to implementing it that way. Remember that FreePascal is its own Pascal compiler, it is not based on Delphi, so it is free to do things however it wants to. It has quite a few Pascal language niceties that Delphi doesn't. Even Generics and Unicode support are other areas where FreePascal differs greatly from Delphi. But, FreePascal does have special compatibility modes meant for migrating Delphi code to FreePascal. In those modes, it tries to closely emulate Delphi 7 or 2009, depending on whether Unicode is being emulated or not.
  20. Well, then you went down the wrong rabbit hole, and you need to start climbing your way out of it. Such as? You really need to stop using ProcessMessages(), and start using proper code designs. Even if you don't want to use timers/threads, there is always TThread.ForceQueue(), for instance. Anything that allows code flow to return to the main UI message loop naturally will be better than manually pumping the message queue artificially. Then file a bug report with Embarcadero. But don't litter your code with ProcessMessages() calls just to satisfy the framework. Find other alternatives. Stay away from ProcessMessages() whenever possible. That was true even in the old days of the VCL. More so now in the days of FMX on mobile.
  21. Remy Lebeau

    Initialize local variables at declaration

    Note that FreePascal supports initializing variables in "old style" declarations. A shame that Delphi does not also support that, seems like something that might have been easy for Embarcadero to add while they were messing around with how variable declarations work.
  22. Remy Lebeau

    HELP: Decoding of data stored in array of char - RFID tag's

    Sorry, I missed the part where you had changed the function declaration to make the UIDI parameter be a single Byte, which is of course wrong. I thought you were still passing that parameter by PChar instead. So, you need to EITHER do this: Function GetSystemInformation(...; UIDI : PByte; ...): LongInt; stdcall;external 'RR3036.dll'; ... Get_Designated_tag_info_status := GetSystemInformation(..., PByte(@(DSFIDAndUID[0].UIDasBytes)), ...); alternatively Get_Designated_tag_info_status := GetSystemInformation(..., @(DSFIDAndUID[0].UIDasBytes[0]), ...); Or this: Function GetSystemInformation(...; var UIDI : Byte; ...): LongInt; stdcall;external 'RR3036.dll'; ... Get_Designated_tag_info_status := GetSystemInformation(..., DSFIDAndUID[0].UIDasBytes[0], ...);
  23. This is definitely not the right way to write code. Avoid ProcessMessages() whenever possible. The example given can be replaced with a simple UI Timer instead, eg: ProgressBar1.Value : =0; ProgressBar1.Max := 100; Timer1.Enabled := True; ... procedure TForm1.Timer1Timer(Sender: TObject); begin ProgressBar1.Value := ProgressBar1.Value + 1; if ProgressBar1.Value >= ProgressBar1.Max then begin Timer1.Enabled := True; Exit; end; //... end; But, if your real project is using the ProgressBar to tracking status of actual tasks, those tasks should be done in the background, and updates to the UI synced with the main UI thread accordingly.
  24. The caller that Bill refers to is whatever code is calling your TProductResource.GetProduct() method. That code is responsible for freeing the returned array when done using it, eg: Arr := SomeProductResource.GetProduct(ClientId); ... Arr.Free; UNLESS - TBllProduct.Singlenton.RetrieveJSONArray() returns an array that is owned by the Singleton, in which case the code that calls your TProductResource.GetProduct() must not free the array at all.
  25. Remy Lebeau

    Is another process running as admin?

    The easiest way to detect this is to simply send the message and handle the case where SendMessage() fails with an ERROR_ACCESS_DENIED error: "When a message is blocked by UIPI the last error, retrieved with GetLastError, is set to 5 (access denied)." It is not, actually.
×