Vorrede fuer alle, die dem Begriff der Systemmodalitaet nichts zuordnen koennen: Ein Fenster ist systemmodal, wenn der Nutzer nur mit diesem Fenster arbeiten und zu keinem anderen Fenster wechseln kann. Er kann dies vielleicht zu einem spaeteren Zeitpunkt
SetSysModalWindow -- historisch gesehen
Frueher, in der guten alten Zeit von Windows 3.11 war noch alles einfach. Wenn der Programmierer die ungeteilte Aufmerksamkeit des Nutzers auf sein Fenster richten wollte, dann rief er die Funktion SetSysModalWindow(HWND) mit dem Fenster-Handle des Fensters, dass die Aufmwerksamkeit bekommen sollte auf. Dieser Aufruf fuehrte dazu, dass kein Wechsel des aktiven Fensters mehr moeglich war, bis dieses geschlossen wurde (oder SetSysModalWindow(HWND) mit 0 als Parameter aufgerufen wird). Eine offensichtliche Anwendung waere ein kleines Programm, um den Rechner gegen unerwuenschten Zugriff zu sperren.
Zu Zeiten von 16-Bit-Windows war diese Funktion state-of-the-art.
Dann kam Windows 95 bzw. Windows NT 4 auf den Markt. Zusammen damit wurde des User-Interface-Konzept von Windows umgekrempelt. Aus kooperativem Multitasking (ein Programm kann den Eingabefokus weitergeben SetSysModalWindow(HWND) aufgerufgen) wurde das praeemptive Multitasking (der Nutzer bestimmt, welches Programm den Eingabefokus hat, und andere Programme koennen nichts dagegen machen). Allerdings, die Kompatibilitaet erzwang ihre Kompromisse -- und so entstanden zwei moegliche Verhaltensweisen des Betriebssystemes auf einen Aufruf von SetSysModalWindow:
Windows 95 (und seine Nachfolger) taten, was Windows 3.11 tat -- das Fenster, dessen Handle uebergeben wurde, wurde systemmodal. Um diese Funktion aus einem 32-Bit-Programm heraus zu nutzen (sie war nur aus dem 16-Bit-Subsystem verfuegbar) war es nur notwendig, ein 16-Bit-Programm zu haben, dass das als Parameter uebergebene Fensterhandle an
SetSysModalWindow(HWND)weitergibt, schon konnten auch Fenster von 32-Bit-Programmen diesen Status der Systemmodalitaet geniessen.Windows NT (und seine Nachfolger) implementierten diesen Aufruf wie ein Setzen des Fensters in der Z-Order auf
HWND_TOPMOST-- also als ein Fenster, welches zwar immer sichtbar bleibt, aber den Eingabefokus verlieren kann. Diesen Zustand gibt man z.B. Dialogen oder einem Media-Player, der im Vordergrund bleiben soll, auch wenn man den Vollbild-Browser nebenbei benutzt. Hier wurde die Systemmodalitaet als solche also abgeschafft. Entsprechend musste man sich bemuehen, wenn man wollte, dass das eigene Programmfenster dennoch quasi-systemmodal ist. Hierzu hat man sich natuerlich viel einfallen lassen. Beispielsweise ein Fenster im Vollbildmodus mit demHWND_TOPMOST-Attribut zu versehen und es regelmaessig wieder mit dem Eingabefokus zu versehen (und so die "Ausbruchszeiten" des Nutzers zu minimieren). Das ist allerdings recht muessig und andere Dialoge koennen (wenn siedanach auch den StatusHWND_TOPMOSTbekommen) das erste Programm ueberlagern. Zum Beispiel der Task-Manager. Also muss man diesen per Registry-Eintrag disablen... kurzum, es ist komplex und nontrivial, den Nutzer "einzusperren".
SetSysModalWindow -- aus Sicht des Interface Design
Katastrophe. Schlimmstes Mittelalter. Die Systemmodalitaet bring den Habitus von DOS-Programmen in die Welt des Multitasking und beraubt sie eben dieser herausragenden Eigenschaft, mehrere Programme gleichzeitig nutzen zu koennen. Systemmodale Dialoge sind schlicht fuer "normale" Programme nicht notwendig, denn kein Programm sollte sich so wichtig nehmen, dass es den Nutzer einsperrt. Systemmodalitaet hat in Anwenderprogrammen nichts verloren. So einfach ist das.
Die Realitaet (und wie ich hierauf gekommen bin)
Im Folgenden werde ich von den Betriebssystemen Microsoft Windows 2000, Windows XP, Windows Vista und Windows 7 ausgehen.
Gibt es noch Systemmodalitaet? Natuerlich gibt es sie. Da wo sie hingehoert -- aus der Hand des
Bei dem Dialog, der nach Ctrl-Alt-Del erscheint, ist wohl unstreitig, dass er sinnigerweise systemmodal ist. Bei UAC -- nun, wuerde es nicht reichen, wenn UAC sich nur vor das betreffende Programm haengen wuerde? Vielleicht nicht, vielleicht wuerden dann Programme im Hintergrung nicht weiterarbeiten, nur weil der Nutzer die UAC-Anforderung uebersehen hat...
Unstreitig duerfte auch sein, dass systemmodale Dialoge vom Betriebssystem ausgehen... und sonst niemandem.
Und dann gibt es da noch ein Beispiel, dass mich ueberhaupt erst hierauf gebracht hat: "Microsoft CardSpace". CardSpace bringt ein Plugin fuer die Systemsteuerung mit sich. Wenn man dieses startet, dann erzeugt es einen systemmodalen Dialog. Schockierend.
Das bekommen Sie entweder mit einem undokumentierten Aufruf in der Windows-API hin (was mal
Wenn man etwas erschaffen will, was andere schon hinbekommen, dann ist ein vielversprechender Ausgangspunkt, sich anzuschauen wie das die anderen den machen. Also habe ich mich dafuer interessiert, wie die anderen Windows-Dialoge ihren Sonderstatus bekommen. Das Zauberwort heisst Desktopwechsel.
Ja, Windows kann mehrere Desktops verwalten. Einer davon ist als "secure" definiert und wird bei Druck auf Ctrl-Alt-Del angesprungen. Bei UAC wird der Desktop ebenfalls gewechselt -- mit dem ausgegrauten anderen Desktop als Hintergrundbild. Und Cardspace wird es ebenso machen.
Die Windows-Desktop-API
Ganz so trivial, wie es einst mit SetSysModalWindow(HWND) war, wird es nicht mehr. Im eher im Gegenteil. Aber dennoch, mit ein wenig Geduld bekommt man einen systemmodalen Dialog hin. Dazu muss man essenziell nur folgendes machen:
- Einen neuen Desktop aufmachen
- Dorthin wechseln
- Dialog aufploeppen und auf sein Ende warten
- "Original-Desktop" oeffnen
- Dorthin wechseln
OpenDesktop, CreateDesktop, SwitchDesktop, und SetThreadDesktop. Die erste Funktion ist notwendig, um spaeter zum "Normaldesktop" zurueckkehren zu koennen. Die Zweite, um einen leeren neuen Desktop anzulegen. Die Dritte wird den Desktop wechseln und mit der vierten sagen wir, auf welchem wir unsere Programmausgabe platzieren moechten.
Ein systemmodales Delphi-Beispiel
Natuerlich soll hier das Proof-of-Concept-Beispiel nicht fehlen. Als weitere Schwierigkeitssteigerung moechte ich das Beispielprogramm fuer Delphi schreiben -- warum wird gleich erklaert. Vorher soll noch gesagt sein, dass das Erstellen von Fenstern auf einem anderen Desktop nur dann funktioniert, wenn das Programm zuvor keine anderen Fenster geoeffnet hat. Also muss man sehr genau aufpassen. Wenn man die Fenster mit C und der Windows-API erstellt, dann kann man das. Hat man aber ein Toolkit unter sich (heisse es nun QT oder VCL oder wie auch immer), dann wird dies ungleich schwieriger. Denn sobald eine VCL-Form beteiligt ist, klappt es scheinbar nicht mehr, denn die Form tut schon beim Aufgefuehrtsein in der uses-Klausel etwas scheinbar stoerendes. Andererseits sind VCL-Formen sehr viel einfacher zu designen als solche, die man in muehseliger Windows-API-Handarbeit zusammenkloeppelt. Wie also kann genau geklaert werden, dass die VCL-Form erst dann erste Aufrufe startet, wenn sie dran ist?
Die einfachste Loesung, die mir eingefallen ist, ist die, die VCL-Form in eine DLL auszulagern. Man gehe also wie folgt vor:
- Erstellen eines neuen VCL-Projektes, die Hauptform so zurechtbauen, wie sie spaeter als systemmodaler Dialog aussehen soll.
- Die Projektdatei oeffnen und so veraendern, dass die erstellte Form aus einer Funktion aufgerufen wird; diese Funktion wird in die Liste der exportierten Funktionen aufgenommen und der Bereich zwischen
beginundend.wird geleert. - Das Schluesselwort
programzu Beginn der Projektdatei wird durchlibraryersetzt.
LoadLibrary geoeffnet und die Einsprungsadresse mit GetProcAddress gesucht. Voila.
In Code schaut das dann wie folgt aus: Das aufrufende Programm:
program sysmodal;
uses
Windows;
var
d: HDESK;
h: THandle;
p: procedure;
begin
d := CreateDesktop('SysmDesk',nil,nil,0,
DESKTOP_CREATEMENU or DESKTOP_CREATEWINDOW or
DESKTOP_ENUMERATE or DESKTOP_HOOKCONTROL or
DESKTOP_WRITEOBJECTS or DESKTOP_READOBJECTS or
DESKTOP_SWITCHDESKTOP or GENERIC_WRITE,
nil);
SwitchDesktop(d);
SetThreadDesktop(d);
h := LoadLibrary('client.dll');
p := GetProcAddress(h, 'dlgProc');
p;
FreeLibrary(h);
d := OpenDesktop('default', 0, FALSE,
DESKTOP_CREATEMENU or DESKTOP_CREATEWINDOW or
DESKTOP_ENUMERATE or DESKTOP_HOOKCONTROL or
DESKTOP_WRITEOBJECTS or DESKTOP_READOBJECTS or
DESKTOP_SWITCHDESKTOP or GENERIC_WRITE);
SwitchDesktop(d);
SetThreadDesktop(d);
end.
Der Rest findet sich in einem anderen Projekt, dem, aus dem die DLL erstellt wird. Bei mir hiess es "client". Hier soll nur kurz die Projektdatei angegeben sein; die Dialogform sei als frmMain in der Datei frmMainU abgelegt.
library client;
uses
frmMainU in 'frmMainU.pas' {frmMain}
{$R *.res}
procedure dlgProc;
var
frmMain : TfrmMain;
begin
frmMain := TfrmMain.create(nil);
frmMain.showmodal;
frmMain.free;
end;
exports
dlgProc;
Fertig! Der Dialog aus der Library erscheint als systemmodaler Dialog auf einem eigenen Desktop. Man kann sich zwar abmelden (nach Ctrl-Alt-Del bekommt man den ueblichen Dialog), aber man kann das aktuelle Programm nicht beenden. Man kann den Task-Manager aufrufen. Aber dieser geht auf dem falschen Desktop auf und hilft also nichts.Es soll sich niemand beschweren, dass ich hiermit irgendwelchen Systemmodaldialogprogrammen den Weg ebne. Haette man bei CardSpace diesen Unsinn nicht gemacht, ich haette nicht angefangen nachzuforschen...