MDI-Anwendungen in C# - Jürgen Bayer

Die Seite wird erstellt Leonard Timm
 
WEITER LESEN
Jürgen Bayer

MDI-Anwendungen in C#
Inhaltsverzeichnis

1     Grundlagen                                          2

1.1     Einrichten der Formulare                          2

1.2     Öffnen von MDI-Childformularen                    3

2     Menüs                                              4

2.1     Erstellen eines Menüs                             4

2.2     Programmierung der Menüpunkte für das Beispiel    6

2.3     Ein Fenster-Menüpunkt                            10

3     Zugriff auf die Childs und den Parent              11

3.1     Zugriff auf MDI-Childs vom MDI-Parent aus        11

3.2     Zugriff auf dem MDI-Parent von MDI-Childs aus    12
MDI-Anwendungen starten mit einem Formular, dessen Eigenschaft IsMdiContainer auf true eingestellt
ist. Diese Formulare werden dann als MDI-Parent bezeichnet. Wenn Sie ausgehend von einem solchen
Formular andere Formulare öffnen, können Sie das Startformular nun über die Eigenschaft MDIParent als
MDI-Parent des anderen Formulars angeben. Das andere Formular wird dann zum MDI-Child. MDI-Child-
Formulare werden immer innerhalb des MDI-Parent angezeigt. Zusätzliche Features, wie das einfache
Einrichten eines Fenster-Menüpunkts, der automatisch alle geöffneten MDI-Childs auflistet, das
halbautomatische Ausrichten der MDI-Childs über die Methode LayoutMDI, und die Tatsache, dass das Menü
eines MDI-Childs in das Menü des MDI-Parent eingeblendet wird, wenn das MDI-Child-Formular aktiv ist,
erleichtern die Programmierung solcher Anwendungen. Microsoft Word ist übrigens ein gutes Beispiel für eine
MDI-Anwendung: Das Hauptfenster von Word ist der MDI-Parent, die geöffneten Dokumente sind MDI-
Childs.
Für eine MDI-Anwendung benötigen Sie zunächst ein Startformular, dessen Eigenschaft IsMdiContainer
auf true eingestellt ist. Das Formular sollte mit einem Menü ausgerüstet werden.

Abbildung 1.1: Ein MDI-Parent-Formular mit Menü

Ich entwickle in diesem Artikel eine einfache Textverarbeitung. Das MDI-Parentformular sollte dazu mit den
Menüpunkten entsprechend Abbildung ausgerüstet werden. Die Menüpunkte Speichern und Schließen werden
später Teil des MDI-Child-Formulars.
MDI-Child-Formulare sind normale Formulare, die auch ein Menü besitzen können. Das Beispiel verwendet
ein Formular mit einem RichTextBox-Steuerelement, in dem der Anwender einen RTF-Text bearbeiten kann.

                                                                                            Grundlagen 2
Abbildung 1.2: Ein Formular mit RichTextBox und Menü als Basis für ein MDI-Child-Formular

Das Format-Menü des Child-Formulars enthält im Beispiel die Menüpunkte Automatischer Zeilenumbruch
und Schriftart.
Bevor ich die Programmierung der Menüpunkte und des Konstruktors des Child-Formulars beschreibe, zeige
ich, wie Sie dieses grundsätzlich öffnen und wie Sie das Menü so einstellen, dass dieses korrekt in das Menü
des Parent-Formulars eingeblendet wird.

Öffnen können Sie ein MDI-Child-Formular wie ein normales Formular. Vor dem Öffnen setzen Sie die
Eigenschaft MDIParent des Formulars allerdings auf das MDI-Parent-Formular:
private void mnuFileNew_Click(object sender,
   System.EventArgs e)
{
   // Neues Editierformular erzeugen
   EditForm frmEdit = new EditForm();

    // und unmodal als MDI-Child anzeigen
    frmEdit.MdiParent = this;
    frmEdit.Show();
}

                                                                                             Grundlagen 3
