Jump to content
wqmeng

FMX with multi taskbar buttons, switch forms by clicking taskbar buttons and bring the clicked form to front?

Recommended Posts

Hello,

 

Now I working with a FMX app with some sub-forms, now need to switch the forms by clicking the taskbar button of them. The problem is when clicking the button, the main form will show up and the sub-form will be on the front of the main form, if click the main form  taskbar button, the sub-form still on the front of the main-form, So would like to know the taskbar button of which form was clicked and then bring the form to be front, let others be behind or hide with their pre-window states.

 

The problem is when click the taskbar button, I could not know which one get clicked, also do not know which form the taskbar button belong to.

 

Anyone knows how to know which taskbar button was clicked and the ownner form it is or any advice?

 

Thank you.

Edited by wqmeng

Share this post


Link to post

By default, FMX does not create a separate Taskbar button for each Form.  So, what are you doing manually to force this?

Share this post


Link to post

Hello, when create the form, add this will add the sub-form to the taskbar button.  But my problem is above need to know which button get clicked then bring the ownner form to be front.  This is useful, if you have 3 or 4 forms in one screen, some of them is maximnized, if you want to bring a behind form to front, you have to min the forms over it, Or click a  taskbar button to get it to be front if know how to.

Also if one of your form was hiden by other apps, then click the taskbar button could bring your form to be front.

 

Plus the taskbar button has the form's caption, it is much easy to find a hiden form by choose from the taskbar buttons.

 

procedure TSubToolBarForm.FormCreate(Sender: TObject);
var
  Wnd: HWND;
  ExStyle: LONG_PTR;
begin
  {$IFDEF MSWINDOWS}
  Wnd := FormToHWND(Self);
  ExStyle := GetWindowLongPtr(Wnd, GWL_EXSTYLE);
  SetWindowLong(Wnd, GWL_EXSTYLE, ExStyle or WS_EX_APPWINDOW);
  {$ENDIF}
end;

 

 

Thank you.

Edited by wqmeng

Share this post


Link to post

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}

 

  • Thanks 1

Share this post


Link to post

Hello  Remy Lebeau

 

I tried to SetWindowSubclass of the sub-from, but the function SubToolBarSubclassProc never get called. 

 

Also I try to replace the old TFMXApplicationService.AddPlatformService; By a new TFMXApplicationService class.

And in the class   

use the function to handle app messages.

function TFMXApplicationService.HandleMessage: Boolean;

 

To my suprise that the HandleMessage could not capture any  WM_ACTIVATE, WM_NCACTIVATE message from the OS,  If I postmessage inside the app, it will invoke.  But other messages such as WM_KEYDOWN will process correctly by HandleMessage.

 

Maybe FMX handles the messages and intercepts some messages such as WM_ACTIVATE, WM_NCACTIVATE somewhere else?

 

Thank you.

Edited by wqmeng

Share this post


Link to post

Seems the code always return fail result. Not sure why.

 

    if not SetWindowSubclass(Wnd, SubToolBarSubclassProc, 1, DWORD_PTR(Self)) then begin
      LMsg := 'SetWindowSubclass failed! Error code: ' + IntToStr(GetLastError());
    end;

and the error code always 0.

Share this post


Link to post
2 hours ago, wqmeng said:

I tried to SetWindowSubclass of the sub-from, but the function SubToolBarSubclassProc never get called.

I assure you that it does.  I tested the code before I posted it.

2 hours ago, wqmeng said:

Also I try to replace the old TFMXApplicationService.AddPlatformService; By a new TFMXApplicationService class.

And in the class   

use the function to handle app messages.

function TFMXApplicationService.HandleMessage: Boolean;

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.

2 hours ago, wqmeng said:

To my suprise that the HandleMessage could not capture any  WM_ACTIVATE, WM_NCACTIVATE message from the OS,

Correct, because those messages do not go through the message queue.

2 hours ago, wqmeng said:

If I postmessage inside the app, it will invoke.  But other messages such as WM_KEYDOWN will process correctly by HandleMessage.

WM_KEY... messages go through the message queue.

2 hours ago, wqmeng said:

Maybe FMX handles the messages and intercepts some messages such as WM_ACTIVATE, WM_NCACTIVATE somewhere else?

