Wie in Warten auf Schließen einer anderen Form [und warum man Dialoge nicht modal machen sollte] beschrieben und begründet, sind modale Dialoge und MessageBoxen out und es gibt mittlerweile bessere Alternativen. Hier zeige ich ein kleines Snippet für die übliche Abfrage beim Schließen eines Fenstern mit ungespeicherten Änderungen (siehe auch den Screenshot weiter unten). Statt in einer MessageBox werden die Buttons, mit denen der Benutzer die Abfrage beantworten kann, direkt in das Form eingeblendet. Der Vorteil liegt darin, dass das Form während der Abfrage voll bedienbar bleibt. Es können also z.B. noch letzte Änderungen vorgenommen werden, bevor der Benutzer diese endgültig speichert. Außerdem bleibt das Form die ganze Zeit auf dem Bildschirm frei verschiebbar.
[EDIT]Das grundlegende Prinzip lässt sich für jede Art von (Sicherheits-)Abfragen und außerdem auch für alle Hinweis-, Warnungs- und Fehlermeldungen nutzen. Eben überall dort, wo man bisher MessageBoxen (oder InputBoxen) verwendet hat.[/EDIT]
using System;
using System.Windows.Forms;
using System.Drawing;
//using System.Runtime.InteropServices;
//*****************************************************************************
public class EditorForm : Form
{
//--------------------------------------------------------------------------
private const int Spacing = 10;
private readonly Panel closeActions;
private readonly int closeActionsWidth;
private readonly Button saveAndCloseButton;
private bool delayClose = true;
//==========================================================================
public EditorForm ()
{
Control container;
Control previousControl;
Text = "Editor - Unbekanntes Dokument";
ClientSize = new Size (640, 480);
{
var currentControl = new RichTextBox ();
currentControl.Dock = DockStyle.Fill;
Controls.Add (currentControl);
previousControl = currentControl;
}
{
var currentControl = closeActions = new Panel ();
currentControl.Dock = DockStyle.Top;
currentControl.Visible = false;
currentControl.BackColor = Color.FromArgb (165, 201, 239);
Controls.Add (currentControl);
container = currentControl;
previousControl = currentControl;
}
{
var currentControl = new Label ();
currentControl.Text = "Welche Aktion möchten Sie durchführen?";
currentControl.Top = Spacing;
currentControl.Left = Spacing;
currentControl.AutoSize = true;
container.Controls.Add (currentControl);
previousControl = currentControl;
}
{
var currentControl = saveAndCloseButton = new Button ();
currentControl.Text = "Speichern und schließen";
currentControl.Top = previousControl.Bottom + Spacing;
currentControl.Left = previousControl.Left;
currentControl.AutoSize = true;
currentControl.FlatStyle = FlatStyle.Flat;
currentControl.Click += SaveAndCloseClick;
container.Controls.Add (currentControl);
previousControl = currentControl;
AcceptButton = currentControl;
}
{
var currentControl = new Button ();
currentControl.Text = "Schließen ohne zu speichern";
currentControl.Top = previousControl.Top;
currentControl.Left = previousControl.Right + Spacing;
currentControl.AutoSize = true;
currentControl.FlatStyle = FlatStyle.Flat;
currentControl.Click += CloseOnlyClick;
container.Controls.Add (currentControl);
previousControl = currentControl;
}
{
var currentControl = new Button ();
currentControl.Text = "Keine";
currentControl.Top = previousControl.Top;
currentControl.Left = previousControl.Right + Spacing;
currentControl.AutoSize = true;
currentControl.FlatStyle = FlatStyle.Flat;
currentControl.Click += DontCloseClick;
container.Controls.Add (currentControl);
previousControl = currentControl;
CancelButton = currentControl;
}
{
container.Height = 0;
closeActionsWidth = 0;
foreach (Control control in container.Controls) {
if (container.Height < control.Bottom) {
container.Height = control.Bottom;
}
if (closeActionsWidth < control.Right) {
closeActionsWidth = control.Right;
}
}
container.Height += Spacing;
closeActionsWidth += Spacing;
}
}
//==========================================================================
protected void SaveAndCloseClick (Object sender, EventArgs e)
{
Save ();
delayClose = false;
Close ();
}
//==========================================================================
protected void CloseOnlyClick (Object sender, EventArgs e)
{
delayClose = false;
Close ();
}
//==========================================================================
protected void DontCloseClick (Object sender, EventArgs e)
{
closeActions.Visible = false;
ControlBox = true;
//this.EnableCloseButton (true);
}
//==========================================================================
protected void Save ()
{
// do save
}
//==========================================================================
protected override void OnFormClosing (FormClosingEventArgs e)
{
base.OnFormClosing (e);
if (delayClose) {
e.Cancel = true;
closeActions.Visible = true;
ControlBox = false;
//this.EnableCloseButton (false);
ActiveControl = saveAndCloseButton;
ClientSize = new Size (Math.Max (ClientSize.Width, closeActionsWidth),
Math.Max (ClientSize.Height, closeActions.Height));
return;
}
}
}
////*****************************************************************************
//public static class WindowHelper
//{
// //--------------------------------------------------------------------------
// private const uint SC_CLOSE = 0xF060;
//
// private const uint MF_ENABLED = 0x00000000;
// private const uint MF_GRAYED = 0x00000001;
// private const uint MF_DISABLED = 0x00000002;
//
// //==========================================================================
// [DllImport ("user32.dll")]
// static extern IntPtr GetSystemMenu (IntPtr hWnd, bool bRevert);
//
// //==========================================================================
// [DllImport ("user32.dll")]
// static extern bool EnableMenuItem (IntPtr hMenu, uint uIdEnableItem, uint uEnable);
//
// //==========================================================================
// public static void EnableCloseButton (this Form form, bool bEnabled)
// {
// IntPtr hSystemMenu = GetSystemMenu (form.Handle, false);
//
// EnableMenuItem (hSystemMenu, SC_CLOSE, (bEnabled ? MF_ENABLED : MF_GRAYED | MF_DISABLED));
// }
//}
//*****************************************************************************
public static class App
{
//==========================================================================
public static void Main (string [] args)
{
Application.Run (new EditorForm ());
}
}
Varianten:
Das Snippet zeigt das grundlegende Prinzip. Die konkrete Ausgestaltung bleibt euch überlassen. So ist die Hintergrundfarbe für das Panel natürlich frei wählbar, genauso wie der ButtonStyle (ich habe mich hier für Flat entschieden). Außerdem können die Beschriftungen der Buttons und deren Anordnung beliebig verändert werden, genauso wie die Aktionen, die sie durchführen. Weiterhin könnte man die Höhe des Forms beim Einblenden des Panels um dessen Höhe vergrößern und beim Ausblenden entsprechend verkleinern. Aktuell wird nur sichergestellt, dass das Form groß genug ist bzw. wird, damit der Inhalt des Panels komplett sichtbar ist.
Natürlich muss man die Buttons und das Panel nicht per handgeschriebenem Code erzeugen, sondern kann sie auch mit den Visual Studio Designer erstellen. Wenn man viele verschiedene Abfragen hat, kann es sinnvoll sein, das Panel und die Buttons erst bei Bedarf zu erzeugen und anschließend zu zerstören. Wenn das eigentliche Fenster nicht nur aus einem Control besteht (hier: RichTextBox), sondern aus mehreren, bietet es sich an, diese Controls auf ein zusätzliches Panel mit DockStyle.Fill zu packen.
Ich habe mit mir gerungen, ob ich - während das Panel angezeigt wird - die Buttons in der Titelleiste ausblenden soll (ControlBox = false) oder nicht. Der Nachteil ist, dass man das Fenster wegen des dann fehlenden Buttons nicht mehr minimieren kann, solange die Abfrage angezeigt wird, obwohl ein Minimieren an sich problemlos möglich wäre. Maximieren geht per Doppelklick auf die Titelleiste weiterhin. Trotzdem habe ich mich für das Ausblenden entschieden, weil ich während der Tests regelmäßig der Versuchung erlegen bin, das Fenster durch einen erneuten Klick auf das X endgültig zu schließen, was ja gerade nicht möglich ist und nicht möglich sein soll. Nach dem (vollständigen) Ausblenden lenkte sich meine Aufmerksamkeit von alleine auf die Abfrage. Natürlich könnte man den X Button auch nur ausgrauen (der Code auf Basis von Schließen-Button (X) von Form ausblenden oder ausgrauen ist auskommentiert im Snippet enthalten). Das war mir persönlich allerdings zu dezent. Aber experimentiert ruhig selbst damit.
[EDIT]Weitere Varianten werden in den folgenden Beiträgen vorgeschlagen.[/EDIT]
Wenn man solche Abfragen häufiger benutzen möchte, bietet es sich an, eine eigene Klasse dafür zu schreiben, der man dann z.B. nur noch die Anzahl und Beschriftung der Buttons übergeben muss und die per eigenem Event mitteilt, welcher Button geklickt wurde. Wer so eine Klasse schreibt, kann sie gerne hier veröffentlichen.
Schlagwörter: 1000 Worte, modal, nichtmodal, nicht-modal, modale, nichtmodale, nicht-modale MessageBox, MessageBoxen, Dialog, Dialoge, Abfrage, Abfragen, Sicherheitsabfrage, Sicherheitsabfragen, Security Query, Queries, Question, Questions, Confirmation Prompt, Message, Input, InputBox, InputBox, InputBoxen
Screenshot: (erstes Bild: normale Ansicht; zweites Bild: Ansicht, nachdem auf das X geklickt wurde)