Verhindern von Benutzereingaben

Veröffentlicht: 07. Sep 2001 | Aktualisiert: 19. Jun 2004

Von Paul DiLascia

Manchmal sind die Standardaktionen unerwünscht, die Windows oder die MFC für das Verschieben oder die Größenänderung von Fenstern und Teilfenstern vorsieht. Diese können mit kleinen Tricks ausgehebelt werden.

* * *

Auf dieser Seite

Frage Frage
Antwort Antwort

Diesen Artikel können Sie hier lesen dank freundlicher Unterstützung der Zeitschrift:

sys.gif

Frage

Ich arbeite mit Visual C++ an einem SDI-Projekt, in dessen Hauptfenster ich zwei von diesen geteilten Fenstern anzeige (Splitter-Windows). Wie kann ich den Anwender davon abhalten, Rahmen- und Kindfenster zu verschieben oder deren Größe zu ändern? Natürlich möchte ich trotzdem in jedem dieser Fenster eine Titelleiste anzeigen.

 

Antwort

Manchmal können einem die Anwender richtig Leid tun. Ständig versuchen die Programmierer, sie vor sich selbst zu beschützen und dies und das von den üblichen GUI-Operationen zu verhindern. Ich kann mich ohne Ende darüber aufregen, wenn ein Programm mir verbieten will, die Fenstergröße zu ändern oder irgendetwas zu kopieren oder einzufügen oder mir in irgendeiner anderen Form ein Verhalten aufdrängt. Mein erster Impuls ist immer, solche Anwendungen in die Mülltonne zu werfen.

Aber was soll's - vielleicht gibt es ja tatsächlich Umstände, unter denen es sinnvoll ist, die Verschiebung oder Größenänderung eines Fensters zu sperren. Der einfachste Weg, die Fenstergröße festzuklopfen, besteht darin, das fragliche Fenster ohne WS_THICKFRAME anzulegen. Wie sollte der Anwender auch die Fenstergröße ändern können, wenn er keinen Rahmen und damit keinen Griff hat, an dem er es anfassen könnte? Nun, wenn Sie WS_THICKFRAME abschalten, hat das Fenster trotzdem noch eine Titelleiste. Folglich kann der Anwender das Fenster immer noch verschieben. Um nicht nur die Größenänderung, sondern auch die Verschiebung zu sperren, müssten Sie zusätzlich WS_CAPTION abschalten. Aber dann hätte das Fenster keine Titelleiste mehr. Oops. Wenn das Programm sich schon anschickt, irgendwelche kritischen Dinge zu tun, bei denen man den Anwender vor sich selbst schützen muss, dann soll sich der Anwender doch wohl zumindest informieren können, mit welchem Programm er eigentlich arbeitet. Also kann man nicht einfach die Titelleiste streichen. Und wie löst man nun dieses scheinbare Dilemma? Wie kann man die Titelleiste beibehalten und trotzdem jede Verschiebung sperren? Und was ist, wenn man den dünnen Rahmen nicht ausstehen kann? Wenn man einen schönen, dicken, fetten Fensterrahmen haben will und trotzdem die Größenänderung sperren möchte?

Tatsächlich gibt es eine praktikable Lösung. Sie können ein Fenster mit dickem Rahmen und Titelleiste anlegen und trotzdem die Verschiebung und Größenänderung sperren. Der Trick besteht in der Auswertung der Nachricht WM_NCHITTEST. Windows schickt dem betreffenden Fenster diese Nachricht, um herauszufinden, wo sich die Maus eigentlich aufhält, sofern sie sich zwar im Fenster, aber nicht auf der Arbeitsfläche des Fensters aufhält. Diese Fensterteile werden unter dem Begriff Nonclient-Area zusammengefasst. Zum Nicht-Client-Bereich gehören, wie sich schon der neuste Neuling denken kann, diejenigen Teile des Fensters, die eben nicht zum Client-Bereich gehören. Also nicht zur Arbeitsfläche. Darunter fallen Menü, Titelleiste und Rahmen. Normalerweise hat man kaum etwas mit den Nicht-Client-Bereichen zu tun. Man braucht eigentlich noch nicht einmal zu wissen, dass es so etwas überhaupt gibt. Gelegentlich aber muss man wohl die Ärmel hochkrempeln und in den Innereien des Systems herumwühlen. Und dies ist so eine Gelegenheit.