Das Menü des Child-Formulars wird abhängig von den Eingeschaften MergeType und MergeOrder der
einzelnen Menüeinträge in das Menü des Parent-Formulars eingeblendet. Für MergeType sind die in Tabelle 1
dargestellten Einstellungen möglich. Beachten Sie, dass Sie die Einstellung für die korrespondierenden
Menüpunkte im Child- und im Parent-Formular vornehmen müssen.
Wert                Bedeutung
Add                 Diese Einstellung gilt für einzelne Menüpunkte und bewirkt, dass der Menüpunkt dem
                    Menü des MDI-Parentformulars hinzugefügt wird, auch wenn es bereits einen Menüpunkt
                    mit derselben Beschriftung auf derselben Menüebene gibt. Bei gleich beschrifteten
                    Menüpunkten auf einer Ebene existieren dann zwei Einträge.

Replace             Diese Einstellung gilt ebenfalls für einzelne Menüpunkte und bewirkt, dass der Menüpunkt
                    des Childformulars einen vorhandenen gleich beschrifteten Menüpunkt des
                    Parentformulars auf derselben Ebene ersetzt.
MergeItems          Diese Einstellung gilt für die Untermenüpunkte eines Menüpunkts und bewirkt, dass das
                    Menü des Childformulars mit einem gleichnamigen Menü des Parentformulars zu einem
                    einzelnen Menüpunkt zusammengefasst wird. Die Art der Zusammenfassung hängt von
                    der Einstellung der untergeordneten Menüpunkte ab (Add, Replace oder Remove).
Remove              Diese Einstellung gilt wieder für einzelne Menüpunkte und bewirkt, dass der Menüpunkt
                    entfernt wird, wenn zwei Menüs kombiniert werden. Sinnvoll ist diese Einstellung für
                    Menüpunkte des Parentformulars, die nicht sichtbar sein sollen, wenn ein Childformular
                    geöffnet ist.

Tabelle 1: Die Einstellungen der MergeType-Eigenschaft für Menüpunkte

Wenn die Untermenüpunkte zweier Menüs kombiniert werden, hängt die Position der Menüpunkte von der
Eigenschaft MergeOrder ab.

                                                                                                   Menüs 4
Die Menüpunkte des Datei-Menüs des Textverarbeitungs-Beispiels sind folgendermaßen eingestellt, damit
dieses Menü korrekt kombiniert wird:
Formular                 Menüpunkt                MergeType                 MergeOrder
Parent                   &Datei                   MergeItems                0
Parent                     &Neu                   Add                       0
Parent                     &Öffnen                Add                       1
Parent                     -                      Add                       5
Parent                     &Beenden               Add                       6
Child                    &Datei                   MergeItems                0
Child                      -                      Add                       2
Child                      &Speichern             Add                       3
Child                      S&chließen             Add                       4
Tabelle 2: Einstellungen der MergeType- und MergeOrder-Eigenschaft der Beispielanwendung

Abbildung 2.1 zeigt das MDI-Parentformular ohne geöffnetes Childformular, in Abbildung 2.2 ist ein
Childformular geöffnet (und damit das Datei-Menü des MDI-Parent mit dem Datei-Menü des MDI-Child
kombiniert).

Abbildung 2.1: Das MDI-Parentformular der Beispielanwendung ohne geöffnetes Childformular

                                                                                            Menüs 5
Abbildung 2.2: Das MDI-Parentformular der Beispielanwendung mit geöffnetem Childformular

Die Menüpunkte, die zum Childformular gehören, beziehen sich immer auf das gerade aktive Childformular.
Die Programmierung der Menüpunkte ist damit sehr einfach.

                                                                       !        "          #    !
Der Neu-Menüpunkt des MDI-Parentformulars erzeugt ein neues MDI-Childformular. Der Öffnen-Menüpunkt
erzeugt ein neues MDI-Childformular, öffnet einen Datei-Öffnen-Dialog und öffnet die vom Anwender
ausgewählte Datei im RichTextBox-Steuerelement des MDI-Childformulars. Das Öffnen der Datei geschieht
im Konstuktor des Childformulars, weswegen dieser ein Argument erhält, das definiert, ob ein neues
Dokument erzeugt oder ein vorhandenes geöffnet werden soll. Zur Verwaltung der Dokumenteninformationen
enthält die Klasse drei Eigenschaften:
// Eigenschaften zur Verwaltung des Dokuments
private string fileName = null;
public string FileName
{
   get {return fileName;}
}

