Entwickler-Ecke

WinForms - Multiple Window Anwendung realisieren


JohnDyr - Do 06.09.18 11:12
Titel: Multiple Window Anwendung realisieren
Moin,

ich bin grade dabei eine Anwendung zu realisieren, welche eine Seitenleiste hat mit mehreren Knöpfen und ein Hauptfenster rechts daneben.

Skizze:

Quelltext
1:
2:
3:
4:
5:
6:
7:
------------------------------
|.Btn1.|.....................|
|------|.....................|
|.Btn2.|.....Hauptfenster....|
|------|.....................|
|.Btn3.|.....................|
------------------------------

Bei der Umsetzung habe ich für das Hauptfenster mehrere überlappende Panels verwendet, welche abhängig vom Btn.Click das Attribut Visibility ändern. Z.B. Btn1.Click -> Panel1.Visible = true; Panel2.Visibilty = false; Panel3.Visibilty = false; etc.

Das funktioniert zwar, allerdings Frage ich mich ob dies die beste Möglichkeit ist. Die Entwicklung bei Anwendung dieses Vorgehens ist ab und an mühselig und etwas hackelig. Vielleicht liegt das daran, dass ich aktuell 6 überlappende Panels habe, wobei das ja eigentlich nicht besonders viel ist...

Lange Rede kurzer Sinn: Würdet ihr das auch so machen?

Moderiert von user profile iconTh69: C#-Tags hinzugefügt


Th69 - Do 06.09.18 13:24

Hallo und :welcome:

ich würde dir raten, für die Panels eigenständige Benutzersteuerelemente (UserControls) zu verwenden, da du diese dann getrennt im Designer bearbeiten kannst. So überfrachtest du auch nicht das Hauptformular mit Aberdutzenden von Komponenten. Desweiteren hast du eine bessere Kapselung und kannst eine einheitliche Schnittstelle dazu anbieten.
Und im Hauptformular (bzw. ich würde daraus dann auch ein UserControl erzeugen und dieses dann im Hauptformular verwenden), würde ich einfach eine List<UserControl> (oder explizit List<IMyUserControl>) als Schnittstelle anbieten, so daß die Buttons dann dynamisch generiert werden und das passende UserControl dann bei Klick angezeigt wird.


JohnDyr - Do 06.09.18 13:58

Danke für den Tipp mit den UserControls. Ich bin mit VisualStudio noch nicht so firm und kenne mich mit den ganzen einzelnen Designelementen noch nicht so gut aus. Ich werde mein Glück damit probieren, komme aber bei Fragen nochmal ggf. zurück aufs Forum.

Danke soweit :-)


Th69 - Do 06.09.18 14:18

Noch ein Tipp:
Wenn du ein UserControl erstellt hast, kannst du im Designer per Copy&Paste (bzw. Cut&Paste) die untergeordneten Panel-Elemente (aus deinem bisherigen Hauptformular) in das UserControl kopieren (bzw. verschieben). So brauchst du dann diese nicht wieder neu erzeugen. Falls du schon Code für einzelne Elemente geschrieben hast (z.B. Initialisierung, Ereignismethoden, ...), so mußt du diese dann ebenfalls herüberkopieren.


JohnDyr - Fr 12.10.18 10:44

Das funktioniert wunderbar, habe es eben umgesetzt! Danke vielmals :)


JohnDyr - So 25.11.18 21:38

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Noch ein Tipp:
Wenn du ein UserControl erstellt hast, kannst du im Designer per Copy&Paste (bzw. Cut&Paste) die untergeordneten Panel-Elemente (aus deinem bisherigen Hauptformular) in das UserControl kopieren (bzw. verschieben). So brauchst du dann diese nicht wieder neu erzeugen. Falls du schon Code für einzelne Elemente geschrieben hast (z.B. Initialisierung, Ereignismethoden, ...), so mußt du diese dann ebenfalls herüberkopieren.


Hi,

