Laden...

Bildergalerie - flowlayoutpanel zu langsam

Erstellt von Gimmick vor 4 Jahren Letzter Beitrag vor 4 Jahren 1.798 Views
G
Gimmick Themenstarter:in
154 Beiträge seit 2015
vor 4 Jahren
Bildergalerie - flowlayoutpanel zu langsam

Hallo zusammen,

ich tüftel gerade an einer Bildergalerie, die neben der Anzeige eines Vorschaubildes noch mehr Funktionen bieten soll und stoße dabei auf ein Performanceproblem.

Bisher hatte ich es so aufgebaut:

  • Auswahl mehrerer Bilder mit blauem Rahmen als optisches Feedback
  • Ein seletiertes Bild ist das "Hauptobjekt", angezeigt durch einen zusätzlichen orangenen Rahmen
  • zu diesem Hauptobjekt werden in einem Datagrid diverse Infos angezeigt
  • Unter jedem Bild befinden sich Buttons: Als "Hauptobjekt" auswählen, Entfernen, Originalbild öffnen
  • Über jedem Bild ist eine Checkbox, um die Bilder einer Auswahl hinzuzufügen
  • Unter jedem Bild sind zwei label für Dateinamen + Datum

Gebaut hatte ich das folgendermaßen:

Das UserControl enthält die Buttons, Checkbox und label. Das Vorschaubild und die Rahmen zeichne ich direkt in das Control.
In das flowlayoutpanel werden die UserControls eingefügt
Das flowlayoutpanel befindet sich dann zusammen mit dem Datagrid in einem SplitContainer

Funktional ist bisher alles super, aber man merkt einfach, dass es bei ~30+ Bildern unglaublich träge wird. Das Scrollen stockt dann und bei Änderung der Fenstergröße hängt die Galerie deutlich nach.

Das Zeichnen erledige ich im Paint-Event des UserControls:


        private void DrawImage(Image Bild, Graphics G)
        {
            G.DrawImage(Bild, new Point(_StartX, 15));
        }

        private void DrawSelection(bool Val, Graphics G)
        {
            if (Val)
            {
                SolidBrush brush = new SolidBrush(Color.LightBlue);
                G.FillRectangle(brush, new Rectangle(5, 5, 190, 170));
                _paintedBlue = true;
            }
            else if (!Val && (_paintedBlue || _paintedOrange))
            {
                SolidBrush brush = new SolidBrush(Color.White);
                G.FillRectangle(brush, new Rectangle(5, 5, 190, 170));
                _paintedBlue = false;
                _paintedOrange = false;
            }
        }

        private void DrawMainSelection(bool Val, Graphics G)
        {
            if (Val)
            {
                SolidBrush brush = new SolidBrush(Color.DarkOrange);
                G.FillRectangle(brush, new Rectangle(15, 10, 170, 150));
                _paintedOrange = true;
            }

        }

        private void LargeGallery_Image_Paint(object sender, PaintEventArgs e)
        {

            DrawSelection(_Selected, e.Graphics);
            DrawMainSelection(_Mainselection, e.Graphics);
            DrawImage(_Bild, e.Graphics);

        }

Auskommentieren der einzelnen paint-methoden macht es schneller, aber ehrlichgesagt ist es selbst ganz ohne Bild und Rahmen, nur mit den Buttons etc., auch schon zu langsam beim Scrollen.

Habt ihr einen Vorschlag, wie man das besser umsetzen könnte?

4.938 Beiträge seit 2008
vor 4 Jahren

GDI-Objekte, wie Brush, Pen o.ä. sollten immer wieder sofort nach Benutzung freigegeben werden (also entweder per Dispose oder aber besser gleich eine using-Anweisung benutzen).

Bei festen Farben reicht es aber direkt auf die Brushes- bzw.Pens-Klasse zuzugreifen:


SolidBrush brush = Brushes.White;

so daß nicht jedesmal ein neues temporäres GDI-Objekt erzeugt wird (und wieder freigegeben werden muß)!

Die langsamere Performance beim Scrollen wird aber wohl eher an dem FlowLayoutPanel sowie der Anzahl der eingefügten Elemente liegen - evtl. müßtest du diese auch selber zeichnen (und die Positionen berechnen).

PS: Auch die beiden fixen Rectangle könntest du als static readonly-Klassenmember einmalig erzeugen...

G
Gimmick Themenstarter:in
154 Beiträge seit 2015
vor 4 Jahren

Die langsamere Performance beim Scrollen wird aber wohl eher an dem FlowLayoutPanel sowie der Anzahl der eingefügten Elemente liegen - evtl. müßtest du diese auch selber zeichnen (und die Positionen berechnen).