private static int counter = 0;

// Eigenschaft, die die Modified-Eigenschaft
// des RichTextBox-Steuerelements veröffentlicht
public bool Modified
{
   get {return rtbDocument.Modified;}
}

Der Konstruktor sieht so aus:
public EditForm(bool openExisting)
{
   InitializeComponent();
   if (openExisting)
   {
      // Vorhandenes Dokument öffnen.
      // Die eventuelle Ausnahme, die beim
      // Öffnen erzeugt wird, wird einfach
      // an den Aufrufer weitergegeben
      ofdRtfFile.InitialDirectory =
         Application.StartupPath;
      if (ofdRtfFile.ShowDialog() == DialogResult.OK)
      {
         // Datei laden

                                                                                               Menüs 6
rtbDocument.LoadFile(ofdRtfFile.FileName,
             RichTextBoxStreamType.RichText);

          // Dokument als nicht modifiziert kennzeichnen
          rtbDocument.Modified = false;

          // Dateiname merken
          this.fileName = ofdRtfFile.FileName;

          // Beschriftung setzen
          System.IO.FileInfo fi =
             new System.IO.FileInfo(this.fileName);
          this.Text = fi.Name;
       }
       else
       {
          // Ausnahme werfen
          throw new CancelException();
       }
    }
    else
    {
       // Neues Dokument
       counter++;
       this.Text = "Dokument " + counter;
    }
}

Das Argument openExisting steuert, ob eine vorhandene Datei geöffnet werden soll. Soll ein vorhandenes
Dokument geöffnet werden (openExisting == true), öffnet das Programm einen Datei-Öffnen-Dialog
(ofdRtfFile). Wenn der Anwender eine Datei ausgewählt hat, wird diese geöffnet. Ausnahmen, die dabei
eventuell erzeugt werden, werden einfach an den Aufrufer weitergegeben. Betätigt der Anwender den
Abbrechen-Schalter im Datei-Öffnen-Dialog, erzeugt das Programm einfach eine neue Ausnahme, deren
Klasse in der Datei folgendermaßen implementiert ist:
public class CancelException: Exception
{
}

Im Konstruktor wird die Eigenschaft Modified des RichTextBox-Steuerelements auf false gesetzt. Über
diese Eigenschaft ermittelt das Programm beim Schließen des Formulars, ob der Text im Steuerelement
geändert wurde.
Im MDI-Parent-Formular sieht der Quellcode des Neu- und des Öffnen-Menüpunkts folgendermaßen aus:
private void mnuFileNew_Click(object sender,
   System.EventArgs e)
{
   // Neues Editierformular erzeugen
   EditForm frmEdit = new EditForm(false);

    // und unmodal als MDI-Child anzeigen
    frmEdit.MdiParent = this;
    frmEdit.Show();
}

private void mnuFileOpen_Click(object sender,
   System.EventArgs e)
{
   // Neues Editierformular erzeugen
   bool ok = false; EditForm frmEdit = null;
   try
   {
       // Der Konstruktor ruft den Datei-Öffnen-Dialog
       // auf und erzeugt eine Ausnahme, wenn die Datei
       // nicht geöffnet werden kann oder wenn der
       // Anwender den Abbrechen-Schalter betätigt
       frmEdit = new EditForm(true);
       ok = true;
   }

                                                                                             Menüs 7
catch (CancelException)
    {
       // Nichts machen
    }
    catch (Exception ex)
    {
       MessageBox.Show(ex.Message,
          Application.ProductName,
          MessageBoxButtons.OK,
          MessageBoxIcon.Error);
    }
    if (ok)
    {
       // Wenn alles OK ist, Form unmodal
       // als MDI-Child anzeigen
       frmEdit.MdiParent = this;
       frmEdit.Show();
    }
}