Sobald der Anwender die Titelleiste der Anwendung anklickt, schickt Windows dem Fenster eine WM_NCHITTEST-Nachricht. Die Standard-Fensterfunktion untersucht die Mauskoordinaten genauer und gibt einen der speziellen Treffercodes zurück, die in Tabelle T1 aufgelistet werden. Wenn die Maus zum Beispiel auf der Titelleiste steht, kommt die Standard-Fensterfunktion zum Ergebnis HTCAPTION. Steht die Maus auf dem linken oder rechten Teil des Rahmens, meldet die Standard-Fensterfunktion dies mit HTLEFT oder HTRIGHT. Diese Codes weisen Windows darauf hin, dass es an der Zeit ist, sich an die Verschiebung oder Größenänderung zu machen.

T1 Die Treffercodes für den Treffertest mit der Maus

Code

Position der Maus

HTBORDER

Auf dem Rahmen eines Fensters, das keinen Größeneinstellungsrahmen hat. Wird benutzt, um Verschiebungen oder Größenänderungen zu sperren. Kann trotzdem zur Aktivierung der Anwendung dienen.

HTBOTTOM

Auf dem unteren horizontalen Teil des Fensterrahmens

HTBOTTOMLEFT

In der unteren linken Ecke des Fensterrahmens

HTBOTTOMRIGHT

In der unteren rechten Ecke des Fensterrahmens

HTCAPTION

Auf der Titelleiste

HTCLIENT

Auf der Arbeitsfläche

HTERROR

Auf dem Bildschirmhintergrund oder auf einer Trennlinie zwischen Fenstern (wie HTNOWHERE, nur zeigt die Fensterfunktion DefWndProc mit einem Pieps einen Fehler an)

HTGROWBOX

Auf einem Größenänderungsfeld

HTHSCROLL

Auf der horizontalen Rollleiste

HTLEFT

Auf dem linken Fensterrand

HTMAXBUTTON

Auf der Maximierfläche

HTMENU

Im Menübereich

HTMINBUTTON

Auf der Minimierfläche

HTNOWHERE

Auf dem Bildschirmhintergrund oder auf einer Trennlinie zwischen Fenstern

HTREDUCE

Auf der Minimierfläche

HTRIGHT

Auf dem rechten Fensterrand

HTSIZE

In einem Größenänderungsfeld (wie HTGROWBOX)

HTSYSMENU

Auf einem Steuermenü oder einer Schließen-Schaltfläche eines Kindfensters

HTTOP

Auf dem oberen horizontalen Fensterrahmen

HTTOPLEFT

In der oberen linken Ecke des Fensterrahmens

HTTOPRIGHT

In der oberen rechten Ecke des Fensterrahmens

HTTRANSPARENT

In einem Fenster, das derzeit von einem anderen Fenster überdeckt wird

HTVSCROLL

Auf der vertikalen Rollleiste

HTZOOM

Auf der Maximierfläche

Nun können Sie die Verschiebung oder Größenänderung verhindern, indem Sie WM_NCHITTEST selbst bearbeiten und zum Beispiel ON_NCHITTEST überschreiben. Statt HTCAPTION oder HTLEFT oder HTRIGHT können Sie ... ja, was können Sie eigentlich zurückgeben? Sie könnten es mit HTNOWHERE versuchen. Dann werden Sie aber schnell das damit verbundene Problem erkennen. Wenn nämlich ein anderes Fenster Ihr Fenster überlagert und Sie die Titelleiste Ihres Fensters anklicken, geschieht rein gar nichts. Und ich meine rein gar nichts. Windows aktiviert noch nicht einmal Ihre Anwendung. Seufz. Nun, wie wäre es mit HTTRANSPARENT? Dasselbe Problem. Sowohl HTTRANSPARENT als auch HTNOWHERE hinterlassen Ihren Rahmen in einem Zustand existenziellen Zweifels.

Der richtige Rückgabewert ist HTBORDER. Also derselbe Wert, den die Standard-Fensterfunktion zurückgibt, sobald der Anwender den Rahmen eines Fensters mit dickem Rahmen anklickt (also mit Größeneinstellungsrahmen). Nicht schlecht, was? Wenn Sie HTBORDER zurückgeben, aktiviert Windows Ihr Fenster, ohne die Verschiebe- oder Größenänderungsaktionen einzuleiten.