ich habe nun leider doch ein Problem mit den UCs. Wie gehe ich am besten vor, wenn ich aus einem User Control heraus ein anderes aufrufen möchte? Angenommen ich habe UC A und dort ist ein Button, welcher UC A schließen und UC B aufrufen soll. Stehe da etwas auf dem Schlauch :(


Th69 - Mo 26.11.18 10:31

Das ist ein typisches Problem, zu dem ich auch einen Artikel verfasst habe: Kommunikation von 2 Forms [http://www.bitel.net/dghm1164/programming/Kommunikation_von_2_Forms.html] (gilt aber allgemein für Klassen, also auch UserControls).

UserControls sind trotzdem aber keine Forms, so daß diese nicht geschlossen werden können (höchstens auf der Form versteckt).
Willst du also von einem UserControl zu einem anderen UserControl in deiner Anwendung wechseln, so solltest du ein Ereignis (event) bereitstellen, welches dann von der Hauptform abonniert wird (da dieses ja die Liste aller UserControls enthält) und das führt dann den Wechsel durch (analog zum Button-Klick) - in meinem Artikel unter "Lösung: Verwendung von Eigenschaften (Properties) und Ereignissen (Events) / 2. Ereignisse" zu finden.


JohnDyr - Mo 26.11.18 11:07

Besten Dank, ich lese mir den Artikel gleich durch :-)

VG,
JohnDyr

Edit:

Ziemlich gut erklärt! Werde das später Zuhause direkt mal ausprobieren. Thx :)


JohnDyr - Mo 26.11.18 20:16

Ich habe nun leider doch Schwierigkeiten es umzusetzen. Deshalb versuche ich es hier nochmal...

Nochmal zum Sachverhalt: Mein Programm hat links eines Sidebar, und rechts ein Panel "Master", wo die UserControls angezeigt werden.


Quelltext
1:
2:
3:
4:
5:
6:
---------------------------------------
|.BtnOrders......|.....................|
|----------------|.....................|
|.BtnCreateOrder.|.....PnlMaster.......|
|----------------|.....................|
---------------------------------------


Bei Klick auf BtnOrders wird folgender Code ausgeführt:


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
private void BtnOrders_Click(object sender, EventArgs e)
{
  if (!PnlMaster.Controls.Contains(UcOrders.Instance))
  {
    PnlMaster.Controls.Add(UcOrders.Instance);
    UcOrders.Instance.Dock = DockStyle.Fill;
    UcOrders.Instance.BringToFront();
  }
  else
    UcOrders.Instance.BringToFront();
}


Bei Klick auf BtnCreateOrder wird analog dazu, jedoch mit einer anderen UserControl (UcCreateOrder), diese geöffnet. Innerhalb von UcOrders habe ich ein Button 1. Auf Klick dessen möchte ich, dass sich das UcCreateOrder öffnet. Dem Tutorial zufolge müsste dies ja dann so geschehen, dass aus dem UcOrders heraus ein Event an die Main Form gefeuert wird. Dieses Event muss dazu führen, dass sich UcCreateOrder öffnet. Im Prinzip kann ich also sagen, sobald ich in der Main Form bin, öffne die Form mit derselben Logik wie bei einem Button Click.

Folgende Logik habe ich in UcOrder implementiert:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
public event EventHandler OpenCreateOrder; // Ereignis deklarieren

protected virtual void OnOpenCreateOrder(EventArgs e)
{
  EventHandler ev = OpenCreateOrder;
  if (ev != null)
    ev(this, e); // abonnierte Ereignismethode(n) aufrufen
}

private void button1_Click(object sender, EventArgs e)
{
  OnOpenCreateOrder(e);
}


Jetzt fehlt mir noch die Logik, dass die Main Form dieses Event was in UcOrder getriggert wird, abonnieren muss. Dementsprechend versuche ich etwas wie...


Quelltext
1:
2:
3:
4:
5:
public FrmMain()
{
  InitializeComponent();
        ucOrders.OpenCreateOrder += XYZ; // wenn das Event OpenCreateOrder gefeuert wird, führe BtnCreateOrder_Click aus.
}