Der Speichern-Menüpunkt des MDI-Childformulars ruft die Methode SsaveFile auf:
private void mnuFileSave_Click(object sender,
   System.EventArgs e)
{
   // Dokument speichern
   this.saveFile();
}

// Methode zum Speichern
private bool saveFile()
{
   // Dateiname ermitteln
   if (fileName == null)
   {
      // Datei-Speichern-Dialog öffnen
      sfdRtfFile.InitialDirectory =
         Application.StartupPath;
      if (sfdRtfFile.ShowDialog() == DialogResult.OK)
      {
         fileName = sfdRtfFile.FileName;
      }
   }

    // Datei speichern
    if (fileName != null)
    {
       try
       {
           // Datei speichern
           rtbDocument.SaveFile(fileName,
              RichTextBoxStreamType.RichText);

           // Dokument als nicht modifiziert kennzeichnen
           rtbDocument.Modified = false;

           // Beschriftung setzen
           System.IO.FileInfo fi =
              new System.IO.FileInfo(this.fileName);
           this.Text = fi.Name;
           return true;
        }
        catch (Exception ex)
        {
           MessageBox.Show(ex.Message,
             Application.ProductName,
             MessageBoxButtons.OK,
             MessageBoxIcon.Error);
           return false;
        }
    }

                                                                                 Menüs 8
else
    {
       return false;
    }
}

Der Schließen-Menüpunkt schließt das Formular (was auch sonst ...):
private void mnuFileClose_Click(object sender,
   System.EventArgs e)
{
   // Dokument schließen (die Abfrage, ob gespeichert
   // werden soll, erfolgt im Closing-Ereignis)
   this.Close();
}

Im Closing-Ereignis des Formulars fragt das Programm ab, ob das Dokument geändert wurde und ruft
gegebenenfalls die Save-Methode auf:
private void EditForm_Closing(object sender,
   System.ComponentModel.CancelEventArgs e)
{
   // Überprüfen, ob das Dokument geändert wurde
   if (rtbDocument.Modified)
   {
      // Abfragen, ob gespeichert werden soll
      switch (MessageBox.Show("Möchten Sie Ihre " +
         "Änderungen an '" + this.Text + "' speichern?",
         Application.ProductName,
         MessageBoxButtons.YesNoCancel,
         MessageBoxIcon.Question))
      {
         case DialogResult.Yes:
            if (this.saveFile() == false)
            {
               // Wenn nicht gespeichert werden kann,
               // wird das Schließen abgebrochen
               e.Cancel = true;
            }
            break;

            case DialogResult.Cancel:
               // Abbrechen
               e.Cancel = true;
               break;
        }
    }
}

Nun fehlen zunächst noch die Ereignisbehandlungsmethoden für die Menüpunkte des Format-Menüs. Die
Methode für die Schriftart nutzt einen Schriftauswahldialog (fdRtfFont):
private void mnuFormatFont_Click(object sender,
   System.EventArgs e)
{
   // Schriftart der aktuellen Selektion definieren
   if (fdRtfFont.ShowDialog() == DialogResult.OK)
   {
      rtbDocument.SelectionFont = fdRtfFont.Font;
      rtbDocument.SelectionColor = fdRtfFont.Color;
   }
}

                                                                                        Menüs 9
Die Methode für den automatischen Zeilenumbruch schaltet die Eigenschaft WordWrap des RichTextBox-
Steuerelements um:
private void mnuFormatWordWrap_Click(object sender,
   System.EventArgs e)
{
   // WordWrap umschalten
   mnuFormatWordWrap.Checked =
      !mnuFormatWordWrap.Checked;
   rtbDocument.WordWrap = mnuFormatWordWrap.Checked;
}

   $                                     !      "