Natürlich habe ich ein kleines Beispielprogramm geschrieben, das Ihnen vorführen soll, wie das in der Praxis aussieht. Es heißt NoSize und ist auf der Begleit-CD zu finden. Der wesentliche Teil spielt sich in der Klasse CMainFrame ab, und zwar in der Funktion CMainFrame::OnNcHitTest. Diese Funktion bildet alle "schlechten" Treffertests auf HTBORDER ab.

UINT CMainFrame::OnNcHitTest(CPoint point) 
{ 
    // Besorge den Treffercode 
    UINT hit = CFrameWnd::OnNcHitTest(point); 
    // Sperre die folgenden Codes (bilde sie auf HTBORDER ab) 
    static char DisallowCodes[] = { 
        HTLEFT,HTRIGHT,HTTOP,..., 
        HTSIZE,HTCAPTION }; 
    // Gib den C-Einsteigern was zum Grübeln... :-) 
    return strchr(DisallowCodes, hit)) ? HTBORDER: hit; 
}

In der Datei werden Sie auch noch einige nützliche TRACE-Anweisungen finden, die Ihnen bei der Untersuchung helfen, was eigentlich vor sich geht. Mit den oben gezeigten magischen Zeilen ist der Anwender nicht mehr in der Lage, das Fenster zu verschieben oder die Fenstergröße zu ändern.

Oder können Sie es doch noch? Haben wir tatsächlich schon alle Wege erfasst, auf denen sich ein Fenster verschieben oder verstellen lässt? Wie steht es mit den Mini/Maxi-Flächen auf der Titelleiste? Was ist mit den Menübefehlen Verschieben / Größe ändern im Systemmenü (Bild B1)? Sagen Sie mir nicht, dass Sie noch gar nicht daran gedacht haben. Wie sich nun zeigt, ist es gar nicht so einfach, die eigenen Größenvorstellungen vom Fenster durchzusetzen. Zum Glück ist der Rest aber ziemlich geradlinig. Um die Mini/Maxi/Wiederherstellen-Schaltflächen auf der Titelleiste abzuschalten, brauchen Sie nur das Fensterformat in PreCreateWindow anzupassen (also die "Style-Flags").

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) 
{ 
    // keine Mini/Maxi-Schalter auf der Titelleiste! 
    cs.style &= ~(WS_MINIMIZEBOX|WS_MAXIMIZEBOX); 
    return CFrameWnd::PreCreateWindow(cs); 
}

Zur Entfernung der unerwünschten Befehle aus dem Systemmenü brauchen Sie ein paar Zeilen in CMainFrame::OnCreate:

static UINT BadCommands[] = {  
    SC_SIZE, SC_MOVE, 
    SC_MINIMIZE, SC_MAXIMIZE, SC_RESTORE, 0 
}; 
CMenu *pSysMenu = GetSystemMenu(FALSE); 
for (int i=0; BadCommands[i]; i++) { 
    pSysMenu->RemoveMenu(BadCommands[i], MF_BYCOMMAND); 
}

Nun sieht das Systemmenü aus wie in Bild B2.

16verh01.gif

B1 Die Größenänderung kann auch via Menü stattfinden.

16verh02.gif

B2 Das neue Systemmenü.

Vielleicht geben Sie sich nun der Überzeugung hin, auch die Freigeister unter den Anwendern gebändigt zu haben, die Ihr Fenster wider jede Gebrauchsanleitung verschieben oder in der Größe ändern wollen. Aber es gibt noch ein weiteres Schlupfloch. Auch unter Windows führen nun mal viele Wege nach Rom.
Wenn Sie Ihre Anwendung mit der MFC entwickeln, haben Sie vermutlich eine Statusleiste mit Größengriffen - Sie wissen schon, diese seltsamen kleinen Linien, die in der unteren rechten Ecke des Fensters ein kleines Dreieck bilden (Bild B3). Der Anwender braucht nur an diesen kleinen Zöpfen zu ziehen, um die Fenstergröße zu ändern.

16verh03.gif

B3 Auch die Statusleiste hat "Größengriffe".

Was können Sie also tun, um auch diese Zuflucht zu versperren? Folgendes:

// In CMainFrame::OnCreate 
ModifyStyle(WS_THICKFRAME,0);  
// kein dicker Rahmen 
m_wndStatusBar.Create(...); 
ModifyStyle(0,WS_THICKFRAME);  
// setze den dicken Rahmen wieder ein

