Jump to content

Remy Lebeau

Members
  • Content Count

    2943
  • Joined

  • Last visited

  • Days Won

    133

Everything posted by Remy Lebeau

  1. Then why are you fighting it? Why do you insist on giving each Form window its own Taskbar button? That is not the customary UI experience for Windows apps. It doesn't matter what you set the Owner to. FMX simply iterates the Screen.Forms list to discover which Forms it wants to minimize/restore. It doesn't care about the owner relationships between windows. VCL is a more strategic about those relationships. If you minimize the whole app, it remembers the order of windows, and tries to restore that order when the app is restored. You can't change a window's Owner using SetParent(). Normally, you would specify the Owner when calling the Win32 CreateWindow/Ex() API, and VCL allows you to do that by overriding CreateParams(), but FMX does not. The only way to change a window's Owner after creation is with the Win32 SetWindowLongPtr(GWL_HWNDPARENT) API. It sounds like a lot of extra work and headache for little gain. FMX does not have any equivalent to VCL's TApplication.ShowMainForm property. It forces the MainForm to be initially visible, there is no option to change that. Yes. For instance, in FMX.Forms.pas, TApplication.CreateMainForm() forces FMainForm.Visible=True. Are you referring to the ITaskbarList.AddTab() API? If you are going to go this route, then using ITaskbarList3.RegisterTab() instead may make more sense. Rather than giving each sub Form its own Taskbar button, you could instead attach all of the sub Forms to the Taskbar button of the MainForm. The user will still be able to see all of the sub Forms, but they won't clutter up the Taskbar. I see no such feature in the ITaskbarList... APIs for the main Taskbar buttons. Are you looking at the ITaskbarList3.ThumbBarAddButtons() API? That is meant for embedded a Thumbnail preview with buttons onto a single Taskbar button. I suppose you could have a separate Toolbar item for each sub Form. If you have a subclass on each sub Form then you wouldn't need to resort to this, as each subclass would already know which Wnd it calling it. Unless, are you referring to having only 1 subclass on the "main" Form, and then have the Taskbar toolbar buttons all send messages to that Form's Wnd? I suppose something like that could work. Correct. You would assign a unique ID to each toolbar item, and then the registered Wnd will receive a WM_COMMAND message containing the id of the clicked item. Button IDs cannot hold Window handles, so you will have to use something else. For example, each sub Form's index in the Screen.Forms list. Or, put the sub Forms in your own list, and then use those indexes instead.
  2. That is to be expected. FMX has code in its default WndProc which handles messages like WM_SYSCOMMAND and WM_WINDOWPOSCHANGED to minimize the whole app when the MainForm is minimized, and restore the whole app when the MainForm is restored. If you don't want this behavior, then you will have to subclass the MainForm after all and handle those messages yourself to skip FMX's logic. I think that is a side effect of removing the WS_EX_APPWINDOW style from the hidden ApplicationHWND window, so it can't appear on the Taskbar anymore. Also, TPlatformWin.MinimizeApp() forces the ApplicationHWND window to be visible while hiding all of the Form windows (to ensure the default Taskbar button stays visible while the MainForm is hidden). All the more reason you will have to block the default minimize code if you want different behavior. Frankly, I think you should stop trying to force your own Taskbar behavior, and just let the default behavior do its job normally.
  3. Remy Lebeau

    Documentation links on indyproject.org not working

    Thanks, I have updated the file on Indy's repo.
  4. Oh, OK. Setting Active=False does stop listening for new connections, but it ALSO immediately closes any existing client connections, too. Then it waits for everything to finish cleaning up before it exits back to your code. If you want existing clients to continue their work uninterrupted, then yes. Try something like this: ShuttingDown := True; IdHTTPServer1.StopListening; Timer1.Enabed := True; ... procedure TMyForm.Timer1Timer(Sender: TObject); begin with IdHTTPServer1.Contexts.LockList do try if Count > 0 then Exit; finally IdHTTPServer1.Contexts.UnlockList; end; Timer1.Enabled := False; IdHTTPServer1.Active := False; Application.Terminate; end; ... procedure TMyForm.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); begin if ShuttingDown then begin AResponseInfo.ResponseNo := 503; AResponse.CustomHeaders.Values['Retry-After'] := '5'; AResponseInfo.CloseConnection := True; Exit; end; // process normally ... if ShuttingDown then AResponseInfo.CloseConnection := True; end;
  5. Remy Lebeau

    Documentation links on indyproject.org not working

    You can try emailing it to me at "remy at lebeausoftware dot org". Or upload to DropBox or Google Drive or somewhere and I'll download it.
  6. Nevermind. I must not have been remembering things correctly, because I double-checked and while Form2 was working as expected, Form1 was fighting with FMX. So I simply disabled FMX's default taskbar button and forced a new Taskbar button on Form1, and then removed the subclasses from both windows, and now the focus shifting between the windows works correctly. Main Form unit MultiFormUnit1; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); {$IFDEF MSWINDOWS} protected procedure CreateHandle; override; {$ENDIF} private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation uses MultiFormUnit2 {$IFDEF MSWINDOWS} , Winapi.Windows , FMX.Platform.Win {$ENDIF} ; {$R *.fmx} procedure TForm1.Button1Click(Sender: TObject); begin TForm2.Create(Self).Show; end; {$IFDEF MSWINDOWS} procedure TForm1.CreateHandle; var Wnd: HWND; ExStyle: LONG_PTR; begin inherited; Wnd := FormToHWND(Self); ExStyle := GetWindowLongPtr(Wnd, GWL_EXSTYLE); SetWindowLongPtr(Wnd, GWL_EXSTYLE, ExStyle or WS_EX_APPWINDOW); end; {$ENDIF} procedure TForm1.FormCreate(Sender: TObject); {$IFDEF MSWINDOWS} var Wnd: HWND; ExStyle: LONG_PTR; {$ENDIF} begin {$IFDEF MSWINDOWS} Wnd := ApplicationHWND; ExStyle := GetWindowLongPtr(Wnd, GWL_EXSTYLE); SetWindowLongPtr(Wnd, GWL_EXSTYLE, (ExStyle and (not WS_EX_APPWINDOW)) or WS_EX_TOOLWINDOW); {$ENDIF} end; end. Child Form unit MultiFormUnit2; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs; type TForm2 = class(TForm) procedure FormClose(Sender: TObject; var Action: TCloseAction); private {$IFDEF MSWINDOWS} protected procedure CreateHandle; override; {$ENDIF} { Private declarations } public { Public declarations } end; var Form2: TForm2; implementation {$R *.fmx} {$IFDEF MSWINDOWS} uses Winapi.Windows, FMX.Platform.Win; procedure TForm2.CreateHandle; var Wnd: HWND; ExStyle: LONG_PTR; begin inherited; Wnd := FormToHWND(Self); ExStyle := GetWindowLongPtr(Wnd, GWL_EXSTYLE); SetWindowLongPtr(Wnd, GWL_EXSTYLE, ExStyle or WS_EX_APPWINDOW); end; {$ENDIF} procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction); begin Action := TCloseAction.caFree; end; end.
  7. How are you "updating code" for a running instance? You can StopListening() to let existing clients finish their work, but then you will have to manually monitor the client threads to detect when they have all terminated. Such as by monitoring the TIdHTTPServer.Contexts.Count until it falls to 0. Also, if you have KeepAlives enabled on the server, a client thread will not terminate until the client disconnects after their own KeepAlive timeout has elapsed. TIdHTTPServer does not implement KeepAlive timeouts on its end yet (work in progress), but you can use a ReadTimeout to fake it. Also, with KeepAlives enabled, clients could continue to send new requests as long as the KeepAlive doesn't elapse. If you want to prevent further requests, you would have to set a flag during shutdown and then check that flag in your OnCommand... events so you can close connections gracefully (ie, AResponseInfo.CloseConnection = True).
  8. Unless the connection is closed while the server is in the middle of exchanging an HTTP message, that is. Then the client will likely get a transmission error, and have to retry the request that aborted.
  9. Setting the Active property to False does a full server shutdown. It blocks the calling thread until all clients are fully disconnected and all threads have fully terminated. Yes. Why? The whole point of setting the Active property to False is to shut down the server completely. Meaning what, exactly? What are you actually trying to accomplish? StopListening() merely closes the listening sockets and their threads. It does not close any clients or stop their threads. Closing a listening socket does not close any clients that connected to it, as those are independent sockets once they have been accepted. StopListening() will stop accepting new clients without shutting down the server completely. But you can't transfer the existing clients to another server, if that is your goal.
  10. Then you are doing something different than me. When I run my test app, it does not behave the way you describe. That's fine, since the subclass proc is registered per-window, and uses the window it is called on. That is not what happens for me. Form1 becomes active. You have explained yourself just fine. That is not what happens for me. That is what happens for me.
  11. That does not happen in my test project. Here is the full code I used: Main Form: unit MultiFormUnit1; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); {$IFDEF MSWINDOWS} protected procedure CreateHandle; override; {$ENDIF} private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation uses MultiFormUnit2 {$IFDEF MSWINDOWS} , Winapi.Windows , Winapi.Messages , Winapi.CommCtrl , FMX.Platform.Win {$ENDIF} ; {$R *.fmx} procedure TForm1.Button1Click(Sender: TObject); begin TForm2.Create(Self).Show; end; {$IFDEF MSWINDOWS} function SubclassProc(Wnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM; uIdSubclass: UINT_PTR; dwRefData: DWORD_PTR): LRESULT; stdcall; begin case uMsg of WM_NCDESTROY: begin RemoveWindowSubclass(Wnd, SubclassProc, uIdSubclass); end; WM_ACTIVATE: begin if wParam in [WA_ACTIVE, WA_CLICKACTIVE] then begin SetFocus(Wnd); //SetForeGroundWindow(Wnd); //BringWindowToTop(Wnd); end; end; end; Result := DefSubclassProc(Wnd, uMsg, wParam, lParam); end; procedure TForm1.CreateHandle; var Wnd: HWND; ExStyle: LONG_PTR; begin inherited; Wnd := FormToHWND(Self); // a Taskbar button is already handled for the MainForm by FMX, don't force it here... //ExStyle := GetWindowLongPtr(Wnd, GWL_EXSTYLE); //SetWindowLong(Wnd, GWL_EXSTYLE, ExStyle or WS_EX_APPWINDOW); SetWindowSubclass(Wnd, SubclassProc, 1, DWORD_PTR(Self)); end; {$ENDIF} end. Child Form: unit MultiFormUnit2; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs; type TForm2 = class(TForm) procedure FormClose(Sender: TObject; var Action: TCloseAction); private {$IFDEF MSWINDOWS} protected procedure CreateHandle; override; {$ENDIF} { Private declarations } public { Public declarations } end; var Form2: TForm2; implementation {$R *.fmx} {$IFDEF MSWINDOWS} uses Winapi.Windows, Winapi.Messages, Winapi.CommCtrl, FMX.Platform.Win; function SubclassProc(Wnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM; uIdSubclass: UINT_PTR; dwRefData: DWORD_PTR): LRESULT; stdcall; begin case uMsg of WM_NCDESTROY: begin RemoveWindowSubclass(Wnd, SubclassProc, uIdSubclass); end; WM_ACTIVATE: begin if wParam in [WA_ACTIVE, WA_CLICKACTIVE] then begin SetFocus(Wnd); //SetForeGroundWindow(Wnd); //BringWindowToTop(Wnd); end; end; end; Result := DefSubclassProc(Wnd, uMsg, wParam, lParam); end; procedure TForm2.CreateHandle; var Wnd: HWND; ExStyle: LONG_PTR; begin inherited; Wnd := FormToHWND(Self); ExStyle := GetWindowLongPtr(Wnd, GWL_EXSTYLE); SetWindowLong(Wnd, GWL_EXSTYLE, ExStyle or WS_EX_APPWINDOW); SetWindowSubclass(Wnd, SubclassProc, 1, DWORD_PTR(Self)); end; {$ENDIF} procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction); begin Action := TCloseAction.caFree; end; end. Then you must be doing something wrong in how you setup your subclassing. A subclass is per-window. The only way the child form subclass would be called for the main form is if you registered the child form subclass for the main form window by mistake. That is not how it works. The MainFormOnTaskbar property controls whether the Taskbar button belongs to the hidden TApplication window or the TApplication.MainForm window. If TApplication owns the button, then it delegates various actions to the MainForm. That is not necessary.
  12. I assure you that it does. I tested the code before I posted it. That will not work in this situation, as HandleMessage() can see only messages that are posted to the main message queue, it will not see messages that are sent directly to the Form window. FMX does not dispatch non-queued messages in any way that the app's code can override. Which is why I suggest using a manual window subclass instead. Correct, because those messages do not go through the message queue. WM_KEY... messages go through the message queue. FMX does handle a few non-queued messages, such as WM_ACTIVATE. But clearly not in a way that behaves as you want. SetWindowSubclass() does not use GetLastError() for reporting errors. The only reason I can think of for SetWindowSubclass() to fail is if you are not using ComCtrl32.dll v6, or it has not been loaded yet. Does your project have an app manifest that enables ComCtl32.dll v6? It should be enabled by default. Also, you may need to call Winapi.CommCtrl.InitCommonControlsEx() at app startup, as the Winapi.CommCtrl unit loads SetWindowSubclass() dynamically and only if ComCtrl32.dll has already already loaded by a previous Winapi.CommCtrl call (specifically, it is loaded only by InitCommonControlsEx(), TaskDialogIndirect(), or TaskDialog()).
  13. Remy Lebeau

    Documentation links on indyproject.org not working

    I didn't create the zip file, so I can't help with that error. If someone wants to fix the zip, I'll replace it. The only link that is not working for me right now is the 10.1.5 doc archive. I have no control over other people's sites.
  14. Remy Lebeau

    Documentation links on indyproject.org not working

    That link work fine for me.
  15. Remy Lebeau

    Disable all controls on panel

    Indy has a TIdAntiFreeze component that processes pending UI messages during blocking socket operations. This would allow the WaitFor() call to process messages while waiting for the data to arrive. Though, that wouldn't address the case where the button click occurs after the last WaitFor() call had already exited but the panel hasn't been enabled yet.
  16. Remy Lebeau

    Documentation links on indyproject.org not working

    Also see: https://www.indyproject.org/2021/02/10/links-to-old-indy-website-pages-are-currently-broken/ I never had free time to fix it.
  17. Unlike VCL, FMX hides Win32 window messages from you. And, it ignores most window messages in general, it only handles a handful of select messages for its own purpose. Since you are forcing the Taskbar button to appear on a sub-Form, you will have to manually subclass the Form's window so you can handle window messages like WM_ACTIVATE, eg: type TSubToolBarForm = class(TForm) private ... {$IFDEF MSWINDOWS} protected procedure CreateHandle; override; {$ENDIF} ... end; ... {$IFDEF MSWINDOWS} uses Winapi.Windows, Winapi.Messages, Winapi.CommCtrl, FMX.Platform.Win; function SubToolBarSubclassProc(Wnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM; uIdSubclass: UINT_PTR; dwRefData: DWORD_PTR): LRESULT; stdcall; begin case uMsg of WM_NCDESTROY: begin RemoveWindowSubclass(Wnd, SubToolBarSubclassProc, uIdSubclass); end; WM_ACTIVATE: begin if wParam in [WA_ACTIVE, WA_CLICKACTIVE] then begin SetFocus(Wnd); //SetForeGroundWindow(Wnd); //BringWindowToTop(Wnd); end; end; end; Result := DefSubclassProc(Wnd, uMsg, wParam, lParam); end; procedure TSubToolBarForm.CreateHandle; var Wnd: HWND; ExStyle: LONG_PTR; begin inherited; Wnd := FormToHWND(Self); ExStyle := GetWindowLongPtr(Wnd, GWL_EXSTYLE); SetWindowLong(Wnd, GWL_EXSTYLE, ExStyle or WS_EX_APPWINDOW); SetWindowSubclass(Wnd, SubclassProc, 1, DWORD_PTR(Self)); end; {$ENDIF}
  18. By default, FMX does not create a separate Taskbar button for each Form. So, what are you doing manually to force this?
  19. No, they were merged into Indy's master code about a month after the release of 12.2. https://www.indyproject.org/2024/10/20/sasl-oauth-support-finally-added-to-master/
  20. Indy recently got a few new OAuth components that work with SMTP/POP3/IMAP4 SASL-based authentication. However, getting/refreshing a token is up to you to handle separately via each OAuth provider's API (via HTTP/REST, etc), but once you have a token then Indy can login with it. Geoffrey Smith's example predates those components, but he handles both REST and SASL portions. Indy doesn't have any components to handle the REST portion for you, but its TIdHTTP component can certain be used to send REST requests.
  21. Remy Lebeau

    REST Datasnap Server Accepting ECDH Ciphers

    It could happen if the app is running on Android 6+ where BoringSSL has replaced OpenSSL but might use the old OpenSSL .so filenames.
  22. Remy Lebeau

    Enable Discussions on github ?

    @Anders MelanderThanks I'll look into that. Indy has a lot of open issue tickets, I'm sure a good chunk of them could be discussions.
  23. Remy Lebeau

    REST Datasnap Server Accepting ECDH Ciphers

    Sorry I have no idea. I've never worked with Datasnap before and done know if/how it exposes access to configure Indy.
  24. Remy Lebeau

    Enable Discussions on github ?

    I went ahead and enabled Discussions on GitHub. We'll see how it works out. I'll still monitor this forum and others, of course.
  25. Remy Lebeau

    REST Datasnap Server Accepting ECDH Ciphers

    There are a few 3rd party GitHub repos that add OpenSSL 3.x/TLS 1.3 to Indy, including: https://github.com/MWASoftware/Indy.proposedUpdate https://github.com/JPeterMugaas/TaurusTLS
×