Das MDI-Parent-Formular besitzt normalerweise ein Menü Fenster, über den Sie die einzelnen geöffneten
Dokumente erreichen und ausrichten können. Eine Liste der geöffneten Dokumente erhalten Sie automatisch,
wenn Sie die Eigenschaft MdiList dieses Menüs auf true setzen. Für die üblichen Menüpunkte zum
Ausrichten der Fenster nutzen Sie die LayoutMdi-Methode:
private void mnuWindowCascade_Click(object sender,
   System.EventArgs e)
{
   // Fenster kaskadierend anordnen
   this.LayoutMdi(MdiLayout.Cascade);
}

private void mnuWindowTileVertical_Click(object sender,
   System.EventArgs e)
{
   // Fenster vertikal geteilt anordnen
   this.LayoutMdi(MdiLayout.TileVertical);
}

private void mnuWindowTileHorizontal_Click(object sender,
   System.EventArgs e)
{
   // Fenster horizontal geteilt anordnen
   this.LayoutMdi(MdiLayout.TileHorizontal);
}

private void mnuWindowArrangeIcons_Click(object sender,
   System.EventArgs e)
{
   // Fenstersymbole anordnen
   this.LayoutMdi(MdiLayout.ArrangeIcons);
}

                                                                                             Menüs 10
$%

$       %
Wenn Sie vom MDI-Parent aus auf MDI-Childs zugreifen wollen, können Sie die Eigenschaft
ActiveMdiChild verwenden, die eine Referenz auf das gerade aktive MDI-Childformular darstellt. Im
Ereignis MdiChildActivate können Sie darauf reagieren, dass ein MDI-Childformular aktiviert wurde. Das
folgende Beispiel schreibt den Dateinamen des aktiven Dokuments und eine Information darüber, ob das
Dokument ungespeicherte Änderungen enthält, in ein StatusBar-Steuerelement auf dem MDI-Parent-Formular:
// Methode zum Setzen des Textes der StatusBar
public void SetStaturBarText()
{
   if (ActiveMdiChild != null)
   {
      sbr.Text = ((EditForm)ActiveMdiChild).FileName;
      if (((EditForm)ActiveMdiChild).Modified)
      {
         sbr.Text += " (geändert)";
      }
   }
   else
   {
      sbr.Text = "";
   }
}

private void StartForm_MdiChildActivate(object sender,
   System.EventArgs e)
{
   // Wenn ein MDI-Child-Formular aktiviert wird:
   // Den Dateinamen des Dokuments und eine Information
   // darüber, ob die letzten Änderungen am Text noch
   // nicht gespeichert sind, in der StatusBar ausgeben
   this.SetStaturBarText();
}

                                                              Zugriff auf die Childs und den Parent 11
$       %
Ein MDI-Childformular kann über seine Eigenschaft MdiParent auch auf das MDI-Parentformular zugreifen.
Das folgende Beispiel erweitert die Speichern-Methode so, dass beim Speichern die SetStaturBarText-
Methode des MDI-Parent aufgerufen wird:
bool saveFile()
{
   // Dateiname ermitteln
   if (fileName == null)
   {
      // Datei-Speichern-Dialog öffnen
      sfdRtfFile.InitialDirectory =
         Application.StartupPath;
      if (sfdRtfFile.ShowDialog() == DialogResult.OK)
      {
         fileName = sfdRtfFile.FileName;
      }
   }

    // Datei speichern
    if (fileName != null)
    {
       try
       {
           // Datei speichern
           rtbDocument.SaveFile(fileName,
              RichTextBoxStreamType.RichText);

          // Dokument als nicht modifiziert kennzeichnen
          rtbDocument.Modified = false;

          // Beschriftung setzen
          System.IO.FileInfo fi =
             new System.IO.FileInfo(this.fileName);
          this.Text = fi.Name;

          // StatusBar-Text des MDI-Parent neu definieren
          ((StartForm)this.MdiParent).SetStaturBarText();

          return true;
       }
       catch (Exception ex)
       {
          MessageBox.Show(ex.Message,
            Application.ProductName,
            MessageBoxButtons.OK,
            MessageBoxIcon.Error);
          return false;
       }
    }
    else
    {
       return false;
    }
}

                                                             Zugriff auf die Childs und den Parent 12
Sie können auch lesen