Schalten Sie einfach den dicken Rahmen ab, bevor Sie die Statusleiste anlegen. Anschließend schalten Sie ihn wieder ein. Wenn Sie die Statusleiste anlegen, schaut die MFC in den Formatflags des Fensters nach und entscheidet dann, ob die Statusleiste Größengriffe erhält oder nicht. Da man die Griffe anscheinend nicht mehr los wird, sobald die Statusleiste fertig zusammengebaut ist, bleibt eigentlich nur noch der Ausweg, die MFC hereinzulegen. Warum auch nicht. Bild B4 zeigt das Ergebnis - eine Statusleiste ohne Größengriffe.

16verh04.gif

B4 Statusleiste ohne Größengriffe.

Puh! OnNcHitTest, Mini/Maxi-Schaltflächen, Systemmenü, die MFC-Statusleiste hinter's Licht geführt... allerhand Arbeit. Nun kann niemand mehr das Fenster verschieben oder dessen Größe ändern. Wundern Sie sich aber nicht, wenn sich die Kunden beschweren oder die Anwendung kurzerhand vom Computer löschen.

Frage
Ich habe eine MFC-Anwendung, die ein geteiltes Fenster benutzt. Ich möchte aber nicht, dass die Anwender diese Teilungsschiene verschieben. Wie kann ich den Anwender davon abhalten?

Antwort
Was um Himmels Willen sollen die vielen "wie halte ich den Anwender davon ab"-Fragen? Nun, die schlichte Wahrheit ist, dass ich mir diese Frage selbst ausgedacht habe. Nach der letzten Frage und einem Blick auf die nächste, in der von einem teilbaren Fenster die Rede war, war mir klar, dass diese Frage unweigerlich kommen würde. Also kürze ich die Sache einfach ab und erspare meiner Posteingangsbox einigen Stress.

16verh05.gif

B5 Justierbares geteiltes Fenster.

16verh06.gif

B6 Keine Größenänderung mehr erlaubt.

Zum Glück ist es ziemlich leicht, den hoffnungsfroh zur Teilungsschiene greifenden Anwender eines teilbaren Fensters zu enttäuschen. Sie brauchen nur zwei Dinge zu tun. Erstens müssen Sie die Größenverstellung mit der Maus verhindern. Zweitens müssen Sie den Größeneinstellungscursor (Bild B5) gegen den normalen Pfeil austauschen (Bild B6). Das lässt sich alles mit der Überschreibung von zwei Funktionen erledigen:

// überschreiben: keine Größenänderung zulassen 
void CMySplitterWnd::OnLButtonDown(UINT, CPoint) 
{ 
    return; // keine Reaktion 
} 
// überschreiben: kein Größeneinstellungscursor 
void CMySplitterWnd::OnMouseMove(UINT, CPoint) 
{ 
    return; // dito 
}

Das ist die Art von Code, die mir richtig gut gefällt. Er tut rein gar nichts. Normalerweise wechselt die Teilungsschiene in den Zieh-Modus, wenn man sie mit der Maus anklickt. Das geht schon aus den folgenden MFC-Zeilen hervor:

// (in WinSplit.cpp) 
void CSplitterWnd::OnLButtonDown(UINT, CPoint pt) 
{ 
    if (m_bTracking) 
        return; 
    StartTracking(HitTest(pt)); 
}

Um die Größenänderung zu verhindern, brauchen Sie nur OnLButtonDown zu überschreiben und der Überschreibungsfunktion keine Arbeit zu geben. Und wenn CSplitterWnd::OnMouseMove den Cursor in eine Form bringt, die wie ein Kondensator in einer elektronischen Schaltung aussieht (Bild B5), können Sie das in ähnlicher Weise verhindern, indem Sie den Kondensator einfach kurzschließen (Entschuldigung... ich konnt's mir nicht verkeifen). Dazu überschreiben Sie die Funktion und lassen die Überschreibungsfunktion wieder untätig sein.

Es gibt eine dritte Funktion, die dem Anwender die Größenänderung im geteilten Fenster ermöglicht, nämlich CSplitterWnd::DoKeyboardSplit. Diese Funktion wurde wegen des Menübefehls Fenster / Teilen implementiert, mit dessen Hilfe die Anwender die Teilungsschiene via Tastatur verschieben können. Allerdings wird es nicht erforderlich sein, DoKeyboardSplit zu überschreiben, weil Sie es ja selbst sind, der die Anwendung entwickelt. Soll der Anwender die Teilungsschiene nicht verschieben können, bieten Sie im Fenstermenü einfach keinen Teilungsbefehl an.