Dazu hätte ich einige Fragen:

Was bedeutet in dem Fall "selber zeichnen"? Ich verstehe das so, dass ich dann das FlowLayoutPanel rauswerfe, eigene ScrollBars einfüge und anhand der Scrollposition für jedes Bild schaue:

  1. Wo liegt es
  2. Was sehe ich davon

Und dann entsprechend einzeichne.

Aber wie mache ich das mit nutzbaren Controls wie Buttons?
Kann ich evtl. mein UserControl so lassen wie es ist, das Control als Bitmap zwischenlagern und dann in der Zeichenfläche die Mausposition gegen jede errechnete Control/Bild-Position gegenchecken, den Klick abfangen, den Button in den pressed-Zustant setzen -> neues Bild generieren und das einzeichnen?
Oder im Worstcase das Usercontrol als Sammlung an Controls und Koordinaten speichern und nur den Button neu zeichnen?

Da müsste man dann wohl am besten noch eine Listung sichtbarer Objekte führen, damit nur da geprüft wird, wo auch geklickt werden kann.

Klingt aufwendig, oder denke ich zu kompliziert?

GDI-Objekte, wie Brush, Pen o.ä. sollten immer wieder sofort nach Benutzung freigegeben werden (also entweder per Dispose oder aber besser gleich eine using-Anweisung benutzen).

Bei festen Farben reicht es aber direkt auf die Brushes- bzw.Pens-Klasse zuzugreifen:

  
SolidBrush brush = Brushes.White;  
  

so daß nicht jedesmal ein neues temporäres GDI-Objekt erzeugt wird (und wieder freigegeben werden muß)!

PS: Auch die beiden fixen Rectangle könntest du als static readonly-Klassenmember einmalig erzeugen...

Da hast Du absolut Recht, ich wurschtel das Anfangs meistens erstmal so rein und räume später auf, wenn klar ist wohin die Reise überhaupt geht ^^.

W
955 Beiträge seit 2010
vor 4 Jahren

* versuche mal durch Verringern der Controls rauszubekommen warum das so lange dauert, wer also der Störenfried ist. Also was du im Code gemacht hast auch auf die Controls anwenden.
* im WPF gibt es virtuelle Container, z.B. VirtualStackPanel. Diese generieren Elemente erst wenn sie sichtbar werden. Google mal ob es Ansätze/Codebeispiele/Controls für WinForms existieren dann brauchst du das Rad nicht neu erfinden.

G
Gimmick Themenstarter:in
154 Beiträge seit 2015
vor 4 Jahren

Hallo.

* versuche mal durch Verringern der Controls rauszubekommen warum das so lange dauert, wer also der Störenfried ist. Also was du im Code gemacht hast auch auf die Controls anwenden.

Habe schon ein Testprojekt erstell, dass einfach nur ein UserControl mit 3 Buttons x-mal in das Panel packt. Es steht und fällt mit Größe des Fensters, bzw. vermutlich eher mit der Anzahl der gleichzeitig sichtbaren Controls im Panel.
Da hängt beim Scrollen einfach alles.

* im WPF gibt es virtuelle Container, z.B. VirtualStackPanel. Diese generieren Elemente erst wenn sie sichtbar werden. Google mal ob es Ansätze/Codebeispiele/Controls für WinForms existieren dann brauchst du das Rad nicht neu erfinden.

Wie intern die Elemente gehandhabt werden weiß ich nicht. Es kann aber auch an der Zeichengeschwindigkeit liegen und in dem Fall wird selber alles Zeichnen den Braten nicht fett machen. Gerade wenn man scrollt und dann immer alles neu zeichnen... ich seh schon die Flackerhölle vor mir.

Habe auch gerade mal das Ganze in WPF mit einem Wrappanel nachgebaut (einfach ScrollViewer + Wrappanel + 200 UserControls) -> scrollt ohne Probleme.

Der Plan war die Galerie über eine DLL in ein bestehendes Form-Programm einzubauen, wenn ich dafür jetzt WPF nutzen würde, hätte ich dann durch die Mischung mit Problemen zu rechnen? Oder bremst evtl. das langsamste Glied der Kette und WPF in Forms ist genauso lahm wie Forms alleine?

4.938 Beiträge seit 2008
vor 4 Jahren

Da muß es noch eine andere Ursache geben, daß es beim Scrollen hängt.

Bei meinen Spieleprojekten verwende ich auch öfters selbstgezeichnete Controls (inkl. JPG-Bildern) in Panels mit Scrollbalken (und konnte bisher nie eine Verlangsamung feststellen - auch nicht bei mehreren 100 Elementen).

