myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
» Regeln
» Wie poste ich richtig?
» Forum-FAQ

Mitglieder
» Liste / Suche
» Wer ist wo online?

Ressourcen
» openbook: Visual C#
» openbook: OO
» Microsoft Docs

Team
» Kontakt
» Übersicht
» Wir über uns

» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Gemeinschaft » .NET-Komponenten und C#-Snippets » [Snippet] Nicht-modale Abfrage als Alternative für MessageBoxen
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

[Snippet] Nicht-modale Abfrage als Alternative für MessageBoxen

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
herbivore
myCSharp.de-Poweruser/ Experte

avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.461
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

[Snippet] Nicht-modale Abfrage als Alternative für MessageBoxen

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Beschreibung:

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]

C#-Code:
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)

herbivore hat dieses Bild (verkleinerte Version) angehängt:
nicht-modale_abfrage.png
Volle Bildgröße

13.09.2012 10:39 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
bredator bredator ist männlich
myCSharp.de-Mitglied

avatar-155.gif


Dabei seit: 08.09.2010
Beiträge: 350
Entwicklungsumgebung: VS 2017 Pro, VS Code


bredator ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Danke für das Snippet. Sieht sehr gut aus und ist tatsächlich eine sehr gute Alternative für modale Dialoge. ;)
14.09.2012 09:03 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
blutiger_anfänger blutiger_anfänger ist männlich
myCSharp.de-Mitglied

avatar-178.gif


Dabei seit: 26.04.2008
Beiträge: 293
Entwicklungsumgebung: VS 2013
Herkunft: Meerbusch, NRW


blutiger_anfänger ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top


mycsharp.de  Moderationshinweis von herbivore (16.09.2012 08:53):

Zur Info: Der folgende Code ändert - wenn das Panel bereits angezeigt und erneut auf Schließen (X) geklickt wird - für einen kurzen Moment die Hintergrundfarbe des Panels im schnellen Wechsel zwischen rot und blau, so dass es auffällig flackert.
 

Ich finde das ControlBox ausbleden eher ungünstig. Ich denke, dass das unnatürlich aussieht und die User verwirren könnte. Wenn du die Aufmerksamkeit auf das Panel lenken willst, warum dann nicht zum Beispiel so?

C#-Code:
protected override void OnFormClosing(FormClosingEventArgs e)
    {
        base.OnFormClosing(e);
        if (delayClose && !closeActions.Visible)
        {
            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;
        }
        else if (delayClose && closeActions.Visible)
        {
            e.Cancel = true;
            closeActions.Tag = 0;
            Timer tiBlink = new Timer();
            tiBlink.Interval = 50;
            tiBlink.Tick += new EventHandler(tiBlink_Tick);
            tiBlink.Start();
            return;
        }
    }

    void tiBlink_Tick(object sender, EventArgs e)
    {
        int state = ((int)closeActions.Tag);
        if (state < 12)
        {
            if (closeActions.BackColor != Color.LightCoral)
                closeActions.BackColor = Color.LightCoral;
            else
                closeActions.BackColor = Color.FromArgb(165, 201, 239);

            closeActions.Tag = (state + 1);
        }
        else
        {
            closeActions.BackColor = Color.FromArgb(165, 201, 239);
            (sender as Timer).Stop();
        }
    }

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von blutiger_anfänger am 15.09.2012 12:34.

15.09.2012 12:33 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.461
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo blutiger_anfänger,

darüber, was (un)natürlich wirkt, kann man sicher geteilter Meinung sein. Deshalb habe ich zu eigenen Experimenten aufgefordert und als ein solches begrüße ich deinen Vorschlag. Jeder kann selbst entscheiden, welche Variante er wählt oder ob er eine weitere Variante implementieren will.

herbivore
16.09.2012 09:00 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
chilic
myCSharp.de-Poweruser/ Experte

Dabei seit: 12.02.2010
Beiträge: 2.005


chilic ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo

Lob! Als jemand der kürzlich eine Diskussion darüber angestoßen hat, finde ich das wirklich interessant und cool dass du dir darüber Gedanken gemacht hast.

Wie man mit der ControlBox oder der "Modalität" des Panels umgeht, kann man ja selbst je nach Anforderung regeln. Obs ungewohnt wirkt oder in welcher Ausführung es praktikabel ist, hängt ja auch vom aktuellen Fall ab.

In meinem Fall, einem evtl. selten genutzen aber dafür für den Betrieb einflussreichem Formular würde ich den Hinweis über das ganze Formular einblenden damit der Benutzer auswählen müssen (praktisch modal) was er tun will. Das gibt klare Verhältnisse vor, zum Beispiel: ok ich arbeite jetzt doch noch weiter und nehme damit zur Kenntnis dass noch nichts gespeichert wurde und habe auch eine Reaktion auf den Schließen-Button bemerkt.
Sonst verliert man sich im weiterarbeiten, das Panel gerät in Vergessenheit und beim nächsten Klick aufs X passiert nichts mehr (Panel ist ja schon da). Anruf folgt :-)

Aber man hat ja alle Freiheiten, das Panel einzublenden. Und vor allem auch bei den Hinweistexten, die man viel beschreibender machen kann als eine MessageBox es kann.

Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von chilic am 16.09.2012 19:13.

16.09.2012 19:06 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Poweruser/ Experte

avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 49.461
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin

Themenstarter Thema begonnen von herbivore

herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo chilic,

richtig, das Prinzip kann man nicht nur für Abfragen, sondern auch für Hinweistexte oder sogar Fehlermeldungen anwenden. Ich habe jetzt einen entsprechenden Absatz in den Startbeitrag eingefügt.

Dass man weiterarbeiten kann, während das Panel angezeigt wird, halte ich - gerade bei Fehlermeldungen, aber auch sonst - für den entscheidenden Vorteil. Denn es ist mir schon oft passiert, dass ich eine Fehlermeldung weggeklickt habe (und musste), um das eigentliche Fenster zur Fehlerbehebung wieder bedienen zu können ... und dann nicht mehr (ausreichend genau) wusste, was in der MessageBox stand. Das eingeblendete Panel kann man dagegen solange stehen lassen, bis man den Fehler tatsächlich behoben hat. Das ist besonderes nützlich, wenn mehrere Schritte zur Fehlerbehebung nötig sind und diese im Meldungstext somit während der gesamten Fehlerbehebung angezeigt werden.

Aber selbst wenn man sich bei einer Abfrage entscheidet, das Panel mit DockStyle.Fill (statt DockStyle.Top) zu versehen und damit ein inhaltliches Weiterarbeiten mit dem Fenster praktisch unmöglich macht, bleibt wenigstens noch der Vorteil, dass das Fenster selbst frei verschiebbar bleibt. Trotzdem sehe ich diese Möglichkeit klar als die schlechtere Wahl. Aber natürlich kann das jeder selbst entscheiden.

Um die kleine Klippe zu umschiffen, dass das Panel von Benutzer nicht bemerkt oder über das Weiterarbeiten ganz vergessen wird, wurden mehrere Vorschläge gemacht und es gibt sicher noch weitere Möglichkeiten. Diesen Punkt bekommt man also sicher in den Griff. Wenn man ganz sicher gehen will, könnte man beim zweiten (oder n-ten) Klick auf den Schließen-(X)-Button, das Panel wieder ausblenden und eine normale MessageBox anzeigen. Quasi als Rückfallmöglichkeit für die Unverbesserlichen. :-) Aber auch das ist nur ein Vorschlag und jeder muss für sich selbst die Vor- und Nachteile abwägen.

Natürlich stellt sich bei Neuerungen immer auch die Frage, wie die Benutzer damit klarkommen, ob sie die Vorteile für sich erkennen oder es dagegen als Verschlechterung ansehen. Aber das kann man auf der theoretischen Ebene nicht abschließend klären. Im Zweifel rechtfertigen die objektiv vorhandenen Vorteile bei gleichzeitig geringem Realisierungsaufwand mindestens ein Praxisexperiment. Meine Freundin, die sicher kein Computerfreak ist, kam jedenfalls ohne jegliche Erklärung (nur: "Klick mal aufs X") mit dem Panel auf Anhieb prima klar.

herbivore
17.09.2012 08:02 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Lennart Lennart ist männlich
myCSharp.de-Mitglied

Dabei seit: 25.08.2008
Beiträge: 416
Entwicklungsumgebung: VS 2010 Prof.
Herkunft: Bawü


Lennart ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hi,

verwende das Panel zur Zeit um Eingabeparameter aus dynamischen Vorlagen abzufragen. Weiß nicht ob das deine Intention war aber mir gefällts ganz gut.
Danke also auch von mir für die Vorlage!

Lennart hat dieses Bild (verkleinerte Version) angehängt:
Abfrage.png
Volle Bildgröße

25.09.2012 12:59 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegen mehr als 8 Monate.
tom-essen tom-essen ist männlich
myCSharp.de-Poweruser/ Experte

avatar-2140.png


Dabei seit: 15.05.2005
Beiträge: 1.815
Entwicklungsumgebung: VS.NET 2013
Herkunft: NRW


tom-essen ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo,

habe gerade noch einen anderen Vorteil entdeckt:
Wenn man die jeweiligen Inhalte für diesen Bereich als einzelne Controls "anbietet" (also dann auch ganz im Sinne von WPF), hat man für spätere Szenarien eine bessere Kontrolle, diese Inhalte rel. schnell in anderen Controls (Forms, tabPages, ...) zu verwenden.

edit: Ergänzung, um die Aussage etwas klarer zu machen.
Wenn man z.B. eine Liste mit zentralen Controls verwaltet, oder einen InjectionContainer, oder einfach nur zentrale Methoden, welche einem ein für einen bestimmten zweck geeignetes Control zurückgeben (z.B. Eingabe der Zugangsdaten für eine SQL-verbindung), dann kann man hier relativ schnell umschalten zwischen modalen Dialogen oder dem von herbivore vorgeschlagenen Weg, da in beiden Fällen einfach nur die zentrale Methode, Liste, ... abgerufen und das Control eingebunden werden muss.

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von tom-essen am 05.08.2013 15:13.

24.06.2013 13:08 Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 7 Jahre.
Der letzte Beitrag ist älter als 6 Jahre.
Antwort erstellen


© Copyright 2003-2019 myCSharp.de-Team | Impressum | Datenschutz | Alle Rechte vorbehalten. | Dieses Portal verwendet zum korrekten Betrieb Cookies. 18.09.2019 13:20