FlowLayoutPanel und UserControls - Performanceproblem
-
Servus,
ich habe ein kleines Performanceproblem in einer WinForms-Anwendung auf Basis von .NET 4 und C#.
Die Sache sieht folgendermaßen aus:
Ich habe eine Form, die am linken Rand ein TreeView (die Nodes da drin ziehen sich über drei Ebenen) und in der Mitte sowie den rechten Rand ein vertikal layoutendes FlowLayoutPanel hat.
Wenn man nun im TreeView auf eine Level 1-Node klickt, wird das FlowLayoutPanel mit von mir erstellten UserControls gefüllt. Es handelt sich immer um denselben Typ von UserControl, nur die Informationen die dargestellt werden, sind pro Control etwas andere. Das UserControl enthält selbst auch einige Controls.. Labels, Buttons, ComboBox.. im Gesamten aber unter 10 eigene 0815-Controls.
Nun, meine UserControls werden so bis zur Anzahl an 30 Stück eigentlich auch recht fix gezeichnet und alles ist okay. Nur ist es leider so, dass wir auch einen Satz Projektdaten haben, bei denen es leicht mal 200 UserControls für das FlowLayoutPanel werden können oO. Da sitzt man dann min. 30sec davor, bis die alle dargestellt sind und das ist logischerweise nicht so der Knaller.Die Frage ist also, wie kann ich das Hinzufügen der UserControls am Besten beschleunigen (Das Erstellen der Controls braucht kaum Zeit btw.)? Suspend/ResumeLayout ist klar, reißt es aber nicht raus.. Würde eine asynchrone Lösung funktionieren, bei der z.B. die ersten 10 UserControls hinzugefügt werden und der Rest dann im Hintergrund, während man diese ersten 10 UserControls bereits benutzen kann?
Oder sollte ich 10 UserControls anzeigen (bzw. so viel halt nötig - abhängig von Dialoggröße) und den Rest einfach mit Proxy-Objekten füllen? Da müsste man die Proxy-Objekte eben bei einem "ScrollIntoView"-Event o.ä. dann durch die richtigen Objekte ersetzen.Erschwerend kommt hinzu, dass ein Klick auf eine Level 2-Node sozusagen in die Detailansicht eines UserControls führt. Diese Detailansicht ersetzt dann die bisher dargestellten UserControls im FlowLayoutPanel. Sie ist zwar ruck-zuck aufgebaut, aber wenn der User wieder auf Level 1-Node und somit Übersicht zurückwechselt, geht das Spiel mit Hinzufügen von den vielen UserControls von vorne los. Okay, man muss das irgendwie cachen, aber die Frage ist wie, denn das Hinzufügen zum Panel frisst ja so viel Zeit. Eventuell anstatt einem FlowLayoutPanel lieber ein TabControl mit einem Tab für Level 1-Node Übersicht und einem Tab für Level 2-Node Details?
Was meint ihr? :S Wo setze ich da am Besten an?
Danke im Voraus
GPC
-
Hab noch nie geschafft WinForms mit UserControls in die Knie zu zwingen, aber mal sehen, ob ich mit einem "Brainstorming" trotzdem helfen kann.
Den Artikel kennst du schon?
http://msdn.microsoft.com/en-us/magazine/cc163630.aspxWobei, ich glaube nicht, dass er hier sehr hilfreiches enthalten wird.
Was eine weitere Möglichkeit wäre, ist WPF. Nein, nicht die ganze Anwendung auf WPF umstellen, sondern nur WPF für die Liste zu verwenden.
http://msdn.microsoft.com/en-us/library/system.windows.forms.integration.elementhost.aspxGrund: WPF ist deutlich leichtgewichtiger in solchen Dingen.
Es gäbe auch noch die Möglichkeit, dass du das Scrolling selber implementierst. Zum Beispiel setzt du so viele UserControls hin, wie es Platz hat. Und wenn sich die Scrollposition verändert, schiebst du Daten von unten nach oben oder umgekehrt. Dadurch sind immer nur eine gewisse Anzahl an UserControls in der Ansicht und diese Anzahl verändert sich nie. Gescrollt werden quasi nur die Daten und nicht die UserControls.
Dürfte nicht einmal so schwer zu implementieren sein. Einfach eineVScrollBar
nehmen und auf die Events reagieren und dann im Code hinten zusammenhängen. Du musst dann wahrscheinlich noch aufSizeChanged
reagieren, um die Anzahl UserControls anzupassen.Mehr sinnvolles will mir grad nicht einfallen.
Grüssli
-
GPC schrieb:
Würde eine asynchrone Lösung funktionieren, bei der z.B. die ersten 10 UserControls hinzugefügt werden und der Rest dann im Hintergrund, während man diese ersten 10 UserControls bereits benutzen kann?
Wie, im Hintergrund? Die GUI muss ja bei Winforms in einem Thread ablaufen. Ich wüsste nicht, wie man da etwas auslagern könnte.
GPC schrieb:
Oder sollte ich 10 UserControls anzeigen (bzw. so viel halt nötig - abhängig von Dialoggröße) und den Rest einfach mit Proxy-Objekten füllen? Da müsste man die Proxy-Objekte eben bei einem "ScrollIntoView"-Event o.ä. dann durch die richtigen Objekte ersetzen.
Ja so in der Art würde ich es versuchen. Kürzlich gab es hier eine Frage bzgl. Kacheln, die dynamisch (beim scrollen) nachgeladen und gerendert werden. Grob in die Richtung sollte das auch mit UCs funktionieren.
Mit folgenden SuspenDrawing- und ResumeDrawing-Methoden habe ich schon häufig sehr gute Erfahrungen gemacht, wenn Winforms in die Knie ging. Dass es in Deinem Fall hilft bezweifel ich aber ein Versuch wäre es wert. Hilfreich sind die Methoden v.a. wenn die Form grafisch etwas aufgemotzt ist und Layouting etc. ins Spiel kommt.
internal class WinApiStuff { [DllImport("user32.dll")] private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam); private const int WM_SETREDRAW = 11; public static void SuspendDrawing(Control c) { SendMessage(c.Handle, WM_SETREDRAW, false, 0); } public static void ResumeDrawing(Control c, bool refresh) { SendMessage(c.Handle, WM_SETREDRAW, true, 0); if (refresh) { c.Refresh(); c.Invalidate(); } } }
-
Dravere schrieb:
Hab noch nie geschafft WinForms mit UserControls in die Knie zu zwingen, aber mal sehen, ob ich mit einem "Brainstorming" trotzdem helfen kann.
Bisher habe ich das auch nicht geschafft, aber ich glaube das FlowLayoutPanel ist die Bremse. Es wäre nett, wenn ich in's .NET Framework rein-profilen könnte, um mehr Informationen zu haben.
Was eine weitere Möglichkeit wäre, ist WPF. Nein, nicht die ganze Anwendung auf WPF umstellen, sondern nur WPF für die Liste zu verwenden.
http://msdn.microsoft.com/en-us/library/system.windows.forms.integration.elementhost.aspxDas VirtualizingStackPanel in WPF wäre etwas, das mir sehr helfen würde. Allerdings müsste ich dann auch die UserControls neu in WPF erstellen, oder?
Es gäbe auch noch die Möglichkeit, dass du das Scrolling selber implementierst. Zum Beispiel setzt du so viele UserControls hin, wie es Platz hat. Und wenn sich die Scrollposition verändert, schiebst du Daten von unten nach oben oder umgekehrt. Dadurch sind immer nur eine gewisse Anzahl an UserControls in der Ansicht und diese Anzahl verändert sich nie. Gescrollt werden quasi nur die Daten und nicht die UserControls.
Dürfte nicht einmal so schwer zu implementieren sein. Einfach eineVScrollBar
nehmen und auf die Events reagieren und dann im Code hinten zusammenhängen. Du musst dann wahrscheinlich noch aufSizeChanged
reagieren, um die Anzahl UserControls anzupassen.Das klingt nach einem vielversprechenden Ansatz... damit hätte ich auch das Caching-Problem erledigt
Danke mal soweit.µ schrieb:
GPC schrieb:
Würde eine asynchrone Lösung funktionieren, bei der z.B. die ersten 10 UserControls hinzugefügt werden und der Rest dann im Hintergrund, während man diese ersten 10 UserControls bereits benutzen kann?
Wie, im Hintergrund? Die GUI muss ja bei Winforms in einem Thread ablaufen. Ich wüsste nicht, wie man da etwas auslagern könnte.
Ja, das ist ein guter Einwand... schön wäre eben gewesen wenn z.B. ein BackgroundWorker nach und nach das FlowLayoutPanel mit UserControls füllt und man währenddessen schon die ersten paar UserControls sehen + bearbeiten kann. Von der Grundidee eigentlich gar nicht übel.
Mit folgenden SuspenDrawing- und ResumeDrawing-Methoden habe ich schon häufig sehr gute Erfahrungen gemacht, wenn Winforms in die Knie ging. Dass es in Deinem Fall hilft bezweifel ich aber ein Versuch wäre es wert.
Die kenne ich schon
Und nein, die helfen mir leider nicht :p Aber trotzdem auch danke für die Denkanstöße
-
GPC schrieb:
Ja, das ist ein guter Einwand... schön wäre eben gewesen wenn z.B. ein BackgroundWorker nach und nach das FlowLayoutPanel mit UserControls füllt und man währenddessen schon die ersten paar UserControls sehen + bearbeiten kann. Von der Grundidee eigentlich gar nicht übel.
Genau das geht eben (leider) nicht. Alles was Zeit frisst (Rendern, Layouting) muss zwingend im GUI-Thread stattfinden.
-
Ich denke mit einem Timer kriegst Du es gut hin.
namespace LazyControls { public partial class Form1 : Form { Timer timer1 = new Timer(); public Form1() { InitializeComponent(); timer1.Interval = 10; timer1.Tick+=new EventHandler(timer1_Tick); timer1.Enabled = true; } int count = 0; private void timer1_Tick(object sender, EventArgs e) { if (flowLayoutPanel1.Controls.Count < 100) { slowControl c = new slowControl(); c.label1.Text = (count++).ToString(); flowLayoutPanel1.Controls.Add(c); } else timer1.Enabled = false; } } }
slowControl wird im Konstruktor mit Thread.Sleep für eine kurze Zeitspanne lahmgelegt um die Sache zu simulieren.
Die GUI bleibt trotzdem bedienbar weil zwischen zwei Ticks andere Events verarbeitet werden können.
-
@µ
Geniale Idee!Das ist vom feeling her genau so, wie ich es mir vorgestellt habe
Danke vielmals! :xmas1: