Laden...

"Vorladen" von Forms

Erstellt von Gremgiz vor 14 Jahren Letzter Beitrag vor 14 Jahren 6.551 Views
G
Gremgiz Themenstarter:in
106 Beiträge seit 2006
vor 14 Jahren
"Vorladen" von Forms

Hallo,

Ich habe eine ziemlich komplexe Form gebastelt, die an verschiedenen Stellen im programm immer wieder aufgerufen werden muss (tabbed browsing). Wenn ich die jedesmal mit form Form = new form() lade, dauert mir das zu lange. Daher ist meine Idee, das ganze bereits beim Start des Programmes zu laden und in den Speicher zu schreiben, so dass ich dann jeweils eine Kopie davon bekommen kann. Ist das möglich und wenn ja, wie sehen die Lösungsansätze dazu aus?

4.506 Beiträge seit 2004
vor 14 Jahren

Hallo Gremgiz,

ob Du eine Kopie von WinForms erstellen / produzieren kannst weiß ich ehrlich gesagt nicht. Habe auf die schnelle nur Codeproject - Clone/Serialize/Copy WinForm gefunden, wahrscheinlich hilfts.

Aber Du kannst initial die Form mittels "new MyForm()" erstellen lassen und Dir die Form dann in einer Variablen vorhalten. Später dann kannst Du diese dann mit "Show()" bzw "ShowDialog()" anzeigen lassen.

Grüße
Norman-Timo

A: “Wie ist denn das Wetter bei euch?”
B: “Caps Lock.”
A: “Hä?”
B: “Na ja, Shift ohne Ende!”

G
Gremgiz Themenstarter:in
106 Beiträge seit 2006
vor 14 Jahren

Hallo Norman-Timo,

vielen Dank für die Antwort. Den Link werde ich mir anschauen. Das mit dem Vorhalten klappt leider nicht, da ich immer neue Instanzen benötige, da der benutzer in diese Form immer Daten eingibt und nicht immer alle Tabs vorher schließt

Gruß
Gremgiz

G
Gremgiz Themenstarter:in
106 Beiträge seit 2006
vor 14 Jahren

Der Tipp mit dem Link hilft leider nicht weiter. Meine Form ist ja fertig. Für einzelne Steuerelemente ist das Ok. Bei komplexen Sachen nicht mehr

F
10.010 Beiträge seit 2004
vor 14 Jahren

Das laden einer Form ist selten langsam, es sei denn du hast hunderte Controls dadrin.
Meist ist es eher der falsche Zeitpunkt des setzens von irgendwelchen details.

Was machst du also alles im Construktor, bzw. im Load?

Gelöschter Account
vor 14 Jahren

und oft ist es auch ein schneepalleffekt, wenn du bereits vor dem initialen befüllen der controls alle events anhängst, die nicht absolut notwenig sind.

G
Gremgiz Themenstarter:in
106 Beiträge seit 2006
vor 14 Jahren

Aus gewissen Überlegungen heraus, sind Code und GUI komplett getrennt. Das heißt, das eigentliche GUI hat weder Konstruktoren noch Events. Diese werden alle in der zugehörigen Code Klasse definiert. DIe interne Stopuhr von C# gibt mir Zeiten von 1,5 -2,5 Sekunden aus, um die Form per NEW zu erstellen und eine weitere Sekunde, um diese in ein übergeordnetes Control einzubetten.

Ich tippe mal, dass ein Backgroundworker vielleicht die Lösung ist. Damit werde ich mal rumprobieren. Wenn es weitere Hinweise gibt, wäre ich dankbar

Danke
Gremgiz

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo Gremgiz,

das eigentliche Erzeugen des Forms (new Form) muss im GUI-Thread erfolgen. Das lässt sich nicht in einen anderen Thread auslagern (siehe [FAQ] Warum blockiert mein GUI? und [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke)).

Ich schließe mich der Vermutung von FZelle und JAck30lena an, dass du entweder zuviele Controls hast oder dass das Laden/Setzen von Daten und nicht das Erzeugen der Controls das Problem ist.

herbivore

3.825 Beiträge seit 2006
vor 14 Jahren

Ich habe gerade mal in meiner Anwendung geschaut :

Dass Öffnen eines normalen Forms dauert 2 sec. Das Öffnen des größten Forms (so 800 Controls) dauert 5 sec.
Das große Form braucht man fast nie, das ist egal.

Aber die 2 sec finde ich schon fast zu langsam.
Über VPN dauert es statt 2 sec ungefähr 4 - 5 sec.

Meine Überlegungen :
Die Forms die oft verwendet werden, oder die dieser User oft benutzt, vorladen.
Im ersten Schritt könnte man nur die Daten, die aus der Datenbank geholt werden, vorladen, das sind bei mir z.B. die Berechtigungen des Users. Würde den Start über VPN beschleunigen.

Wieviel Zeit braucht denn Dein winForm frm = new winForm(); ?
Wieviel Zeit braucht denn Dein frm.ShowDialog(); )

Kannst Du das Form vielleicht klein / minimiert / unsichtbar erzeugen ?

Grüße Bernd

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

G
Gremgiz Themenstarter:in
106 Beiträge seit 2006
vor 14 Jahren

Hallo zusammen,

erst mal herzlichen Dank für die vielen Tipps und Anregungen. Ich glaube ich habe die Lösung gefunden - zumindest funktioniert es gut.

Die Ladezeiten des GUIs kann ich schlecht beeinflussen, aber ich kann das GUI im Hintergrund laden und dann holen. Grundlage für diese Überlegung und schlußendliche Umsetzung ist Controls von Thread aktualisieren lassen (Invoke-/TreeView-Beispiel). Für dieses sehr gut gelungene Tutorial herzlichen Dank an alle, die daran mitgewirkt haben

Was wird nun im Einzelnen gemacht:
Bevor ich die Schritte im Detail erläutere, die Struktur des Programms: Beim Start des Programms wird der Benutzer verifiziert. Wenn dies erfolgreich verlaufen ist, wird die Hauptmaske geladen, in der das "Problem"-GUI als Tab dargestellt werden soll. Mit Aufruf dieses Haupt-GUIs wird das „Problem“-GUI bereits das erstemal geladen. Dieses "Problem"-GUI kann vom Anwender beliebig oft aufgerufen werden. Dies erfolgt über einen Button.

  1. Delegaten im Haupt-GUI erstellen
public partial class Main_GUI
{
   delegate void Del_LoadGUI();
}
  1. Wenn der Benutzer auf den Button klickt, der das "Problem"-GUI anzeigen soll, neuen Thread erzeugen
private void Button_Click(object sender, EventArgs e)
{
   Thread t = new Thread(new ThreadStart(Thr_ShowGUI));
   t.Start();
}
  1. Das bereits vorgeladene „Problem“-GUI darstellen
protected void Thr_ShowGUI() 
{
   //Den Tab anzeigen, muss separat erfolgen wegen Tread-Sicherheit
    PageView();

    //Im Hintergrund einen neuen Tab erzeugen
    BeginInvoke(new Del_LoadGUI(Thr_LoadGUI));
}
  1. Erzeugen des eigentlichen Tabs mit dem “Problem”-GUI
    Dabei benötigen wir noch ein paar klassenweite Variablen, für das spätere Management (darauf wird hier aber nicht eingegangen). Nachdem der Tab erzeugt wurde, wird dieser unsichtbar gemacht, so dass er noch nicht angezeigt wird.
protected void Thr_LoadGUI() 
{
   //Ableitungen erstellen
    New_GUI Next_GUI = new New_GUI();  //Das GUI an sich
    Page_Ticket = new TabControl.Page(); //Tab für das GUI

    //Tabzähler erhöhen
    TabCount++;

    //Namen des Tabs festlegen für spätere Verwaltung
    Page_GUI.Name = "Tab" + TabCount.ToString();

    //Angezeigten Text des Tabs einstellen
    Page_GUI.Text = "Neuer Tab";

    //Tab befüllen und hinzufügen
    Page_GUI.Controls.Add(Next_GUI);
    Tab_Main.Pages.Add(Page_GUI);

    //Tab unsichtbar machen
    Page_GUI.Visible = false;
}

Definition der klassenweiten Variablen (machen das Leben leichter auch wenn nicht sauber objektorientiert)

public partial class Main_GUI
{
   //Den Teil haben wir schon eingefügt
   delegate void Del_LoadGUI();

   //Klassenvariablen
   TabControl.Page Page_GUI = null;
   Int TabCount = 0;

}
  1. Den Tab sichtbar machen und dabei auf Thread-Sicherheit achten
private void PageView()
{
   if (Page_GUI.InvokeRequired)
   {
      Page_GUI.Invoke(new MethodInvoker(PageView));
      return;
   }
   Page_GUI.Visible = true;
   TabControl.SelectedPage = Page_GUI;
   TabControl.Update();
}

Wichtig hierbei ist das Update(); das dafür sorgt, dass das GUI direkt neu gezeichnet wird und zur Verfügung steht, bevor das neue/nächste GUI geladen wird.

  1. Vorladen des ersten GUIs
    Wie ja bereits anfänglich erwähnt, wird das erste „Problem“-GUI direkt beim Aufrufen der Hauptform geladen. Das passiert über das Load-Event der Form
private void Main_GUI_Load(object sender, EventArgs e)
{
   Invoke(new Del_LoadGUI(Thr_LoadGUI));
}

Interessant hierbei ist der Synchrone Aufruf des Threads im Gegensatz zu dem sonstigen Asynchronen Aufruf. So kann man erreichen, dass die Hauptform auch sofort einsatzfähig ist und nicht erst noch hängt, weil der Thread im Hintergrund noch läuft. Klar braucht auch diese Ausführung Zeit, die man aber mittels eines Splashscreens gut abfangen kann – und es ist nur einmal beim Start des Programms.

Anmerkungen:
Da ich nicht mit den WindowsForms arbeite, sondern mit einem anderen Control Set, bin ich mir nicht sicher, ob ich in den Codebeispielen alles korrekt auf WinForms rückgeschrieben habe. Bei Bedarf kann man dies ja ändern 😃

Gruß
Gremgiz

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo Gremgiz,

hm, für mich sieht das alles ein bisschen von hinten durch die Brust ins Auge aus. Letztlich läuft doch alles darauf hinaus, dass du die Aktion Thr_ShowGUI in einem Thread startest, der die Aktionen PageView und Thr_LoadGUI dann wieder einzeln zurück an den GUI-Thread delegiert (allerdings verwirrenderweise auf unterschiedliche Art).

Wenn man davon ausgeht, dass PageView und Thr_LoadGUI in etwa gleich lange dauern, dann ist damit nur gewonnen, dass eine lange Blockierung des GUIs durch zwei halb solange Blockierungen ersetzt wird. Schneller wird dadurch nichts und parallel wird auch nichts ausgeführt.

herbivore

1.665 Beiträge seit 2006
vor 14 Jahren

Wir haben das Problem teilweise so gelöst, dass wir bestimmte Dialogteile nur bei Bedarf erzeugen und hinzugefügen (per Toolbar/Ribbon Button z.B. "Details", "Bearbeiter").
Oft ist es nämlich der Fall, dass nicht alle Controls gebraucht werden und deshalb zunächst nur die wichtigsten per default angezeigt werden.

Außerdem geschieht das Daten laden asynchron.

G
Gremgiz Themenstarter:in
106 Beiträge seit 2006
vor 14 Jahren

Hallo,

@Herbivore: ob meine Lösung jetzt optimal ist, wage ich zu bezweifeln. Das ist so ziemlich meine erste Arbeit mit Threads. Hast du Vorschläge, wie man das optimieren kann? Das ganze an sich geht schneller - oder fühlt sich zumindest so an - was ja auch ein nicht zu unterschätzender Aspekt der Benutzerpsychologie ist. Genau gemessen habe ich die Zeit nicht, jedoch mal mit "mitzählen" ist die Zeit halbiert worden. Nachdem das GUI aufgebaut ist, ist es noch ca. 0,5 -1 s blockiert - bis halt alle Threads fertig sind.

@JunkyXL: Das mache ich auch so. Alle initial benötigten Controls werden geladen - der Rest später. Es bleiben aber immer noch genügend über 😃

Gruß
gremgiz

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo Gremgiz,

Genau gemessen habe ich die Zeit nicht, jedoch mal mit "mitzählen" ist die Zeit halbiert worden. Nachdem das GUI aufgebaut ist, ist es noch ca. 0,5 -1 s blockiert - bis halt alle Threads fertig sind.

ok, dieser Effekt in der Tat eine mögliche Folge.

Hast du Vorschläge, wie man das optimieren kann?

Einen eigenen Delegattyp Del_LoadGUI brauchst du nicht definieren. Verwende MethodInvoker. Außerdem solltest du PageView und Thr_LoadGUI gleich behandeln:

BeginInvoke (new MethodInvoker (PageView);
BeginInvoke (new MethodInvoker (LoadGUI));

Entsprechend sollte dann das InvokeRequired/BeginInvoke aus dem Rumpf von PageView rausfliegen.

herbivore

G
Gremgiz Themenstarter:in
106 Beiträge seit 2006
vor 14 Jahren

Hallo Herbivore,

Vielen Dank für die Tipps. Habe es mal umgesetzt und es funktioniert ganz gut.

Gruß
Gremgiz

G
Gremgiz Themenstarter:in
106 Beiträge seit 2006
vor 14 Jahren

Hallo,

ich habe über das Problem noch mal ein wenig nachgegrübelt.

Hierzu mal meine Gedanken. Dazu würde ich mich über Kommentare freuen:

  1. Ansatz: Form global bekanntmachen
    Man nehmen eine Klasse die das Globales heißt und die Form halten wird:
public class Globales
{
   public static Form Form1 {get; set;}
}

Dann beim Start des Programms das ganze setzen:

Globales.Form1 = new Form;

Beim Aufruf der Form das ganze wieder rausholen und das halt jedesmal:

Form Form2 = Globales.Form1;

Was ich dabei beobachtet habe ist, dass immer nur ein Tab damit befüllt wird. Alle älteren Tabs werden glöscht oder besser leer angezeigt. Keine Ahnung warum

  1. Ansatz: IClonable
    Der generelle Ansatz ist wie oben, nur dass die Klasse um IClonable erweitert wird und dann immer nur ein Clone erstellt wird. Dabei knallt es allerdings mit einer InvalidCastException - wobei ich hier nicht weiß warum?

Kann mir hier noch mal jemand gute Tipps geben?

Danke
gremgiz

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo Gremgiz,

zu 1. Da du oben schreibst, dass es um eine Form geht, die an verschiedenen Stellen im programm immer wieder aufgerufen werden muss (tabbed browsing), sehe ich nicht, wie dir dein Global-Ansatz helfen soll. Form Form2 = Globales.Form1; erzeugt ja kein neues Fenster, sondern setzt nur Form2 auf das bestehende.

zu 2. Das Objekt zu klonen verbessert gegenüber neu erzeugen nichts und wäre bei Windows-Forms weder einfach noch schön.

Im Prinzip wurde sowieso schon alles gesagt, was du wissen musst.

herbivore