Auf das XYZ komme ich nicht... wie muss ich das Umsetzen? Stehe total auf dem Schlauch gerade :( :(

Bin für jede Hilfe dankbar.


Ralf Jansen - Mo 26.11.18 20:32

Sobald du in Visual Studio das += für den Delegaten getippt hast einfach mal auf den Tooltip warten und machen was in dem Tooltip steht ;) Hint: TAB klicken
Dann bekommst du automatisch die passende Methode generiert die du nutzen kannst.


JohnDyr - Mo 26.11.18 20:50

Nun, ich habe dadurch ein Vorschlag bekommen und diesen implementiert. Der Code lässt sich auch kompilieren und ausführen. Beim klick auf den button1 in UcOrders passiert allerdings nichts :(

Folgenden Code habe ich bisher implementiert:

UcOrder.cs


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
public event EventHandler<EventArgs> OpenCreateOrder; // Ereignis deklarieren

protected virtual void OnOpenCreateOrder(EventArgs e)
{
  EventHandler<EventArgs> ev = OpenCreateOrder;
  if (ev != null)
    ev(this, e); // abonnierte Ereignismethode(n) aufrufen
}

private void button1_Click(object sender, EventArgs e)
{
  OnOpenCreateOrder(e);
}


FrmMain.cs

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
public FrmMain()
{
  InitializeComponent();
  UcOrders ucOrders = new UcOrders();
  ucOrders.OpenCreateOrder += UcOrders_OpenCreateOrder;
}

private void UcOrders_OpenCreateOrder(object sender, EventArgs e)
{
  if (!PnlMaster.Controls.Contains(UcCreateOrder.Instance))
  {
    PnlMaster.Controls.Add(UcCreateOrder.Instance);
    UcCreateOrder.Instance.Dock = DockStyle.Fill;
    UcCreateOrder.Instance.BringToFront();
  }
  else
    UcCreateOrder.Instance.BringToFront();
}


Moderiert von user profile iconTh69: Vollzitat entfernt.
Moderiert von user profile iconTh69: C#-Tags hinzugefügt


Ralf Jansen - Mo 26.11.18 21:08

Wie ist die Instance-Property implementiert?

Moderiert von user profile iconTh69: C#-Tags hinzugefügt


Th69 - Di 27.11.18 09:38

Und setze mal einen Haltepunkt (breakpoint) auf die erste Zeile von UcOrders_OpenCreateOrder(...). Dann wirst du sehen, daß das Programm dort gar nicht hinkommt.

Du legst in dem Form-Konstruktor eine lokale Variable vom Typ UcOrders an, diese ist aber nicht dieselbe Instanz mit der du über den Button das UserControl anzeigst.
Ich denke, dann sollte es wohl so aussehen:

C#-Quelltext
1:
UcOrders.Instance.OpenCreateOrder += UcOrders_OpenCreateOrder;                    

Ich (und auch viele andere Entwickler) halten allerdings nichts vom Singleton-Pattern [https://de.wikipedia.org/wiki/Singleton_(Entwurfsmuster)] (und sehen es eher als Anti-Pattern: zu viele Nachteile [https://de.wikipedia.org/wiki/Singleton_(Entwurfsmuster)#Nachteile]).

Ich würde einfach Membervariablen für die verschiedenen UserControls in der Form-Klasse anlegen (außerdem in eine Liste einfügen, wie in meinem ersten Beitrag erklärt) und diese dann benutzen.


JohnDyr - Di 27.11.18 11:16

@Th69, du hast absolut recht... Danke vielmals. Habe es nun gefixed und es funktioniert. Auch durch setzen des Breakpoints war es nachvollziehbar. Zum Thema Singleton Pattern: Ich werde nochmal überlegen ob ich es tatsächlich nochmal Refactore. Ich denke hierzu gibt es viele Vorteile als auch Nachteile. Ungern möchte ich hierzu eine Diskussion entfachen, da es ja ursprünglich um was anderes ging. Vielleicht aber in einem anderen Thread :-)

Zitat:
Wie ist die Instance-Property implementiert?


Der Vollständigkeit halber nochmal die Implementierung der Instance:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
public static Uc1 Instance
{
  get
  {
    if (_instance == null)
      _instance = new Uc1 ();
    return _instance;
  }
}


Jedenfalls funktioniert es jetzt. Danke nochmal für eure Hilfe!!!