FMX does handle a few non-queued messages, such as WM_ACTIVATE. But clearly not in a way that behaves as you want.

58 minutes ago, wqmeng said:

Seems the code always return fail result. Not sure why.

 


    if not SetWindowSubclass(Wnd, SubToolBarSubclassProc, 1, DWORD_PTR(Self)) then begin
      LMsg := 'SetWindowSubclass failed! Error code: ' + IntToStr(GetLastError());
    end;

and the error code always 0.

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()).

Edited by Remy Lebeau

Share this post


Link to post

Hello Remy Lebeau

 

After InitCommonControlsEx, the SubToolBarSubclassProc works now,  Thank you so much.

 

var
  icc: TInitCommonControlsEx;

  icc.dwSize := SizeOf(icc);
  icc.dwICC := ICC_INTERNET_CLASSES or ICC_USEREX_CLASSES or ICC_DATE_CLASSES;
  InitCommonControlsEx(icc);

 

Share this post


Link to post

There is still a problem when clicking the main form taskbar button, if you have already activated the child form, the application will activate the child form instead of the main form. And the main form subclass procedure will not be called but the child form subclass procedure. It seems that the Wnd of the main form taskbar button is not always the same as the main form, it will change to the active child form so that when you click the main form taskbar button, the active child form will be active. On VCL forms, it seems that it can be changed by Application.MainFormOnTaskbar := False, but FMX does not have it. So there should be something in UpdateApplicationHwnd or something else change the main form handle of the taskbar button (main) to be the active child form handle. If we can get the title of the taskbar button that was just clicked intead of the Wnd, maybe we can switch forms more flexibly by the title.

 

Thank you.

Edited by wqmeng

Share this post


Link to post
14 hours ago, wqmeng said:

There is still a problem when clicking the main form taskbar button, if you have already activated the child form, the application will activate the child form instead of the main form.

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.
Quote

And the main form subclass procedure will not be called but the child form subclass procedure.

 

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. 

Quote

It seems that the Wnd of the main form taskbar button is not always the same as the main form, it will change to the active child form so that when you click the main form taskbar button, the active child form will be active.

That is not how it works.

Quote

On VCL forms, it seems that it can be changed by Application.MainFormOnTaskbar := False, but FMX does not have it.

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.

Quote

So there should be something in UpdateApplicationHwnd or something else change the main form handle of the taskbar button (main) to be the active child form handle.

That is not necessary. 

Edited by Remy Lebeau

Share this post


Link to post

Hello Remy, 

 

I test your codes, it is the same behavior as my, in my test app, the main form and  the subform both use the same subclassproc in the unit2, I do not copy it to unit1.

I would like to click the taskbar button of form1, then get the form1 to be front and active. If created a form2, and the form2 is already active. Then when you click the taskbar button of form1, You will see that the form1 does not active, the form2 is still the active form. 

 

Maybe I do not explain clearly? Thank you very much.

 

1) click mouse button on a form2, the form2 is active form. form1(main) is not active.

2) click the form1 taskbar button

The form1 still not the active form, the form2 always keep active.

 

In my app, I just want the result

Click form1 taskbar button, form1 active and to the front.

Click form2/form3 taskbar button, form2/form3 active and to the front.

 

Now it works for form2, form3, but not work for form1 when the form1 is not active.

 

Thank you very much.

Share this post


Link to post

You see, if the form1 is not  active, after you click the taskbar button form1, the form2 is still the active form, you have to use mouse to click the form1 to get it active.

My codes attached.

Thank you so much.

TestWindowSubClassProc.7z

Edited by wqmeng

Share this post


Link to post
7 hours ago, wqmeng said:

I test your codes, it is the same behavior as my

Then you are doing something different than me. When I run my test app, it does not behave the way you describe. 

7 hours ago, wqmeng said:

in my test app, the main form and  the subform both use the same subclassproc in the unit2, I do not copy it to unit1.

That's fine, since the subclass proc is registered per-window, and uses the window it is called on. 

7 hours ago, wqmeng said:

I would like to click the taskbar button of form1, then get the form1 to be front and active. If created a form2, and the form2 is already active. Then when you click the taskbar button of form1, You will see that the form1 does not active, the form2 is still the active form.

That is not what happens for me. Form1 becomes active.

7 hours ago, wqmeng said:

