Code zu Fenstern und Formularen in .NET

Verhindern mehrfachen Behandelns von Ereignissen bei Formularvererbung

Über Formularvererbung können bestehende Formulare wiederverwendet und erweitert werden. Manchmal ist es erforderlich, daß die Implementierung hinter einem Ereignis des Steuerelements einer Basisklasse im abgeleiteten Formular geändert werden muß. Die Entwicklungsumgebung erlaubt zwar das Hinzufügen einer weiteren Ereignisbehandlungsprozedur, was aber dazu führt, daß zur Laufzeit beide Ereignisbehandlungsprozeduren aufgerufen werden.

RemoveHandler Me.Button1.Click, AddressOf MyBase.Button1_Click
Entfernen der Ereignisbehandlungsprozedur der Basisklasse.

In dieser Situation würde man es sich wünschen, daß die Ereignisbehandlungsprozedur aus der Basisklasse nicht ausgeführt wird. Zu diesem Zweck kann man im Konstruktor des abgeleiteten Formulars die Ereignisbehandlungsprozedur aus der Basisklasse vom Ereignis des Steuerelements lösen. Das Entfernen der Ereignisbehandlungsprozedur kann mit der Anweisung RemoveHandler durchgeführt werden. Damit das Beispiel funktioniert, muß die Ereignisbehandlungsprozedur in der Basisklasse als Protected deklariert werden. Im abgeleiteten Formular kann man der Ereignisbehandlungsprozedur entweder einen anderen Namen geben oder die Prozedur der Basisklasse überschreiben.

Entfernen der Schließen-Systemschaltfläche eines Formulars

Windows-Forms-Formularen bieten zwar die Möglichkeit, die Systemschaltflächen zum Minimieren und Maximieren getrennt zu deaktivieren, will man aber nur die Systemschaltfläche Schließen außer Gefecht setzen, wird man verzweifelt nach einer entsprechenden Eigenschaft suchen. Die einzige im .NET Framework vorgesehene Möglichkeit besteht darin, alle Systemschaltflächen durch Setzen der ControlBox-Eigenschaft des Formulars auf False zu entfernen. Dadurch wird dem Benutzer allerdings die Möglichkeit genommen, das Fenster zu minimieren oder zu maximieren.

Eine einfache Lösung besteht darin, das Ereignis Closing des Formulars zu behandeln und innerhalb der Ereignisbehandlungsprozedur die Eigenschaft Cancel des Ereignisargumentobjekts auf False zu setzen. Es ist wohl offensichtlich, daß diese Vorgehensweise nicht die beste Lösung darstellt. Der Benutzer könnte verunsichert werden, wenn er mehrmals auf die Schaltfläche klickt, sich aber das Fenster trotzdem nicht schließt. Auch das Anzeigen einer Meldung mit einem Hinweis darauf, daß das Fenster nicht geschlossen werden kann, wirkt unprofessionell:

Private Sub Form1_Closing( _
    ByVal sender As Object, _
    ByVal e As CancelEventArgs _
) Handles MyBase.Closing
    e.Cancel = True
End Sub
Außerkraftsetzen der Schließen-Systemschaltfläche durch Abbruch des Vorgangs bei Eintreten des Closing-Ereignisses.

Eine etwas elegantere Lösung zum Entfernen der Schaltfläche liegt im Überschreiben der Eigenschaft CreateParams des Formulars. In der überschriebenen Eigenschaft werden die Fensterstile so erweitert, daß die Systemschaltfläche zum Schließen des Formulars automatisch deaktiviert wird:

Protected Overrides ReadOnly Property CreateParams() As CreateParams
    Get
        Const CS_DBLCLKS As Int32 = &H8
        Const CS_NOCLOSE As Int32 = &H200
        Dim cp As CreateParams = MyBase.CreateParams
        cp.ClassStyle = CS_DBLCLKS Or CS_NOCLOSE
        Return cp
    End Get
End Property
Entfernen der Schließen-Systemschaltfläche durch Ändern der Fensterstile.

Weiters wäre eine Lösung auf Basis der Windows-API denkbar. Durch Entfernen des Eintrags Schließen und der darüber befindlichen horizontalen Linie aus dem Systemmenü wird auch gleich die dazugehörige Systemschaltfläche deaktiviert:

Private Declare Function GetSystemMenu Lib "user32.dll" ( _
    ByVal hWnd As IntPtr, _
    ByVal bRevert As Boolean _
) As IntPtr

Private Declare Function GetMenuItemCount Lib "user32.dll" ( _
    ByVal hMenu As IntPtr _
) As Int32

Private Declare Function DrawMenuBar Lib "user32.dll" ( _
    ByVal hWnd As IntPtr _
) As Boolean

Private Declare Function RemoveMenu Lib "user32.dll" ( _
    ByVal hMenu As IntPtr, _
    ByVal uPosition As Int32, _
    ByVal uFlags As Int32 _
) As Boolean

Private Const MF_BYPOSITION As Int32 = &H400
Private Const MF_REMOVE As Int32 = &H1000