Für das Selberzeichnen von Standard-Steuerelementen gibt es die entsprechenden Renderer-Klassen, z.B. ButtonRenderer (dies sehe ich aber als allerletzte Möglichkeit zur Optimierung).

Falls es aber zu Flackern kommt, dann hilft [Artikel] Flackernde Controls und flackerndes Zeichnen vermeiden.

G
Gimmick Themenstarter:in
154 Beiträge seit 2015
vor 4 Jahren

Da muß es noch eine andere Ursache geben, daß es beim Scrollen hängt.

Bei meinen Spieleprojekten verwende ich auch öfters selbstgezeichnete Controls (inkl. JPG-Bildern) in Panels mit Scrollbalken (und konnte bisher nie eine Verlangsamung feststellen - auch nicht bei mehreren 100 Elementen).

Das ist jetzt evtl. ein Missverständnis, für den Test habe ich einfach ein UserControl mit 3 Buttons per Schleife 100 mal in die Controls des flowlayoutpanels gepackt.

Hab das gerade nochmal auf einem anderen Rechner probiert. Die Kiste hat etwas mehr Hubraum - da stockt erstmal nichts, aber auch hier sieht man wie sich die Controls aufbauen / gestreckt dargestellt werden und wenn man mehrmals hoch-runter-scrollt braucht das System (also auch Windows außerhalb des Programs) ein paar Sekunden bevor überhaupt wieder Mauseingaben funktionieren.

Für das Selberzeichnen von Standard-Steuerelementen gibt es die entsprechenden Renderer-Klassen, z.B.
>
(dies sehe ich aber als allerletzte Möglichkeit zur Optimierung).

Falls es aber zu Flackern kommt, dann hilft
>
.

Ok, danke, schaue ich mir auch mal an.
Habe auch mal über den ElementHost ein WPF-Warppanel in ein Forms gepackt. Auf den ersten Blick lief auch das ganz gut. Im Detail habe ich mir das aber noch nicht angesehen.

W
955 Beiträge seit 2010
vor 4 Jahren

Wie groß sind die Bilder? Du hast was von Vorschau geschrieben ... Nicht dass das nicht klappt und die Hütte Gigabytes schaufelt?

G
Gimmick Themenstarter:in
154 Beiträge seit 2015
vor 4 Jahren

Wie groß sind die Bilder? Du hast was von Vorschau geschrieben ... Nicht dass das nicht klappt und die Hütte Gigabytes schaufelt?

Moin,

die erste "Komplett-Testversion" hat 150*150 px Bilder in die Controls gezeichnet, die letzten Kurztests haben gar keine Bilder gezeichnet, nur die 3 Buttons.

4.938 Beiträge seit 2008
vor 4 Jahren

Dann mache mal einen Test ohne das FlowLayoutPanel, aber mit einem ScrollableControl wie z.B. Panel und prüfe dann das Scrollverhalten (am besten auch mal beide Versionen, also mit Bildern oder nur mit den Buttons).

Sind die Bilder denn einmalig in den Speicher geladen oder lädst du diese immer jeweils von Platte nach?

G
Gimmick Themenstarter:in
154 Beiträge seit 2015
vor 4 Jahren

Für einen Kurztest habe ich mal 300 Buttons direkt in ein Panel mit Autoscroll gepackt.
Scroll-Balken stockt nicht, aber die Anzeige der Buttons ist dennoch recht langsam. Zudem hängt auch hier nach ein paar Mal hoch-runter-scrollen auch systemweit die Eingabe.

Sind die Bilder denn einmalig in den Speicher geladen oder lädst du diese immer jeweils von Platte nach?

Die Bilder lade ich einmalig.

Ich habe das Problem jetzt mal in sofern umschifft:

DLL:

  • enthält Forms-Usercontrol mit Splitcontainer

  • Splitcontainer enthält einen "ElementHost"

  • WPF-UserControl mit Wrapper + ScrollViewer

  • WPF-UserControl für die Buttons, Bildinfos und das Bild

Das UserControl mit dem Splitcontainer kommt dann in ein TabControl in der Forms-Anwendung.

Das ist ein "wenig" umständlich, läuft aber. Habe mal ~1000 Bilder geladen und die Darstellung war gar kein Problem.

Jetzt habe ich aber das Problem, dass die Optik der WPF-Controls anpassen muss, weil ich in Forms den "Flatstyle" nutze.
Ist aber mein erster Kontakt mit WPF, werde am WE vermutlich dazu mal einen eigenen Thread erstellen 😁

Habt ihr das in Forms denn mal gegenprobiert?