[Windows] Form in Taskleiste

Für Fehler in Lazarus, um diese von anderen verifizieren zu lassen.
Antworten
compmgmt
Beiträge: 351
Registriert: Mi 25. Nov 2015, 17:06
OS, Lazarus, FPC: Win 10 Pro | Lazarus 1.8.2 | FPC 3.0.4
CPU-Target: i386 + x86_64
Wohnort: in der Nähe von Stuttgart
Kontaktdaten:

[Windows] Form in Taskleiste

Beitrag von compmgmt »

Guten Abend allerseits,

da ich zwei Monitore besitze ist mir schon öfter aufgefallen dass bei allen meinen bisherigen GUI-Projekten die Anwendung/Main Form in der Taskleiste immer auf dem ersten Monitor angezeigt wird, auch wenn
1. die Form vom ersten auf den zweiten Monitor verschoben wird.
2. die Form auf dem zweiten Monitor erscheint (bspw. durch poDesigned).

Mit der WinApi Funktion MonitorFromWindow und dem Parameter MONITOR_DEFAULTTONEAREST erkennt windows korrekt dass sich die Form auf einem andern Monitor befindet (was ja auch Sinn macht, weil Windows alle Fenster managed).

Wie es sich auf andere Systeme bzw grafische Oberflächen auswirkt ist mir nicht bekannt.

Habt ihr ähnliche Erfahrungen oder ist das schon bekannt?

FPC 3.0.4 32-Bit
Laz 1.8.2 32-Bit
Win 10 Pro 64-Bit

Code: Alles auswählen

InitiateSystemShutdownExA(nil, nil, 0, true, false, $0005000F);
Have fun with this snippet ;)

sstvmaster
Beiträge: 575
Registriert: Sa 22. Okt 2016, 23:12
OS, Lazarus, FPC: W10, L 2.2.6
CPU-Target: 32+64bit
Wohnort: Dresden

Re: [Windows] Form in Taskleiste

Beitrag von sstvmaster »

Hi, ist mir selber noch nicht aufgefallen bzw. habe ich noch nicht darauf geachtet. Probieren kann ich es aber erst wenn ich wieder auf Arbeit bin, da ich hier zuhause nur einen habe.

Habe mal gesucht wo die API in Lazarus steht, dazu habe ich folgendes gefunden:

In der customform.inc gibt es eine Funktion "GetMonitor":

Auszug:

Code: Alles auswählen

function TCustomForm.GetMonitor: TMonitor;
var
  ParentForm: TCustomForm;
begin
  if Assigned(Parent) then
  begin
    ParentForm := GetParentForm(Self);
    if Assigned(ParentForm) then
      Result := ParentForm.Monitor
    else
      Result := nil;
  end else
  begin
    if HandleAllocated then
      Result := Screen.MonitorFromWindow(Handle, mdNearest)
    else
      Result := Screen.MonitorFromPoint(point(Left,Top));
  end;
end;



LG und guten Rutsch, Maik
LG Maik

Windows 10,
- Lazarus 2.2.6 (stable) + fpc 3.2.2 (stable)
- Lazarus 2.2.7 (fixes) + fpc 3.3.1 (main/trunk)

compmgmt
Beiträge: 351
Registriert: Mi 25. Nov 2015, 17:06
OS, Lazarus, FPC: Win 10 Pro | Lazarus 1.8.2 | FPC 3.0.4
CPU-Target: i386 + x86_64
Wohnort: in der Nähe von Stuttgart
Kontaktdaten:

Re: [Windows] Form in Taskleiste

Beitrag von compmgmt »

Ich hatte die MonitorFromWindow Funktion direkt aus der User32.dll eingebunden da ich sie in der unit windows nicht gefunden habe, aber so wie du gehts natürlich einfacher.

Edit: Hab sie gefunden, ist in LCLIntf.

Code: Alles auswählen

InitiateSystemShutdownExA(nil, nil, 0, true, false, $0005000F);
Have fun with this snippet ;)

compmgmt
Beiträge: 351
Registriert: Mi 25. Nov 2015, 17:06
OS, Lazarus, FPC: Win 10 Pro | Lazarus 1.8.2 | FPC 3.0.4
CPU-Target: i386 + x86_64
Wohnort: in der Nähe von Stuttgart
Kontaktdaten:

Re: [Windows] Form in Taskleiste

Beitrag von compmgmt »

So, ich hab noch mal ein bisschen rumprobiert. Dazu wollte ich das Taskbar item mit ITaskbarList (un)sichtbar machen. Hier eine Möglichkeit wie es in Delphi funktionieren sollte (laut Delphi-Praxis Forenbeiträgen):

Code: Alles auswählen

...
uses shlobj, comobj, windows;
...
  private
    FTaskbar: ITaskbarList3;
...
 
procedure TWnd_Test.FormShow(Sender: TObject);
begin
  FTaskbar := CreateComObject(CLSID_TaskbarList) as ITaskbarList3;
  FTaskbar.HrInit;
end;
 
procedure TWnd_Test.Btn_AddTabClick(Sender: TObject);
begin
  FTaskbar.AddTab(Handle);
end;
 
procedure TWnd_Test.Btn_DeleteTabClick(Sender: TObject);
begin
  FTaskbar.DeleteTab(Handle);