''' <summary>
'''   Deaktiviert die "Schließen"-Systemschaltfläche des in
'''   <paramref name="Form"/> übergebenen Formulars.
''' </summary>
''' <param name="Form">
'''   Formular, dessen "Schließen"-Systemschaltfläche deaktiviert
'''   werden soll.
''' </param>
Private Sub RemoveCloseButton(ByVal Form As Form)
    Dim hMenu As IntPtr = GetSystemMenu(Form.Handle, False)
    If Not hMenu.Equals(IntPtr.Zero) Then
        Dim n As Int32 = GetMenuItemCount(hMenu)
        If n > 0 Then
            RemoveMenu(hMenu, n - 1, MF_BYPOSITION Or MF_REMOVE)
            RemoveMenu(hMenu, n - 2, MF_BYPOSITION Or MF_REMOVE)
            DrawMenuBar(Form.Handle)
        End If
    End If
End Sub
Entfernen der Schließen-Systemschaltfläche durch Entfernen des dazugehörigen Systemmenüeintrags.

Ermitteln der Ursache für das Schließen eines Formulars

Fenster können aus verschiedensten Gründen geschlossen werden:

In einigen Fällen ist es erforderlich, die Ursache für das Schließen eines Fensters zu kennen. Zu diesem Zweck stehen verschiedene Wege bereit, die wir in Folge ansehen wollen. Im folgenden Beispiel wird Einsicht in einen Stackframe eines Stacktraces genommen und so ermittelt, welche Plattformfunktion zuletzt aufgerufen wurde:

Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Windows.Forms

Public Class SampleForm
    Inherits Form

    ⋮

    Private Sub SampleForm_Closing( _
        ByVal sender As Object, _
        ByVal e As CancelEventArgs _
    ) Handles MyBase.Closing
        Dim f As New StackTrace(True).GetFrame(7)
        Select Case f.GetMethod().Name
            Case "SendMessage"
                MsgBox("Schließen in Folge eines Aufrufs im Code.")
            Case "CallWindowProc"
                MsgBox("Schließen über das Systemmenü.")
            Case "DispatchMessageW"
                MsgBox("Schließen über den Task-Manager.")
            Case Else
                MsgBox("Unbekannte Ursache für das Schließen.")
        End Select
    End Sub
End Class
Ursache für das Schließen eines Formulars ermitteln.

Ob das Fenster über die Systemschaltfläche oder das Systemmenü geschlossen wurde, kann man auch durch Überschreiben der Methode WndProc des Formulars ermitteln. Über Hineinhorchen in die Nachrichtenschleife kann zudem herausgefunden werden, ob das Herunterfahren des Betriebssystems der Grund für das Schließen des das Fenster ist:

Private Const WM_SYSCOMMAND As Int32 = &H112

Private Const SC_CLOSE As Int32 = &HF060

Protected Overrides Sub WndProc(ByRef m As Message)
     If m.Msg = WM_SYSCOMMAND AndAlso m.WParam.ToInt32() = SC_CLOSE Then
         MsgBox( _
             "Über die Systemschalftläche oder das Systemmenü geschlossen." _
         )
     End If
     MyBase.WndProc(m)
End Sub
Abhören der Fensterprozedur nach WM_SYSCOMMAND.

Anzeigen der Systeminformationen

In Visual Basic 6.0 war eine Formularvorlage für einen Informationsdialog enthalten, den man in seine Anwendungen einbinden konnte. Dieser Dialog enthielt auch eine Schaltfläche, über die die Systeminformationen angezeigt werden konnten. Auch wenn der Sinn einer solchen Schaltfläche in Frage zu stellen ist, kann es vorkommen, daß man dem Benutzer die Möglichkeit geben will, die Systeminformationen anzuzeigen.

Folgendes Beispiel liest aus der Systemregistrierung den Pfad der Anwendung aus, in der die Systeminformationen implementiert sind. Existiert die Datei, dann wird sie gestartet. Auf Fehlerbehandlung wurde verzichtet. Unter alten Windows-Versionen kann der Pfad nicht auf diese Weise abgefragt werden. Damit das Beispiel funktioniert, müssen die Namensräume Microsoft.Win32 und System.IO importiert werden:

Dim FileName As String = _
    Registry.LocalMachine.OpenSubKey( _
        "SOFTWARE\Microsoft\Shared Tools\MSINFO" _
    ).GetValue( _
        "Path", _
        "" _
    )
If File.Exists(FileName) Then
    Process.Start(FileName)
End If
Starten der Systeminformationen.

Scheinbares Senken des Speicherbedarfs von Formularen

Windows-Forms-Formulare benötigen im nicht-minimierten Zustand sehr viel Arbeitsspeicher. Über die Windows-API-Funktion SetProcessWorkingSize kann der Speicherbedarf scheinbar reduziert werden. Tatsächlich wird jedoch nur das Minimieren des Hauptfensters der Anwendung simuliert, was bewirkt, daß Daten der Anwendung ausgelagert und der Speicher anderen Anwendungen zur Verfügung gestellt wird. Dem zugrunde liegt der Gedanke, daß minimierte Anwendungen für einige Zeit vom Benutzer nicht verwendet werden und deshalb andere Anwendungen Speicher dringender benötigen.

Private Declare Function SetProcessWorkingSetSize Lib "kernel32.dll" ( _
    ByVal hProcess As IntPtr, _
    ByVal dwMinimumWorkingSetSize As Int32, _
    ByVal dwMaximumWorkingSetSize As Int32 _
) As Boolean

Public Function SaveMemory() As Boolean
    Return _
        SetProcessWorkingSetSize( _
            Process.GetCurrentProcess().Handle, _
            -1, _
            -1 _
        )
End Function
Scheinbare Reduktion des Speicherbedarfs von Windows-Forms-Formularen.