Jump to content

Leaderboard


Popular Content

Showing content with the highest reputation on 03/24/23 in all areas

  1. Uwe Raabe

    Compiling Delphi Apps on Azure DevOps Pileline

    Perhaps this may help a bit: Azure DevOps and Delphi – Build Agents
  2. The question is: why do you want your app to be able to do this? As Dalija mentioned, users can easily go to the home screen anyway. Even if you were able to achieve it, if you submit it to the App Store it's highly likely that the app will be rejected. I suggest reading this: https://developer.apple.com/library/archive/qa/qa1561/_index.html
  3. Uwe Raabe

    String literals more then 255 chars

    Because they prohibit the formatter from concatenating the lines.
  4. Why? iOS users know how to move application to the background using Home button. Such functionality does not belong in the iOS application. Just remove the Exit button or hide it on the iOS and avoid calling problematic code on iOS with compiler directives.
  5. Anders Melander

    Not Threadsafe??

    I think he would like for the application not to appear "hung" while it's waiting for the database task to complete.
  6. Lajos Juhász

    Set Parent of Component to Application.MainForm from Unit

    A simple debug would tell you the reason. At the moment of Application.MainForm.Formcreate the Application.MainForm property is nil.
  7. loki5100

    Delphi 11.2 and MacOS SDK 15.5

    Hi I have EXACTLY the same problem 😞 I use also a macOS virtual machine and the connection between Delphi and PAServer is very very slow exactly like you. I even try to reinstall the macos from scratch, nothing change. but on some other macos computer it's work fine. I quite sure the problem is located in PAServer I have just created this jira : https://quality.embarcadero.com/browse/RSP-41260 please add a note to show that it's not only connected to me
  8. Dave Nottage

    Delphi 11.2 and MacOS SDK 15.5

    I've seen another developer have this issue - he has talked with an engineer from Embarcadero and they are yet to find what the problem is. This is something new. Best I can suggest is to report the issues to https://quality.embarcadero.com/
  9. Patrick PREMARTIN

    Which is the best book to learn Delphi

    Kevin Bond's book is a progressive course used in schools in South Africa. It has a progressive approach with lots of examples. It was used for last summer learning Delphi bootcamp :
  10. Fr0sT.Brutal

    Not Threadsafe??

    Just launch the thread and let it work, catch the event when it's done.
  11. Lars Fosdal

    Compiling Delphi Apps on Azure DevOps Pileline

    I'd be interested in reading a summary of your findings!
  12. Anders Melander

    Not Threadsafe??

    Yes, if your end is to make a mess of things, then threads will work wonders for you.
  13. Anders Melander

    Not Threadsafe??

    I'm guessing the purpose of that code block is to keep the application responsive while the tread is executing, right? ProcessMessages is very rarely the solution - for anything. It's fine in quick and dirty, test code, but it doesn't belong in production code. While there is no problem with ProcessMessages in regard to threads in your code (you're not calling it from a thread), it often makes your UI vulnerable to undesired reentrancy. For example, let's say you have a button on a form with the following OnClick handler: procedure TMyForm.MyButtonClick(Sender: TObject); begin // Increment a counter to show we're busy Tag := Tag + 1; Caption := IntToStr(Tag); // Emulate a task that takes a long time for var i := 0 to 1000 do begin // Busy, busy... Sleep(100); // ...But stay "reponsive"... Application.ProcessMessages; end; // We're done; Decrement the counter Tag := Tag - 1; Caption := IntToStr(Tag); end; The problem here is that unless you take extra steps to prevent it, the user can just keep pressing the button (you'll see the counter keeps incrementing); You've created a recursion through ProcessMessages. The easiest way to prevent that is the disable the UI (e.g. Form.Enabled := False), but the best way is to not use ProcessMessages in the first place. Have you tried pressing the Windows close button (or just [Alt]+[F4]) while that code is executing? I'll bet that doesn't end well. You can create a message loop that processes selected messages instead. For example WM_PAINT and a few others are safe to handle. Here's one I've used in an application I worked on. It is called while doing work that updates a progress bar (with an Abort button). There's a bunch of stuff that only makes in that particular application, but I'm sure you get the meaning. type TFormRedacted = class(TForm) private FProgressStart: TStopWatch; FProgressThrottle: TStopWatch; FLastMessagePump: TStopwatch; FProgressEnableAbort: boolean; FProgressAborted: boolean; ... end; procedure TFormRedacted.ProcessProgressMessages(Force: boolean); var Msg: TMsg; begin if (not Force) and (FLastMessagePump.IsRunning) and (FLastMessagePump.ElapsedMilliseconds < MessagePumpRate * ProgressUpdateFactor) then exit; FLastMessagePump.Reset; var ProgressHandle := ButtonProgressAbort.Handle; // Indicate to madExcept freeze detection that we're not frozen FreezeDetection.IndicateApplicationNotFrozen; try // Allow threads to synchronize to avoid deadlock (e.g. busy loop showing progress waiting for thread to complete (e.g. spell check dictionary load)). CheckSynchronize; Msg.message := 0; try // Look for Escape key pressed. if (FProgressEnableAbort) and (Application.Active) and (GetAsyncKeyState(VK_ESCAPE) <> 0) then begin // Wait for Escape key to be released again. while (GetAsyncKeyState(VK_ESCAPE) <> 0) do Sleep(1); // Clear message queue of keyboard messages while (PeekMessage(Msg, 0, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) do begin if (Msg.message = WM_QUIT) then exit; end; PromptAbortProgress; // Wait for Escape key to be released so dismissing the abort prompt // dialog with escape doesn't trigger a new prompt. while (GetAsyncKeyState(VK_ESCAPE) <> 0) do Sleep(1); // Clear message queue of keyboard messages while (PeekMessage(Msg, 0, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) do begin if (Msg.message = WM_QUIT) then exit; end; end; // Process mouse move for all windows so hot tracking works. // Don't process mouse movement if a mouse key is down. // This tries to avoid recursions caused by scrollbar movement causing work that // ends up calling this method. while (PeekMessage(Msg, 0, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_NOREMOVE)) and (GetKeyState(VK_LBUTTON) = 0) do begin PeekMessage(Msg, 0, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE); if (Msg.message = WM_QUIT) then exit; DispatchMessage(Msg); end; // Process mouse hover/enter/exit messages for all windows so button state will be updated while (PeekMessage(Msg, 0, WM_NCMOUSEHOVER, WM_MOUSELEAVE, PM_REMOVE)) do begin if (Msg.message = WM_QUIT) then exit; DispatchMessage(Msg); end; // Process timer message for all windows so animation works - THIS IS DANGEROUS SINCE ALL TIMERS WILL BE PROCESSED while (PeekMessage(Msg, 0, WM_TIMER, WM_TIMER, PM_REMOVE)) do begin if (Msg.message = WM_QUIT) then exit; DispatchMessage(Msg); end; // Process mouse messages for button so user can press Stop button while (PeekMessage(Msg, ProgressHandle, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE)) do begin if (Msg.message = WM_QUIT) then exit; DispatchMessage(Msg); end; // Process cursor update messages for all windows so cursor stays responsive while (PeekMessage(Msg, 0, WM_SETCURSOR, WM_SETCURSOR, PM_REMOVE)) do begin if (Msg.message = WM_QUIT) then exit; DispatchMessage(Msg); end; // Process progress bar messages - This includes WM_TIMER and WM_PAINT used for progress bar animation while PeekMessage(Msg, Progress.Handle, 0, 0, PM_REMOVE) do begin if (Msg.message = WM_QUIT) then exit; DispatchMessage(Msg); end; // Process paint messages for all windows so UI can repaint itself while PeekMessage(Msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE) or PeekMessage(Msg, 0, WM_ERASEBKGND, WM_ERASEBKGND, PM_REMOVE) or PeekMessage(Msg, Handle, DXM_SKINS_POSTREDRAW, DXM_SKINS_POSTREDRAW, PM_REMOVE) or PeekMessage(Msg, 0, WM_PRINT, WM_PRINT, PM_REMOVE) or PeekMessage(Msg, 0, WM_PRINTCLIENT, WM_PRINTCLIENT, PM_REMOVE) do begin if (Msg.message = WM_QUIT) then exit; DispatchMessage(Msg); end; PeekMessage(Msg, 0, WM_NULL, WM_NULL, PM_NOREMOVE); // Avoid window ghosting due to unresponsiveness on Vista+ finally if (Msg.message = WM_QUIT) then begin PostQuitMessage(Msg.wParam); FProgressAborted := True; end; end; finally FLastMessagePump.Start; end; if (FProgressAborted) then AbortProgress; end; Here's another version of it: https://bitbucket.org/anders_melander/better-translation-manager/src/f96e7dcdba22667560178d32aebb5137484107f0/Source/amProgress.pas#lines-444
  14. Anders Melander

    Drawing text with GDI

    https://github.com/graphics32/graphics32/tree/master/Examples/Drawing/TextVPR
  15. Generally any kind of stream should be suitable for using in background threads, but also they cannot be simultaneously accessed and shared between threads. Just look at it logically - how can you use stream that holds a state (if nothing else than current position that changes as you read and write) be thread-safe? While you could in theory protect simultaneous access with locks and share stream across threads, you wouldn't gain much from that as stream would have to be locked and accessible to one thread at the time. If you think you could speed some operation by sharing stream and using is in parallel, you will only slow the whole process down, not speed it up. When using TCompressionStream and TDecompressionStream in background thread, OpProgress event handler will run in the context of background thread and you need to take that into account if you want to show progress in the UI and synchronize code.
  16. Uwe Raabe

    String literals more then 255 chars

    Actually I use these quite often. In the case of your SQL, which could as well come from the FireDAC query editor, I just copy the whole code, create a string array constant with a simple template, multi-paste the SQL and format the results: const cArr: TArray<string> = [ // 'SELECT', // 'DATE_FORMAT(co.order_date, ''%Y-%m'') AS order_month,', // 'DATE_FORMAT(co.order_date, ''%Y-%m-%d'') AS order_day,', // 'COUNT(DISTINCT co.order_id) AS num_orders,', // 'COUNT(ol.book_id) AS num_books,', // 'SUM(ol.price) AS total_price,', // 'SUM(COUNT(ol.book_id)) OVER (', // ' ORDER BY DATE_FORMAT(co.order_date, ''%Y-%m-%d'')', // ') AS running_total_num_books', // 'FROM cust_order co', // 'INNER JOIN order_line ol ON co.order_id = ol.order_id', // 'GROUP BY ', // ' DATE_FORMAT(co.order_date, ''%Y-%m''),', // ' DATE_FORMAT(co.order_date, ''%Y-%m-%d'')', // 'ORDER BY co.order_date ASC;', // '']; procedure UseSQL; begin var qry := TFDQuery.Create(nil); try qry.SQL.AddStrings(cArr); // ... finally qry.Free; end; end; The same scheme also works for JSON or XML. Interesting, that you bring up the SQL text, which is neatly split into multiple lines - probably for better readability. Despite allowing string constants with more than 255 characters in one line, it would be more helpful to have string constants over multiple lines preserving linefeeds. Then it wouldn't even matter if the lines are limited to 255 characters each, although I expect this limit being lifted anyway whenever such a feature will be implemented.
  17. Btw, you might want to check out the Image32Background and PanAndZoom examples in the image32_background branch. They demonstrate how to do the following without any custom code (they are now built-in optional features in TImage32/TImgView32): and panning & animated (using cubic tweening) exponential zoom with pivot point:
  18. I would just store a reference to your object and the object owner in the layer and then notify the owner when the layer is moved. Something like this: type TObjectLayer = class; TObjectLayerNotification = ( olnDestroy, // Subscribers should remove reference to layer olnPosition // Layer has moved ); IObjectLayerNotification = interface ['{C5715B62-6D20-4BEE-841A-A898AA67D6F7}'] procedure ObjectLayerNotification(ALayer: TObjectLayer; ANotification: TObjectLayerNotification); end; TObjectLayer = class(TIndirectBitmapLayer) private FSubscribers: TList<IObjectLayerNotification>; FObjectID: TSomeType; protected procedure Notify(ANotification: IObjectLayerNotification); procedure DoSetLocation(const NewLocation: TFloatRect); override; public destructor Destroy; override; procedure Subscribe(const ASubscriber: IObjectLayerNotification); procedure Unsubscribe(const ASubscriber: IObjectLayerNotification); property ObjectID: TSomeType read FObjectID write FObjectID; end; destructor TObjectLayer.Destroy; begin Notify(olnDestroy); FSubscribers.Free; inherited; end; procedure TObjectLayer.DoSetLocation(const NewLocation: TFloatRect); begin inherited DoSetLocation(NewLocation); Notify(olnPosition); end; procedure TObjectLayer.Notify(ANotification: IObjectLayerNotification); begin if (FSubscribers = nil) then exit; for var Subscriber in FSubscribers.ToArray do // ToArray for stability Subscriber.ObjectLayerNotification(Self, ANotification); end; procedure TObjectLayer.Subscribe(const ASubscriber: IObjectLayerNotification); begin if (FSubscribers = nil) then FSubscribers := TList<IObjectLayerNotification>.Create; FSubscribers.Add(ASubscriber); end; procedure TObjectLayer.Unsubscribe(const ASubscriber: IObjectLayerNotification); begin if (FSubscribers <> nil) then FSubscribers.Remove(ASubscriber); end; The object ID is stored in the ObjectID property (change the type to whatever type you use an an ID). The owner must implement the IObjectLayerNotification interface and call Subscribe on the layer to get notifications. This is a pretty standard observer pattern. If you are using the TRubberbandLayer then there's a OnConstrain event where you can examine and modify the move/resize. If you are doing move/resize with some other method then I'll need some information about that. I usually implement rubber-band selection via the TImgView32 mouse events (i.e. I'm not using a layer). So I manage the selection-in-progress state (usually just the mouse-down position) and any current selection on the form. In the mouse-up handler, I create a rectangle polygon from the mouse-down pos and the mouse-up pos and then either replace the current selection with the new one or merge the two (union), depending on the keyboard shift state. The selection is stored as a polygon. You can also use a polypolygon depending on your needs. The selection is drawn by a custom layer (visible only when there actually is a selection). The layer has a copy of the polygon and draws a marching ants (btw, try googling "marching ants") animated line using a stipple pattern and a timer. Here's the Paint method of the layer: procedure TSelectionLayer.Paint(Buffer: TBitmap32); begin try // Update local copy of selection polygon UpdateCache; if (BitmapEditor.HasSelection) then begin Buffer.SetStipple(SelectionStipple); Buffer.StippleCounter := FSelectionStippleCounter; Buffer.StippleStep := 1; PolylineXSP(Buffer, FCachedSelection, not SelectionInProgress); end; except // Prevent AV flood due to repaint Visible := False; raise; end; end; and the setup and control of the stipple pattern: constructor TSelectionLayer.Create(ABitmapEditor: TBitmapEditor); begin inherited Create(ABitmapEditor); FCacheValid := False; FTimer := TTimer.Create(nil); FTimer.Interval := 50; FTimer.OnTimer := OnTimer; FTimer.Enabled := False; CreateStipple(FSelectionStipple, $F0F0F0F0, clBlack32, clWhite32); end; procedure TSelectionLayer.OnTimer(Sender: TObject); begin // TODO : Remove dependency on Forms unit if (not Application.Active) then exit; FSelectionStippleCounter := FSelectionStippleCounter+1.5; if (FSelectionStippleCounter >= Length(SelectionStipple)) then FSelectionStippleCounter := FSelectionStippleCounter - Length(SelectionStipple); Update(FBitmapRect); end;
  19. The code was copied from TCustomBitmapLayer in GR32_Layers, so you can find it there: type TImage32Access = class(TCustomImage32); Stretchtransfer is declared in GR32_Resamplers. I'm considering making TIndirectBitmapLayer the base class for TCustomBitmapLayer.
  20. Lars Fosdal

    Delphi job

    Quick, Good, Cheap - you can only pick two.
×