end;
Für diesen Fall würde auch ITaskbarList (statt ITaskbarList3) reichen, aber darum geht es nicht. Beim Klick auf die Buttons passiert nichts.

Nun habe ich versucht das ganze bei fremden Fenstern zu machen: Mit Erfolg. Wenn man ein Application.Title Wert (nicht Form Caption!) mit FindWindow() sucht und das ganze macht funktioniert es. So kann man auch sein eigenes Taskbar Item (un)sichtbar machen. Dazu habe ich in die Form ein Edit namens Edt_AppTitle gepackt. Der von mir modifizierte Code hat funktioniert:

Code: Alles auswählen

procedure TWnd_Test.Btn_AddTabClick(Sender: TObject);
var
   h: HWND;
begin
   h := FindWindow(nil, LPCSTR(Edt_AppTitle.Caption));
   if h <> INVALID_HANDLE_VALUE then begin
      FTaskbar.AddTab(h);
      // um zu zeigen dass Form und Taskbar Item ein unterschiedliches Handle haben:
      Caption := 'Form: ' + IntToStr(Handle) + ' | Taskbar Item: ' + IntToStr(h);
   end;
end;
 
procedure TWnd_Test.Btn_DeleteTabClick(Sender: TObject);
var
   h: HWND;
begin
   h := FindWindow(nil, LPCSTR(Edt_AppTitle.Caption));
   if h <> INVALID_HANDLE_VALUE then begin
      FTaskbar.DeleteTab(h);
      // um zu zeigen dass Form und Taskbar Item ein unterschiedliches Handle haben:
      Caption := 'Form: ' + IntToStr(Handle) + ' | Taskbar Item: ' + IntToStr(h);
   end;
end;
Beim Eingeben des Application.Title Wertes in das Edit Feld und anschließendes Klicken auf die Button funktioniert es.

Das führt mich zu meiner Theorie: Das Taskbar Item von LCL Anwendungen ist gar nicht von der eigenen Form, sondern von einer im nicht sichtbaren Bereich befindlichen Form, was dazu führt dass Verschiebungen der eigenen Form auf den anderen Monitor zu keiner Änderung des Taskbar Items führen, weil dieses ja zu der unsichtbaren Form gehört, welche sich noch auf dem ersten Monitor befindet
Dateianhänge
TaskbarList.zip
Taskbar Item Beispielprojekt
(4.56 KiB) 186-mal heruntergeladen
Man sieht dass Form und Taskbar Item ein unterschiedliches Handle besitzen
Man sieht dass Form und Taskbar Item ein unterschiedliches Handle besitzen
TaskbarListTest.png (3.49 KiB) 2878 mal betrachtet

Code: Alles auswählen

InitiateSystemShutdownExA(nil, nil, 0, true, false, $0005000F);
Have fun with this snippet ;)

compmgmt
Beiträge: 351
Registriert: Mi 25. Nov 2015, 17:06
OS, Lazarus, FPC: Win 10 Pro | Lazarus 1.8.2 | FPC 3.0.4
CPU-Target: i386 + x86_64
Wohnort: in der Nähe von Stuttgart
Kontaktdaten:

Re: [Windows] Form in Taskleiste

Beitrag von compmgmt »

Ich bins nochmal. Hab es jetzt hinbekommen:

Code: Alles auswählen

uses ... {$ifdef mswindows}, windows {$endif};
...
  private
    {$ifdef mswindows}
      FTaskbarItemHandle: HWND;
    {$endif}
...
procedure TWnd_Test.FormChangeBounds(Sender: TObject);
begin
  {$ifdef mswindows}
    if FTaskbarItemHandle <> INVALID_HANDLE_VALUE then begin
      SetWindowPos(FTaskbarItemHandle, HWND_TOP, Left + (Width div 2), Top + (Height div 2), 1, 1, SWP_NOACTIVATE);
    end;
  {$endif}
end;
 
procedure TWnd_Test.FormCreate(Sender: TObject);
var
  AppTitle: String;
begin
  {$ifdef mswindows}
    // das temporäre umsetzen des Application Titles wird gemacht um die Form eindeutig zu identifizieren, sollten mehrere Instanzen dieser Anwendung laufen.
    // Sichtbar für den Endnutzer ist das jedoch nicht.
    AppTitle := Application.Title;
    Application.Title := Application.ExeName;
    FTaskbarItemHandle := FindWindow(nil, LPCSTR(Application.Title));
    Application.Title := AppTitle;
  {$endif}
end;
Beim Verschieben des eigenen Fensters wird das ominöse Fenster in die Mitte des eigenen Fensters mit der Größe 1x1 gesetzt (0x0 geht nicht), ist aber dennoch nicht sichtbar und nicht aktiv (SWP_NOACTIVATE), damit die eigene Form nicht flackert. Das hier ist eine Art Notlösung für Leute die eine solche Funktion benötigen. Die IFDEFs habe ich gleich reingemacht damit man das ohne Änderung unter Windows und Linux nutzen kann. Dennoch sollte das mal überarbeitet werden, auch wenn es nur ein kleines Detail ist.

Edit: Die Größe ist nur noch 1x1 statt so groß wie das eigene Fenster, da sonst beim maximieren und wiederherstellen ein schwarzer Balken links neben der eigenen Form entstand.

Code: Alles auswählen

InitiateSystemShutdownExA(nil, nil, 0, true, false, $0005000F);
Have fun with this snippet ;)

Antworten