Maybe I do not explain clearly?

You have explained yourself just fine. 

7 hours ago, wqmeng said:

1) click mouse button on a form2, the form2 is active form. form1(main) is not active.

2) click the form1 taskbar button

The form1 still not the active form, the form2 always keep active.

That is not what happens for me.

7 hours ago, wqmeng said:

In my app, I just want the result

Click form1 taskbar button, form1 active and to the front.

Click form2/form3 taskbar button, form2/form3 active and to the front.

That is what happens for me. 

Share this post


Link to post

Hi Remy,

So this is weird, did you change any property of the form or did I miss something in the application configuration? Where do you call InitCommonControlsEx? Can you attach your complete test project here?

I have tested my code and the code you posted here. If form2 is active, clicking the form1 taskbar button will still keep form2 active, because I think in FMX, if the main form is hidden by a child form, then FMX should consider that the child form should keep the focus when only one taskbar button for all the FMX forms created and get clicked. This should be the intention of FMX design. Since we added multiple taskbar buttons, we want each button to activate its own form, which will conflict with the design of FMX and therefore cause problems. But as you explained above, it really confuses me.

Thank you very much.

Share this post


Link to post

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.

 

  • Thanks 1

Share this post


Link to post

Hello

Hide the default app taskbar button do active the forms by clicking their taskbar button. While I find that when click the min button to minimize the form1 window, it will hide all the taskbar buttons. And a window's title bar will shows on desktop, zero width and height, this is the default main zero window managed by FMX?

 

Thank you.

Edited by wqmeng

Share this post


Link to post
1 hour ago, wqmeng said:

While I find that when click the min button to minimize the form1 window, it will hide all the taskbar buttons.

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.

1 hour ago, wqmeng said:

And a window's title bar will shows on desktop, zero width and height, this is the default main zero window managed by FMX?

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.

Share this post


Link to post

Hello Remy

 

Yes, it is better to let the fmx does as it is.

 

What I find more information about this matter,

1, Find that when VCL, there could setup the sub-form's WndParent to be desktop, that will keep the sub-form as a seperate form as the main form as? So that when delphi restore the main form  after clicking the taskbar button, it will not find any child-forms and then active itself instead find a child-form and then restore the child-form if it is active before?

So I tried to but not work as VCL app, not sure why.

procedure TForm2.FormCreate(Sender: TObject);
begin
  Winapi.Windows.SetParent(FormToHWND(Self), GetDesktopWindow);
end;

procedure TForm2.FormShow(Sender: TObject);
begin
  Winapi.Windows.SetParent(FormToHWND(Self), GetDesktopWindow);
end;

 

2, If the sub-form works as we expect, so we could hide the main-form and its taskbar button at the whole app life-time? And use the sub-form1 to act as a fake main-form do what the main-form's work? It sound be simple and easy to do just add a new form, and set the new form as the main-form, and min/hide it when the fmx app is running.

 

I tried to create form1(hide), form2(fake-main, when close the app will terminate too), form3.

From my test currently, the prolem is the when fmx app start, the form1 will flash before hide it.  From another post, seems it is need to modify the FMX source code to avoid the flash of main form.  

 

I do not test this on my app yet so do not know if it will cause any other side effect yet. As my app manage other form behavior such as min max fullscreen restore, but I think it should work while need to adjust my codes to work with multi sub-forms.

 

3, Another find before 2 is the itaskbar interface from win api, In the api, seems it could set a button id, and pass the id back to the message with the  LParam, So that we could use our form's wnd as the id and  then when the subclassproc get the message, check that LParam and then setfocus on the correct form.  I do not know if we could pass the LParam to the setwindowlongptr or some other api could do that, I think there should has. So we could use the api instead itaskbar inerface for the same thing.

I do not test this yet.

 

Your posts here are very very helpful.

 

Thank you so much.

Edited by wqmeng

Share this post


Link to post

Tried 2, also has problem, If form 3 or other sub-form was hiden by other apps.  Click a taskbar button will bring all the hiden sub-form to be front than other app, though the sub-forms will not get the focus.

 

3 The id seems is the tumbbutton id on the taskbar button pre-view window, not the taskbar button id.

Share this post


Link to post
12 hours ago, wqmeng said:

Yes, it is better to let the fmx does as it is.

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.

Quote

1, Find that when VCL, there could setup the sub-form's WndParent to be desktop, that will keep the sub-form as a seperate form as the main form as? So that when delphi restore the main form  after clicking the taskbar button, it will not find any child-forms and then active itself instead find a child-form and then restore the child-form if it is active before?

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.

Quote

So I tried to but not work as VCL app, not sure why.


procedure TForm2.FormCreate(Sender: TObject);
begin
  Winapi.Windows.SetParent(FormToHWND(Self), GetDesktopWindow);
end;

procedure TForm2.FormShow(Sender: TObject);
begin
  Winapi.Windows.SetParent(FormToHWND(Self), GetDesktopWindow);
end;

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.

Quote

2, If the sub-form works as we expect, so we could hide the main-form and its taskbar button at the whole app life-time? And use the sub-form1 to act as a fake main-form do what the main-form's work? It sound be simple and easy to do just add a new form, and set the new form as the main-form, and min/hide it when the fmx app is running.

It sounds like a lot of extra work and headache for little gain.

Quote

I tried to create form1(hide), form2(fake-main, when close the app will terminate too), form3.

From my test currently, the prolem is the when fmx app start, the form1 will flash before hide it.

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.

Quote

From another post, seems it is need to modify the FMX source code to avoid the flash of main form.  

Yes.  For instance, in FMX.Forms.pas, TApplication.CreateMainForm() forces FMainForm.Visible=True.

Quote

3, Another find before 2 is the itaskbar interface from win api,

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.

Quote

In the api, seems it could set a button id, and pass the id back to the message with the  LParam,

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.

Quote

So that we could use our form's wnd as the id and  then when the subclassproc get the message,

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.

6 hours ago, wqmeng said:

3 The id seems is the tumbbutton id on the taskbar button pre-view window, not the taskbar button id.

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.

Edited by Remy Lebeau

Share this post


Link to post

Hello Remy

 

I tried hooking the left mouse button and capturing the thumbnail preview window title, but after trying all possible methods, it is still difficult to get the window text. Use Wnd := WindowFromPoint(MouseInfo^.pt); to try to get the window, then GetWindowText, it is '', then try to use CoCUIAutomation to try to get it, hr := UIAutomation.ElementFromPoint(Point, Element); The element can be obtained with the class name of 'TaskListThumbnailWnd' but no name, try to get the child elements from it, can only get the child elements through Element.FindAll(TreeScope_Children, Condition, children); The class name is 'TaskListOverlayWnd' and there are no other child elements under this child element, and there is no name.

I also tried Spy++ and Inspect to find more details, but both of them can't capture the thumbnail preview window well, because it is a pop-up window, when using spy++ you can't get the thumbnail preview window pop-up, if using Inspect, when the thumbnail preview window pops up, it will hide the Inspect window, if you click it, then Inspect will show the result of restoring the window after clicking.

I will try more if I can.

So if the above function can't get the title of the window, Using a fake main window and try to subclassproc to override WINPROC, maybe we can do it.

Thank you very much.

Share this post


Link to post
9 hours ago, wqmeng said:

I tried hooking the left mouse button and capturing the thumbnail preview window title, but after trying all possible methods, it is still difficult to get the window text. Use Wnd := WindowFromPoint(MouseInfo^.pt); to try to get the window, then GetWindowText, it is '',

The coordinates in a mouse event are relative to the client area of the window that is being clicked on.  On the other hand, WindowFromPoint() expects screen coordinates instead.  Are you accounting for that?  But, I don't see how getting the HWND of the preview window is going to help you, since you need the HWND of a particular Form instead.  Which goes back to my earlier suggestion to register the preview window's toolbar buttons in such a way that when you receive WM_COMMAND from it you can determine the Form that the event represents.

9 hours ago, wqmeng said:

then try to use CoCUIAutomation to try to get it, hr := UIAutomation.ElementFromPoint(Point, Element); The element can be obtained with the class name of 'TaskListThumbnailWnd' but no name, try to get the child elements from it, can only get the child elements through Element.FindAll(TreeScope_Children, Condition, children); The class name is 'TaskListOverlayWnd' and there are no other child elements under this child element, and there is no name.

Now you are just delving into OS internals and making this task vastly more complicated on yourself.

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×